import React, { useState, useEffect, memo, useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import ReactPaginate from "react-paginate";
import { ChevronDown, MoreVertical, Plus, Trash } from "react-feather";
import DataTable from "react-data-table-component";
import {
Card,
CardHeader,
CardTitle,
Input,
Label,
Row,
Col,
Button,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
UncontrolledDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
FormGroup,
Form,
} from "reactstrap";
import Select from "react-select";
import { Edit } from "@mui/icons-material";
import { useSnackbar } from "notistack";
import { default as SweetAlert } from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { getDataCenters, createDataCenter, updateDataCenter, deleteDataCenter } from "../redux/actions/dataCenter";
import { useTranslation } from "react-i18next";
import { getSectors, getSectorById, getSubSectorById, getConsuptionUnits } from "../redux/actions/datas";
import { getAllEmissionSources } from "../redux/actions/emissionSources";
import { permissionCheck } from "../components/permission-check";
import { customFilterForSelect } from "../utility/Utils";
import { MapContainer, TileLayer, Marker, useMapEvents } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import axios from 'axios';
import { debounce } from 'lodash';
// Add Nominatim service configuration
const NOMINATIM_BASE_URL = 'https://nominatim.openstreetmap.org';
const nominatimAxios = axios.create({
baseURL: NOMINATIM_BASE_URL,
headers: {
'User-Agent': 'SGE-DataCenter-Management' // Required by Nominatim's usage policy
}
});
const Swal = withReactContent(SweetAlert);
// Fix Leaflet marker icon issue
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});
// Map marker component that handles clicks
const MapMarker = ({ position, setPosition, setSelectedDataCenter }) => {
useMapEvents({
click(e) {
setPosition([e.latlng.lat, e.latlng.lng]);
// Use Nominatim reverse geocoding directly
nominatimAxios.get(`/reverse?format=json&lat=${e.latlng.lat}&lon=${e.latlng.lng}`)
.then(response => {
const address = response.data.display_name;
setSelectedDataCenter(prev => ({
...prev,
address,
latitude: e.latlng.lat,
longitude: e.latlng.lng
}));
})
.catch(error => {
console.error('Error getting address:', error);
// Still update coordinates even if address lookup fails
setSelectedDataCenter(prev => ({
...prev,
latitude: e.latlng.lat,
longitude: e.latlng.lng
}));
});
}
});
// Only render marker if position exists and has valid coordinates
return position && position[0] && position[1] ? : null;
};
const DataCenterManagement = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const { enqueueSnackbar } = useSnackbar();
const [currentPage, setCurrentPage] = useState(1);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [searchValue, setSearchValue] = useState("");
const [showAddModal, setShowAddModal] = useState(false);
const [selectedDataCenter, setSelectedDataCenter] = useState({
name: "",
externalId: "",
number: "",
address: "",
latitude: null,
longitude: null,
ayposURL: "",
city: "",
emissionScopeId: null,
sectorId: null,
subSectorId: null,
emissionSourceId: null,
consuptionUnitId: null,
activitySubUnitId: null
});
const [mapPosition, setMapPosition] = useState(null);
const dataCenterStore = useSelector((state) => state.dataCenter);
const datasStore = useSelector((state) => state.datas);
const emissionSourceStore = useSelector((state) => state.emissionSources);
const [sectorsOptions, setSectorsOptions] = useState([]);
const [subSectorsOptions, setSubSectorsOptions] = useState([]);
const [emissionSourcesOptions, setEmissionSourcesOptions] = useState([]);
const [consuptionUnitsOptions, setConsuptionUnitsOptions] = useState([]);
const [activitySubUnitsOptions, setActivitySubUnitsOptions] = useState([]);
const [emissionScopesOptions, setEmissionScopesOptions] = useState([]);
// Add state for selected sector and sub sector like in data input
const [selectedSector, setSelectedSector] = useState(null);
const [selectedSubSector, setSelectedSubSector] = useState(null);
const [editingDataCenter, setEditingDataCenter] = useState(null);
const initialColumns = [
{
name: t("DataCenter.id"),
selector: (row) => row.externalId,
sortable: true,
minWidth: "100px",
cell: (row) => {row.externalId},
},
{
name: t("DataCenter.name"),
selector: (row) => row.dataCenter,
sortable: true,
minWidth: "200px",
},
{
name: "Dashboard",
selector: (row) => row.ayposURL,
sortable: false,
minWidth: "150px",
cell: (row) => (
{row.ayposURL ? (
) : (
-
)}
),
},
{
name: t("Actions"),
allowOverflow: false,
maxWidth: "150px",
cell: (row) => {
return (
{permissionCheck("datacenter_update") && (
handleEditDataCenter(row)}
>
{t("Cruds.edit")}
)}
{permissionCheck("datacenter_delete") && (
handleDeleteDataCenter(row)}
>
{t("Cruds.delete")}
)}
);
},
},
];
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
useEffect(() => {
dispatch(getDataCenters());
dispatch(getSectors());
}, [dispatch]);
useEffect(() => {
setSectorsOptions(
datasStore?.sectors?.map((sector) => ({
value: sector?.id,
label: sector?.tag,
}))
);
}, [datasStore?.sectors]);
useEffect(() => {
setSubSectorsOptions([]);
setSubSectorsOptions(
datasStore?.sector?.subSectors?.map((subSector) => ({
value: subSector?.id,
label: subSector?.tag,
}))
);
}, [datasStore?.sector]);
useEffect(() => {
setActivitySubUnitsOptions(
datasStore?.subSector?.activitySubUnits?.map((activitySubUnit) => ({
value: activitySubUnit?.id,
label: activitySubUnit?.tag,
}))
);
}, [datasStore?.subSector]);
useEffect(() => {
setEmissionSourcesOptions(
emissionSourceStore?.emissionSources
?.filter((source) => source.convertUnitCheck != false)
?.map((source) => ({
value: source?.id,
label: source?.tag,
}))
);
}, [emissionSourceStore?.emissionSources]);
useEffect(() => {
if (selectedDataCenter?.emissionSourceId) {
dispatch(
getConsuptionUnits({
id: selectedDataCenter?.emissionSourceId,
sector: selectedDataCenter?.sectorId,
})
);
}
}, [selectedDataCenter?.emissionSourceId]);
useEffect(() => {
if (selectedSubSector != null) {
dispatch(getAllEmissionSources(selectedSubSector));
}
}, [selectedSubSector]);
useEffect(() => {
if (selectedSector != null) {
dispatch(getSectorById(selectedSector));
}
}, [selectedSector]);
useEffect(() => {
if (selectedSubSector != null) {
dispatch(getSubSectorById(selectedSubSector));
}
}, [selectedSubSector]);
useEffect(() => {
setConsuptionUnitsOptions(
datasStore?.consuptionUnits?.map((consuptionUnit) => ({
value: consuptionUnit?.unit?.id,
label: consuptionUnit?.unit?.description,
}))
);
}, [datasStore?.consuptionUnits]);
useEffect(() => {
setEmissionScopesOptions([
{
label: "Şehir İçi",
value: false,
},
{
label: "Şehir Dışı",
value: true,
},
]);
}, []);
const handleEditDataCenter = (row) => {
setEditingDataCenter(row);
setSelectedDataCenter({
name: row.dataCenter,
externalId: row.externalId,
number: row.number,
address: row.address,
latitude: row.latitude,
longitude: row.longitude,
ayposURL: row.ayposURL,
city: row.city,
emissionScopeId: row.emissionScope?.id,
sectorId: row.sector?.id,
subSectorId: row.subSector?.id,
emissionSourceId: row.emissionSource?.id,
consuptionUnitId: row.consuptionUnit?.id,
activitySubUnitId: row.activitySubUnit?.id
});
// Set the selected sector and sub sector for cascading dropdowns
setSelectedSector(row.sector?.id);
setSelectedSubSector(row.subSector?.id);
// Only set map position if we have both address and valid coordinates
setMapPosition(row.address && row.latitude && row.longitude ? [row.latitude, row.longitude] : null);
setShowAddModal(true);
};
const handleDeleteDataCenter = async (row) => {
const result = await Swal.fire({
title: t("Common.areYouSure"),
text: t("Common.cantRevert"),
icon: "warning",
showCancelButton: true,
confirmButtonText: t("Common.yes"),
cancelButtonText: t("Common.no"),
customClass: {
confirmButton: "btn btn-primary",
cancelButton: "btn btn-outline-danger ms-1",
},
buttonsStyling: false,
});
if (result.value) {
try {
await dispatch(deleteDataCenter(row.id));
enqueueSnackbar(t("DataCenter.deleteSuccess"), { variant: "success" });
} catch (error) {
console.error("Delete error:", error);
enqueueSnackbar(
error?.message || t("DataCenter.deleteError"),
{ variant: "error" }
);
}
}
};
const handleSubmit = async () => {
if (!selectedDataCenter.name || !selectedDataCenter.externalId) {
enqueueSnackbar(t("Common.fillRequiredFields"), { variant: "error" });
return;
}
try {
// Ensure number is set for new data centers
const dataToSubmit = {
...selectedDataCenter,
number: selectedDataCenter.number || 1, // Default to 1 if not set
city: selectedDataCenter.city, // Add city to the payload
emissionScopeId: selectedDataCenter.emissionScopeId,
sectorId: selectedDataCenter.sectorId,
subSectorId: selectedDataCenter.subSectorId,
emissionSourceId: selectedDataCenter.emissionSourceId,
consuptionUnitId: selectedDataCenter.consuptionUnitId,
activitySubUnitId: selectedDataCenter.activitySubUnitId
};
if (editingDataCenter) {
// Update existing data center
await dispatch(updateDataCenter(editingDataCenter.id, dataToSubmit));
enqueueSnackbar(t("DataCenter.updateSuccess"), { variant: "success" });
} else {
// Create new data center
await dispatch(createDataCenter(dataToSubmit));
enqueueSnackbar(t("DataCenter.createSuccess"), { variant: "success" });
}
handleCloseModal();
} catch (error) {
console.error("Submit error:", error);
enqueueSnackbar(
error?.message || t("DataCenter.submitError"),
{ variant: "error" }
);
}
};
const handleCloseModal = () => {
setShowAddModal(false);
setSelectedDataCenter({
name: "",
externalId: "",
number: "",
address: "",
latitude: null,
longitude: null,
ayposURL: "",
city: ""
});
setMapPosition(null);
setEditingDataCenter(null);
};
const handleFilter = (e) => {
setSearchValue(e.target.value);
};
const CustomPagination = () => (
setCurrentPage(page.selected + 1)}
pageCount={Math.ceil(dataCenterStore.total / rowsPerPage)}
breakLabel="..."
pageRangeDisplayed={2}
marginPagesDisplayed={2}
activeClassName="active"
pageClassName="page-item"
breakClassName="page-item"
nextLinkClassName="page-link"
pageLinkClassName="page-link"
breakLinkClassName="page-link"
previousLinkClassName="page-link"
nextClassName="page-item next-item"
previousClassName="page-item prev-item"
containerClassName="pagination react-paginate separated-pagination pagination-sm justify-content-end pe-1 mt-1"
/>
);
// Add geocoding function
const geocodeAddress = useCallback(async (address) => {
if (!address || !address.trim()) {
setMapPosition(null);
setSelectedDataCenter(prev => ({
...prev,
latitude: null,
longitude: null
}));
return false;
}
try {
const response = await nominatimAxios.get(`/search?format=json&q=${encodeURIComponent(address)}`);
if (response.data && response.data[0]) {
const { lat, lon } = response.data[0];
const newPosition = [parseFloat(lat), parseFloat(lon)];
setMapPosition(newPosition);
setSelectedDataCenter(prev => ({
...prev,
latitude: parseFloat(lat),
longitude: parseFloat(lon)
}));
return true;
}
// If no results found, clear the coordinates
setMapPosition(null);
setSelectedDataCenter(prev => ({
...prev,
latitude: null,
longitude: null
}));
return false;
} catch (error) {
console.error('Error geocoding address:', error);
// On error, clear the coordinates
setMapPosition(null);
setSelectedDataCenter(prev => ({
...prev,
latitude: null,
longitude: null
}));
return false;
}
}, []);
// Update the address input handler
const handleAddressChange = (e) => {
const newAddress = e.target.value;
setSelectedDataCenter(prev => ({
...prev,
address: newAddress
}));
// If address is empty, clear the coordinates
if (!newAddress.trim()) {
setMapPosition(null);
setSelectedDataCenter(prev => ({
...prev,
latitude: null,
longitude: null
}));
} else {
// If address is not empty, try to geocode it after a delay
debouncedGeocode(newAddress);
}
};
// Debounced version of geocoding
const debouncedGeocode = useCallback(
debounce((address) => geocodeAddress(address), 1000),
[geocodeAddress]
);
const renderModal = () => {
return (
{editingDataCenter
? t("DataCenter.edit")
: t("DataCenter.add")}
);
};
return (
{t("DataCenter.title")}
{permissionCheck("datacenter_create") && (
)}
setRowsPerPage(Number(e.target.value))}
>
}
paginationDefaultPage={currentPage}
paginationComponent={CustomPagination}
data={dataCenterStore.dataCenters}
noDataComponent={
{t("Common.noDataAvailable")}
}
/>
{renderModal()}
);
};
export default memo(DataCenterManagement);