TDR Chart von Dropdown Menü Auwahl zeichnen

This commit is contained in:
ISA
2025-03-24 15:00:20 +01:00
parent 69bcbf519d
commit 68614df0cd
14 changed files with 13428 additions and 152 deletions

View File

@@ -0,0 +1,20 @@
digraph TDRReduxStructure {
rankdir=LR;
node [shape=record, fontname=Helvetica];
SlotData [label="{ SlotData | + slotIndex: number\\l+ tdrList: TDRMeasurement[]\\l }"];
TDRMeasurement [label="{ TDRMeasurement | + id: number\\l+ t: string\\l+ d: number\\l+ p: number\\l+ s: number\\l+ a: number\\l }"];
TDMChartSlice [label="{ tdmChartSlice (Redux) | + data: SlotData[]\\l }"];
TDRChartSlice [label="{ tdrChartDataByIdSlice (Redux) | + data: Record<id, TDRChartPoint[]>\\l }"];
fetchAllTDMData [label="fetchAllTDMData.ts", shape=note];
fetchTDRChartThunk [label="fetchTDRChartDataByIdThunk.ts", shape=note];
fetchTDRChartService [label="fetchTDRChartDataById.ts", shape=note];
kabelueberwachung [label="kabelueberwachung.tsx", shape=note];
SlotData -> TDRMeasurement [arrowhead="open", label="has many"];
fetchAllTDMData -> TDMChartSlice;
fetchTDRChartThunk -> TDRChartSlice;
fetchTDRChartThunk -> fetchTDRChartService;
kabelueberwachung -> fetchAllTDMData;
kabelueberwachung -> fetchTDRChartThunk;
}

View File

@@ -7,12 +7,16 @@ import { useSelector } from "react-redux";
import { Chart, registerables } from "chart.js"; import { Chart, registerables } from "chart.js";
import "chartjs-adapter-date-fns"; import "chartjs-adapter-date-fns";
import { getColor } from "../../../../../../utils/colors"; import { getColor } from "../../../../../../utils/colors";
import TDRChartActionBar from "./TDRChartActionBar";
const TDRChart: React.FC<{ isFullScreen: boolean }> = ({ isFullScreen }) => { const TDRChart: React.FC<{ isFullScreen: boolean }> = ({ isFullScreen }) => {
const chartRef = useRef<HTMLCanvasElement>(null); const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart | null>(null); const chartInstance = useRef<Chart | null>(null);
// 🟢 **Hole den ausgewählten Slot und Messkurve aus Redux** // 🟢 **Hole den ausgewählten Slot und Messkurve aus Redux**
const selectedId = useSelector(
(state: RootState) => state.tdrDataById.selectedId
);
const selectedSlot = useSelector( const selectedSlot = useSelector(
(state: RootState) => state.kueChartMode.selectedSlot (state: RootState) => state.kueChartMode.selectedSlot
); );
@@ -20,7 +24,7 @@ const TDRChart: React.FC<{ isFullScreen: boolean }> = ({ isFullScreen }) => {
(state: RootState) => state.kueChartMode.activeMode (state: RootState) => state.kueChartMode.activeMode
); );
const tdrChartData = useSelector((state: RootState) => const tdrChartData = useSelector((state: RootState) =>
selectedSlot !== null ? state.tdrChart.data[selectedSlot] || [] : [] selectedId !== null ? state.tdrDataById.dataById[selectedId] || [] : []
); );
const referenceChartData = useSelector((state: RootState) => const referenceChartData = useSelector((state: RootState) =>
@@ -142,9 +146,10 @@ const TDRChart: React.FC<{ isFullScreen: boolean }> = ({ isFullScreen }) => {
} }
}); });
}, [JSON.stringify(tdrChartData), selectedSlot, selectedChartType]); }, [JSON.stringify(tdrChartData), selectedSlot, selectedChartType]);
return ( return (
<div style={{ width: "100%", height: isFullScreen ? "90%" : "28rem" }}> <div style={{ width: "100%", height: isFullScreen ? "90%" : "28rem" }}>
<TDRChartActionBar />
{tdrChartData.length === 0 ? ( {tdrChartData.length === 0 ? (
<div className="flex items-center justify-center h-full text-gray-500 italic"> <div className="flex items-center justify-center h-full text-gray-500 italic">
Keine Daten verfügbar für diesen Slot Keine Daten verfügbar für diesen Slot

View File

@@ -1,158 +1,61 @@
import React, { useState, useEffect } from "react"; // /components/main/kabelueberwachung/kue705FO/Charts/TDRChart/TDRChartActionBar.tsx
import { useDispatch } from "react-redux"; import React, { useState } from "react";
import { setTDRChartData } from "../../../../../../redux/slices/kabelueberwachungChartSlice"; import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../../../../../redux/store";
import { fetchTDRChartDataById } from "../../../../../../services/fetchTDRChartDataById";
import {
setTDRChartDataById,
setSelectedTDRId,
} from "../../../../../../redux/slices/tdrDataByIdSlice";
const TDRChartActionBar: React.FC = () => { const TDRChartActionBar: React.FC = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [jahr, setJahr] = useState(new Date().getFullYear());
const [monat, setMonat] = useState(new Date().getMonth() + 1);
const [dateiListe, setDateiListe] = useState<string[]>([]);
const [ausgewählteDatei, setAusgewählteDatei] = useState<string>("");
const [sortAscending, setSortAscending] = useState(true);
const getYearFolderName = (year: number): string => { const tdmChartData = useSelector((state: RootState) => state.tdmChart.data);
return `Year_${String(year).slice(-2)}`; const selectedSlot = useSelector(
}; (state: RootState) => state.kueChartMode.selectedSlot
);
// 📌 Laden der Datei-Liste aus directory.json const idsForSlot =
useEffect(() => { selectedSlot !== null ? tdmChartData[selectedSlot] ?? [] : [];
const loadDirectory = async () => {
const yearFolder = getYearFolderName(jahr);
const monthFolder = `Month_${monat.toString().padStart(2, "0")}`;
const path = `/CPLmockData/LastTDR/kue_01/${yearFolder}/${monthFolder}/directory.json`;
try { const [selectedId, setSelectedId] = useState<number | null>(null);
const response = await fetch(path);
if (!response.ok) {
console.error(`Fehler beim Laden der Datei.`);
setDateiListe([]);
setAusgewählteDatei("");
return;
}
const data = await response.json(); const handleSelectChange = async (
let files = data.files.map( e: React.ChangeEvent<HTMLSelectElement>
(file: { filename: string }) => file.filename ) => {
); const id = parseInt(e.target.value);
setSelectedId(id);
if (!sortAscending) { const data = await fetchTDRChartDataById(id);
files.reverse(); // 🔄 Falls bereits sortiert, Reihenfolge umkehren if (!data) return;
}
setDateiListe(files); dispatch(setTDRChartDataById({ id, data }));
if (files.length > 0) { dispatch(setSelectedTDRId(id)); // 👉 wichtig!
setAusgewählteDatei(files[0]); // 🟢 Automatische Auswahl der ersten Datei
loadAndStoreChartData(files[0]); // 🟢 Chart-Daten sofort aktualisieren
}
} catch (error) {
console.error("Fehler beim Laden der Datei directory.json:", error);
}
};
loadDirectory();
}, [jahr, monat, sortAscending]);
// 📌 Daten für die ausgewählte Datei laden
const handleDateiAuswahl = (event: React.ChangeEvent<HTMLSelectElement>) => {
const selectedFile = event.target.value;
setAusgewählteDatei(selectedFile);
loadAndStoreChartData(selectedFile);
};
// 📌 Sortieren der Datei-Liste und Chart sofort aktualisieren
const handleSortToggle = () => {
setSortAscending(!sortAscending);
setDateiListe((prevListe) => {
const newListe = [...prevListe].reverse(); // 🔄 Reihenfolge umkehren
if (newListe.length > 0) {
setAusgewählteDatei(newListe[0]); // 🟢 Erste Datei nach dem Sortieren auswählen
loadAndStoreChartData(newListe[0]); // 🟢 Chart sofort aktualisieren
}
return newListe;
});
};
// 📌 Daten abrufen und in Redux speichern
const loadAndStoreChartData = async (filename: string) => {
const yearFolder = getYearFolderName(jahr);
const monthFolder = `Month_${monat.toString().padStart(2, "0")}`;
const filePath = `/CPLmockData/LastTDR/kue_01/${yearFolder}/${monthFolder}/${filename}`;
try {
const response = await fetch(filePath);
if (!response.ok) {
console.error(`Fehler beim Laden der Datei.`);
return;
}
const data = await response.json();
dispatch(setTDRChartData(data));
} catch (error) {
console.error("Fehler beim Laden der JSON-Daten:", error);
}
}; };
return ( return (
<div className="flex flex-wrap justify-end items-center p-2 bg-gray-100 rounded-lg space-x-2"> <div className="flex flex-wrap justify-end items-center p-2 bg-gray-100 rounded-lg space-x-2">
{/* Jahr Auswahl */} {idsForSlot.length > 0 && (
<label htmlFor="jahrSelect" className="text-sm font-semibold"> <>
Jahr <label htmlFor="tdrIdSelect" className="text-sm font-semibold">
</label> Messung ID
<select </label>
id="jahrSelect" <select
className="border rounded px-2 py-1" id="tdrIdSelect"
value={jahr} value={selectedId ?? ""}
onChange={(e) => setJahr(Number(e.target.value))} onChange={handleSelectChange}
> className="border rounded px-2 py-1 text-sm"
{Array.from({ length: 11 }, (_, i) => 2020 + i).map((year) => ( >
<option key={year} value={year}> <option value="">-- Wähle Messung --</option>
{year} {idsForSlot.map((entry) => (
</option> <option key={entry.id} value={entry.id}>
))} ID {entry.id} {entry.t}
</select> </option>
))}
{/* Monat Auswahl */} </select>
<label htmlFor="monatSelect" className="text-sm font-semibold"> </>
Monat )}
</label>
<select
id="monatSelect"
className="border rounded px-2 py-1"
value={monat}
onChange={(e) => setMonat(Number(e.target.value))}
>
{Array.from({ length: 12 }, (_, i) => i + 1).map((month) => (
<option key={month} value={month}>
{month.toString().padStart(2, "0")}
</option>
))}
</select>
{/* Datei Auswahl */}
<label htmlFor="dateiSelect" className="text-sm font-semibold">
Auswahl
</label>
<select
id="dateiSelect"
className="border rounded px-2 py-1"
value={ausgewählteDatei}
onChange={handleDateiAuswahl}
>
{dateiListe.map((file, index) => (
<option key={index} value={file}>
{file}
</option>
))}
</select>
{/* Sortieren-Button */}
<button
onClick={handleSortToggle}
className="px-3 py-1 bg-blue-500 text-white rounded text-sm"
>
<i className={`bi bi-arrow-${sortAscending ? "down" : "up"}-short`} />
<span className="ml-1">Sortieren</span>
</button>
</div> </div>
); );
}; };

View File

@@ -6,5 +6,5 @@
2: Patch oder Hotfix (Bugfixes oder kleine Änderungen). 2: Patch oder Hotfix (Bugfixes oder kleine Änderungen).
*/ */
const webVersion = "1.6.151"; const webVersion = "1.6.152";
export default webVersion; export default webVersion;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
// /redux/slices/tdmChartSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { fetchAllTDMData } from "../thunks/fetchAllTDMThunk"; import { fetchAllTDMData } from "../thunks/fetchAllTDMThunk";
import type { TDMEntry } from "../../types"; // optional, wenn ausgelagert import type { TDMEntry } from "../../types"; // optional, wenn ausgelagert

View File

@@ -0,0 +1,34 @@
// redux/slices/tdrDataByIdSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface TDRDataState {
dataById: {
[id: number]: { d: number; p: number }[];
};
selectedId: number | null;
}
const initialState: TDRDataState = {
dataById: {},
selectedId: null,
};
const tdrDataByIdSlice = createSlice({
name: "tdrDataById",
initialState,
reducers: {
setTDRChartDataById: (
state,
action: PayloadAction<{ id: number; data: { d: number; p: number }[] }>
) => {
state.dataById[action.payload.id] = action.payload.data;
},
setSelectedTDRId: (state, action: PayloadAction<number>) => {
state.selectedId = action.payload;
},
},
});
export const { setTDRChartDataById, setSelectedTDRId } =
tdrDataByIdSlice.actions;
export default tdrDataByIdSlice.reducer;

View File

@@ -17,6 +17,7 @@ import digitalInputsReducer from "./slices/digitalInputsSlice";
import tdrReferenceChartReducer from "./slices/tdrReferenceChartSlice"; import tdrReferenceChartReducer from "./slices/tdrReferenceChartSlice";
import loopChartReducer from "./slices/loopChartSlice"; import loopChartReducer from "./slices/loopChartSlice";
import tdmChartReducer from "./slices/tdmChartSlice"; import tdmChartReducer from "./slices/tdmChartSlice";
import tdrDataByIdReducer from "./slices/tdrDataByIdSlice";
const store = configureStore({ const store = configureStore({
reducer: { reducer: {
@@ -36,6 +37,7 @@ const store = configureStore({
tdrReferenceChart: tdrReferenceChartReducer, tdrReferenceChart: tdrReferenceChartReducer,
loopChart: loopChartReducer, loopChart: loopChartReducer,
tdmChart: tdmChartReducer, tdmChart: tdmChartReducer,
tdrDataById: tdrDataByIdReducer,
}, },
}); });

View File

@@ -5,18 +5,18 @@ export const fetchAllTDMDataFromServer = async (): Promise<any[]> => {
const isDev = window.location.hostname === "localhost"; const isDev = window.location.hostname === "localhost";
const baseUrl = isDev const slotRequests = Array.from({ length: 32 }, (_, i) => {
? "/CPLmockData/TDM" // z.B. /public/CPLmockData/TDM/slot0.json usw. const url = isDev
: `${window.location.origin}/CPL?Service/empty.acp&TDM`; ? `/CPLmockData/TDM/slot${i}.json` // ✅ korrekt für DEV (Dateien im public-Ordner)
: `${window.location.origin}/CPL?Service/empty.acp&TDM=${i}`; // ✅ korrekt für PROD
const slotRequests = Array.from({ length: 32 }, (_, i) => return fetch(url)
fetch(`${baseUrl}=${i}`)
.then((res) => (res.ok ? res.json() : null)) .then((res) => (res.ok ? res.json() : null))
.catch((err) => { .catch((err) => {
console.error(`❌ Fehler bei Slot ${i}:`, err); console.error(`❌ Fehler bei Slot ${i}:`, err);
return null; return null;
}) });
); });
return await Promise.all(slotRequests); return await Promise.all(slotRequests);
}; };

View File

@@ -0,0 +1,22 @@
// /services/fetchTDRChartDataById.ts
export const fetchTDRChartDataById = async (
id: number
): Promise<any[] | null> => {
const isDev = process.env.NODE_ENV === "development";
const url = isDev
? `http://localhost:3000/CPLmockData/Last100TDR/kue_01/id/${id}.json`
: `${window.location.origin}/CPL?Service/empty.acp&TDR=${id}`;
try {
const response = await fetch(url);
if (!response.ok)
throw new Error(`Fehler beim Laden der TDR-Daten für ID ${id}`);
return await response.json();
} catch (error) {
console.error("❌ Fehler in fetchTDRChartDataById:", error);
return null;
}
};