diff --git a/sge-backend/src/main/java/com/sgs/graphql/dataCenter/mutation/input/DataCenterCreateInput.java b/sge-backend/src/main/java/com/sgs/graphql/dataCenter/mutation/input/DataCenterCreateInput.java index 057e22b..b3defbb 100644 --- a/sge-backend/src/main/java/com/sgs/graphql/dataCenter/mutation/input/DataCenterCreateInput.java +++ b/sge-backend/src/main/java/com/sgs/graphql/dataCenter/mutation/input/DataCenterCreateInput.java @@ -15,7 +15,6 @@ public class DataCenterCreateInput extends BaseCreateInput { private Integer number; private Double consuptionAmount; - @NotNull(message = "Alan ID gereklidir") private UUID areaId; @NotNull(message = "Sektör ID gereklidir") diff --git a/sge-backend/src/main/java/com/sgs/graphql/dataCenter/mutation/input/DataCenterUpdateInput.java b/sge-backend/src/main/java/com/sgs/graphql/dataCenter/mutation/input/DataCenterUpdateInput.java index 7c44336..9e34544 100644 --- a/sge-backend/src/main/java/com/sgs/graphql/dataCenter/mutation/input/DataCenterUpdateInput.java +++ b/sge-backend/src/main/java/com/sgs/graphql/dataCenter/mutation/input/DataCenterUpdateInput.java @@ -1,6 +1,7 @@ package com.sgs.graphql.dataCenter.mutation.input; import com.sgs.lib.dao.mutation.input.BaseUpdateInput; +import javax.validation.constraints.NotNull; import java.util.UUID; public class DataCenterUpdateInput extends BaseUpdateInput { @@ -10,6 +11,7 @@ public class DataCenterUpdateInput extends BaseUpdateInput { private Double consuptionAmount; private UUID areaId; + @NotNull(message = "Sektör ID gereklidir") private UUID sectorId; private UUID subSectorId; private UUID emissionSourceId; diff --git a/sge-backend/src/main/resources/application.properties b/sge-backend/src/main/resources/application.properties index 32c28b9..c5132b2 100644 --- a/sge-backend/src/main/resources/application.properties +++ b/sge-backend/src/main/resources/application.properties @@ -17,20 +17,14 @@ security.jwt.token.secret-key=secret app.survey.base-url=http://localhost.com -# spring.rabbitmq.host=188.132.198.145 -# spring.rabbitmq.port=5672 -# spring.rabbitmq.username=testuser -# spring.rabbitmq.password=JGasF24561AZv2894De - -spring.rabbitmq.host=rabbitmq -#spring.rabbitmq.host=localhost +spring.rabbitmq.host=188.132.198.145 spring.rabbitmq.port=5672 -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest +spring.rabbitmq.username=testuser +spring.rabbitmq.password=JGasF24561AZv2894De spring.rabbitmq.virtual-host=/ spring.rabbitmq.connection-timeout=20000 spring.rabbitmq.template.retry.enabled=true spring.rabbitmq.template.retry.max-attempts=3 spring.rabbitmq.template.retry.initial-interval=1000ms -logging.level.org.springframework.amqp=DEBUG +logging.level.org.springframework.amqp=DEBUG \ No newline at end of file diff --git a/sge-frontend/src/navigation/horizontal/index.js b/sge-frontend/src/navigation/horizontal/index.js index 04271b6..af8d687 100644 --- a/sge-frontend/src/navigation/horizontal/index.js +++ b/sge-frontend/src/navigation/horizontal/index.js @@ -49,9 +49,9 @@ export default [ permissionCheck("paginate_roles_get")) && [ { id: "Organizations", - title: "DataCenters.title", + title: "Data Center Management", icon: , - navLink: "/organizasyonlar", + navLink: "/veri-merkezi-yonetimi", display: (permissionCheck("paginate_datacenters_get") || permissionCheck("data_center_create") || permissionCheck("data_center_update") || @@ -142,9 +142,63 @@ export default [ }, { id: "DataCenter", - title: "Data Centers", + title: "Data Center Overview", icon: , - navLink: "/verimerkezi", + navLink: "/veri-merkezi-genel", + }, + { + id: "Areas", + title: "Areas.areas", + icon: , + display: + permissionCheck("paginate_areas_get") || + permissionCheck("paginate_countries_get") || + permissionCheck("paginate_cities_get") || + permissionCheck("paginate_districts_get") || + permissionCheck("paginate_neighborhoods_get") + ? "" + : "none", + children: (permissionCheck("paginate_areas_get") || + permissionCheck("paginate_countries_get") || + permissionCheck("paginate_cities_get") || + permissionCheck("paginate_districts_get") || + permissionCheck("paginate_neighborhoods_get")) && [ + { + id: "AreasManagement", + title: "Areas.areas", + icon: , + navLink: "/alanlar", + display: permissionCheck("paginate_areas_get") ? "" : "none", + }, + { + id: "Countries", + title: "Areas.countries", + icon: , + navLink: "/ulkeler", + display: permissionCheck("paginate_countries_get") ? "" : "none", + }, + { + id: "Cities", + title: "Areas.cities", + icon: , + navLink: "/iller", + display: permissionCheck("paginate_cities_get") ? "" : "none", + }, + { + id: "Districts", + title: "Areas.districts", + icon: , + navLink: "/ilceler", + display: permissionCheck("paginate_districts_get") ? "" : "none", + }, + { + id: "Neighborhoods", + title: "Areas.neighborhoods", + icon: , + navLink: "/mahalleler", + display: permissionCheck("paginate_neighborhoods_get") ? "" : "none", + }, + ], }, { id: "Survey", diff --git a/sge-frontend/src/navigation/vertical/index.js b/sge-frontend/src/navigation/vertical/index.js index fe24dbf..f3eb6cc 100644 --- a/sge-frontend/src/navigation/vertical/index.js +++ b/sge-frontend/src/navigation/vertical/index.js @@ -49,9 +49,9 @@ export default [ permissionCheck("paginate_roles_get")) && [ { id: "DataCenters", - title: "DataCenters.title", + title: "Data Center Management", icon: , - navLink: "/organizasyonlar", + navLink: "/veri-merkezi-yonetimi", display: (permissionCheck("paginate_datacenters_get") || permissionCheck("data_center_create") || permissionCheck("data_center_update") || @@ -142,9 +142,63 @@ export default [ }, { id: "DataCenter", - title: "DataCenters", + title: "Data Center Overview", icon: , - navLink: "/verimerkezi", + navLink: "/veri-merkezi-genel", + }, + { + id: "Areas", + title: "Areas.areas", + icon: , + display: + permissionCheck("paginate_areas_get") || + permissionCheck("paginate_countries_get") || + permissionCheck("paginate_cities_get") || + permissionCheck("paginate_districts_get") || + permissionCheck("paginate_neighborhoods_get") + ? "" + : "none", + children: (permissionCheck("paginate_areas_get") || + permissionCheck("paginate_countries_get") || + permissionCheck("paginate_cities_get") || + permissionCheck("paginate_districts_get") || + permissionCheck("paginate_neighborhoods_get")) && [ + { + id: "AreasManagement", + title: "Areas.areas", + icon: , + navLink: "/alanlar", + display: permissionCheck("paginate_areas_get") ? "" : "none", + }, + { + id: "Countries", + title: "Areas.countries", + icon: , + navLink: "/ulkeler", + display: permissionCheck("paginate_countries_get") ? "" : "none", + }, + { + id: "Cities", + title: "Areas.cities", + icon: , + navLink: "/iller", + display: permissionCheck("paginate_cities_get") ? "" : "none", + }, + { + id: "Districts", + title: "Areas.districts", + icon: , + navLink: "/ilceler", + display: permissionCheck("paginate_districts_get") ? "" : "none", + }, + { + id: "Neighborhoods", + title: "Areas.neighborhoods", + icon: , + navLink: "/mahalleler", + display: permissionCheck("paginate_neighborhoods_get") ? "" : "none", + }, + ], }, { id: "Survey", diff --git a/sge-frontend/src/redux/actions/areas/index.js b/sge-frontend/src/redux/actions/areas/index.js index 0dd1892..520088c 100644 --- a/sge-frontend/src/redux/actions/areas/index.js +++ b/sge-frontend/src/redux/actions/areas/index.js @@ -12,6 +12,15 @@ export const getAreas = () => { id tag isDeleted + cities { + id + name + coordinates + country { + id + name + } + } } } `, diff --git a/sge-frontend/src/redux/actions/dataCenter/index.js b/sge-frontend/src/redux/actions/dataCenter/index.js index 8adf43a..e439d5e 100644 --- a/sge-frontend/src/redux/actions/dataCenter/index.js +++ b/sge-frontend/src/redux/actions/dataCenter/index.js @@ -34,10 +34,41 @@ export const getDataCenters = () => { latitude longitude area { + id + tag + cities { + id + name + } + districts { + id + name + } + } + emissionScope { + id + tag + description + } + sector { + id + tag + } + subSector { + id + tag + } + emissionSource { + id + tag + } + consuptionUnit { + id + description + } + activitySubUnit { + id tag - name - cityId - districtId } projects { id @@ -45,12 +76,16 @@ export const getDataCenters = () => { physicalMachines { id name - vms { - active { + vms { id - status - name + vmName + state power + calcOn + hostingPm + host + flavorName + tag config { id cpu @@ -58,7 +93,6 @@ export const getDataCenters = () => { disk } } - } } } } @@ -155,10 +189,41 @@ export const createDataCenter = (dataCenterData) => { latitude longitude area { + id + tag + cities { + id + name + } + districts { + id + name + } + } + emissionScope { + id + tag + description + } + sector { + id + tag + } + subSector { + id + tag + } + emissionSource { + id + tag + } + consuptionUnit { + id + description + } + activitySubUnit { + id tag - name - cityId - districtId } } } @@ -169,11 +234,16 @@ export const createDataCenter = (dataCenterData) => { externalId: parseInt(dataCenterData.externalId), ayposURL: dataCenterData.ayposURL || "", number: parseInt(dataCenterData.number) || 1, - areaId: dataCenterData.areaId, + areaId: dataCenterData.areaId || null, address: dataCenterData.address || "", latitude: dataCenterData.latitude ? parseFloat(dataCenterData.latitude) : null, longitude: dataCenterData.longitude ? parseFloat(dataCenterData.longitude) : null, - city: dataCenterData.city + emissionScopeId: dataCenterData.emissionScopeId || null, + sectorId: dataCenterData.sectorId || null, + subSectorId: dataCenterData.subSectorId || null, + emissionSourceId: dataCenterData.emissionSourceId || null, + consuptionUnitId: dataCenterData.consuptionUnitId || null, + activitySubUnitId: dataCenterData.activitySubUnitId || null } } }, @@ -240,10 +310,41 @@ export const updateDataCenter = (id, dataCenterData) => { latitude longitude area { + id + tag + cities { + id + name + } + districts { + id + name + } + } + emissionScope { + id + tag + description + } + sector { + id + tag + } + subSector { + id + tag + } + emissionSource { + id + tag + } + consuptionUnit { + id + description + } + activitySubUnit { + id tag - name - cityId - districtId } } } @@ -255,11 +356,16 @@ export const updateDataCenter = (id, dataCenterData) => { externalId: parseInt(dataCenterData.externalId), ayposURL: dataCenterData.ayposURL || "", number: parseInt(dataCenterData.number) || 1, - areaId: dataCenterData.areaId, + areaId: dataCenterData.areaId || null, address: dataCenterData.address || "", latitude: dataCenterData.latitude ? parseFloat(dataCenterData.latitude) : null, longitude: dataCenterData.longitude ? parseFloat(dataCenterData.longitude) : null, - city: dataCenterData.city + emissionScopeId: dataCenterData.emissionScopeId || null, + sectorId: dataCenterData.sectorId || null, + subSectorId: dataCenterData.subSectorId || null, + emissionSourceId: dataCenterData.emissionSourceId || null, + consuptionUnitId: dataCenterData.consuptionUnitId || null, + activitySubUnitId: dataCenterData.activitySubUnitId || null } } }, @@ -342,6 +448,56 @@ export const deleteDataCenter = (id) => { }; }; +export const getEmissionScopes = () => { + return async (dispatch) => { + dispatch({ + type: "GET_EMISSION_SCOPES_LOADING", + }); + + try { + const response = await ApplicationService.http().post( + "/graphql", + { + query: ` + query GetEmissionScopes { + emissionScopes { + id + tag + description + } + } + ` + }, + { + headers: { + Authorization: "Bearer " + localStorage.getItem("accessToken"), + }, + } + ); + + if (response.data?.errors) { + throw new Error(response.data.errors[0].message); + } + + dispatch({ + type: "GET_EMISSION_SCOPES_SUCCESS", + payload: response.data.data.emissionScopes + }); + + return response.data.data.emissionScopes; + } catch (error) { + console.error("Error fetching emission scopes:", error); + dispatch({ + type: "GET_EMISSION_SCOPES_ERROR", + payload: { + error: error.message || "Failed to fetch emission scopes", + }, + }); + throw error; + } + }; +}; + export const getDataCenterVMs = (dataCenterId) => { return new Promise(async (resolve, reject) => { // Don't make the request if dataCenterId is undefined, null, or empty diff --git a/sge-frontend/src/redux/actions/mainDataTables/index.js b/sge-frontend/src/redux/actions/mainDataTables/index.js index 77c70a9..0db497f 100644 --- a/sge-frontend/src/redux/actions/mainDataTables/index.js +++ b/sge-frontend/src/redux/actions/mainDataTables/index.js @@ -125,11 +125,12 @@ export const getMainDataTablesWithPaginate = (data) => { { paginateMainDataTables( pagination: { page: 0, rowsPerPage: 100 } - criteria: { deleted: false } + criteria: { deleted: false, hasVm: true } sortBy: [{ field: "createdDate", direction: DESC }] ) { content { id + year sector { id tag @@ -138,11 +139,40 @@ export const getMainDataTablesWithPaginate = (data) => { id tag } + activitySubUnit { + id + tag + } + emissionSource { + id + tag + } + emissionScope { + id + tag + } co2 ch4 n2o totalEmission createdDate + vm { + id + vmName + state + power + calcOn + hostingPm + host + flavorName + tag + config { + id + cpu + ram + disk + } + } } pageInfo { totalElements diff --git a/sge-frontend/src/redux/reducers/emissionScope/index.js b/sge-frontend/src/redux/reducers/emissionScope/index.js new file mode 100644 index 0000000..19cd08b --- /dev/null +++ b/sge-frontend/src/redux/reducers/emissionScope/index.js @@ -0,0 +1,33 @@ +const initialState = { + emissionScopes: [], + loading: false, + error: null, +}; + +const emissionScopeReducer = (state = initialState, action) => { + switch (action.type) { + case "GET_EMISSION_SCOPES_LOADING": + return { + ...state, + loading: true, + error: null, + }; + case "GET_EMISSION_SCOPES_SUCCESS": + return { + ...state, + loading: false, + emissionScopes: action.payload, + error: null, + }; + case "GET_EMISSION_SCOPES_ERROR": + return { + ...state, + loading: false, + error: action.payload.error, + }; + default: + return state; + } +}; + +export default emissionScopeReducer; \ No newline at end of file diff --git a/sge-frontend/src/redux/reducers/rootReducer.js b/sge-frontend/src/redux/reducers/rootReducer.js index 1d201a5..696a0a9 100644 --- a/sge-frontend/src/redux/reducers/rootReducer.js +++ b/sge-frontend/src/redux/reducers/rootReducer.js @@ -27,6 +27,7 @@ import surveys from "./surveys"; import uploads from "./upload"; import mailSettings from "./mailSettings"; import dataCenter from "./dataCenter"; +import emissionScope from "./emissionScope"; const rootReducer = combineReducers({ accessToken, @@ -57,6 +58,7 @@ const rootReducer = combineReducers({ uploads, mailSettings, dataCenter, + emissionScope, }); export default rootReducer; diff --git a/sge-frontend/src/router/routes/index.js b/sge-frontend/src/router/routes/index.js index 9ce00a0..a25bdfe 100644 --- a/sge-frontend/src/router/routes/index.js +++ b/sge-frontend/src/router/routes/index.js @@ -25,7 +25,7 @@ const Routes = [ display: permissionCheck("paginate_users_get"), }, { - path: "/organizasyonlar", + path: "/veri-merkezi-yonetimi", component: lazy(() => import("../../views/DataCenterManagement")), display: permissionCheck("paginate_datacenters_get") || permissionCheck("data_center_create") || @@ -74,7 +74,7 @@ const Routes = [ display: permissionCheck("activity_sub_units_get"), }, { - path: "/verimerkezi", + path: "/veri-merkezi-genel", component: lazy(() => import("../../views/DataCenter")), }, { diff --git a/sge-frontend/src/views/DataCenter.js b/sge-frontend/src/views/DataCenter.js index e4599a0..b1b9d60 100644 --- a/sge-frontend/src/views/DataCenter.js +++ b/sge-frontend/src/views/DataCenter.js @@ -41,14 +41,23 @@ const DataCenter = () => { // Projects { name: "Projects", - selector: (row) => - (row.projects || []).map((p) => p.name).join(", "), - sortable: false, + selector: (row) => (row.projects || []).length, + sortable: true, minWidth: "200px", cell: (row) => ( - - {(row.projects || []).map((p) => p.name).join(", ") || "-"} - +
+ {(row.projects || []).length > 0 ? ( +
+ {row.projects.map((project, index) => ( +
0 ? 'mt-1' : ''}`}> + {project.name} +
+ ))} +
+ ) : ( + - + )} +
), }, // Physical Machines @@ -68,26 +77,41 @@ const DataCenter = () => { }, }, { - name: "Total Active VMs", + name: "Virtual Machines", selector: (row) => { const pms = getAllPhysicalMachines(row); - return pms.reduce( - (total, pm) => total + (pm.vms?.active?.length || 0), - 0 - ); + const vms = pms.reduce((acc, pm) => { + if (!pm.vms) return acc; + return { + active: acc.active + pm.vms.filter(vm => vm.state?.toLowerCase() === "active").length, + total: acc.total + pm.vms.length + }; + }, { active: 0, total: 0 }); + return vms.total; }, sortable: true, - minWidth: "150px", + minWidth: "200px", cell: (row) => { const pms = getAllPhysicalMachines(row); - const totalVMs = pms.reduce( - (total, pm) => total + (pm.vms?.active?.length || 0), - 0 - ); + const vms = pms.reduce((acc, pm) => { + if (!pm.vms) return acc; + return { + active: acc.active + pm.vms.filter(vm => vm.state?.toLowerCase() === "active").length, + total: acc.total + pm.vms.length + }; + }, { active: 0, total: 0 }); + return (
- - {totalVMs} + +
+
{vms.total} Total
+
+ {vms.active} Active + + {vms.total - vms.active} Inactive +
+
); }, @@ -138,76 +162,78 @@ const DataCenter = () => {
{pm.name}
- {/* Active VMs */} -

- Active VMs ({pm.vms?.active?.length || 0}): -

- {pm.vms?.active?.length > 0 ? ( -
- {pm.vms.active.map((vm) => ( -
-
-
- Name: {vm.name} -
-
- Status: - - {vm.status} - -
-
- Power: {vm.power} -
-
- Config: CPU: {vm.config?.cpu}, RAM:{" "} - {vm.config?.ram}, Disk: {vm.config?.disk} -
-
-
- ))} + {/* All VMs */} +
+
+ + Virtual Machines ({pm.vms?.length || 0}) +
+
+ {pm.vms?.length > 0 ? ( +
+ + + + + + + + + + + + {pm.vms.map((vm) => { + const isActive = vm.state && ["ACTIVE", "active"].includes(vm.state); + return ( + + + + + + + + ); + })} + +
NameStatusPower (W)ConfigurationHost
+ {vm.vmName || vm.vm_name} + +
+ {vm.state} +
+
+ {vm.power ? ( + {vm.power.toFixed(2)} + ) : ( + - + )} + +
+
+ CPU + {(vm.config?.cpu || (vm.confg && vm.confg[1])) || '-'} +
+
+ RAM + {(vm.config?.ram || (vm.confg && vm.confg[2])) || '-'} GB +
+
+ Disk + {(vm.config?.disk || (vm.confg && vm.confg[3])) || '-'} GB +
+
+
+
+ + {vm.host || vm.hostingPm || (vm.confg && vm.confg[4]) || '-'} +
+
) : ( -

No active VMs

- )} - - {/* Inactive VMs */} -

- Inactive VMs ({pm.vms?.inactive?.length || 0}): -

- {pm.vms?.inactive?.length > 0 ? ( -
- {pm.vms.inactive.map((vm) => ( -
-
-
- Name: {vm.name} -
-
- Status: - - {vm.status} - -
-
- Power: {vm.power} -
-
- Config: CPU: {vm.config?.cpu}, RAM:{" "} - {vm.config?.ram}, Disk: {vm.config?.disk} -
-
-
- ))} +
+ +

No virtual machines found

- ) : ( -

No inactive VMs

)}
))} diff --git a/sge-frontend/src/views/DataCenterManagement.js b/sge-frontend/src/views/DataCenterManagement.js index c344df6..d77f7df 100644 --- a/sge-frontend/src/views/DataCenterManagement.js +++ b/sge-frontend/src/views/DataCenterManagement.js @@ -28,7 +28,8 @@ 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 { 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 { getAllEmissionSources } from "../redux/actions/emissionSources"; @@ -105,6 +106,8 @@ const DataCenterManagement = () => { externalId: "", number: "", address: "", + areaId: null, + cityId: null, latitude: null, longitude: null, ayposURL: "", @@ -120,14 +123,18 @@ const DataCenterManagement = () => { const [mapPosition, setMapPosition] = useState(null); const dataCenterStore = useSelector((state) => state.dataCenter); + const emissionScopeStore = useSelector((state) => state.emissionScope); const datasStore = useSelector((state) => state.datas); const emissionSourceStore = useSelector((state) => state.emissionSources); + const areasStore = useSelector((state) => state.areas); const [sectorsOptions, setSectorsOptions] = useState([]); const [subSectorsOptions, setSubSectorsOptions] = useState([]); const [emissionSourcesOptions, setEmissionSourcesOptions] = useState([]); const [consuptionUnitsOptions, setConsuptionUnitsOptions] = useState([]); const [activitySubUnitsOptions, setActivitySubUnitsOptions] = useState([]); 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); @@ -215,6 +222,8 @@ const DataCenterManagement = () => { useEffect(() => { dispatch(getDataCenters()); dispatch(getSectors()); + dispatch(getAreas()); + dispatch(getEmissionScopes()); }, [dispatch]); useEffect(() => { @@ -295,17 +304,48 @@ const DataCenterManagement = () => { }, [datasStore?.consuptionUnits]); useEffect(() => { - setEmissionScopesOptions([ - { - label: "Şehir İçi", - value: false, - }, - { - label: "Şehir Dışı", - value: true, - }, - ]); - }, []); + if (emissionScopeStore?.emissionScopes) { + setEmissionScopesOptions( + emissionScopeStore.emissionScopes.map((scope) => ({ + value: scope.id, + label: scope.tag, + })) + ); + } + }, [emissionScopeStore?.emissionScopes]); + + // Set areas options when areas data is loaded + useEffect(() => { + if (areasStore?.areas) { + setAreasOptions( + areasStore.areas.map((area) => ({ + value: area.id, + label: area.tag, + })) + ); + } + }, [areasStore?.areas]); + + // Set cities options when selected area changes + useEffect(() => { + if (selectedDataCenter.areaId && areasStore?.areas) { + const selectedArea = areasStore.areas.find( + (area) => area.id === selectedDataCenter.areaId + ); + if (selectedArea?.cities) { + setCitiesOptions( + selectedArea.cities.map((city) => ({ + value: city.id, + label: city.name, + })) + ); + } else { + setCitiesOptions([]); + } + } else { + setCitiesOptions([]); + } + }, [selectedDataCenter.areaId, areasStore?.areas]); const handleEditDataCenter = (row) => { setEditingDataCenter(row); @@ -314,10 +354,12 @@ const DataCenterManagement = () => { externalId: row.externalId, number: row.number, address: row.address, + areaId: row.area?.id, + cityId: null, // City is a string in the backend, not an object latitude: row.latitude, longitude: row.longitude, ayposURL: row.ayposURL, - city: row.city, + city: row.city || "", emissionScopeId: row.emissionScope?.id, sectorId: row.sector?.id, subSectorId: row.subSector?.id, @@ -364,9 +406,56 @@ const DataCenterManagement = () => { } }; + const validateForm = () => { + const errors = []; + + // Required fields + if (!selectedDataCenter.name) { + errors.push(t("DataCenter.nameRequired")); + } + if (!selectedDataCenter.externalId) { + errors.push(t("DataCenter.externalIdRequired")); + } + if (!selectedDataCenter.sectorId) { + errors.push(t("DataCenter.sectorRequired")); + } + + // Validate external ID is a number + if (selectedDataCenter.externalId && isNaN(parseInt(selectedDataCenter.externalId))) { + errors.push(t("DataCenter.externalIdMustBeNumber")); + } + + // Validate coordinates if either is provided + if ((selectedDataCenter.latitude && !selectedDataCenter.longitude) || + (!selectedDataCenter.latitude && selectedDataCenter.longitude)) { + errors.push(t("DataCenter.bothCoordinatesRequired")); + } + + // Validate area and city are selected together + if (selectedDataCenter.areaId && !selectedDataCenter.cityId) { + errors.push(t("DataCenter.cityRequired")); + } + + // Validate sector hierarchy + 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")); + } + + return errors; + }; + const handleSubmit = async () => { - if (!selectedDataCenter.name || !selectedDataCenter.externalId) { - enqueueSnackbar(t("Common.fillRequiredFields"), { variant: "error" }); + const validationErrors = validateForm(); + if (validationErrors.length > 0) { + validationErrors.forEach(error => { + enqueueSnackbar(error, { variant: "error" }); + }); return; } @@ -397,10 +486,20 @@ const DataCenterManagement = () => { handleCloseModal(); } catch (error) { console.error("Submit error:", error); - enqueueSnackbar( - error?.message || t("DataCenter.submitError"), - { variant: "error" } - ); + + // Handle specific error cases + if (error.message?.includes("duplicate")) { + 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" } + ); + } } }; @@ -599,19 +698,49 @@ const DataCenterManagement = () => { - - + + option.value === selectedDataCenter.cityId + )} + onChange={(option) => { + setSelectedDataCenter({ + ...selectedDataCenter, + cityId: option?.value, + city: option?.label || "", + }); + }} + isClearable + filterOption={customFilterForSelect} + isDisabled={!selectedDataCenter.areaId} /> @@ -670,7 +799,7 @@ const DataCenterManagement = () => { - +