Add datacenter with multiple emission source and inputs

This commit is contained in:
2025-08-18 06:12:19 +03:00
parent ebd997a33d
commit c7e60c25eb
2 changed files with 430 additions and 161 deletions

View File

@@ -251,8 +251,8 @@ export const createDataCenter = (dataCenterData) => {
emissionScopeId: dataCenterData.emissionScopeId || null, emissionScopeId: dataCenterData.emissionScopeId || null,
sectorId: dataCenterData.sectorId || null, sectorId: dataCenterData.sectorId || null,
subSectorId: dataCenterData.subSectorId || null, subSectorId: dataCenterData.subSectorId || null,
emissionSourceId: dataCenterData.emissionSourceId || null, dataCenterEmissionSources:
consuptionUnitId: dataCenterData.consuptionUnitId || null, dataCenterData.dataCenterEmissionSources || [],
activitySubUnitId: dataCenterData.activitySubUnitId || null, activitySubUnitId: dataCenterData.activitySubUnitId || null,
}, },
}, },
@@ -382,8 +382,8 @@ export const updateDataCenter = (id, dataCenterData) => {
emissionScopeId: dataCenterData.emissionScopeId || null, emissionScopeId: dataCenterData.emissionScopeId || null,
sectorId: dataCenterData.sectorId || null, sectorId: dataCenterData.sectorId || null,
subSectorId: dataCenterData.subSectorId || null, subSectorId: dataCenterData.subSectorId || null,
emissionSourceId: dataCenterData.emissionSourceId || null, dataCenterEmissionSources:
consuptionUnitId: dataCenterData.consuptionUnitId || null, dataCenterData.dataCenterEmissionSources || [],
activitySubUnitId: dataCenterData.activitySubUnitId || null, activitySubUnitId: dataCenterData.activitySubUnitId || null,
}, },
}, },

View File

@@ -28,26 +28,37 @@ import { Edit } from "@mui/icons-material";
import { useSnackbar } from "notistack"; import { useSnackbar } from "notistack";
import { default as SweetAlert } from "sweetalert2"; import { default as SweetAlert } from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; 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 { getAreas, getAreasWithCriteria } from "../redux/actions/areas";
import { useTranslation } from "react-i18next"; 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 { getAllEmissionSources } from "../redux/actions/emissionSources";
import { permissionCheck } from "../components/permission-check"; import { permissionCheck } from "../components/permission-check";
import { customFilterForSelect } from "../utility/Utils"; import { customFilterForSelect } from "../utility/Utils";
import { MapContainer, TileLayer, Marker, useMapEvents } from 'react-leaflet'; import { MapContainer, TileLayer, Marker, useMapEvents } from "react-leaflet";
import 'leaflet/dist/leaflet.css'; import "leaflet/dist/leaflet.css";
import L from 'leaflet'; import L from "leaflet";
import axios from 'axios'; import axios from "axios";
import { debounce } from 'lodash'; import { debounce } from "lodash";
// Add Nominatim service configuration // Add Nominatim service configuration
const NOMINATIM_BASE_URL = 'https://nominatim.openstreetmap.org'; const NOMINATIM_BASE_URL = "https://nominatim.openstreetmap.org";
const nominatimAxios = axios.create({ const nominatimAxios = axios.create({
baseURL: NOMINATIM_BASE_URL, baseURL: NOMINATIM_BASE_URL,
headers: { 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); const Swal = withReactContent(SweetAlert);
@@ -55,9 +66,9 @@ const Swal = withReactContent(SweetAlert);
// Fix Leaflet marker icon issue // Fix Leaflet marker icon issue
delete L.Icon.Default.prototype._getIconUrl; delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({ L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'), iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
iconUrl: require('leaflet/dist/images/marker-icon.png'), iconUrl: require("leaflet/dist/images/marker-icon.png"),
shadowUrl: require('leaflet/dist/images/marker-shadow.png') shadowUrl: require("leaflet/dist/images/marker-shadow.png"),
}); });
// Map marker component that handles clicks // Map marker component that handles clicks
@@ -66,30 +77,33 @@ const MapMarker = ({ position, setPosition, setSelectedDataCenter }) => {
click(e) { click(e) {
setPosition([e.latlng.lat, e.latlng.lng]); setPosition([e.latlng.lat, e.latlng.lng]);
// Use Nominatim reverse geocoding directly // Use Nominatim reverse geocoding directly
nominatimAxios.get(`/reverse?format=json&lat=${e.latlng.lat}&lon=${e.latlng.lng}`) nominatimAxios
.then(response => { .get(`/reverse?format=json&lat=${e.latlng.lat}&lon=${e.latlng.lng}`)
.then((response) => {
const address = response.data.display_name; const address = response.data.display_name;
setSelectedDataCenter(prev => ({ setSelectedDataCenter((prev) => ({
...prev, ...prev,
address, address,
latitude: e.latlng.lat, latitude: e.latlng.lat,
longitude: e.latlng.lng longitude: e.latlng.lng,
})); }));
}) })
.catch(error => { .catch((error) => {
console.error('Error getting address:', error); console.error("Error getting address:", error);
// Still update coordinates even if address lookup fails // Still update coordinates even if address lookup fails
setSelectedDataCenter(prev => ({ setSelectedDataCenter((prev) => ({
...prev, ...prev,
latitude: e.latlng.lat, latitude: e.latlng.lat,
longitude: e.latlng.lng longitude: e.latlng.lng,
})); }));
}); });
} },
}); });
// Only render marker if position exists and has valid coordinates // 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 = () => { const DataCenterManagement = () => {
@@ -115,15 +129,14 @@ const DataCenterManagement = () => {
emissionScopeId: null, emissionScopeId: null,
sectorId: null, sectorId: null,
subSectorId: null, subSectorId: null,
emissionSourceId: null, dataCenterEmissionSources: [], // Array of emission sources with consumption units
consuptionUnitId: null, activitySubUnitId: null,
activitySubUnitId: null
}); });
const [mapPosition, setMapPosition] = useState(null); const [mapPosition, setMapPosition] = useState(null);
const dataCenterStore = useSelector((state) => { const dataCenterStore = useSelector((state) => {
console.log('DataCenter Store:', state.dataCenter); console.log("DataCenter Store:", state.dataCenter);
return state.dataCenter; return state.dataCenter;
}); });
const emissionScopeStore = useSelector((state) => state.emissionScope); const emissionScopeStore = useSelector((state) => state.emissionScope);
@@ -170,7 +183,7 @@ const DataCenterManagement = () => {
<Button <Button
color="primary" color="primary"
size="sm" size="sm"
onClick={() => window.open(row.ayposURL, '_blank')} onClick={() => window.open(row.ayposURL, "_blank")}
> >
Dashboard Dashboard
</Button> </Button>
@@ -200,7 +213,9 @@ const DataCenterManagement = () => {
onClick={() => handleEditDataCenter(row)} onClick={() => handleEditDataCenter(row)}
> >
<Edit size={15} /> <Edit size={15} />
<span className="align-middle ml-50">{t("Cruds.edit")}</span> <span className="align-middle ml-50">
{t("Cruds.edit")}
</span>
</DropdownItem> </DropdownItem>
)} )}
{permissionCheck("data_center_delete") && ( {permissionCheck("data_center_delete") && (
@@ -210,7 +225,9 @@ const DataCenterManagement = () => {
onClick={() => handleDeleteDataCenter(row)} onClick={() => handleDeleteDataCenter(row)}
> >
<Trash size={15} /> <Trash size={15} />
<span className="align-middle ml-50">{t("Cruds.delete")}</span> <span className="align-middle ml-50">
{t("Cruds.delete")}
</span>
</DropdownItem> </DropdownItem>
)} )}
</DropdownMenu> </DropdownMenu>
@@ -269,16 +286,41 @@ const DataCenterManagement = () => {
); );
}, [emissionSourceStore?.emissionSources]); }, [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(() => { useEffect(() => {
if (selectedDataCenter?.emissionSourceId) { if (
dispatch( showAddModal &&
getConsuptionUnits({ !editingDataCenter &&
id: selectedDataCenter?.emissionSourceId, selectedDataCenter.dataCenterEmissionSources.length === 0
sector: selectedDataCenter?.sectorId, ) {
}) setSelectedDataCenter((prev) => ({
); ...prev,
dataCenterEmissionSources: [
{
emissionSourceId: null,
consuptionUnitId: null,
isDefault: true,
},
],
}));
} }
}, [selectedDataCenter?.emissionSourceId]); }, [
showAddModal,
editingDataCenter,
selectedDataCenter.dataCenterEmissionSources.length,
]);
useEffect(() => { useEffect(() => {
if (selectedSubSector != null) { if (selectedSubSector != null) {
@@ -352,8 +394,18 @@ const DataCenterManagement = () => {
}, [selectedDataCenter.areaId, areasStore?.areas]); }, [selectedDataCenter.areaId, areasStore?.areas]);
const handleEditDataCenter = (row) => { const handleEditDataCenter = (row) => {
console.log('Editing data center:', row); console.log("Editing data center:", row);
setEditingDataCenter(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({ setSelectedDataCenter({
name: row.dataCenter, name: row.dataCenter,
externalId: row.externalId?.toString(), externalId: row.externalId?.toString(),
@@ -368,9 +420,8 @@ const DataCenterManagement = () => {
emissionScopeId: row.emissionScope?.id || null, emissionScopeId: row.emissionScope?.id || null,
sectorId: row.sector?.id || null, sectorId: row.sector?.id || null,
subSectorId: row.subSector?.id || null, subSectorId: row.subSector?.id || null,
emissionSourceId: row.emissionSource?.id || null, dataCenterEmissionSources: emissionSources,
consuptionUnitId: row.consuptionUnit?.id || null, activitySubUnitId: row.activitySubUnit?.id || null,
activitySubUnitId: row.activitySubUnit?.id || null
}); });
// Set the selected sector and sub sector for cascading dropdowns // Set the selected sector and sub sector for cascading dropdowns
@@ -378,7 +429,11 @@ const DataCenterManagement = () => {
setSelectedSubSector(row.subSector?.id); setSelectedSubSector(row.subSector?.id);
// Only set map position if we have both address and valid coordinates // 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); setShowAddModal(true);
}; };
@@ -403,10 +458,9 @@ const DataCenterManagement = () => {
enqueueSnackbar(t("DataCenter.deleteSuccess"), { variant: "success" }); enqueueSnackbar(t("DataCenter.deleteSuccess"), { variant: "success" });
} catch (error) { } catch (error) {
console.error("Delete error:", error); console.error("Delete error:", error);
enqueueSnackbar( enqueueSnackbar(error?.message || t("DataCenter.deleteError"), {
error?.message || t("DataCenter.deleteError"), variant: "error",
{ variant: "error" } });
);
} }
} }
}; };
@@ -473,23 +527,60 @@ const DataCenterManagement = () => {
if (selectedDataCenter.subSectorId && !selectedDataCenter.sectorId) { if (selectedDataCenter.subSectorId && !selectedDataCenter.sectorId) {
errors.push(t("DataCenter.sectorRequired")); errors.push(t("DataCenter.sectorRequired"));
} }
if (selectedDataCenter.emissionSourceId && !selectedDataCenter.subSectorId) { if (
errors.push(t("DataCenter.subSectorRequired")); selectedDataCenter.activitySubUnitId &&
} !selectedDataCenter.subSectorId
if (selectedDataCenter.consuptionUnitId && !selectedDataCenter.emissionSourceId) { ) {
errors.push(t("DataCenter.emissionSourceRequired"));
}
if (selectedDataCenter.activitySubUnitId && !selectedDataCenter.subSectorId) {
errors.push(t("DataCenter.subSectorRequiredForActivity")); 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; return errors;
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
const validationErrors = validateForm(); const validationErrors = validateForm();
if (validationErrors.length > 0) { if (validationErrors.length > 0) {
validationErrors.forEach(error => { validationErrors.forEach((error) => {
enqueueSnackbar(error, { variant: "error" }); enqueueSnackbar(error, { variant: "error" });
}); });
return; return;
@@ -498,20 +589,26 @@ const DataCenterManagement = () => {
try { try {
// Format data according to GraphQL input type // Format data according to GraphQL input type
const dataToSubmit = { const dataToSubmit = {
dataCenter: selectedDataCenter.name, name: selectedDataCenter.name,
externalId: parseInt(selectedDataCenter.externalId), externalId: parseInt(selectedDataCenter.externalId),
number: parseInt(selectedDataCenter.number || "1"), number: parseInt(selectedDataCenter.number || "1"),
address: selectedDataCenter.address, address: selectedDataCenter.address,
areaId: selectedDataCenter.areaId, areaId: selectedDataCenter.areaId,
latitude: selectedDataCenter.latitude ? parseFloat(selectedDataCenter.latitude) : null, latitude: selectedDataCenter.latitude
longitude: selectedDataCenter.longitude ? parseFloat(selectedDataCenter.longitude) : null, ? parseFloat(selectedDataCenter.latitude)
: null,
longitude: selectedDataCenter.longitude
? parseFloat(selectedDataCenter.longitude)
: null,
ayposURL: selectedDataCenter.ayposURL, ayposURL: selectedDataCenter.ayposURL,
emissionScopeId: selectedDataCenter.emissionScopeId, emissionScopeId: selectedDataCenter.emissionScopeId,
sectorId: selectedDataCenter.sectorId, sectorId: selectedDataCenter.sectorId,
subSectorId: selectedDataCenter.subSectorId, subSectorId: selectedDataCenter.subSectorId,
emissionSourceId: selectedDataCenter.emissionSourceId, dataCenterEmissionSources:
consuptionUnitId: selectedDataCenter.consuptionUnitId, selectedDataCenter.dataCenterEmissionSources.filter(
activitySubUnitId: selectedDataCenter.activitySubUnitId (source) => source.emissionSourceId && source.consuptionUnitId
),
activitySubUnitId: selectedDataCenter.activitySubUnitId,
}; };
if (editingDataCenter) { if (editingDataCenter) {
@@ -533,16 +630,17 @@ const DataCenterManagement = () => {
// Handle specific error cases // Handle specific error cases
if (error.message?.includes("duplicate")) { if (error.message?.includes("duplicate")) {
enqueueSnackbar(t("DataCenter.duplicateExternalId"), { variant: "error" }); enqueueSnackbar(t("DataCenter.duplicateExternalId"), {
variant: "error",
});
} else if (error.message?.includes("permission")) { } else if (error.message?.includes("permission")) {
enqueueSnackbar(t("Common.noPermission"), { variant: "error" }); enqueueSnackbar(t("Common.noPermission"), { variant: "error" });
} else if (error.message?.includes("not found")) { } else if (error.message?.includes("not found")) {
enqueueSnackbar(t("DataCenter.resourceNotFound"), { variant: "error" }); enqueueSnackbar(t("DataCenter.resourceNotFound"), { variant: "error" });
} else { } else {
enqueueSnackbar( enqueueSnackbar(error?.message || t("DataCenter.submitError"), {
error?.message || t("DataCenter.submitError"), variant: "error",
{ variant: "error" } });
);
} }
} }
}; };
@@ -554,13 +652,28 @@ const DataCenterManagement = () => {
externalId: "", externalId: "",
number: "", number: "",
address: "", address: "",
areaId: null,
cityId: null,
latitude: null, latitude: null,
longitude: null, longitude: null,
ayposURL: "", ayposURL: "",
city: "" city: "",
emissionScopeId: null,
sectorId: null,
subSectorId: null,
dataCenterEmissionSources: [
{
emissionSourceId: null,
consuptionUnitId: null,
isDefault: true,
},
],
activitySubUnitId: null,
}); });
setMapPosition(null); setMapPosition(null);
setEditingDataCenter(null); setEditingDataCenter(null);
setSelectedSector(null);
setSelectedSubSector(null);
}; };
const handleFilter = (e) => { const handleFilter = (e) => {
@@ -594,45 +707,47 @@ const DataCenterManagement = () => {
const geocodeAddress = useCallback(async (address) => { const geocodeAddress = useCallback(async (address) => {
if (!address || !address.trim()) { if (!address || !address.trim()) {
setMapPosition(null); setMapPosition(null);
setSelectedDataCenter(prev => ({ setSelectedDataCenter((prev) => ({
...prev, ...prev,
latitude: null, latitude: null,
longitude: null longitude: null,
})); }));
return false; return false;
} }
try { 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]) { if (response.data && response.data[0]) {
const { lat, lon } = response.data[0]; const { lat, lon } = response.data[0];
const newPosition = [parseFloat(lat), parseFloat(lon)]; const newPosition = [parseFloat(lat), parseFloat(lon)];
setMapPosition(newPosition); setMapPosition(newPosition);
setSelectedDataCenter(prev => ({ setSelectedDataCenter((prev) => ({
...prev, ...prev,
latitude: parseFloat(lat), latitude: parseFloat(lat),
longitude: parseFloat(lon) longitude: parseFloat(lon),
})); }));
return true; return true;
} }
// If no results found, clear the coordinates // If no results found, clear the coordinates
setMapPosition(null); setMapPosition(null);
setSelectedDataCenter(prev => ({ setSelectedDataCenter((prev) => ({
...prev, ...prev,
latitude: null, latitude: null,
longitude: null longitude: null,
})); }));
return false; return false;
} catch (error) { } catch (error) {
console.error('Error geocoding address:', error); console.error("Error geocoding address:", error);
// On error, clear the coordinates // On error, clear the coordinates
setMapPosition(null); setMapPosition(null);
setSelectedDataCenter(prev => ({ setSelectedDataCenter((prev) => ({
...prev, ...prev,
latitude: null, latitude: null,
longitude: null longitude: null,
})); }));
return false; return false;
} }
@@ -641,18 +756,18 @@ const DataCenterManagement = () => {
// Update the address input handler // Update the address input handler
const handleAddressChange = (e) => { const handleAddressChange = (e) => {
const newAddress = e.target.value; const newAddress = e.target.value;
setSelectedDataCenter(prev => ({ setSelectedDataCenter((prev) => ({
...prev, ...prev,
address: newAddress address: newAddress,
})); }));
// If address is empty, clear the coordinates // If address is empty, clear the coordinates
if (!newAddress.trim()) { if (!newAddress.trim()) {
setMapPosition(null); setMapPosition(null);
setSelectedDataCenter(prev => ({ setSelectedDataCenter((prev) => ({
...prev, ...prev,
latitude: null, latitude: null,
longitude: null longitude: null,
})); }));
} else { } else {
// If address is not empty, try to geocode it after a delay // If address is not empty, try to geocode it after a delay
@@ -675,9 +790,7 @@ const DataCenterManagement = () => {
size="lg" size="lg"
> >
<ModalHeader toggle={handleCloseModal}> <ModalHeader toggle={handleCloseModal}>
{editingDataCenter {editingDataCenter ? t("DataCenter.edit") : t("DataCenter.add")}
? t("DataCenter.edit")
: t("DataCenter.add")}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<Form> <Form>
@@ -685,7 +798,8 @@ const DataCenterManagement = () => {
<Col sm="12"> <Col sm="12">
<FormGroup> <FormGroup>
<Label for="name"> <Label for="name">
{t("DataCenter.name")} <span className="text-danger">*</span> {t("DataCenter.name")}{" "}
<span className="text-danger">*</span>
</Label> </Label>
<Input <Input
type="text" type="text"
@@ -705,7 +819,8 @@ const DataCenterManagement = () => {
<Col sm="6"> <Col sm="6">
<FormGroup> <FormGroup>
<Label for="externalId"> <Label for="externalId">
{t("DataCenter.externalId")} <span className="text-danger">*</span> {t("DataCenter.externalId")}{" "}
<span className="text-danger">*</span>
</Label> </Label>
<Input <Input
type="text" type="text"
@@ -817,7 +932,9 @@ const DataCenterManagement = () => {
{/* Emission Scope Section */} {/* Emission Scope Section */}
<Col sm="12"> <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>
<Col sm="6"> <Col sm="6">
<FormGroup> <FormGroup>
@@ -828,7 +945,8 @@ const DataCenterManagement = () => {
placeholder="Select emission scope" placeholder="Select emission scope"
options={emissionScopesOptions} options={emissionScopesOptions}
value={emissionScopesOptions?.find( value={emissionScopesOptions?.find(
(option) => option.value === selectedDataCenter.emissionScopeId (option) =>
option.value === selectedDataCenter.emissionScopeId
)} )}
onChange={(option) => onChange={(option) =>
setSelectedDataCenter({ setSelectedDataCenter({
@@ -843,7 +961,9 @@ const DataCenterManagement = () => {
</Col> </Col>
<Col sm="6"> <Col sm="6">
<FormGroup> <FormGroup>
<Label for="sector">Sector <span className="text-danger">*</span></Label> <Label for="sector">
Sector <span className="text-danger">*</span>
</Label>
<Select <Select
id="sector" id="sector"
name="sector" name="sector"
@@ -858,8 +978,13 @@ const DataCenterManagement = () => {
...selectedDataCenter, ...selectedDataCenter,
sectorId: option?.value, sectorId: option?.value,
subSectorId: null, subSectorId: null,
emissionSourceId: null, dataCenterEmissionSources: [
consuptionUnitId: null, {
emissionSourceId: null,
consuptionUnitId: null,
isDefault: true,
},
],
activitySubUnitId: null, activitySubUnitId: null,
}); });
}} }}
@@ -877,15 +1002,21 @@ const DataCenterManagement = () => {
placeholder="Select sub sector" placeholder="Select sub sector"
options={subSectorsOptions} options={subSectorsOptions}
value={subSectorsOptions?.find( value={subSectorsOptions?.find(
(option) => option.value === selectedDataCenter.subSectorId (option) =>
option.value === selectedDataCenter.subSectorId
)} )}
onChange={(option) => { onChange={(option) => {
setSelectedSubSector(option?.value); setSelectedSubSector(option?.value);
setSelectedDataCenter({ setSelectedDataCenter({
...selectedDataCenter, ...selectedDataCenter,
subSectorId: option?.value, subSectorId: option?.value,
emissionSourceId: null, dataCenterEmissionSources: [
consuptionUnitId: null, {
emissionSourceId: null,
consuptionUnitId: null,
isDefault: true,
},
],
activitySubUnitId: null, activitySubUnitId: null,
}); });
}} }}
@@ -895,51 +1026,191 @@ const DataCenterManagement = () => {
/> />
</FormGroup> </FormGroup>
</Col> </Col>
<Col sm="6"> <Col sm="12">
<FormGroup> <FormGroup>
<Label for="emissionSource">Emission Source</Label> <Label>Emission Sources & Consumption Units</Label>
<Select <div className="border p-3 rounded bg-light">
id="emissionSource" <small className="text-muted mb-2 d-block">
name="emissionSource" Configure emission sources for this data center. At least
placeholder="Select emission source" one emission source with consumption unit is required.
options={emissionSourcesOptions} </small>
value={emissionSourcesOptions?.find( {selectedDataCenter.dataCenterEmissionSources.map(
(option) => option.value === selectedDataCenter.emissionSourceId (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) => { <Button
setSelectedDataCenter({ color="primary"
...selectedDataCenter, size="sm"
emissionSourceId: option?.value, onClick={() => {
consuptionUnitId: null, setSelectedDataCenter({
}); ...selectedDataCenter,
}} dataCenterEmissionSources: [
isClearable ...selectedDataCenter.dataCenterEmissionSources,
filterOption={customFilterForSelect} {
isDisabled={!selectedDataCenter.subSectorId} emissionSourceId: null,
/> consuptionUnitId: null,
</FormGroup> isDefault: false,
</Col> },
<Col sm="6"> ],
<FormGroup> });
<Label for="consuptionUnit">Consumption Unit</Label> }}
<Select disabled={!selectedDataCenter.subSectorId}
id="consuptionUnit" >
name="consuptionUnit" <Plus size={14} className="me-1" />
placeholder="Select consumption unit" Add Emission Source
options={consuptionUnitsOptions} </Button>
value={consuptionUnitsOptions?.find( </div>
(option) => option.value === selectedDataCenter.consuptionUnitId
)}
onChange={(option) => {
setSelectedDataCenter({
...selectedDataCenter,
consuptionUnitId: option?.value,
});
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!selectedDataCenter.emissionSourceId}
/>
</FormGroup> </FormGroup>
</Col> </Col>
<Col sm="12"> <Col sm="12">
@@ -951,7 +1222,8 @@ const DataCenterManagement = () => {
placeholder="Select activity sub unit" placeholder="Select activity sub unit"
options={activitySubUnitsOptions} options={activitySubUnitsOptions}
value={activitySubUnitsOptions?.find( value={activitySubUnitsOptions?.find(
(option) => option.value === selectedDataCenter.activitySubUnitId (option) =>
option.value === selectedDataCenter.activitySubUnitId
)} )}
onChange={(option) => { onChange={(option) => {
setSelectedDataCenter({ setSelectedDataCenter({
@@ -986,7 +1258,7 @@ const DataCenterManagement = () => {
setSelectedDataCenter({ setSelectedDataCenter({
...selectedDataCenter, ...selectedDataCenter,
latitude: pos[0], latitude: pos[0],
longitude: pos[1] longitude: pos[1],
}); });
}} }}
setSelectedDataCenter={setSelectedDataCenter} setSelectedDataCenter={setSelectedDataCenter}
@@ -1002,14 +1274,13 @@ const DataCenterManagement = () => {
<Button <Button
color="primary" color="primary"
onClick={handleSubmit} onClick={handleSubmit}
disabled={!selectedDataCenter.name || !selectedDataCenter.externalId} disabled={
!selectedDataCenter.name || !selectedDataCenter.externalId
}
> >
{t("Common.save")} {t("Common.save")}
</Button> </Button>
<Button <Button color="secondary" onClick={handleCloseModal}>
color="secondary"
onClick={handleCloseModal}
>
{t("Common.cancel")} {t("Common.cancel")}
</Button> </Button>
</ModalFooter> </ModalFooter>
@@ -1083,9 +1354,7 @@ const DataCenterManagement = () => {
progressPending={dataCenterStore?.loading} progressPending={dataCenterStore?.loading}
progressComponent={<div className="text-center p-3">Loading...</div>} progressComponent={<div className="text-center p-3">Loading...</div>}
noDataComponent={ noDataComponent={
<div className="p-2 text-center"> <div className="p-2 text-center">{t("Common.noDataAvailable")}</div>
{t("Common.noDataAvailable")}
</div>
} }
/> />
</div> </div>