fixed issue GUI and Datacenter tab related

This commit is contained in:
2025-08-07 19:28:56 +03:00
parent 5c0b325005
commit 39fffadbd2
14 changed files with 686 additions and 188 deletions

View File

@@ -15,7 +15,6 @@ public class DataCenterCreateInput extends BaseCreateInput {
private Integer number; private Integer number;
private Double consuptionAmount; private Double consuptionAmount;
@NotNull(message = "Alan ID gereklidir")
private UUID areaId; private UUID areaId;
@NotNull(message = "Sektör ID gereklidir") @NotNull(message = "Sektör ID gereklidir")

View File

@@ -1,6 +1,7 @@
package com.sgs.graphql.dataCenter.mutation.input; package com.sgs.graphql.dataCenter.mutation.input;
import com.sgs.lib.dao.mutation.input.BaseUpdateInput; import com.sgs.lib.dao.mutation.input.BaseUpdateInput;
import javax.validation.constraints.NotNull;
import java.util.UUID; import java.util.UUID;
public class DataCenterUpdateInput extends BaseUpdateInput { public class DataCenterUpdateInput extends BaseUpdateInput {
@@ -10,6 +11,7 @@ public class DataCenterUpdateInput extends BaseUpdateInput {
private Double consuptionAmount; private Double consuptionAmount;
private UUID areaId; private UUID areaId;
@NotNull(message = "Sektör ID gereklidir")
private UUID sectorId; private UUID sectorId;
private UUID subSectorId; private UUID subSectorId;
private UUID emissionSourceId; private UUID emissionSourceId;

View File

@@ -17,20 +17,14 @@ security.jwt.token.secret-key=secret
app.survey.base-url=http://localhost.com app.survey.base-url=http://localhost.com
# spring.rabbitmq.host=188.132.198.145 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.port=5672 spring.rabbitmq.port=5672
spring.rabbitmq.username=guest spring.rabbitmq.username=testuser
spring.rabbitmq.password=guest spring.rabbitmq.password=JGasF24561AZv2894De
spring.rabbitmq.virtual-host=/ spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=20000 spring.rabbitmq.connection-timeout=20000
spring.rabbitmq.template.retry.enabled=true spring.rabbitmq.template.retry.enabled=true
spring.rabbitmq.template.retry.max-attempts=3 spring.rabbitmq.template.retry.max-attempts=3
spring.rabbitmq.template.retry.initial-interval=1000ms spring.rabbitmq.template.retry.initial-interval=1000ms
logging.level.org.springframework.amqp=DEBUG logging.level.org.springframework.amqp=DEBUG

View File

@@ -49,9 +49,9 @@ export default [
permissionCheck("paginate_roles_get")) && [ permissionCheck("paginate_roles_get")) && [
{ {
id: "Organizations", id: "Organizations",
title: "DataCenters.title", title: "Data Center Management",
icon: <Home size={20} />, icon: <Home size={20} />,
navLink: "/organizasyonlar", navLink: "/veri-merkezi-yonetimi",
display: (permissionCheck("paginate_datacenters_get") || display: (permissionCheck("paginate_datacenters_get") ||
permissionCheck("data_center_create") || permissionCheck("data_center_create") ||
permissionCheck("data_center_update") || permissionCheck("data_center_update") ||
@@ -142,9 +142,63 @@ export default [
}, },
{ {
id: "DataCenter", id: "DataCenter",
title: "Data Centers", title: "Data Center Overview",
icon: <Zap size={20} />, icon: <Zap size={20} />,
navLink: "/verimerkezi", navLink: "/veri-merkezi-genel",
},
{
id: "Areas",
title: "Areas.areas",
icon: <Map size={20} />,
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: <ArrowRight size={20} />,
navLink: "/alanlar",
display: permissionCheck("paginate_areas_get") ? "" : "none",
},
{
id: "Countries",
title: "Areas.countries",
icon: <ArrowRight size={20} />,
navLink: "/ulkeler",
display: permissionCheck("paginate_countries_get") ? "" : "none",
},
{
id: "Cities",
title: "Areas.cities",
icon: <ArrowRight size={20} />,
navLink: "/iller",
display: permissionCheck("paginate_cities_get") ? "" : "none",
},
{
id: "Districts",
title: "Areas.districts",
icon: <ArrowRight size={20} />,
navLink: "/ilceler",
display: permissionCheck("paginate_districts_get") ? "" : "none",
},
{
id: "Neighborhoods",
title: "Areas.neighborhoods",
icon: <ArrowRight size={20} />,
navLink: "/mahalleler",
display: permissionCheck("paginate_neighborhoods_get") ? "" : "none",
},
],
}, },
{ {
id: "Survey", id: "Survey",

View File

@@ -49,9 +49,9 @@ export default [
permissionCheck("paginate_roles_get")) && [ permissionCheck("paginate_roles_get")) && [
{ {
id: "DataCenters", id: "DataCenters",
title: "DataCenters.title", title: "Data Center Management",
icon: <Zap size={20} />, icon: <Zap size={20} />,
navLink: "/organizasyonlar", navLink: "/veri-merkezi-yonetimi",
display: (permissionCheck("paginate_datacenters_get") || display: (permissionCheck("paginate_datacenters_get") ||
permissionCheck("data_center_create") || permissionCheck("data_center_create") ||
permissionCheck("data_center_update") || permissionCheck("data_center_update") ||
@@ -142,9 +142,63 @@ export default [
}, },
{ {
id: "DataCenter", id: "DataCenter",
title: "DataCenters", title: "Data Center Overview",
icon: <Zap size={20} />, icon: <Zap size={20} />,
navLink: "/verimerkezi", navLink: "/veri-merkezi-genel",
},
{
id: "Areas",
title: "Areas.areas",
icon: <Map size={20} />,
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: <ArrowRight size={20} />,
navLink: "/alanlar",
display: permissionCheck("paginate_areas_get") ? "" : "none",
},
{
id: "Countries",
title: "Areas.countries",
icon: <ArrowRight size={20} />,
navLink: "/ulkeler",
display: permissionCheck("paginate_countries_get") ? "" : "none",
},
{
id: "Cities",
title: "Areas.cities",
icon: <ArrowRight size={20} />,
navLink: "/iller",
display: permissionCheck("paginate_cities_get") ? "" : "none",
},
{
id: "Districts",
title: "Areas.districts",
icon: <ArrowRight size={20} />,
navLink: "/ilceler",
display: permissionCheck("paginate_districts_get") ? "" : "none",
},
{
id: "Neighborhoods",
title: "Areas.neighborhoods",
icon: <ArrowRight size={20} />,
navLink: "/mahalleler",
display: permissionCheck("paginate_neighborhoods_get") ? "" : "none",
},
],
}, },
{ {
id: "Survey", id: "Survey",

View File

@@ -12,6 +12,15 @@ export const getAreas = () => {
id id
tag tag
isDeleted isDeleted
cities {
id
name
coordinates
country {
id
name
}
}
} }
} }
`, `,

View File

@@ -34,10 +34,41 @@ export const getDataCenters = () => {
latitude latitude
longitude longitude
area { 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 tag
name
cityId
districtId
} }
projects { projects {
id id
@@ -45,12 +76,16 @@ export const getDataCenters = () => {
physicalMachines { physicalMachines {
id id
name name
vms { vms {
active {
id id
status vmName
name state
power power
calcOn
hostingPm
host
flavorName
tag
config { config {
id id
cpu cpu
@@ -58,7 +93,6 @@ export const getDataCenters = () => {
disk disk
} }
} }
}
} }
} }
} }
@@ -155,10 +189,41 @@ export const createDataCenter = (dataCenterData) => {
latitude latitude
longitude longitude
area { 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 tag
name
cityId
districtId
} }
} }
} }
@@ -169,11 +234,16 @@ export const createDataCenter = (dataCenterData) => {
externalId: parseInt(dataCenterData.externalId), externalId: parseInt(dataCenterData.externalId),
ayposURL: dataCenterData.ayposURL || "", ayposURL: dataCenterData.ayposURL || "",
number: parseInt(dataCenterData.number) || 1, number: parseInt(dataCenterData.number) || 1,
areaId: dataCenterData.areaId, areaId: dataCenterData.areaId || null,
address: dataCenterData.address || "", address: dataCenterData.address || "",
latitude: dataCenterData.latitude ? parseFloat(dataCenterData.latitude) : null, latitude: dataCenterData.latitude ? parseFloat(dataCenterData.latitude) : null,
longitude: dataCenterData.longitude ? parseFloat(dataCenterData.longitude) : 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 latitude
longitude longitude
area { 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 tag
name
cityId
districtId
} }
} }
} }
@@ -255,11 +356,16 @@ export const updateDataCenter = (id, dataCenterData) => {
externalId: parseInt(dataCenterData.externalId), externalId: parseInt(dataCenterData.externalId),
ayposURL: dataCenterData.ayposURL || "", ayposURL: dataCenterData.ayposURL || "",
number: parseInt(dataCenterData.number) || 1, number: parseInt(dataCenterData.number) || 1,
areaId: dataCenterData.areaId, areaId: dataCenterData.areaId || null,
address: dataCenterData.address || "", address: dataCenterData.address || "",
latitude: dataCenterData.latitude ? parseFloat(dataCenterData.latitude) : null, latitude: dataCenterData.latitude ? parseFloat(dataCenterData.latitude) : null,
longitude: dataCenterData.longitude ? parseFloat(dataCenterData.longitude) : 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) => { export const getDataCenterVMs = (dataCenterId) => {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
// Don't make the request if dataCenterId is undefined, null, or empty // Don't make the request if dataCenterId is undefined, null, or empty

View File

@@ -125,11 +125,12 @@ export const getMainDataTablesWithPaginate = (data) => {
{ {
paginateMainDataTables( paginateMainDataTables(
pagination: { page: 0, rowsPerPage: 100 } pagination: { page: 0, rowsPerPage: 100 }
criteria: { deleted: false } criteria: { deleted: false, hasVm: true }
sortBy: [{ field: "createdDate", direction: DESC }] sortBy: [{ field: "createdDate", direction: DESC }]
) { ) {
content { content {
id id
year
sector { sector {
id id
tag tag
@@ -138,11 +139,40 @@ export const getMainDataTablesWithPaginate = (data) => {
id id
tag tag
} }
activitySubUnit {
id
tag
}
emissionSource {
id
tag
}
emissionScope {
id
tag
}
co2 co2
ch4 ch4
n2o n2o
totalEmission totalEmission
createdDate createdDate
vm {
id
vmName
state
power
calcOn
hostingPm
host
flavorName
tag
config {
id
cpu
ram
disk
}
}
} }
pageInfo { pageInfo {
totalElements totalElements

View File

@@ -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;

View File

@@ -27,6 +27,7 @@ import surveys from "./surveys";
import uploads from "./upload"; import uploads from "./upload";
import mailSettings from "./mailSettings"; import mailSettings from "./mailSettings";
import dataCenter from "./dataCenter"; import dataCenter from "./dataCenter";
import emissionScope from "./emissionScope";
const rootReducer = combineReducers({ const rootReducer = combineReducers({
accessToken, accessToken,
@@ -57,6 +58,7 @@ const rootReducer = combineReducers({
uploads, uploads,
mailSettings, mailSettings,
dataCenter, dataCenter,
emissionScope,
}); });
export default rootReducer; export default rootReducer;

View File

@@ -25,7 +25,7 @@ const Routes = [
display: permissionCheck("paginate_users_get"), display: permissionCheck("paginate_users_get"),
}, },
{ {
path: "/organizasyonlar", path: "/veri-merkezi-yonetimi",
component: lazy(() => import("../../views/DataCenterManagement")), component: lazy(() => import("../../views/DataCenterManagement")),
display: permissionCheck("paginate_datacenters_get") || display: permissionCheck("paginate_datacenters_get") ||
permissionCheck("data_center_create") || permissionCheck("data_center_create") ||
@@ -74,7 +74,7 @@ const Routes = [
display: permissionCheck("activity_sub_units_get"), display: permissionCheck("activity_sub_units_get"),
}, },
{ {
path: "/verimerkezi", path: "/veri-merkezi-genel",
component: lazy(() => import("../../views/DataCenter")), component: lazy(() => import("../../views/DataCenter")),
}, },
{ {

View File

@@ -41,14 +41,23 @@ const DataCenter = () => {
// Projects // Projects
{ {
name: "Projects", name: "Projects",
selector: (row) => selector: (row) => (row.projects || []).length,
(row.projects || []).map((p) => p.name).join(", "), sortable: true,
sortable: false,
minWidth: "200px", minWidth: "200px",
cell: (row) => ( cell: (row) => (
<span> <div>
{(row.projects || []).map((p) => p.name).join(", ") || "-"} {(row.projects || []).length > 0 ? (
</span> <div className="d-flex flex-column">
{row.projects.map((project, index) => (
<div key={project.id} className={`badge badge-light-primary ${index > 0 ? 'mt-1' : ''}`}>
{project.name}
</div>
))}
</div>
) : (
<span className="text-muted">-</span>
)}
</div>
), ),
}, },
// Physical Machines // Physical Machines
@@ -68,26 +77,41 @@ const DataCenter = () => {
}, },
}, },
{ {
name: "Total Active VMs", name: "Virtual Machines",
selector: (row) => { selector: (row) => {
const pms = getAllPhysicalMachines(row); const pms = getAllPhysicalMachines(row);
return pms.reduce( const vms = pms.reduce((acc, pm) => {
(total, pm) => total + (pm.vms?.active?.length || 0), if (!pm.vms) return acc;
0 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, sortable: true,
minWidth: "150px", minWidth: "200px",
cell: (row) => { cell: (row) => {
const pms = getAllPhysicalMachines(row); const pms = getAllPhysicalMachines(row);
const totalVMs = pms.reduce( const vms = pms.reduce((acc, pm) => {
(total, pm) => total + (pm.vms?.active?.length || 0), if (!pm.vms) return acc;
0 return {
); active: acc.active + pm.vms.filter(vm => vm.state?.toLowerCase() === "active").length,
total: acc.total + pm.vms.length
};
}, { active: 0, total: 0 });
return ( return (
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<Monitor size={16} className="mr-1" /> <Monitor size={16} className="mr-2" />
<span>{totalVMs}</span> <div>
<div className="font-weight-bold">{vms.total} Total</div>
<div className="small">
<span className="text-success">{vms.active} Active</span>
<span className="text-muted mx-1"></span>
<span className="text-warning">{vms.total - vms.active} Inactive</span>
</div>
</div>
</div> </div>
); );
}, },
@@ -138,76 +162,78 @@ const DataCenter = () => {
<div key={pm.id} className="mb-3 border rounded p-3"> <div key={pm.id} className="mb-3 border rounded p-3">
<h6 className="text-primary">{pm.name}</h6> <h6 className="text-primary">{pm.name}</h6>
{/* Active VMs */} {/* All VMs */}
<p className="mb-2"> <div className="mb-2 d-flex justify-content-between align-items-center">
<strong>Active VMs ({pm.vms?.active?.length || 0}):</strong> <h6 className="mb-0">
</p> <Monitor size={16} className="mr-1" />
{pm.vms?.active?.length > 0 ? ( Virtual Machines ({pm.vms?.length || 0})
<div className="ml-3"> </h6>
{pm.vms.active.map((vm) => ( </div>
<div {pm.vms?.length > 0 ? (
key={vm.id} <div className="table-responsive mt-2">
className="mb-2 p-2 border-left border-success" <table className="table table-bordered table-hover">
> <thead className="thead-light">
<div className="row"> <tr>
<div className="col-md-3"> <th>Name</th>
<strong>Name:</strong> {vm.name} <th>Status</th>
</div> <th>Power (W)</th>
<div className="col-md-2"> <th>Configuration</th>
<strong>Status:</strong> <th>Host</th>
<span className="badge badge-success ml-1"> </tr>
{vm.status} </thead>
</span> <tbody>
</div> {pm.vms.map((vm) => {
<div className="col-md-2"> const isActive = vm.state && ["ACTIVE", "active"].includes(vm.state);
<strong>Power:</strong> {vm.power} return (
</div> <tr key={vm.id}>
<div className="col-md-5"> <td>
<strong>Config:</strong> CPU: {vm.config?.cpu}, RAM:{" "} <span className="font-weight-bold">{vm.vmName || vm.vm_name}</span>
{vm.config?.ram}, Disk: {vm.config?.disk} </td>
</div> <td>
</div> <div className={`d-inline-block px-2 py-1 rounded-pill ${isActive ? 'bg-light-success text-success' : 'bg-light-warning text-warning'}`}>
</div> {vm.state}
))} </div>
</td>
<td>
{vm.power ? (
<span>{vm.power.toFixed(2)}</span>
) : (
<span className="text-muted">-</span>
)}
</td>
<td>
<div className="d-flex align-items-center">
<div className="mr-3">
<small className="text-muted d-block">CPU</small>
<span>{(vm.config?.cpu || (vm.confg && vm.confg[1])) || '-'}</span>
</div>
<div className="mr-3">
<small className="text-muted d-block">RAM</small>
<span>{(vm.config?.ram || (vm.confg && vm.confg[2])) || '-'} GB</span>
</div>
<div>
<small className="text-muted d-block">Disk</small>
<span>{(vm.config?.disk || (vm.confg && vm.confg[3])) || '-'} GB</span>
</div>
</div>
</td>
<td>
<div className="d-flex align-items-center">
<Server size={14} className="mr-1" />
<span>{vm.host || vm.hostingPm || (vm.confg && vm.confg[4]) || '-'}</span>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div> </div>
) : ( ) : (
<p className="text-muted ml-3">No active VMs</p> <div className="text-center p-3 bg-light-secondary rounded">
)} <Monitor size={24} className="text-muted mb-1" />
<p className="text-muted mb-0">No virtual machines found</p>
{/* Inactive VMs */}
<p className="mb-2 mt-3">
<strong>Inactive VMs ({pm.vms?.inactive?.length || 0}):</strong>
</p>
{pm.vms?.inactive?.length > 0 ? (
<div className="ml-3">
{pm.vms.inactive.map((vm) => (
<div
key={vm.id}
className="mb-2 p-2 border-left border-warning"
>
<div className="row">
<div className="col-md-3">
<strong>Name:</strong> {vm.name}
</div>
<div className="col-md-2">
<strong>Status:</strong>
<span className="badge badge-warning ml-1">
{vm.status}
</span>
</div>
<div className="col-md-2">
<strong>Power:</strong> {vm.power}
</div>
<div className="col-md-5">
<strong>Config:</strong> CPU: {vm.config?.cpu}, RAM:{" "}
{vm.config?.ram}, Disk: {vm.config?.disk}
</div>
</div>
</div>
))}
</div> </div>
) : (
<p className="text-muted ml-3">No inactive VMs</p>
)} )}
</div> </div>
))} ))}

View File

@@ -28,7 +28,8 @@ 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 } 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 { 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";
@@ -105,6 +106,8 @@ const DataCenterManagement = () => {
externalId: "", externalId: "",
number: "", number: "",
address: "", address: "",
areaId: null,
cityId: null,
latitude: null, latitude: null,
longitude: null, longitude: null,
ayposURL: "", ayposURL: "",
@@ -120,14 +123,18 @@ const DataCenterManagement = () => {
const [mapPosition, setMapPosition] = useState(null); const [mapPosition, setMapPosition] = useState(null);
const dataCenterStore = useSelector((state) => state.dataCenter); const dataCenterStore = useSelector((state) => state.dataCenter);
const emissionScopeStore = useSelector((state) => state.emissionScope);
const datasStore = useSelector((state) => state.datas); const datasStore = useSelector((state) => state.datas);
const emissionSourceStore = useSelector((state) => state.emissionSources); const emissionSourceStore = useSelector((state) => state.emissionSources);
const areasStore = useSelector((state) => state.areas);
const [sectorsOptions, setSectorsOptions] = useState([]); const [sectorsOptions, setSectorsOptions] = useState([]);
const [subSectorsOptions, setSubSectorsOptions] = useState([]); const [subSectorsOptions, setSubSectorsOptions] = useState([]);
const [emissionSourcesOptions, setEmissionSourcesOptions] = useState([]); const [emissionSourcesOptions, setEmissionSourcesOptions] = useState([]);
const [consuptionUnitsOptions, setConsuptionUnitsOptions] = useState([]); const [consuptionUnitsOptions, setConsuptionUnitsOptions] = useState([]);
const [activitySubUnitsOptions, setActivitySubUnitsOptions] = useState([]); const [activitySubUnitsOptions, setActivitySubUnitsOptions] = useState([]);
const [emissionScopesOptions, setEmissionScopesOptions] = 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 // Add state for selected sector and sub sector like in data input
const [selectedSector, setSelectedSector] = useState(null); const [selectedSector, setSelectedSector] = useState(null);
@@ -215,6 +222,8 @@ const DataCenterManagement = () => {
useEffect(() => { useEffect(() => {
dispatch(getDataCenters()); dispatch(getDataCenters());
dispatch(getSectors()); dispatch(getSectors());
dispatch(getAreas());
dispatch(getEmissionScopes());
}, [dispatch]); }, [dispatch]);
useEffect(() => { useEffect(() => {
@@ -295,17 +304,48 @@ const DataCenterManagement = () => {
}, [datasStore?.consuptionUnits]); }, [datasStore?.consuptionUnits]);
useEffect(() => { useEffect(() => {
setEmissionScopesOptions([ if (emissionScopeStore?.emissionScopes) {
{ setEmissionScopesOptions(
label: "Şehir İçi", emissionScopeStore.emissionScopes.map((scope) => ({
value: false, value: scope.id,
}, label: scope.tag,
{ }))
label: "Şehir Dışı", );
value: true, }
}, }, [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) => { const handleEditDataCenter = (row) => {
setEditingDataCenter(row); setEditingDataCenter(row);
@@ -314,10 +354,12 @@ const DataCenterManagement = () => {
externalId: row.externalId, externalId: row.externalId,
number: row.number, number: row.number,
address: row.address, address: row.address,
areaId: row.area?.id,
cityId: null, // City is a string in the backend, not an object
latitude: row.latitude, latitude: row.latitude,
longitude: row.longitude, longitude: row.longitude,
ayposURL: row.ayposURL, ayposURL: row.ayposURL,
city: row.city, city: row.city || "",
emissionScopeId: row.emissionScope?.id, emissionScopeId: row.emissionScope?.id,
sectorId: row.sector?.id, sectorId: row.sector?.id,
subSectorId: row.subSector?.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 () => { const handleSubmit = async () => {
if (!selectedDataCenter.name || !selectedDataCenter.externalId) { const validationErrors = validateForm();
enqueueSnackbar(t("Common.fillRequiredFields"), { variant: "error" }); if (validationErrors.length > 0) {
validationErrors.forEach(error => {
enqueueSnackbar(error, { variant: "error" });
});
return; return;
} }
@@ -397,10 +486,20 @@ const DataCenterManagement = () => {
handleCloseModal(); handleCloseModal();
} catch (error) { } catch (error) {
console.error("Submit error:", error); console.error("Submit error:", error);
enqueueSnackbar(
error?.message || t("DataCenter.submitError"), // Handle specific error cases
{ variant: "error" } 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 = () => {
</Col> </Col>
<Col sm="6"> <Col sm="6">
<FormGroup> <FormGroup>
<Label for="city">{t("DataCenter.city")}</Label> <Label for="area">Area</Label>
<Input <Select
type="text" id="area"
name="city" name="area"
id="city" placeholder="Select area"
placeholder={t("DataCenter.city")} options={areasOptions}
value={selectedDataCenter.city} value={areasOptions?.find(
onChange={(e) => (option) => option.value === selectedDataCenter.areaId
)}
onChange={(option) => {
setSelectedDataCenter({ setSelectedDataCenter({
...selectedDataCenter, ...selectedDataCenter,
city: e.target.value, areaId: option?.value,
}) cityId: null,
} city: "",
});
}}
isClearable
filterOption={customFilterForSelect}
/>
</FormGroup>
</Col>
<Col sm="6">
<FormGroup>
<Label for="city">City</Label>
<Select
id="city"
name="city"
placeholder="Select city"
options={citiesOptions}
value={citiesOptions?.find(
(option) => option.value === selectedDataCenter.cityId
)}
onChange={(option) => {
setSelectedDataCenter({
...selectedDataCenter,
cityId: option?.value,
city: option?.label || "",
});
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!selectedDataCenter.areaId}
/> />
</FormGroup> </FormGroup>
</Col> </Col>
@@ -670,7 +799,7 @@ const DataCenterManagement = () => {
</Col> </Col>
<Col sm="6"> <Col sm="6">
<FormGroup> <FormGroup>
<Label for="sector">Sector</Label> <Label for="sector">Sector <span className="text-danger">*</span></Label>
<Select <Select
id="sector" id="sector"
name="sector" name="sector"

View File

@@ -3,7 +3,7 @@ import { MaterialReactTable } from "material-react-table";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardHeader, CardTitle, Alert } from "reactstrap"; import { Card, CardHeader, CardTitle, Alert } from "reactstrap";
import { getVMEmissionSummary } from "../../redux/actions/mainDataTables/index"; import { getMainDataTablesWithPaginate } from "../../redux/actions/mainDataTables/index";
import { editNumbers } from "../../components/edit-numbers"; import { editNumbers } from "../../components/edit-numbers";
function MainDataTables() { function MainDataTables() {
@@ -13,13 +13,18 @@ function MainDataTables() {
const [error, setError] = useState(null); const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
try { const fetchData = async () => {
// Fetch VM emission data try {
dispatch(getVMEmissionSummary()); setLoading(true);
} catch (err) { await dispatch(getMainDataTablesWithPaginate());
console.error('Error in MainDataTables:', err); } catch (err) {
setError(err.message); console.error('Error in MainDataTables:', err);
} setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [dispatch]); }, [dispatch]);
// Debug log for store data // Debug log for store data
@@ -27,44 +32,46 @@ function MainDataTables() {
console.log('Current store data:', mainDataTablesStore); console.log('Current store data:', mainDataTablesStore);
}, [mainDataTablesStore]); }, [mainDataTablesStore]);
const [loading, setLoading] = useState(true);
const columns = [ const columns = [
{ {
header: "VM ID", header: t("ID"),
accessorKey: "vmId", accessorKey: "id",
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>, Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
}, },
{ {
header: "Data Center", header: t("Year"),
accessorKey: "dataCenter", accessorKey: "year",
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>, Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
}, },
{ {
header: "Project", header: t("Sector"),
accessorKey: "project", accessorKey: "sector",
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>, Cell: ({ cell, row }) => <span>{row.original?.sector?.tag || "-"}</span>,
}, },
{ {
header: "Physical Machine", header: t("Sub Sector"),
accessorKey: "physicalMachine", accessorKey: "subSector",
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>, Cell: ({ cell, row }) => <span>{row.original?.subSector?.tag || "-"}</span>,
}, },
{ {
header: "Virtual Machine", header: t("Activity Sub Unit"),
accessorKey: "vmName", accessorKey: "activitySubUnit",
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>, Cell: ({ cell, row }) => <span>{row.original?.activitySubUnit?.tag || "-"}</span>,
}, },
{ {
header: "VM Power (W)", header: t("Emission Source"),
accessorKey: "vmPower", accessorKey: "emissionSource",
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>, Cell: ({ cell, row }) => <span>{row.original?.emissionSource?.tag || "-"}</span>,
}, },
{ {
header: "VM Status", header: t("Emission Scope"),
accessorKey: "vmStatus", accessorKey: "emissionScope",
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>, Cell: ({ cell, row }) => <span>{row.original?.emissionScope?.tag || "-"}</span>,
}, },
{ {
header: "Total Emissions", header: t("Total Emission"),
accessorKey: "totalEmission", accessorKey: "totalEmission",
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>, Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
}, },
@@ -84,17 +91,18 @@ function MainDataTables() {
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>, Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
}, },
{ {
header: "Created Date", header: t("Created Date"),
accessorKey: "createdDate", accessorKey: "createdDate",
Cell: ({ cell }) => ( Cell: ({ cell }) => (
<span> <span>
{cell.getValue() ? new Date(cell.getValue()).toLocaleString() : "-"} {cell.getValue() ? new Date(cell.getValue()).toLocaleString() : "-"}
</span> </span>
), ),
sortable: true,
}, },
]; ];
const tableData = mainDataTablesStore?.vmEmissionSummary || []; const tableData = mainDataTablesStore?.mainDataTablesWithPaginate?.content || [];
console.log('VM Emission data:', tableData); console.log('VM Emission data:', tableData);
if (error) { if (error) {
@@ -109,7 +117,7 @@ function MainDataTables() {
<div style={{ marginTop: "2%" }}> <div style={{ marginTop: "2%" }}>
<Card> <Card>
<CardHeader className="border-bottom"> <CardHeader className="border-bottom">
<CardTitle tag="h4">{t("Carbon Emission Data")}</CardTitle> <CardTitle tag="h4">{t("Raw Data")}</CardTitle>
</CardHeader> </CardHeader>
<MaterialReactTable <MaterialReactTable
columns={columns} columns={columns}
@@ -137,7 +145,9 @@ function MainDataTables() {
density: 'compact' density: 'compact'
}} }}
state={{ state={{
isLoading: !mainDataTablesStore?.vmEmissionSummary isLoading: loading,
showProgressBars: true,
showSkeletons: true,
}} }}
/> />
</Card> </Card>