refactorring dashboard

This commit is contained in:
ISA
2025-06-27 07:23:34 +02:00
parent 7da872ae07
commit d3a8556b2f
18 changed files with 63 additions and 56 deletions

View File

@@ -0,0 +1,92 @@
"use client"; // components/main/uebersicht/Baugruppentraeger.tsx
import React, { useMemo, useEffect } from "react";
import { useSelector } from "react-redux";
import { useRouter } from "next/navigation";
import { RootState, useAppDispatch } from "@/redux/store";
import KabelModulStatus from "./modulesStatus/KabelModulStatus";
import { getKueDataThunk } from "@/redux/thunks/getKueDataThunk";
const Baugruppentraeger: React.FC = () => {
const dispatch = useAppDispatch();
const router = useRouter(); // useRouter für Navigation hinzufügen
// Redux-Variablen direkt hier abrufen
const {
kueOnline: kueOnlineRaw,
kueVersion,
kueCableBreak,
kueAlarm1,
kueAlarm2,
kueGroundFault,
} = useSelector((state: RootState) => state.kueDataSlice);
// `kueOnline` sicherstellen, dass es nur Zahlen enthält
const kueOnline = useMemo(
() =>
kueOnlineRaw.map((value) =>
typeof value === "string" ? parseFloat(value) || 0 : value
),
[kueOnlineRaw]
);
// Klick-Handler für Routing
const handleModuleClick = (rackNumber: number) => {
router.push(`/kabelueberwachung?rack=${rackNumber}`);
};
const baugruppen: JSX.Element[] = [];
const numBaugruppen = Math.ceil(kueOnline.length / 8);
for (let i = 0; i < numBaugruppen; i++) {
const slots = kueOnline.slice(i * 8, (i + 1) * 8);
baugruppen.push(
<div
key={i}
className="flex bg-white shadow-md rounded-lg mb-4 xl:mb-0 lg:mb-0 border border-gray-200 w-full laptop:scale-y-75 xl:scale-y-90"
>
<div className="flex gap-1">
{slots.map((version, index) => {
const slotNumber = i * 8 + index + 1;
const isSlotOnline = kueOnline[slotNumber - 1] === 1;
const rawModuleVersion = kueVersion[slotNumber - 1] || version;
// Sicherstellen, dass moduleVersion eine Zahl ist
const moduleVersion =
typeof rawModuleVersion === "number"
? rawModuleVersion
: parseFloat(rawModuleVersion) || 0;
const rackNumber = Math.ceil(slotNumber / 8);
return (
<div
key={slotNumber}
className="cursor-pointer"
onClick={() => handleModuleClick(rackNumber)}
>
<KabelModulStatus
slot={slotNumber}
isOnline={isSlotOnline}
moduleVersion={moduleVersion}
kueCableBreak={kueCableBreak}
kueAlarm1={kueAlarm1}
kueAlarm2={kueAlarm2}
kueGroundFault={kueGroundFault}
/>
</div>
);
})}
</div>
</div>
);
}
//--------------------------------------------
useEffect(() => {
dispatch(getKueDataThunk());
}, [dispatch]);
//--------------------------------------------
return <>{baugruppen}</>;
};
export default Baugruppentraeger;

View File

@@ -0,0 +1,105 @@
"use client";
import React, { useEffect, useState } from "react";
type Meldung = {
t: string; // Zeitstempel
s: number; // Status
c: string; // Farbe
m: string; // Meldung
i: string; // Modul/Quelle
};
const Last20MessagesTable: React.FC<{ className?: string }> = ({
className,
}) => {
const [messages, setMessages] = useState<Meldung[]>([]);
useEffect(() => {
const fetchLast20Messages = async () => {
const today = new Date();
const prior30 = new Date();
prior30.setDate(today.getDate() - 30);
const format = (d: Date) =>
`${d.getFullYear()};${(d.getMonth() + 1)
.toString()
.padStart(2, "0")};${d.getDate().toString().padStart(2, "0")}`;
const from = format(prior30);
const to = format(today);
const isDev =
typeof window !== "undefined" &&
window.location.hostname === "localhost";
const url = isDev
? `/api/cpl/last20MessagesAPIHandler`
: `/CPL?Service/ae.ACP&MSS1=${from};${to};All`;
try {
const res = await fetch(url);
const raw = await res.json();
const data = Array.isArray(raw) ? raw : raw.data;
if (!Array.isArray(data)) return;
const sorted = [...data].sort(
(a, b) => new Date(b.t).getTime() - new Date(a.t).getTime()
);
const last20 = sorted.slice(0, 20); // NEUESTE zuerst
setMessages(last20);
} catch (err) {
console.error("Fehler beim Laden der Meldungen:", err);
}
};
fetchLast20Messages();
}, []);
return (
<div className={`bg-white p-1 rounded-lg overflow-auto ${className}`}>
<div className="overflow-x-auto overflow-y-auto border rounded shadow-sm h-[95%] pt-1">
<table className="min-w-full border">
<thead className="bg-gray-100 text-left sticky top-0 z-10">
<tr>
<th className="p-2 border">Prio</th>
<th className="p-2 border">Zeit</th>
<th className="p-2 border">Quelle</th>
<th className="p-2 border">Meldung</th>
<th className="p-2 border">Status</th>
</tr>
</thead>
<tbody>
{messages.length === 0 ? (
<tr>
<td
colSpan={5}
className="text-center italic text-gray-500 p-4"
>
Keine Meldungen verfügbar.
</td>
</tr>
) : (
messages.map((msg, index) => (
<tr key={index} className="hover:bg-gray-50">
<td className="border p-2">
<div
className="w-4 h-4 rounded"
style={{ backgroundColor: msg.c }}
></div>
</td>
<td className="border p-2 whitespace-nowrap">{msg.t}</td>
<td className="border p-2">{msg.i}</td>
<td className="border p-2">{msg.m}</td>
<td className="border p-2">{msg.s}</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
);
};
export default Last20MessagesTable;

View File

@@ -0,0 +1,110 @@
"use client"; //components/main/uebersicht/NetworkInfo.tsx
import React, { useEffect } from "react";
import Image from "next/image";
import { useSelector, useDispatch } from "react-redux";
import { RootState, AppDispatch } from "@/redux/store";
import { getSystemSettingsThunk } from "@/redux/thunks/getSystemSettingsThunk";
import { getOpcUaSettingsThunk } from "@/redux/thunks/getOpcUaSettingsThunk";
const NetworkInfo: React.FC = () => {
const dispatch: AppDispatch = useDispatch();
// ✅ OPC UA Daten laden, wenn Komponente angezeigt wird
useEffect(() => {
dispatch(getSystemSettingsThunk());
dispatch(getOpcUaSettingsThunk());
}, [dispatch]);
// Werte direkt aus Redux holen
const ip =
useSelector((state: RootState) => state.systemSettingsSlice.ip) ||
"Unbekannt";
const subnet =
useSelector((state: RootState) => state.systemSettingsSlice.subnet) ||
"Unbekannt";
const gateway =
useSelector((state: RootState) => state.systemSettingsSlice.gateway) ||
"Unbekannt";
const opcUaZustandRaw = useSelector(
(state: RootState) => state.opcuaSettingsSlice.opcUaZustand
);
// OPC-UA Zustand in lesbaren Text umwandeln
const opcUaZustand =
Number(opcUaZustandRaw) === 1
? "Server betriebsbereit"
: Number(opcUaZustandRaw) === 0
? "Server außer Betrieb"
: "Unbekannt";
return (
<div className="w-full flex-direction: row flex">
<div className=" flex-grow flex justify-between items-center mt-1 bg-white p-2 rounded-lg shadow-md border border-gray-200 laptop:m-0 laptop:scale-y-75 2xl:scale-y-75">
<div className="flex items-center space-x-4">
<Image
src="/images/IP-icon.svg"
alt="IP Address"
width={24}
height={24}
className="w-6 text-littwin-blue"
priority
/>
<div>
<p className="text-xs text-gray-500">IP-Adresse</p>
<p className="text-sm font-medium text-gray-700">{ip}</p>
</div>
</div>
<div className="flex items-center space-x-4">
<Image
src="/images/subnet-mask.svg"
alt="subnet mask"
width={24}
height={24}
className="w-6"
priority
/>
<div>
<p className="text-xs text-gray-500">Subnet-Maske</p>
<p className="text-sm font-medium text-gray-700">{subnet}</p>
</div>
</div>
<div className="flex items-center space-x-4">
<Image
src="/images/gateway.svg"
alt="gateway"
width={24}
height={24}
className="w-6"
priority
/>
<div>
<p className="text-xs text-gray-500">Gateway</p>
<p className="text-sm font-medium text-gray-700">{gateway}</p>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="text-xs font-bold text-littwin-blue">OPC-UA</div>
<div>
<p className="text-xs text-gray-500">Status</p>
<p className="text-sm font-medium text-gray-700">{opcUaZustand}</p>
</div>
</div>
{/* OPC UA Nodeset Name */}
{/*
<div className="flex items-center space-x-4">
<div>
<p className="text-xs text-gray-500">Nodeset Name</p>
<p className="text-sm font-medium text-gray-700">
{opcUaNodesetName}
</p>
</div>
</div>
*/}
</div>
</div>
);
};
export default NetworkInfo;

View File

@@ -0,0 +1,42 @@
"use client"; // components/main/uebersicht/VersionInfo.tsx
import React from "react";
import { Icon } from "@iconify/react";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store";
type VersionInfoProps = {
className?: string;
};
const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
const appVersion =
useSelector((state: RootState) => state.systemSettingsSlice.appVersion) ||
"Unbekannt";
const webVersion = useSelector(
(state: RootState) => state.webVersionSlice.appVersion
);
return (
<div
className={`bg-gray-50 rounded-lg shadow-sm border border-gray-200 w-full laptop:p-2 ${className}`}
>
<h2 className="text-lg font-semibold text-gray-700 mb-2">
Versionsinformationen
</h2>
<div className="flex flex-row p-2 space-x-2">
<Icon icon="bx:code-block" className="text-xl text-blue-400" />
<p className="text-sm text-gray-600">
Applikationsversion: {appVersion}
</p>
</div>
<div className="flex flex-row p-2 space-x-2">
<Icon icon="mdi:web" className="text-xl text-blue-400" />
<p className="text-sm text-gray-600">Webversion: {webVersion}</p>
</div>
</div>
);
};
export default VersionInfo;

View File

@@ -0,0 +1,15 @@
// components/Access1Status.jsx
import React from "react";
const Access1Status = () => {
return (
<div className="border border-gray-400 w-20 h-10 flex items-center justify-start bg-blue-500">
{/* Grüner Streifen auf der linken Seite */}
<div className=" left-0 top-0 h-full w-1/6 bg-green-500 mr-2"></div>
{/* Blauer Hauptbereich */}
<div className="flex flex-row text-white text-xs ">Access 1</div>
</div>
);
};
export default Access1Status;

View File

@@ -0,0 +1,15 @@
// components/Access2Status.jsx
import React from "react";
const Access2Status = () => {
return (
<div className="border border-gray-400 w-20 h-10 flex items-center justify-start bg-blue-500">
{/* Grüner Streifen auf der linken Seite */}
<div className=" left-0 top-0 h-full w-1/6 bg-green-500 mr-2"></div>
{/* Blauer Hauptbereich */}
<div className="flex flex-row text-white text-xs ">Access 2</div>
</div>
);
};
export default Access2Status;

View File

@@ -0,0 +1,15 @@
// components/CPLStatus.jsx
import React from "react";
const CPLStatus = () => {
return (
<div className="border border-gray-400 w-20 h-10 flex items-center justify-start bg-littwin-blue">
{/* Grüner Streifen auf der linken Seite */}
<div className=" left-0 top-0 h-full w-1/6 bg-green-500 mr-2"></div>
{/* Blauer Hauptbereich */}
<div className="text-white text-xs ">CPL</div>
</div>
);
};
export default CPLStatus;

View File

@@ -0,0 +1,70 @@
const KabelModulStatus: React.FC<KabelModulStatusProps> = ({
slot,
kueCableBreak,
kueAlarm1,
kueAlarm2,
kueGroundFault,
isOnline,
moduleVersion,
}) => {
if (!isOnline) {
return (
<div className="border border-gray-400 w-10 h-20 flex items-center justify-center bg-gray-200">
<div className="text-xs text-gray-500">Leer</div>
</div>
);
}
// Modultyp basierend auf der Version bestimmen
let moduleName = "";
let moduleType = "";
if (moduleVersion === 419) {
moduleName = "KÜ705";
moduleType = "FO";
} else if (moduleVersion === 350) {
moduleName = "KÜ605";
moduleType = "µC";
} else if (moduleVersion === 1100) {
moduleName = "KÜSS";
moduleType = "___";
}
// Status nur prüfen, wenn der Slot aktiv ist (kueOnline für den Slot ist 1)
const isCableBreak =
Array.isArray(kueCableBreak) && kueCableBreak[slot - 1] === 1;
const isAlarm1 = Array.isArray(kueAlarm1) && kueAlarm1[slot - 1] === 1;
const isAlarm2 = Array.isArray(kueAlarm2) && kueAlarm2[slot - 1] === 1;
const groundFault =
Array.isArray(kueGroundFault) && kueGroundFault[slot - 1] === 1;
return (
<div className="border border-gray-400 w-10 h-20 flex flex-col scale-100 xl:scale-90">
<div className="bg-littwin-blue flex-grow flex flex-col items-center justify-center text-white text-[10px]">
<div className="flex w-full mb-1 items-start justify-start">{slot}</div>
<div className="text-[10px]">{moduleName}</div>
<div className="text-[10px]">{moduleType}</div>
</div>
<div
className={`w-full h-2/6 ${
isCableBreak || isAlarm1 || isAlarm2 || groundFault
? "bg-red-500"
: "bg-green-500"
}`}
></div>
<div className="bg-littwin-blue w-full h-1/6"></div>
</div>
);
};
export default KabelModulStatus;
interface KabelModulStatusProps {
slot: number;
kueCableBreak: number[];
kueAlarm1: number[];
kueAlarm2: number[];
kueGroundFault: number[];
isOnline: boolean;
moduleVersion: number;
}

View File

@@ -0,0 +1,15 @@
// components/Access1Status.jsx
import React from "react";
const Access1Status = () => {
return (
<div className="border border-gray-400 w-20 h-10 flex items-center justify-start bg-blue-500">
{/* Grüner Streifen auf der linken Seite */}
<div className=" left-0 top-0 h-full w-1/6 bg-green-500 mr-2"></div>
{/* Blauer Hauptbereich */}
<div className="flex flex-row text-white text-xs ">XIOPM 1</div>
</div>
);
};
export default Access1Status;

View File

@@ -0,0 +1,15 @@
// components/Access1Status.jsx
import React from "react";
const Access1Status = () => {
return (
<div className="border border-gray-400 w-20 h-10 flex items-center justify-start bg-blue-500">
{/* Grüner Streifen auf der linken Seite */}
<div className=" left-0 top-0 h-full w-1/6 bg-green-500 mr-2"></div>
{/* Blauer Hauptbereich */}
<div className="flex flex-row text-white text-xs ">XIOPM 2</div>
</div>
);
};
export default Access1Status;