Files
sgeUpdated/src/views/DataCenterManagement.js

967 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { getAreas } from "../redux/actions/areas";
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] ? <Marker position={position} /> : 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: "",
areaId: null,
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 areasStore = useSelector((state) => state.areas);
const datasStore = useSelector((state) => state.datas);
const emissionSourceStore = useSelector((state) => state.emissionSources);
const [areasOptions, setAreasOptions] = useState([]);
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) => <span>{row.externalId}</span>,
},
{
name: t("DataCenter.name"),
selector: (row) => row.dataCenter,
sortable: true,
minWidth: "200px",
},
{
name: t("DataCenter.city"),
selector: (row) => row.area?.cityNames,
sortable: true,
minWidth: "150px",
cell: (row) => <span>{row.area?.cityNames || "-"}</span>,
},
{
name: "AYPOS",
selector: (row) => row.ayposURL,
sortable: false,
minWidth: "150px",
cell: (row) => (
<div className="d-flex justify-content-center w-100">
{row.ayposURL ? (
<Button
color="primary"
size="sm"
onClick={() => window.open(row.ayposURL, '_blank')}
>
AYPOS
</Button>
) : (
<span>-</span>
)}
</div>
),
},
{
name: t("Actions"),
allowOverflow: false,
maxWidth: "150px",
cell: (row) => {
return (
<div className="d-flex">
<UncontrolledDropdown>
<DropdownToggle className="pl-1" tag="span">
<MoreVertical size={15} />
</DropdownToggle>
<DropdownMenu container={"body"} end>
{permissionCheck("datacenter_update") && (
<DropdownItem
tag="a"
className="w-100"
onClick={() => handleEditDataCenter(row)}
>
<Edit size={15} />
<span className="align-middle ml-50">{t("Cruds.edit")}</span>
</DropdownItem>
)}
{permissionCheck("datacenter_delete") && (
<DropdownItem
tag="a"
className="w-100"
onClick={() => handleDeleteDataCenter(row)}
>
<Trash size={15} />
<span className="align-middle ml-50">{t("Cruds.delete")}</span>
</DropdownItem>
)}
</DropdownMenu>
</UncontrolledDropdown>
</div>
);
},
},
];
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
useEffect(() => {
dispatch(getDataCenters());
dispatch(getAreas());
dispatch(getSectors());
}, [dispatch]);
useEffect(() => {
setAreasOptions(
areasStore?.areas?.map((area) => ({
value: area?.id,
label: area?.tag,
}))
);
}, [areasStore]);
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,
areaId: row.area?.id,
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: "",
areaId: null,
address: "",
latitude: null,
longitude: null,
ayposURL: "",
city: ""
});
setMapPosition(null);
setEditingDataCenter(null);
};
const handleFilter = (e) => {
setSearchValue(e.target.value);
};
const CustomPagination = () => (
<ReactPaginate
previousLabel=""
nextLabel=""
forcePage={currentPage - 1}
onPageChange={(page) => 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 (
<Modal
isOpen={showAddModal}
toggle={handleCloseModal}
className="modal-dialog-centered"
size="lg"
>
<ModalHeader toggle={handleCloseModal}>
{editingDataCenter
? t("DataCenter.edit")
: t("DataCenter.add")}
</ModalHeader>
<ModalBody>
<Form>
<Row>
<Col sm="12">
<FormGroup>
<Label for="name">
{t("DataCenter.name")} <span className="text-danger">*</span>
</Label>
<Input
type="text"
name="name"
id="name"
placeholder={t("DataCenter.name")}
value={selectedDataCenter.name}
onChange={(e) =>
setSelectedDataCenter({
...selectedDataCenter,
name: e.target.value,
})
}
/>
</FormGroup>
</Col>
<Col sm="6">
<FormGroup>
<Label for="externalId">
{t("DataCenter.externalId")} <span className="text-danger">*</span>
</Label>
<Input
type="text"
name="externalId"
id="externalId"
placeholder={t("DataCenter.externalId")}
value={selectedDataCenter.externalId}
onChange={(e) =>
setSelectedDataCenter({
...selectedDataCenter,
externalId: e.target.value,
})
}
/>
</FormGroup>
</Col>
<Col sm="6">
<FormGroup>
<Label for="ayposURL">{t("DataCenter.ayposURL")}</Label>
<Input
type="text"
name="ayposURL"
id="ayposURL"
placeholder={t("DataCenter.ayposURL")}
value={selectedDataCenter.ayposURL}
onChange={(e) =>
setSelectedDataCenter({
...selectedDataCenter,
ayposURL: e.target.value,
})
}
/>
</FormGroup>
</Col>
<Col sm="6">
<FormGroup>
<Label for="city">{t("DataCenter.city")}</Label>
<Input
type="text"
name="city"
id="city"
placeholder={t("DataCenter.city")}
value={selectedDataCenter.city}
onChange={(e) =>
setSelectedDataCenter({
...selectedDataCenter,
city: e.target.value,
})
}
/>
</FormGroup>
</Col>
<Col sm="12">
<FormGroup>
<Label for="area">{t("DataCenter.area")}</Label>
<Select
id="area"
name="area"
placeholder={t("DataCenter.selectArea")}
options={areasOptions}
value={areasOptions?.find(
(option) => option.value === selectedDataCenter.areaId
)}
onChange={(option) =>
setSelectedDataCenter({
...selectedDataCenter,
areaId: option?.value,
})
}
isClearable
filterOption={customFilterForSelect}
/>
</FormGroup>
</Col>
<Col sm="12">
<FormGroup>
<Label for="address">{t("DataCenter.address")}</Label>
<div className="d-flex">
<Input
type="text"
name="address"
id="address"
placeholder={t("DataCenter.address")}
value={selectedDataCenter.address}
onChange={handleAddressChange}
/>
<Button
color="primary"
className="ml-1"
onClick={() => {
if (selectedDataCenter.address) {
geocodeAddress(selectedDataCenter.address);
}
}}
>
{t("DataCenter.findOnMap")}
</Button>
</div>
</FormGroup>
</Col>
{/* Emission Scope Section */}
<Col sm="12">
<h5 className="mt-3 mb-2 text-primary">Emission Scope Configuration</h5>
</Col>
<Col sm="6">
<FormGroup>
<Label for="emissionScope">Emission Scope</Label>
<Select
id="emissionScope"
name="emissionScope"
placeholder="Select emission scope"
options={emissionScopesOptions}
value={emissionScopesOptions?.find(
(option) => option.value === selectedDataCenter.emissionScopeId
)}
onChange={(option) =>
setSelectedDataCenter({
...selectedDataCenter,
emissionScopeId: option?.value,
})
}
isClearable
filterOption={customFilterForSelect}
/>
</FormGroup>
</Col>
<Col sm="6">
<FormGroup>
<Label for="sector">Sector</Label>
<Select
id="sector"
name="sector"
placeholder="Select sector"
options={sectorsOptions}
value={sectorsOptions?.find(
(option) => 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}
/>
</FormGroup>
</Col>
<Col sm="6">
<FormGroup>
<Label for="subSector">Sub Sector</Label>
<Select
id="subSector"
name="subSector"
placeholder="Select sub sector"
options={subSectorsOptions}
value={subSectorsOptions?.find(
(option) => option.value === selectedDataCenter.subSectorId
)}
onChange={(option) => {
setSelectedSubSector(option?.value);
setSelectedDataCenter({
...selectedDataCenter,
subSectorId: option?.value,
emissionSourceId: null,
consuptionUnitId: null,
activitySubUnitId: null,
});
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!selectedDataCenter.sectorId}
/>
</FormGroup>
</Col>
<Col sm="6">
<FormGroup>
<Label for="emissionSource">Emission Source</Label>
<Select
id="emissionSource"
name="emissionSource"
placeholder="Select emission source"
options={emissionSourcesOptions}
value={emissionSourcesOptions?.find(
(option) => option.value === selectedDataCenter.emissionSourceId
)}
onChange={(option) => {
setSelectedDataCenter({
...selectedDataCenter,
emissionSourceId: option?.value,
consuptionUnitId: null,
});
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!selectedDataCenter.subSectorId}
/>
</FormGroup>
</Col>
<Col sm="6">
<FormGroup>
<Label for="consuptionUnit">Consumption Unit</Label>
<Select
id="consuptionUnit"
name="consuptionUnit"
placeholder="Select consumption unit"
options={consuptionUnitsOptions}
value={consuptionUnitsOptions?.find(
(option) => option.value === selectedDataCenter.consuptionUnitId
)}
onChange={(option) => {
setSelectedDataCenter({
...selectedDataCenter,
consuptionUnitId: option?.value,
});
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!selectedDataCenter.emissionSourceId}
/>
</FormGroup>
</Col>
<Col sm="12">
<FormGroup>
<Label for="activitySubUnit">Activity Sub Unit</Label>
<Select
id="activitySubUnit"
name="activitySubUnit"
placeholder="Select activity sub unit"
options={activitySubUnitsOptions}
value={activitySubUnitsOptions?.find(
(option) => option.value === selectedDataCenter.activitySubUnitId
)}
onChange={(option) => {
setSelectedDataCenter({
...selectedDataCenter,
activitySubUnitId: option?.value,
});
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!selectedDataCenter.subSectorId}
/>
</FormGroup>
</Col>
<Col sm="12">
<FormGroup>
<Label>{t("DataCenter.location")}</Label>
<div style={{ height: "400px", width: "100%" }}>
<MapContainer
center={mapPosition || [39.9334, 32.8597]} // Default to Ankara
zoom={13}
style={{ height: "100%", width: "100%" }}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
/>
<MapMarker
position={mapPosition}
setPosition={(pos) => {
setMapPosition(pos);
setSelectedDataCenter({
...selectedDataCenter,
latitude: pos[0],
longitude: pos[1]
});
}}
setSelectedDataCenter={setSelectedDataCenter}
/>
</MapContainer>
</div>
</FormGroup>
</Col>
</Row>
</Form>
</ModalBody>
<ModalFooter>
<Button
color="primary"
onClick={handleSubmit}
disabled={!selectedDataCenter.name || !selectedDataCenter.externalId}
>
{t("Common.save")}
</Button>
<Button
color="secondary"
onClick={handleCloseModal}
>
{t("Common.cancel")}
</Button>
</ModalFooter>
</Modal>
);
};
return (
<Card>
<CardHeader className="border-bottom">
<CardTitle tag="h4">{t("DataCenter.title")}</CardTitle>
{permissionCheck("datacenter_create") && (
<Button
className="ml-2"
color="primary"
onClick={() => setShowAddModal(true)}
>
<Plus size={15} />
<span className="align-middle ml-50">{t("DataCenter.create")}</span>
</Button>
)}
</CardHeader>
<Row className="mx-0 mt-1 mb-50">
<Col sm="6">
<div className="d-flex align-items-center">
<Label for="sort-select">{t("Show")}</Label>
<Input
className="dataTable-select"
type="select"
id="sort-select"
value={rowsPerPage}
onChange={(e) => setRowsPerPage(Number(e.target.value))}
>
<option value={10}>10</option>
<option value={25}>25</option>
<option value={50}>50</option>
<option value={75}>75</option>
<option value={100}>100</option>
</Input>
</div>
</Col>
<Col
className="d-flex align-items-center justify-content-sm-end mt-sm-0 mt-1"
sm="6"
>
<Label className="me-1" for="search-input">
{t("Filter")}
</Label>
<Input
className="dataTable-filter"
type="text"
bsSize="sm"
id="search-input"
value={searchValue}
onChange={handleFilter}
placeholder={t("DataCenter.searchPlaceholder")}
/>
</Col>
</Row>
<div className="react-dataTable">
<DataTable
noHeader
pagination
columns={serverSideColumns}
paginationPerPage={rowsPerPage}
className="react-dataTable"
sortIcon={<ChevronDown size={10} />}
paginationDefaultPage={currentPage}
paginationComponent={CustomPagination}
data={dataCenterStore.dataCenters}
noDataComponent={
<div className="p-2 text-center">
{t("Common.noDataAvailable")}
</div>
}
/>
</div>
{renderModal()}
</Card>
);
};
export default memo(DataCenterManagement);