feat: AnalogInputsChart mit DateRangePicker und vollständiger Redux-Integration erweitert

- analogInputsHistorySlice angepasst: zeitraum, vonDatum, bisDatum und data hinzugefügt
- Typdefinitionen im Slice und Thunk korrigiert
- getAnalogInputsHistoryThunk erweitert, um vonDatum und bisDatum zu akzeptieren
- DateRangePicker korrekt in AnalogInputsChart.tsx integriert
- Fehler bei Selector-Zugriffen und Dispatch behoben
This commit is contained in:
ISA
2025-07-11 14:01:15 +02:00
parent ca84ac6bb5
commit d278a79030
9 changed files with 128 additions and 77 deletions

View File

@@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false
NEXT_PUBLIC_EXPORT_STATIC=false
NEXT_PUBLIC_USE_CGI=false
# App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.594
NEXT_PUBLIC_APP_VERSION=1.6.597
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)

View File

@@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL
NEXT_PUBLIC_EXPORT_STATIC=true
NEXT_PUBLIC_USE_CGI=true
# App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.594
NEXT_PUBLIC_APP_VERSION=1.6.597
NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,3 +1,36 @@
## [1.6.597] 2025-07-11
- feat(api): Zeitraum und Eingang als Pflichtparameter für AnalogInputs-API eingeführt
- API-Handler für /api/cpl/getAnalogInputsHistory überarbeitet
- `zeitraum` (DIA0, DIA1, DIA2) und `eingang` (18) sind jetzt Pflichtfelder
- Bei fehlenden oder ungültigen Parametern strukturierte Fehlerantwort mit Beispielen
- Daten werden nun gezielt pro Eingang und Zeitraum geladen (z.B. AE3 + DIA1)
- Bessere Fehlerbehandlung bei nicht vorhandenen Dateien
---
## [1.6.596] 2025-07-11
- feat(api): Zeitraum und Eingang als Pflichtparameter für AnalogInputs-API eingeführt
- API-Handler für /api/cpl/getAnalogInputsHistory überarbeitet
- `zeitraum` (DIA0, DIA1, DIA2) und `eingang` (18) sind jetzt Pflichtfelder
- Bei fehlenden oder ungültigen Parametern strukturierte Fehlerantwort mit Beispielen
- Daten werden nun gezielt pro Eingang und Zeitraum geladen (z.B. AE3 + DIA1)
- Bessere Fehlerbehandlung bei nicht vorhandenen Dateien
---
## [1.6.595] 2025-07-11
- feat(api): Zeitraum und Eingang als Pflichtparameter für AnalogInputs-API eingeführt
- API-Handler für /api/cpl/getAnalogInputsHistory überarbeitet
- `zeitraum` (DIA0, DIA1, DIA2) und `eingang` (18) sind jetzt Pflichtfelder
- Bei fehlenden oder ungültigen Parametern strukturierte Fehlerantwort mit Beispielen
- Daten werden nun gezielt pro Eingang und Zeitraum geladen (z.B. AE3 + DIA1)
- Bessere Fehlerbehandlung bei nicht vorhandenen Dateien
---
## [1.6.594] 2025-07-11
- feat(api): Zeitraum und Eingang als Pflichtparameter für AnalogInputs-API eingeführt

View File

@@ -21,8 +21,14 @@ import {
import "chartjs-adapter-date-fns";
import { de } from "date-fns/locale";
import { useSelector, useDispatch } from "react-redux";
import type { RootState, AppDispatch } from "../../../redux/store";
import type { RootState, AppDispatch } from "@/redux/store";
import { getAnalogInputsHistoryThunk } from "@/redux/thunks/getAnalogInputsHistoryThunk";
import DateRangePicker from "@/components/common/DateRangePicker";
import { Listbox } from "@headlessui/react";
import {
setVonDatum,
setBisDatum,
} from "@/redux/slices/analogInputsChartSlice";
// Basis-Registrierung (ohne Zoom-Plugin)
ChartJS.register(
@@ -46,6 +52,7 @@ export default function AnalogInputsChart({
) as unknown as AnalogInput | null;
const dispatch = useDispatch<AppDispatch>();
type AnalogInputHistoryPoint = { t: string | number | Date; m: number };
const { data } = useSelector(
@@ -53,10 +60,20 @@ export default function AnalogInputsChart({
) as {
data: { [key: string]: AnalogInputHistoryPoint[] };
};
const zeitraum = useSelector(
(state: RootState) => state.analogInputsHistory.zeitraum
);
const handleFetchData = () => {
if (!selectedId || !zeitraum) return;
dispatch(getAnalogInputsHistoryThunk({ eingang: selectedId, zeitraum }));
};
useEffect(() => {
dispatch(getAnalogInputsHistoryThunk());
}, [dispatch]);
if (selectedId && zeitraum) {
dispatch(getAnalogInputsHistoryThunk({ eingang: selectedId, zeitraum }));
}
}, [dispatch, selectedId, zeitraum]);
// ✅ Zoom-Plugin dynamisch importieren und registrieren
useEffect(() => {

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "cpl-v4",
"version": "1.6.594",
"version": "1.6.597",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cpl-v4",
"version": "1.6.594",
"version": "1.6.597",
"dependencies": {
"@fontsource/roboto": "^5.1.0",
"@headlessui/react": "^2.2.4",

View File

@@ -1,6 +1,6 @@
{
"name": "cpl-v4",
"version": "1.6.594",
"version": "1.6.597",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -2,22 +2,33 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { getAnalogInputsHistoryThunk } from "../thunks/getAnalogInputsHistoryThunk";
type InputHistoryState = {
data: Record<number, any[]>;
isLoading: boolean;
error: string | null;
export type AnalogInputsHistoryEntry = {
t: string;
m: number;
};
const initialState: InputHistoryState = {
interface AnalogInputsHistoryState {
data: Record<string, AnalogInputsHistoryEntry[]>;
isLoading: boolean;
error: string | null;
zeitraum: string; // z.B. "DIA0", "DIA1", "DIA2"
}
const initialState: AnalogInputsHistoryState = {
data: {},
isLoading: false,
error: null,
zeitraum: "DIA0",
};
const analogInputsHistorySlice = createSlice({
name: "analogInputsHistory",
initialState,
reducers: {},
reducers: {
setZeitraum: (state, action: PayloadAction<string>) => {
state.zeitraum = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(getAnalogInputsHistoryThunk.pending, (state) => {
@@ -26,8 +37,17 @@ const analogInputsHistorySlice = createSlice({
})
.addCase(
getAnalogInputsHistoryThunk.fulfilled,
(state, action: PayloadAction<Record<number, any[]>>) => {
state.data = action.payload;
(
state,
action: PayloadAction<{
eingang: number;
zeitraum: string;
daten: AnalogInputsHistoryEntry[];
}>
) => {
const key = String(action.payload.eingang + 99); // z.B. 100 für AE1
state.data[key] = action.payload.daten;
state.zeitraum = action.payload.zeitraum;
state.isLoading = false;
}
)
@@ -38,4 +58,6 @@ const analogInputsHistorySlice = createSlice({
},
});
export const { setZeitraum } = analogInputsHistorySlice.actions;
export default analogInputsHistorySlice.reducer;

View File

@@ -1,15 +1,25 @@
// /redux/thunks/getAnalogInputsHistoryThunk.ts
import { createAsyncThunk } from "@reduxjs/toolkit";
import { fetchAnalogInputsHistoryService } from "@/services/fetchAnalogInputsHistoryService";
// redux/thunks/getAnalogInputsHistoryThunk.ts
export const getAnalogInputsHistoryThunk = createAsyncThunk(
"analogInputsHistory/fetch",
async (_, { rejectWithValue }) => {
import { createAsyncThunk } from "@reduxjs/toolkit";
import { fetchAnalogInputsHistory } from "@/services/fetchAnalogInputsHistoryService";
import { AnalogInputsHistoryEntry } from "../slices/analogInputsHistorySlice";
export const getAnalogInputsHistoryThunk = createAsyncThunk<
{
eingang: number;
zeitraum: string;
daten: AnalogInputsHistoryEntry[];
},
{ eingang: number; zeitraum: string }
>("analogInputsHistory/fetch", async ({ eingang, zeitraum }, thunkAPI) => {
try {
const data = await fetchAnalogInputsHistoryService();
return data;
const response = await fetchAnalogInputsHistory(eingang, zeitraum);
return {
eingang,
zeitraum,
daten: response.daten,
};
} catch (error: any) {
return rejectWithValue(error.message || "Unbekannter Fehler");
return thunkAPI.rejectWithValue(error.message ?? "Fehler beim Laden");
}
}
);
});

View File

@@ -1,56 +1,24 @@
// services/fetchAnalogInputsHistoryService.ts
// services/fetchAnalogInputsHistory.ts
export async function fetchAnalogInputsHistoryService(): Promise<
Record<number, unknown[]>
> {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
import { AnalogInputsHistoryEntry } from "@/redux/slices/analogInputsHistorySlice";
const formatDate = (date: Date) =>
`${date.getFullYear()};${String(date.getMonth() + 1).padStart(
2,
"0"
)};${String(date.getDate()).padStart(2, "0")}`;
const fromDate = formatDate(yesterday);
const toDate = formatDate(today);
const result: Record<number, unknown[]> = {};
const isDev = process.env.NODE_ENV === "development";
if (isDev) {
try {
// ⬇️ ENTWICKLUNG: über API-Handler
const response = await fetch("/api/cpl/getAnalogInputsHistory");
// 🔍 Log: Rohantwort prüfen
console.log("📡 [DEV] Raw response:", response);
if (!response.ok) {
throw new Error(
`❌ Fehler beim Laden der Mock-Daten: ${response.statusText}`
export async function fetchAnalogInputsHistory(
eingang: number,
zeitraum: string
): Promise<{ daten: AnalogInputsHistoryEntry[] }> {
const res = await fetch(
`/api/cpl/getAnalogInputsHistory?eingang=${eingang}&zeitraum=${zeitraum}`
);
if (!res.ok) {
throw new Error("Serverantwort war nicht erfolgreich");
}
// ✅ Versuch, JSON zu parsen
const data = await response.json();
// 🔍 Log: JSON anzeigen
//console.log("✅ [DEV] Parsed JSON:", data);
// 🔍 Validitätsprüfung (optional)
JSON.stringify(data); // Wenn das fehlschlägt, wird catch ausgelöst
return data;
} catch (error) {
console.error("❗ [DEV] Fehler beim Verarbeiten der JSON-Daten:", error);
return {};
}
const json = await res.json();
return { daten: json.daten }; // Nur das Feld "daten" extrahieren
}
// ⬇️ PRODUKTION: direkt vom CPL-Webserver holen
/* // ⬇️ PRODUKTION: direkt vom CPL-Webserver holen
for (let i = 0; i < 8; i++) {
const inputNumber = i + 1;
const sourceId = 99 + inputNumber;
@@ -70,3 +38,4 @@ export async function fetchAnalogInputsHistoryService(): Promise<
return result;
}
*/