forked from BLC/sgeUpdated
Add datacenter with multiple emission source and inputs
This commit is contained in:
@@ -28,26 +28,37 @@ 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, getEmissionScopes } from "../redux/actions/dataCenter";
|
||||
import {
|
||||
getDataCenters,
|
||||
createDataCenter,
|
||||
updateDataCenter,
|
||||
deleteDataCenter,
|
||||
getEmissionScopes,
|
||||
} from "../redux/actions/dataCenter";
|
||||
import { getAreas, getAreasWithCriteria } from "../redux/actions/areas";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getSectors, getSectorById, getSubSectorById, getConsuptionUnits } from "../redux/actions/datas";
|
||||
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';
|
||||
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 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
|
||||
}
|
||||
"User-Agent": "SGE-DataCenter-Management", // Required by Nominatim's usage policy
|
||||
},
|
||||
});
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
@@ -55,9 +66,9 @@ 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')
|
||||
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
|
||||
@@ -66,30 +77,33 @@ const MapMarker = ({ position, setPosition, setSelectedDataCenter }) => {
|
||||
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 => {
|
||||
nominatimAxios
|
||||
.get(`/reverse?format=json&lat=${e.latlng.lat}&lon=${e.latlng.lng}`)
|
||||
.then((response) => {
|
||||
const address = response.data.display_name;
|
||||
setSelectedDataCenter(prev => ({
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
address,
|
||||
latitude: e.latlng.lat,
|
||||
longitude: e.latlng.lng
|
||||
longitude: e.latlng.lng,
|
||||
}));
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error getting address:', error);
|
||||
.catch((error) => {
|
||||
console.error("Error getting address:", error);
|
||||
// Still update coordinates even if address lookup fails
|
||||
setSelectedDataCenter(prev => ({
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
latitude: e.latlng.lat,
|
||||
longitude: e.latlng.lng
|
||||
longitude: e.latlng.lng,
|
||||
}));
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Only render marker if position exists and has valid coordinates
|
||||
return position && position[0] && position[1] ? <Marker position={position} /> : null;
|
||||
return position && position[0] && position[1] ? (
|
||||
<Marker position={position} />
|
||||
) : null;
|
||||
};
|
||||
|
||||
const DataCenterManagement = () => {
|
||||
@@ -115,15 +129,14 @@ const DataCenterManagement = () => {
|
||||
emissionScopeId: null,
|
||||
sectorId: null,
|
||||
subSectorId: null,
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
activitySubUnitId: null
|
||||
dataCenterEmissionSources: [], // Array of emission sources with consumption units
|
||||
activitySubUnitId: null,
|
||||
});
|
||||
|
||||
const [mapPosition, setMapPosition] = useState(null);
|
||||
|
||||
const dataCenterStore = useSelector((state) => {
|
||||
console.log('DataCenter Store:', state.dataCenter);
|
||||
console.log("DataCenter Store:", state.dataCenter);
|
||||
return state.dataCenter;
|
||||
});
|
||||
const emissionScopeStore = useSelector((state) => state.emissionScope);
|
||||
@@ -138,7 +151,7 @@ const DataCenterManagement = () => {
|
||||
const [emissionScopesOptions, setEmissionScopesOptions] = useState([]);
|
||||
const [areasOptions, setAreasOptions] = useState([]);
|
||||
const [citiesOptions, setCitiesOptions] = useState([]);
|
||||
|
||||
|
||||
// Add state for selected sector and sub sector like in data input
|
||||
const [selectedSector, setSelectedSector] = useState(null);
|
||||
const [selectedSubSector, setSelectedSubSector] = useState(null);
|
||||
@@ -170,7 +183,7 @@ const DataCenterManagement = () => {
|
||||
<Button
|
||||
color="primary"
|
||||
size="sm"
|
||||
onClick={() => window.open(row.ayposURL, '_blank')}
|
||||
onClick={() => window.open(row.ayposURL, "_blank")}
|
||||
>
|
||||
Dashboard
|
||||
</Button>
|
||||
@@ -200,7 +213,9 @@ const DataCenterManagement = () => {
|
||||
onClick={() => handleEditDataCenter(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">{t("Cruds.edit")}</span>
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.edit")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("data_center_delete") && (
|
||||
@@ -210,7 +225,9 @@ const DataCenterManagement = () => {
|
||||
onClick={() => handleDeleteDataCenter(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">{t("Cruds.delete")}</span>
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
@@ -269,16 +286,41 @@ const DataCenterManagement = () => {
|
||||
);
|
||||
}, [emissionSourceStore?.emissionSources]);
|
||||
|
||||
// Remove the old emission source effect since we now handle it in the emission sources component
|
||||
// useEffect(() => {
|
||||
// if (selectedDataCenter?.emissionSourceId) {
|
||||
// dispatch(
|
||||
// getConsuptionUnits({
|
||||
// id: selectedDataCenter?.emissionSourceId,
|
||||
// sector: selectedDataCenter?.sectorId,
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
// }, [selectedDataCenter?.emissionSourceId]);
|
||||
|
||||
// Ensure there's always at least one emission source entry for new data centers
|
||||
useEffect(() => {
|
||||
if (selectedDataCenter?.emissionSourceId) {
|
||||
dispatch(
|
||||
getConsuptionUnits({
|
||||
id: selectedDataCenter?.emissionSourceId,
|
||||
sector: selectedDataCenter?.sectorId,
|
||||
})
|
||||
);
|
||||
if (
|
||||
showAddModal &&
|
||||
!editingDataCenter &&
|
||||
selectedDataCenter.dataCenterEmissionSources.length === 0
|
||||
) {
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
dataCenterEmissionSources: [
|
||||
{
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
}, [selectedDataCenter?.emissionSourceId]);
|
||||
}, [
|
||||
showAddModal,
|
||||
editingDataCenter,
|
||||
selectedDataCenter.dataCenterEmissionSources.length,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSubSector != null) {
|
||||
@@ -352,8 +394,18 @@ const DataCenterManagement = () => {
|
||||
}, [selectedDataCenter.areaId, areasStore?.areas]);
|
||||
|
||||
const handleEditDataCenter = (row) => {
|
||||
console.log('Editing data center:', row);
|
||||
console.log("Editing data center:", row);
|
||||
setEditingDataCenter(row);
|
||||
|
||||
// Convert dataCenterEmissionSources to the format expected by the form
|
||||
const emissionSources = row.dataCenterEmissionSources
|
||||
? row.dataCenterEmissionSources.map((dces) => ({
|
||||
emissionSourceId: dces.emissionSource?.id,
|
||||
consuptionUnitId: dces.consuptionUnit?.id,
|
||||
isDefault: dces.isDefault || false,
|
||||
}))
|
||||
: [];
|
||||
|
||||
setSelectedDataCenter({
|
||||
name: row.dataCenter,
|
||||
externalId: row.externalId?.toString(),
|
||||
@@ -368,17 +420,20 @@ const DataCenterManagement = () => {
|
||||
emissionScopeId: row.emissionScope?.id || null,
|
||||
sectorId: row.sector?.id || null,
|
||||
subSectorId: row.subSector?.id || null,
|
||||
emissionSourceId: row.emissionSource?.id || null,
|
||||
consuptionUnitId: row.consuptionUnit?.id || null,
|
||||
activitySubUnitId: row.activitySubUnit?.id || null
|
||||
dataCenterEmissionSources: emissionSources,
|
||||
activitySubUnitId: row.activitySubUnit?.id || null,
|
||||
});
|
||||
|
||||
|
||||
// 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);
|
||||
setMapPosition(
|
||||
row.address && row.latitude && row.longitude
|
||||
? [row.latitude, row.longitude]
|
||||
: null
|
||||
);
|
||||
setShowAddModal(true);
|
||||
};
|
||||
|
||||
@@ -403,17 +458,16 @@ const DataCenterManagement = () => {
|
||||
enqueueSnackbar(t("DataCenter.deleteSuccess"), { variant: "success" });
|
||||
} catch (error) {
|
||||
console.error("Delete error:", error);
|
||||
enqueueSnackbar(
|
||||
error?.message || t("DataCenter.deleteError"),
|
||||
{ variant: "error" }
|
||||
);
|
||||
enqueueSnackbar(error?.message || t("DataCenter.deleteError"), {
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
const errors = [];
|
||||
|
||||
|
||||
// Required fields validation
|
||||
if (!selectedDataCenter.name?.trim()) {
|
||||
errors.push(t("DataCenter.nameRequired"));
|
||||
@@ -448,7 +502,7 @@ const DataCenterManagement = () => {
|
||||
try {
|
||||
const lat = parseFloat(selectedDataCenter.latitude);
|
||||
const lng = parseFloat(selectedDataCenter.longitude);
|
||||
|
||||
|
||||
if (isNaN(lat) || lat < -90 || lat > 90) {
|
||||
errors.push(t("DataCenter.invalidLatitude"));
|
||||
}
|
||||
@@ -473,23 +527,60 @@ const DataCenterManagement = () => {
|
||||
if (selectedDataCenter.subSectorId && !selectedDataCenter.sectorId) {
|
||||
errors.push(t("DataCenter.sectorRequired"));
|
||||
}
|
||||
if (selectedDataCenter.emissionSourceId && !selectedDataCenter.subSectorId) {
|
||||
errors.push(t("DataCenter.subSectorRequired"));
|
||||
}
|
||||
if (selectedDataCenter.consuptionUnitId && !selectedDataCenter.emissionSourceId) {
|
||||
errors.push(t("DataCenter.emissionSourceRequired"));
|
||||
}
|
||||
if (selectedDataCenter.activitySubUnitId && !selectedDataCenter.subSectorId) {
|
||||
if (
|
||||
selectedDataCenter.activitySubUnitId &&
|
||||
!selectedDataCenter.subSectorId
|
||||
) {
|
||||
errors.push(t("DataCenter.subSectorRequiredForActivity"));
|
||||
}
|
||||
|
||||
// Emission sources validations
|
||||
if (selectedDataCenter.dataCenterEmissionSources.length > 0) {
|
||||
const validSources = selectedDataCenter.dataCenterEmissionSources.filter(
|
||||
(source) => source.emissionSourceId && source.consuptionUnitId
|
||||
);
|
||||
|
||||
if (validSources.length === 0) {
|
||||
errors.push(t("DataCenter.atLeastOneEmissionSource"));
|
||||
}
|
||||
|
||||
// Check for incomplete emission sources
|
||||
selectedDataCenter.dataCenterEmissionSources.forEach((source, index) => {
|
||||
if (
|
||||
(source.emissionSourceId && !source.consuptionUnitId) ||
|
||||
(!source.emissionSourceId && source.consuptionUnitId)
|
||||
) {
|
||||
errors.push(
|
||||
t("DataCenter.incompleteEmissionSource", { index: index + 1 })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Check for duplicate emission sources
|
||||
const sourceIds = validSources.map((s) => s.emissionSourceId);
|
||||
const duplicates = sourceIds.filter(
|
||||
(id, index) => sourceIds.indexOf(id) !== index
|
||||
);
|
||||
if (duplicates.length > 0) {
|
||||
errors.push(t("DataCenter.duplicateEmissionSources"));
|
||||
}
|
||||
|
||||
// Ensure exactly one default emission source
|
||||
const defaultSources = validSources.filter((s) => s.isDefault);
|
||||
if (defaultSources.length === 0) {
|
||||
errors.push(t("DataCenter.oneDefaultEmissionSourceRequired"));
|
||||
} else if (defaultSources.length > 1) {
|
||||
errors.push(t("DataCenter.onlyOneDefaultEmissionSourceAllowed"));
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const validationErrors = validateForm();
|
||||
if (validationErrors.length > 0) {
|
||||
validationErrors.forEach(error => {
|
||||
validationErrors.forEach((error) => {
|
||||
enqueueSnackbar(error, { variant: "error" });
|
||||
});
|
||||
return;
|
||||
@@ -498,20 +589,26 @@ const DataCenterManagement = () => {
|
||||
try {
|
||||
// Format data according to GraphQL input type
|
||||
const dataToSubmit = {
|
||||
dataCenter: selectedDataCenter.name,
|
||||
name: selectedDataCenter.name,
|
||||
externalId: parseInt(selectedDataCenter.externalId),
|
||||
number: parseInt(selectedDataCenter.number || "1"),
|
||||
address: selectedDataCenter.address,
|
||||
areaId: selectedDataCenter.areaId,
|
||||
latitude: selectedDataCenter.latitude ? parseFloat(selectedDataCenter.latitude) : null,
|
||||
longitude: selectedDataCenter.longitude ? parseFloat(selectedDataCenter.longitude) : null,
|
||||
latitude: selectedDataCenter.latitude
|
||||
? parseFloat(selectedDataCenter.latitude)
|
||||
: null,
|
||||
longitude: selectedDataCenter.longitude
|
||||
? parseFloat(selectedDataCenter.longitude)
|
||||
: null,
|
||||
ayposURL: selectedDataCenter.ayposURL,
|
||||
emissionScopeId: selectedDataCenter.emissionScopeId,
|
||||
sectorId: selectedDataCenter.sectorId,
|
||||
subSectorId: selectedDataCenter.subSectorId,
|
||||
emissionSourceId: selectedDataCenter.emissionSourceId,
|
||||
consuptionUnitId: selectedDataCenter.consuptionUnitId,
|
||||
activitySubUnitId: selectedDataCenter.activitySubUnitId
|
||||
dataCenterEmissionSources:
|
||||
selectedDataCenter.dataCenterEmissionSources.filter(
|
||||
(source) => source.emissionSourceId && source.consuptionUnitId
|
||||
),
|
||||
activitySubUnitId: selectedDataCenter.activitySubUnitId,
|
||||
};
|
||||
|
||||
if (editingDataCenter) {
|
||||
@@ -523,26 +620,27 @@ const DataCenterManagement = () => {
|
||||
await dispatch(createDataCenter(dataToSubmit));
|
||||
enqueueSnackbar(t("DataCenter.createSuccess"), { variant: "success" });
|
||||
}
|
||||
|
||||
|
||||
// Refresh the data centers list
|
||||
await dispatch(getDataCenters());
|
||||
|
||||
handleCloseModal();
|
||||
} catch (error) {
|
||||
console.error("Submit error:", error);
|
||||
|
||||
|
||||
// Handle specific error cases
|
||||
if (error.message?.includes("duplicate")) {
|
||||
enqueueSnackbar(t("DataCenter.duplicateExternalId"), { variant: "error" });
|
||||
enqueueSnackbar(t("DataCenter.duplicateExternalId"), {
|
||||
variant: "error",
|
||||
});
|
||||
} else if (error.message?.includes("permission")) {
|
||||
enqueueSnackbar(t("Common.noPermission"), { variant: "error" });
|
||||
} else if (error.message?.includes("not found")) {
|
||||
enqueueSnackbar(t("DataCenter.resourceNotFound"), { variant: "error" });
|
||||
} else {
|
||||
enqueueSnackbar(
|
||||
error?.message || t("DataCenter.submitError"),
|
||||
{ variant: "error" }
|
||||
);
|
||||
enqueueSnackbar(error?.message || t("DataCenter.submitError"), {
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -554,13 +652,28 @@ const DataCenterManagement = () => {
|
||||
externalId: "",
|
||||
number: "",
|
||||
address: "",
|
||||
areaId: null,
|
||||
cityId: null,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
ayposURL: "",
|
||||
city: ""
|
||||
city: "",
|
||||
emissionScopeId: null,
|
||||
sectorId: null,
|
||||
subSectorId: null,
|
||||
dataCenterEmissionSources: [
|
||||
{
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
activitySubUnitId: null,
|
||||
});
|
||||
setMapPosition(null);
|
||||
setEditingDataCenter(null);
|
||||
setSelectedSector(null);
|
||||
setSelectedSubSector(null);
|
||||
};
|
||||
|
||||
const handleFilter = (e) => {
|
||||
@@ -594,45 +707,47 @@ const DataCenterManagement = () => {
|
||||
const geocodeAddress = useCallback(async (address) => {
|
||||
if (!address || !address.trim()) {
|
||||
setMapPosition(null);
|
||||
setSelectedDataCenter(prev => ({
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
latitude: null,
|
||||
longitude: null
|
||||
longitude: null,
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await nominatimAxios.get(`/search?format=json&q=${encodeURIComponent(address)}`);
|
||||
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 => ({
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
latitude: parseFloat(lat),
|
||||
longitude: parseFloat(lon)
|
||||
longitude: parseFloat(lon),
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no results found, clear the coordinates
|
||||
setMapPosition(null);
|
||||
setSelectedDataCenter(prev => ({
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
latitude: null,
|
||||
longitude: null
|
||||
longitude: null,
|
||||
}));
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error geocoding address:', error);
|
||||
console.error("Error geocoding address:", error);
|
||||
// On error, clear the coordinates
|
||||
setMapPosition(null);
|
||||
setSelectedDataCenter(prev => ({
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
latitude: null,
|
||||
longitude: null
|
||||
longitude: null,
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
@@ -641,18 +756,18 @@ const DataCenterManagement = () => {
|
||||
// Update the address input handler
|
||||
const handleAddressChange = (e) => {
|
||||
const newAddress = e.target.value;
|
||||
setSelectedDataCenter(prev => ({
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
address: newAddress
|
||||
address: newAddress,
|
||||
}));
|
||||
|
||||
// If address is empty, clear the coordinates
|
||||
if (!newAddress.trim()) {
|
||||
setMapPosition(null);
|
||||
setSelectedDataCenter(prev => ({
|
||||
setSelectedDataCenter((prev) => ({
|
||||
...prev,
|
||||
latitude: null,
|
||||
longitude: null
|
||||
longitude: null,
|
||||
}));
|
||||
} else {
|
||||
// If address is not empty, try to geocode it after a delay
|
||||
@@ -675,9 +790,7 @@ const DataCenterManagement = () => {
|
||||
size="lg"
|
||||
>
|
||||
<ModalHeader toggle={handleCloseModal}>
|
||||
{editingDataCenter
|
||||
? t("DataCenter.edit")
|
||||
: t("DataCenter.add")}
|
||||
{editingDataCenter ? t("DataCenter.edit") : t("DataCenter.add")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
@@ -685,7 +798,8 @@ const DataCenterManagement = () => {
|
||||
<Col sm="12">
|
||||
<FormGroup>
|
||||
<Label for="name">
|
||||
{t("DataCenter.name")} <span className="text-danger">*</span>
|
||||
{t("DataCenter.name")}{" "}
|
||||
<span className="text-danger">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -705,7 +819,8 @@ const DataCenterManagement = () => {
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="externalId">
|
||||
{t("DataCenter.externalId")} <span className="text-danger">*</span>
|
||||
{t("DataCenter.externalId")}{" "}
|
||||
<span className="text-danger">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -814,10 +929,12 @@ const DataCenterManagement = () => {
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
|
||||
{/* Emission Scope Section */}
|
||||
<Col sm="12">
|
||||
<h5 className="mt-3 mb-2 text-primary">Emission Scope Configuration</h5>
|
||||
<h5 className="mt-3 mb-2 text-primary">
|
||||
Emission Scope Configuration
|
||||
</h5>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
@@ -828,7 +945,8 @@ const DataCenterManagement = () => {
|
||||
placeholder="Select emission scope"
|
||||
options={emissionScopesOptions}
|
||||
value={emissionScopesOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.emissionScopeId
|
||||
(option) =>
|
||||
option.value === selectedDataCenter.emissionScopeId
|
||||
)}
|
||||
onChange={(option) =>
|
||||
setSelectedDataCenter({
|
||||
@@ -843,7 +961,9 @@ const DataCenterManagement = () => {
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="sector">Sector <span className="text-danger">*</span></Label>
|
||||
<Label for="sector">
|
||||
Sector <span className="text-danger">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
id="sector"
|
||||
name="sector"
|
||||
@@ -858,8 +978,13 @@ const DataCenterManagement = () => {
|
||||
...selectedDataCenter,
|
||||
sectorId: option?.value,
|
||||
subSectorId: null,
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
dataCenterEmissionSources: [
|
||||
{
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
activitySubUnitId: null,
|
||||
});
|
||||
}}
|
||||
@@ -877,15 +1002,21 @@ const DataCenterManagement = () => {
|
||||
placeholder="Select sub sector"
|
||||
options={subSectorsOptions}
|
||||
value={subSectorsOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.subSectorId
|
||||
(option) =>
|
||||
option.value === selectedDataCenter.subSectorId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
setSelectedSubSector(option?.value);
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
subSectorId: option?.value,
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
dataCenterEmissionSources: [
|
||||
{
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
activitySubUnitId: null,
|
||||
});
|
||||
}}
|
||||
@@ -895,51 +1026,191 @@ const DataCenterManagement = () => {
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<Col sm="12">
|
||||
<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
|
||||
<Label>Emission Sources & Consumption Units</Label>
|
||||
<div className="border p-3 rounded bg-light">
|
||||
<small className="text-muted mb-2 d-block">
|
||||
Configure emission sources for this data center. At least
|
||||
one emission source with consumption unit is required.
|
||||
</small>
|
||||
{selectedDataCenter.dataCenterEmissionSources.map(
|
||||
(source, index) => (
|
||||
<Row key={index} className="mb-2 align-items-end">
|
||||
<Col sm="5">
|
||||
<Label
|
||||
for={`emissionSource-${index}`}
|
||||
className="form-label"
|
||||
>
|
||||
Emission Source *
|
||||
</Label>
|
||||
<Select
|
||||
id={`emissionSource-${index}`}
|
||||
placeholder="Select emission source..."
|
||||
options={emissionSourcesOptions}
|
||||
value={emissionSourcesOptions?.find(
|
||||
(option) =>
|
||||
option.value === source.emissionSourceId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
const updatedSources = [
|
||||
...selectedDataCenter.dataCenterEmissionSources,
|
||||
];
|
||||
updatedSources[index] = {
|
||||
...updatedSources[index],
|
||||
emissionSourceId: option?.value,
|
||||
consuptionUnitId: null, // Reset consumption unit when emission source changes
|
||||
};
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
dataCenterEmissionSources: updatedSources,
|
||||
});
|
||||
// Fetch consumption units for the selected emission source
|
||||
if (option?.value) {
|
||||
dispatch(
|
||||
getConsuptionUnits({
|
||||
id: option.value,
|
||||
sector: selectedDataCenter?.sectorId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
isClearable
|
||||
filterOption={customFilterForSelect}
|
||||
isDisabled={!selectedDataCenter.subSectorId}
|
||||
styles={{
|
||||
placeholder: (provided) => ({
|
||||
...provided,
|
||||
color: "#6e6b7b",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col sm="4">
|
||||
<Label
|
||||
for={`consuptionUnit-${index}`}
|
||||
className="form-label"
|
||||
>
|
||||
Consumption Unit *
|
||||
</Label>
|
||||
<Select
|
||||
id={`consuptionUnit-${index}`}
|
||||
placeholder="Select consumption unit..."
|
||||
options={consuptionUnitsOptions}
|
||||
value={consuptionUnitsOptions?.find(
|
||||
(option) =>
|
||||
option.value === source.consuptionUnitId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
const updatedSources = [
|
||||
...selectedDataCenter.dataCenterEmissionSources,
|
||||
];
|
||||
updatedSources[index] = {
|
||||
...updatedSources[index],
|
||||
consuptionUnitId: option?.value,
|
||||
};
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
dataCenterEmissionSources: updatedSources,
|
||||
});
|
||||
}}
|
||||
isClearable
|
||||
filterOption={customFilterForSelect}
|
||||
isDisabled={!source.emissionSourceId}
|
||||
styles={{
|
||||
placeholder: (provided) => ({
|
||||
...provided,
|
||||
color: "#6e6b7b",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col sm="3">
|
||||
<Label className="form-label d-block">
|
||||
Actions
|
||||
</Label>
|
||||
<div className="d-flex gap-2">
|
||||
<Button
|
||||
color={
|
||||
source.isDefault
|
||||
? "primary"
|
||||
: "outline-secondary"
|
||||
}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const updatedSources = [
|
||||
...selectedDataCenter.dataCenterEmissionSources,
|
||||
];
|
||||
// First, set all sources to not default
|
||||
updatedSources.forEach(
|
||||
(s) => (s.isDefault = false)
|
||||
);
|
||||
// Then set the selected one as default
|
||||
updatedSources[index].isDefault = true;
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
dataCenterEmissionSources: updatedSources,
|
||||
});
|
||||
}}
|
||||
title="Set as default emission source"
|
||||
>
|
||||
{source.isDefault ? "★ Default" : "☆ Default"}
|
||||
</Button>
|
||||
<Button
|
||||
color="outline-danger"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const updatedSources =
|
||||
selectedDataCenter.dataCenterEmissionSources.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
// If we're removing the default source and there are other sources, make the first one default
|
||||
if (
|
||||
source.isDefault &&
|
||||
updatedSources.length > 0
|
||||
) {
|
||||
updatedSources[0].isDefault = true;
|
||||
}
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
dataCenterEmissionSources: updatedSources,
|
||||
});
|
||||
}}
|
||||
disabled={
|
||||
selectedDataCenter.dataCenterEmissionSources
|
||||
.length === 1
|
||||
}
|
||||
title="Remove emission source"
|
||||
>
|
||||
<Trash size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
)}
|
||||
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}
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
dataCenterEmissionSources: [
|
||||
...selectedDataCenter.dataCenterEmissionSources,
|
||||
{
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
isDefault: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
disabled={!selectedDataCenter.subSectorId}
|
||||
>
|
||||
<Plus size={14} className="me-1" />
|
||||
Add Emission Source
|
||||
</Button>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="12">
|
||||
@@ -951,7 +1222,8 @@ const DataCenterManagement = () => {
|
||||
placeholder="Select activity sub unit"
|
||||
options={activitySubUnitsOptions}
|
||||
value={activitySubUnitsOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.activitySubUnitId
|
||||
(option) =>
|
||||
option.value === selectedDataCenter.activitySubUnitId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
setSelectedDataCenter({
|
||||
@@ -965,7 +1237,7 @@ const DataCenterManagement = () => {
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
|
||||
<Col sm="12">
|
||||
<FormGroup>
|
||||
<Label>{t("DataCenter.location")}</Label>
|
||||
@@ -986,7 +1258,7 @@ const DataCenterManagement = () => {
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
latitude: pos[0],
|
||||
longitude: pos[1]
|
||||
longitude: pos[1],
|
||||
});
|
||||
}}
|
||||
setSelectedDataCenter={setSelectedDataCenter}
|
||||
@@ -1002,14 +1274,13 @@ const DataCenterManagement = () => {
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={handleSubmit}
|
||||
disabled={!selectedDataCenter.name || !selectedDataCenter.externalId}
|
||||
disabled={
|
||||
!selectedDataCenter.name || !selectedDataCenter.externalId
|
||||
}
|
||||
>
|
||||
{t("Common.save")}
|
||||
</Button>
|
||||
<Button
|
||||
color="secondary"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
<Button color="secondary" onClick={handleCloseModal}>
|
||||
{t("Common.cancel")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
@@ -1083,9 +1354,7 @@ const DataCenterManagement = () => {
|
||||
progressPending={dataCenterStore?.loading}
|
||||
progressComponent={<div className="text-center p-3">Loading...</div>}
|
||||
noDataComponent={
|
||||
<div className="p-2 text-center">
|
||||
{t("Common.noDataAvailable")}
|
||||
</div>
|
||||
<div className="p-2 text-center">{t("Common.noDataAvailable")}</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -1094,4 +1363,4 @@ const DataCenterManagement = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DataCenterManagement);
|
||||
export default memo(DataCenterManagement);
|
||||
|
||||
Reference in New Issue
Block a user