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")}
setSelectedDataCenter({ ...selectedDataCenter, name: e.target.value, }) } /> setSelectedDataCenter({ ...selectedDataCenter, externalId: e.target.value, }) } /> setSelectedDataCenter({ ...selectedDataCenter, ayposURL: e.target.value, }) } /> setSelectedDataCenter({ ...selectedDataCenter, city: e.target.value, }) } />
{/* Emission Scope Section */}
Emission Scope Configuration
option.value === selectedDataCenter.sectorId )} onChange={(option) => { setSelectedSector(option?.value); setSelectedDataCenter({ ...selectedDataCenter, sectorId: option?.value, subSectorId: null, emissionSourceId: null, consuptionUnitId: null, activitySubUnitId: null, }); }} isClearable filterOption={customFilterForSelect} /> option.value === selectedDataCenter.emissionSourceId )} onChange={(option) => { setSelectedDataCenter({ ...selectedDataCenter, emissionSourceId: option?.value, consuptionUnitId: null, }); }} isClearable filterOption={customFilterForSelect} isDisabled={!selectedDataCenter.subSectorId} /> option.value === selectedDataCenter.activitySubUnitId )} onChange={(option) => { setSelectedDataCenter({ ...selectedDataCenter, activitySubUnitId: option?.value, }); }} isClearable filterOption={customFilterForSelect} isDisabled={!selectedDataCenter.subSectorId} />
{ setMapPosition(pos); setSelectedDataCenter({ ...selectedDataCenter, latitude: pos[0], longitude: pos[1] }); }} setSelectedDataCenter={setSelectedDataCenter} />
); }; 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);