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_EXPORT_STATIC=false
|
||||||
NEXT_PUBLIC_USE_CGI=false
|
NEXT_PUBLIC_USE_CGI=false
|
||||||
# App-Versionsnummer
|
# 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)
|
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_EXPORT_STATIC=true
|
||||||
NEXT_PUBLIC_USE_CGI=true
|
NEXT_PUBLIC_USE_CGI=true
|
||||||
# App-Versionsnummer
|
# App-Versionsnummer
|
||||||
NEXT_PUBLIC_APP_VERSION=1.6.500
|
NEXT_PUBLIC_APP_VERSION=1.6.502
|
||||||
NEXT_PUBLIC_CPL_MODE=production
|
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
|
## [1.6.500] – 2025-06-27
|
||||||
|
|
||||||
- feat: implement Date in anlog inputs chart
|
- feat: implement Date in anlog inputs chart
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export default function AnalogInputsChart({
|
|||||||
|
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: `Verlauf der letzten 24 Stunden`,
|
text: `Verlauf der letzten 30 Tage`,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
pan: {
|
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",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.500",
|
"version": "1.6.502",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.500",
|
"version": "1.6.502",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "^5.1.0",
|
"@fontsource/roboto": "^5.1.0",
|
||||||
"@iconify-icons/ri": "^1.2.10",
|
"@iconify-icons/ri": "^1.2.10",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.500",
|
"version": "1.6.502",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// /pages/api/cpl/messages.ts
|
// /pages/api/cpl/getMessagesAPIHandler.ts
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { promises as fs } from "fs";
|
import { promises as fs } from "fs";
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
// /pages/meldungen.tsx
|
// /pages/meldungen.tsx
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import DateRangePickerMeldungen from "@/components/main/reports/DateRangePickerMeldungen";
|
import DateRangePickerMeldungen from "@/components/main/reports/DateRangePickerMeldungen";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { getMessagesThunk } from "@/redux/thunks/getMessagesThunk";
|
||||||
|
|
||||||
type Meldung = {
|
type Meldung = {
|
||||||
t: string;
|
t: string;
|
||||||
@@ -9,10 +11,14 @@ type Meldung = {
|
|||||||
c: string;
|
c: string;
|
||||||
m: string;
|
m: string;
|
||||||
i: string;
|
i: string;
|
||||||
|
v: string; // NEU: für Anzeige
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Messages() {
|
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");
|
const [sourceFilter, setSourceFilter] = useState<string>("Alle");
|
||||||
|
|
||||||
@@ -26,49 +32,18 @@ export default function Messages() {
|
|||||||
const [fromDate, setFromDate] = useState<string>(formatDate(prior30));
|
const [fromDate, setFromDate] = useState<string>(formatDate(prior30));
|
||||||
const [toDate, setToDate] = useState<string>(formatDate(today));
|
const [toDate, setToDate] = useState<string>(formatDate(today));
|
||||||
|
|
||||||
const fetchMessages = async () => {
|
useEffect(() => {
|
||||||
const from = new Date(fromDate);
|
dispatch(getMessagesThunk({ fromDate, toDate }));
|
||||||
const to = new Date(toDate);
|
}, [dispatch, fromDate, 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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredMessages =
|
const filteredMessages =
|
||||||
sourceFilter === "Alle"
|
sourceFilter === "Alle"
|
||||||
? messages
|
? 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();
|
const allSources = Array.from(
|
||||||
// einmal beim laden de Seite die Meldungen abrufen
|
new Set(messages.map((m: Meldung) => m.i))
|
||||||
useEffect(() => {
|
).sort();
|
||||||
fetchMessages();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
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">
|
<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}
|
setToDate={setToDate}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={fetchMessages}
|
onClick={() => dispatch(getMessagesThunk({ fromDate, toDate }))}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded h-fit"
|
className="bg-littwin-blue text-white px-4 py-2 rounded h-fit"
|
||||||
>
|
>
|
||||||
Anzeigen
|
Anzeigen
|
||||||
@@ -90,7 +65,7 @@ export default function Messages() {
|
|||||||
<select
|
<select
|
||||||
value={sourceFilter}
|
value={sourceFilter}
|
||||||
onChange={(e) => setSourceFilter(e.target.value)}
|
onChange={(e) => setSourceFilter(e.target.value)}
|
||||||
className=" border border-solid border-spacing-1 px-4 py-3 rounded h-fit ml-6"
|
className="border border-solid border-spacing-1 px-4 py-3 rounded h-fit ml-6"
|
||||||
>
|
>
|
||||||
<option value="Alle">Alle Quellen</option>
|
<option value="Alle">Alle Quellen</option>
|
||||||
{allSources.map((src) => (
|
{allSources.map((src) => (
|
||||||
@@ -109,11 +84,11 @@ export default function Messages() {
|
|||||||
<th className="p-2 border">Zeitstempel</th>
|
<th className="p-2 border">Zeitstempel</th>
|
||||||
<th className="p-2 border">Quelle</th>
|
<th className="p-2 border">Quelle</th>
|
||||||
<th className="p-2 border">Meldung</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredMessages.map((msg, index) => (
|
{filteredMessages.map((msg: Meldung, index: number) => (
|
||||||
<tr key={index} className="hover:bg-gray-50">
|
<tr key={index} className="hover:bg-gray-50">
|
||||||
<td className="border p-2">
|
<td className="border p-2">
|
||||||
<div
|
<div
|
||||||
@@ -124,7 +99,7 @@ export default function Messages() {
|
|||||||
<td className="border p-2">{msg.t}</td>
|
<td className="border p-2">{msg.t}</td>
|
||||||
<td className="border p-2">{msg.i}</td>
|
<td className="border p-2">{msg.i}</td>
|
||||||
<td className="border p-2">{msg.m}</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>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</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 systemVoltTempReducer from "./slices/systemVoltTempSlice";
|
||||||
import analogInputsHistoryReducer from "./slices/analogInputsHistorySlice";
|
import analogInputsHistoryReducer from "./slices/analogInputsHistorySlice";
|
||||||
import selectedAnalogInputReducer from "./slices/selectedAnalogInputSlice";
|
import selectedAnalogInputReducer from "./slices/selectedAnalogInputSlice";
|
||||||
|
import messagesReducer from "./slices/messagesSlice";
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
@@ -52,6 +53,7 @@ const store = configureStore({
|
|||||||
systemVoltTemp: systemVoltTempReducer,
|
systemVoltTemp: systemVoltTempReducer,
|
||||||
analogInputsHistory: analogInputsHistoryReducer,
|
analogInputsHistory: analogInputsHistoryReducer,
|
||||||
selectedAnalogInput: selectedAnalogInputReducer,
|
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);
|
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;
|
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