feat: Redux-Integration für Meldungen, Anzeige von 'v' statt 's' in UI
This commit is contained in:
@@ -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.500
|
||||
NEXT_PUBLIC_APP_VERSION=1.6.502
|
||||
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)
|
||||
|
||||
|
||||
@@ -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.500
|
||||
NEXT_PUBLIC_APP_VERSION=1.6.502
|
||||
NEXT_PUBLIC_CPL_MODE=production
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,3 +1,13 @@
|
||||
## [1.6.502] – 2025-06-30
|
||||
|
||||
- feat: implement date in analog inputs chart
|
||||
|
||||
---
|
||||
## [1.6.501] – 2025-06-30
|
||||
|
||||
- feat: implement date in analog inputs chart
|
||||
|
||||
---
|
||||
## [1.6.500] – 2025-06-27
|
||||
|
||||
- feat: implement Date in anlog inputs chart
|
||||
|
||||
@@ -132,7 +132,7 @@ export default function AnalogInputsChart({
|
||||
|
||||
title: {
|
||||
display: true,
|
||||
text: `Verlauf der letzten 24 Stunden`,
|
||||
text: `Verlauf der letzten 30 Tage`,
|
||||
},
|
||||
zoom: {
|
||||
pan: {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "cpl-v4",
|
||||
"version": "1.6.500",
|
||||
"version": "1.6.502",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cpl-v4",
|
||||
"version": "1.6.500",
|
||||
"version": "1.6.502",
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.1.0",
|
||||
"@iconify-icons/ri": "^1.2.10",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cpl-v4",
|
||||
"version": "1.6.500",
|
||||
"version": "1.6.502",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// /pages/api/cpl/messages.ts
|
||||
// /pages/api/cpl/getMessagesAPIHandler.ts
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import path from "path";
|
||||
import { promises as fs } from "fs";
|
||||
@@ -2,6 +2,8 @@
|
||||
// /pages/meldungen.tsx
|
||||
import React, { useState, useEffect } from "react";
|
||||
import DateRangePickerMeldungen from "@/components/main/reports/DateRangePickerMeldungen";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getMessagesThunk } from "@/redux/thunks/getMessagesThunk";
|
||||
|
||||
type Meldung = {
|
||||
t: string;
|
||||
@@ -9,10 +11,14 @@ type Meldung = {
|
||||
c: string;
|
||||
m: string;
|
||||
i: string;
|
||||
v: string; // NEU: für Anzeige
|
||||
};
|
||||
|
||||
export default function Messages() {
|
||||
const [messages, setMessages] = useState<Meldung[]>([]);
|
||||
const dispatch = useDispatch();
|
||||
const { data: messages, loading } = useSelector(
|
||||
(state: any) => state.messages
|
||||
);
|
||||
|
||||
const [sourceFilter, setSourceFilter] = useState<string>("Alle");
|
||||
|
||||
@@ -26,49 +32,18 @@ export default function Messages() {
|
||||
const [fromDate, setFromDate] = useState<string>(formatDate(prior30));
|
||||
const [toDate, setToDate] = useState<string>(formatDate(today));
|
||||
|
||||
const fetchMessages = async () => {
|
||||
const from = new Date(fromDate);
|
||||
const to = new Date(toDate);
|
||||
|
||||
const fy = from.getFullYear();
|
||||
const fm = String(from.getMonth() + 1).padStart(2, "0");
|
||||
const fd = String(from.getDate()).padStart(2, "0");
|
||||
const ty = to.getFullYear();
|
||||
const tm = String(to.getMonth() + 1).padStart(2, "0");
|
||||
const td = String(to.getDate()).padStart(2, "0");
|
||||
|
||||
const isDev =
|
||||
typeof window !== "undefined" && window.location.hostname === "localhost";
|
||||
//http://10.10.0.118/CPL?Service/empty.ACP&MSS1=2025;06;01;2025;06;26;All
|
||||
const url = isDev
|
||||
? `/api/cpl/messages`
|
||||
: `/CPL?Service/ae.ACP&MSS1=${fy};${fm};${fd};${ty};${tm};${td};All`;
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
const raw = await res.json();
|
||||
const data = Array.isArray(raw) ? raw : raw.data;
|
||||
if (!Array.isArray(data)) {
|
||||
console.error("Die empfangenen Daten sind kein Array:", data);
|
||||
return;
|
||||
}
|
||||
setMessages(data);
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Laden der Meldungen:", err);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
dispatch(getMessagesThunk({ fromDate, toDate }));
|
||||
}, [dispatch, fromDate, toDate]);
|
||||
|
||||
const filteredMessages =
|
||||
sourceFilter === "Alle"
|
||||
? messages
|
||||
: messages.filter((m) => m.i === sourceFilter);
|
||||
: messages.filter((m: Meldung) => m.i === sourceFilter);
|
||||
|
||||
const allSources = Array.from(new Set(messages.map((m) => m.i))).sort();
|
||||
// einmal beim laden de Seite die Meldungen abrufen
|
||||
useEffect(() => {
|
||||
fetchMessages();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const allSources = Array.from(
|
||||
new Set(messages.map((m: Meldung) => m.i))
|
||||
).sort();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 p-4 h-[calc(100vh-13vh-8vh)] laptop:h-[calc(100vh-10vh-5vh)] xl:h-[calc(100vh-10vh-6vh)] laptop:gap-0">
|
||||
@@ -82,7 +57,7 @@ export default function Messages() {
|
||||
setToDate={setToDate}
|
||||
/>
|
||||
<button
|
||||
onClick={fetchMessages}
|
||||
onClick={() => dispatch(getMessagesThunk({ fromDate, toDate }))}
|
||||
className="bg-littwin-blue text-white px-4 py-2 rounded h-fit"
|
||||
>
|
||||
Anzeigen
|
||||
@@ -109,11 +84,11 @@ export default function Messages() {
|
||||
<th className="p-2 border">Zeitstempel</th>
|
||||
<th className="p-2 border">Quelle</th>
|
||||
<th className="p-2 border">Meldung</th>
|
||||
<th className="p-2 border">Status</th>
|
||||
<th className="p-2 border">Wert</th> {/* statt Status */}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredMessages.map((msg, index) => (
|
||||
{filteredMessages.map((msg: Meldung, index: number) => (
|
||||
<tr key={index} className="hover:bg-gray-50">
|
||||
<td className="border p-2">
|
||||
<div
|
||||
@@ -124,7 +99,7 @@ export default function Messages() {
|
||||
<td className="border p-2">{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>
|
||||
<td className="border p-2">{msg.v}</td> {/* NEU */}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// public/CPL/SERVICE/knotenAuslesen.js
|
||||
|
||||
window.knotenData = {
|
||||
nodeIDs: [],
|
||||
linkIDs: [],
|
||||
linkLengths: [],
|
||||
};
|
||||
|
||||
function leseKnotenpunkte(slotIndex) {
|
||||
try {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const node = window.kueNodeID?.[i] ?? "";
|
||||
window.knotenData.nodeIDs[i] = node;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const link = window.kueLinkID?.[i] ?? "";
|
||||
window.knotenData.linkIDs[i] = link;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const length = window.kueLinkLength?.[i] ?? "";
|
||||
window.knotenData.linkLengths[i] = length;
|
||||
}
|
||||
|
||||
console.log("✅ Knotenpunkte geladen:", window.knotenData);
|
||||
} catch (error) {
|
||||
console.error("❌ Fehler beim Laden der Knotenpunkte:", error);
|
||||
}
|
||||
}
|
||||
BIN
redux/slices.zip
BIN
redux/slices.zip
Binary file not shown.
47
redux/slices/messagesSlice.ts
Normal file
47
redux/slices/messagesSlice.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// redux/slices/messagesSlice.ts
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { getMessagesThunk } from "../thunks/getMessagesThunk";
|
||||
|
||||
type Meldung = {
|
||||
t: string;
|
||||
s: number;
|
||||
c: string;
|
||||
m: string;
|
||||
i: string;
|
||||
v: string;
|
||||
};
|
||||
|
||||
interface MessagesState {
|
||||
data: Meldung[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: MessagesState = {
|
||||
data: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const messagesSlice = createSlice({
|
||||
name: "messages",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(getMessagesThunk.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(getMessagesThunk.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.data = action.payload;
|
||||
})
|
||||
.addCase(getMessagesThunk.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.error.message ?? "Unbekannter Fehler";
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default messagesSlice.reducer;
|
||||
Binary file not shown.
@@ -25,6 +25,7 @@ import loopChartTypeSlice from "./slices/loopChartTypeSlice";
|
||||
import systemVoltTempReducer from "./slices/systemVoltTempSlice";
|
||||
import analogInputsHistoryReducer from "./slices/analogInputsHistorySlice";
|
||||
import selectedAnalogInputReducer from "./slices/selectedAnalogInputSlice";
|
||||
import messagesReducer from "./slices/messagesSlice";
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
@@ -52,6 +53,7 @@ const store = configureStore({
|
||||
systemVoltTemp: systemVoltTempReducer,
|
||||
analogInputsHistory: analogInputsHistoryReducer,
|
||||
selectedAnalogInput: selectedAnalogInputReducer,
|
||||
messages: messagesReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
BIN
redux/thunks.zip
BIN
redux/thunks.zip
Binary file not shown.
10
redux/thunks/getMessagesThunk.ts
Normal file
10
redux/thunks/getMessagesThunk.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// redux/thunks/getMessagesThunk.ts
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import { fetchMessagesService } from "@/services/fetchMessagesService";
|
||||
|
||||
export const getMessagesThunk = createAsyncThunk(
|
||||
"messages/getAll",
|
||||
async ({ fromDate, toDate }: { fromDate: string; toDate: string }) => {
|
||||
return await fetchMessagesService(fromDate, toDate);
|
||||
}
|
||||
);
|
||||
BIN
redux/types.zip
BIN
redux/types.zip
Binary file not shown.
@@ -20,7 +20,10 @@ export const fetchLast20MessagesFromWindow = async (): Promise<
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
|
||||
const raw = (window as any).win_last20Messages;
|
||||
interface WindowWithLast20Messages extends Window {
|
||||
win_last20Messages?: unknown;
|
||||
}
|
||||
const raw = (window as WindowWithLast20Messages).win_last20Messages;
|
||||
|
||||
return raw ? String(raw) : null;
|
||||
};
|
||||
|
||||
26
services/fetchMessagesService.ts
Normal file
26
services/fetchMessagesService.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
// services/fetchMessagesService.ts
|
||||
export const fetchMessagesService = async (
|
||||
fromDate: string,
|
||||
toDate: string
|
||||
) => {
|
||||
const from = new Date(fromDate);
|
||||
const to = new Date(toDate);
|
||||
|
||||
const fy = from.getFullYear();
|
||||
const fm = String(from.getMonth() + 1).padStart(2, "0");
|
||||
const fd = String(from.getDate()).padStart(2, "0");
|
||||
const ty = to.getFullYear();
|
||||
const tm = String(to.getMonth() + 1).padStart(2, "0");
|
||||
const td = String(to.getDate()).padStart(2, "0");
|
||||
|
||||
const isDev =
|
||||
typeof window !== "undefined" && window.location.hostname === "localhost";
|
||||
const url = isDev
|
||||
? `/api/cpl/getMessagesAPIHandler`
|
||||
: `/CPL?Service/ae.ACP&MSS1=${fy};${fm};${fd};${ty};${tm};${td};All`;
|
||||
|
||||
const res = await fetch(url);
|
||||
const raw = await res.json();
|
||||
const data = Array.isArray(raw) ? raw : raw.data;
|
||||
return data;
|
||||
};
|
||||
Reference in New Issue
Block a user