feat: Meldungen in in Iso Chart
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.667
|
NEXT_PUBLIC_APP_VERSION=1.6.668
|
||||||
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.667
|
NEXT_PUBLIC_APP_VERSION=1.6.668
|
||||||
NEXT_PUBLIC_CPL_MODE=production
|
NEXT_PUBLIC_CPL_MODE=production
|
||||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
|||||||
|
## [1.6.668] – 2025-07-31
|
||||||
|
|
||||||
|
- feat: implement chart modal with report functionality for cable monitoring
|
||||||
|
|
||||||
|
- Add chartTitle state management to kabelueberwachungChartSlice with "Messkurve"/"Meldungen" options
|
||||||
|
- Update IsoChartActionBar dropdown to show current chartTitle value with proper binding
|
||||||
|
- Implement conditional rendering in IsoChartView between IsoMeasurementChart and Report components
|
||||||
|
- Create Report.tsx component using same data structure as MeldungenView (Meldung type)
|
||||||
|
- Add slot-based message filtering for specific cable monitoring units (KÜ)
|
||||||
|
- Integrate getMessagesThunk for consistent data loading across components
|
||||||
|
- Style Report component with consistent table layout, German date formatting, and Littwin branding
|
||||||
|
- Enable seamless switching between measurement chart and filtered messages in modal
|
||||||
|
|
||||||
|
---
|
||||||
## [1.6.667] – 2025-07-31
|
## [1.6.667] – 2025-07-31
|
||||||
|
|
||||||
- feat: TDR --> Messkurven TDR anzeigen und dort Schalter Messung aktivieren
|
- feat: TDR --> Messkurven TDR anzeigen und dort Schalter Messung aktivieren
|
||||||
|
|||||||
@@ -88,6 +88,87 @@ export const useIsoChartLoader = () => {
|
|||||||
return { loadIsoChartData };
|
return { loadIsoChartData };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------------useIsoDataLoader Hook
|
||||||
|
export const useIsoDataLoader = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { vonDatum, bisDatum, selectedMode, slotNumber } = useSelector(
|
||||||
|
(state: RootState) => state.kabelueberwachungChartSlice
|
||||||
|
);
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
const [year, month, day] = dateString.split("-");
|
||||||
|
return `${year};${month};${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getApiUrl = (mode: "DIA0" | "DIA1" | "DIA2", slotNumber: number) => {
|
||||||
|
const type = 3; // Fest auf Isolationswiderstand gesetzt
|
||||||
|
const typeFolder = "isolationswiderstand";
|
||||||
|
|
||||||
|
const baseUrl =
|
||||||
|
process.env.NODE_ENV === "development"
|
||||||
|
? `/api/cpl/slotDataAPIHandler?slot=${slotNumber}&messart=${typeFolder}&dia=${mode}&vonDatum=${vonDatum}&bisDatum=${bisDatum}`
|
||||||
|
: `${window.location.origin}/CPL?seite.ACP&${mode}=${formatDate(
|
||||||
|
vonDatum
|
||||||
|
)};${formatDate(bisDatum)};${slotNumber};${type};`;
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
if (slotNumber === null) {
|
||||||
|
console.log("⚠️ Kein Slot ausgewählt - automatisches Laden übersprungen");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiUrl = getApiUrl(selectedMode, slotNumber);
|
||||||
|
if (!apiUrl) return;
|
||||||
|
|
||||||
|
dispatch(setLoading(true));
|
||||||
|
dispatch(setChartOpen(false));
|
||||||
|
dispatch(setIsoMeasurementCurveChartData([]));
|
||||||
|
|
||||||
|
const MIN_LOADING_TIME_MS = 1000;
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: "GET",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`Fehler: ${response.status}`);
|
||||||
|
|
||||||
|
const jsonData = await response.json();
|
||||||
|
const elapsedTime = Date.now() - startTime;
|
||||||
|
const waitTime = Math.max(0, MIN_LOADING_TIME_MS - elapsedTime);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
||||||
|
|
||||||
|
console.log("▶️ Automatisches Laden - Isolationswiderstand-Daten für:");
|
||||||
|
console.log(" Slot:", slotNumber);
|
||||||
|
console.log(" Modus:", selectedMode);
|
||||||
|
console.log(" Von:", vonDatum);
|
||||||
|
console.log(" Bis:", bisDatum);
|
||||||
|
|
||||||
|
if (Array.isArray(jsonData) && jsonData.length > 0) {
|
||||||
|
dispatch(setIsoMeasurementCurveChartData(jsonData));
|
||||||
|
dispatch(setChartOpen(true));
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"⚠️ Keine Messdaten im gewählten Zeitraum gefunden (automatisches Laden)"
|
||||||
|
);
|
||||||
|
dispatch(setIsoMeasurementCurveChartData([]));
|
||||||
|
dispatch(setChartOpen(false));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Fehler beim automatischen Laden der Daten:", err);
|
||||||
|
} finally {
|
||||||
|
dispatch(setLoading(false));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { loadData };
|
||||||
|
};
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------------IsoChartActionBar
|
//-----------------------------------------------------------------------------------IsoChartActionBar
|
||||||
const IsoChartActionBar: React.FC = () => {
|
const IsoChartActionBar: React.FC = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -183,67 +264,71 @@ const IsoChartActionBar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<DateRangePicker />
|
{/* DateRangePicker - nur bei Messkurve anzeigen */}
|
||||||
|
{chartTitle === "Messkurve" && <DateRangePicker />}
|
||||||
|
|
||||||
<Listbox
|
{/* DIA0-DIA2 Dropdown - nur bei Messkurve anzeigen */}
|
||||||
value={selectedMode}
|
{chartTitle === "Messkurve" && (
|
||||||
onChange={(value) => {
|
<Listbox
|
||||||
dispatch(setSelectedMode(value));
|
value={selectedMode}
|
||||||
dispatch(setBrushRange({ startIndex: 0, endIndex: 0 }));
|
onChange={(value) => {
|
||||||
}}
|
dispatch(setSelectedMode(value));
|
||||||
>
|
dispatch(setBrushRange({ startIndex: 0, endIndex: 0 }));
|
||||||
<div className="relative w-48">
|
}}
|
||||||
<Listbox.Button className="w-full border px-3 py-1 rounded text-left bg-white flex justify-between items-center text-sm">
|
>
|
||||||
<span>
|
<div className="relative w-48">
|
||||||
{
|
<Listbox.Button className="w-full border px-3 py-1 rounded text-left bg-white flex justify-between items-center text-sm">
|
||||||
{
|
<span>
|
||||||
DIA0: "Alle Messwerte",
|
|
||||||
DIA1: "Stündliche Werte",
|
|
||||||
DIA2: "Tägliche Werte",
|
|
||||||
}[selectedMode]
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 text-gray-400"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M5.23 7.21a.75.75 0 011.06.02L10 10.585l3.71-3.355a.75.75 0 111.02 1.1l-4.25 3.85a.75.75 0 01-1.02 0l-4.25-3.85a.75.75 0 01.02-1.06z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Listbox.Button>
|
|
||||||
<Listbox.Options className="absolute z-50 mt-1 w-full border rounded bg-white shadow max-h-60 overflow-auto text-sm">
|
|
||||||
{["DIA0", "DIA1", "DIA2"].map((mode) => (
|
|
||||||
<Listbox.Option
|
|
||||||
key={mode}
|
|
||||||
value={mode}
|
|
||||||
className={({ selected, active }) =>
|
|
||||||
`px-4 py-1 cursor-pointer ${
|
|
||||||
selected
|
|
||||||
? "bg-littwin-blue text-white"
|
|
||||||
: active
|
|
||||||
? "bg-gray-200"
|
|
||||||
: ""
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
DIA0: "Alle Messwerte",
|
DIA0: "Alle Messwerte",
|
||||||
DIA1: "Stündliche Werte",
|
DIA1: "Stündliche Werte",
|
||||||
DIA2: "Tägliche Werte",
|
DIA2: "Tägliche Werte",
|
||||||
}[mode]
|
}[selectedMode]
|
||||||
}
|
}
|
||||||
</Listbox.Option>
|
</span>
|
||||||
))}
|
<svg
|
||||||
</Listbox.Options>
|
className="w-5 h-5 text-gray-400"
|
||||||
</div>
|
viewBox="0 0 20 20"
|
||||||
</Listbox>
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M5.23 7.21a.75.75 0 011.06.02L10 10.585l3.71-3.355a.75.75 0 111.02 1.1l-4.25 3.85a.75.75 0 01-1.02 0l-4.25-3.85a.75.75 0 01.02-1.06z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Listbox.Button>
|
||||||
|
<Listbox.Options className="absolute z-50 mt-1 w-full border rounded bg-white shadow max-h-60 overflow-auto text-sm">
|
||||||
|
{["DIA0", "DIA1", "DIA2"].map((mode) => (
|
||||||
|
<Listbox.Option
|
||||||
|
key={mode}
|
||||||
|
value={mode}
|
||||||
|
className={({ selected, active }) =>
|
||||||
|
`px-4 py-1 cursor-pointer ${
|
||||||
|
selected
|
||||||
|
? "bg-littwin-blue text-white"
|
||||||
|
: active
|
||||||
|
? "bg-gray-200"
|
||||||
|
: ""
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
DIA0: "Alle Messwerte",
|
||||||
|
DIA1: "Stündliche Werte",
|
||||||
|
DIA2: "Tägliche Werte",
|
||||||
|
}[mode]
|
||||||
|
}
|
||||||
|
</Listbox.Option>
|
||||||
|
))}
|
||||||
|
</Listbox.Options>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Dropdown für Auswahl zwischen "Messkurve" und "Meldungen" */}
|
{/* Dropdown für Auswahl zwischen "Messkurve" und "Meldungen" - immer anzeigen */}
|
||||||
<Listbox
|
<Listbox
|
||||||
value={chartTitle}
|
value={chartTitle}
|
||||||
onChange={(value) => dispatch(setChartTitle(value))}
|
onChange={(value) => dispatch(setChartTitle(value))}
|
||||||
@@ -285,14 +370,18 @@ const IsoChartActionBar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
|
|
||||||
<button
|
{/* Daten laden Button - nur bei Messkurve anzeigen */}
|
||||||
onClick={handleFetchData}
|
{chartTitle === "Messkurve" && (
|
||||||
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm"
|
<button
|
||||||
>
|
onClick={handleFetchData}
|
||||||
Daten laden
|
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm"
|
||||||
</button>
|
>
|
||||||
|
Daten laden
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{isLoading && (
|
{/* Loading Indicator - nur bei Messkurve anzeigen */}
|
||||||
|
{chartTitle === "Messkurve" && isLoading && (
|
||||||
<div className="flex items-center space-x-2 text-sm text-gray-500">
|
<div className="flex items-center space-x-2 text-sm text-gray-500">
|
||||||
<div className="w-4 h-4 border-2 border-t-2 border-blue-500 rounded-full animate-spin" />
|
<div className="w-4 h-4 border-2 border-t-2 border-blue-500 rounded-full animate-spin" />
|
||||||
<span>Lade Daten...</span>
|
<span>Lade Daten...</span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import ReactModal from "react-modal";
|
import ReactModal from "react-modal";
|
||||||
import IsoMeasurementChart from "./IsoMeasurementChart";
|
import IsoMeasurementChart from "./IsoMeasurementChart";
|
||||||
import IsoChartActionBar from "./IsoChartActionBar";
|
import IsoChartActionBar, { useIsoDataLoader } from "./IsoChartActionBar";
|
||||||
import Report from "./Report";
|
import Report from "./Report";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { AppDispatch } from "@/redux/store";
|
import { AppDispatch } from "@/redux/store";
|
||||||
@@ -36,6 +36,7 @@ const IsoChartView: React.FC<IsoChartViewProps> = ({
|
|||||||
slotIndex,
|
slotIndex,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const { loadData } = useIsoDataLoader();
|
||||||
|
|
||||||
const { isFullScreen, chartTitle } = useSelector(
|
const { isFullScreen, chartTitle } = useSelector(
|
||||||
(state: RootState) => state.kabelueberwachungChartSlice
|
(state: RootState) => state.kabelueberwachungChartSlice
|
||||||
@@ -92,8 +93,18 @@ const IsoChartView: React.FC<IsoChartViewProps> = ({
|
|||||||
|
|
||||||
// Set default to Messkurve
|
// Set default to Messkurve
|
||||||
dispatch(setChartTitle("Messkurve"));
|
dispatch(setChartTitle("Messkurve"));
|
||||||
|
|
||||||
|
// Automatisch Daten laden nach kurzer Verzögerung
|
||||||
|
// um sicherzustellen, dass alle Redux-States gesetzt sind
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
loadData();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Cleanup timer
|
||||||
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [isOpen, slotIndex, dispatch]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isOpen, slotIndex, dispatch]); // loadData bewusst ausgelassen um Endlosschleife zu vermeiden
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactModal
|
<ReactModal
|
||||||
|
|||||||
@@ -33,11 +33,48 @@ const Report: React.FC = () => {
|
|||||||
(allMessages: Meldung[], slot: number) => {
|
(allMessages: Meldung[], slot: number) => {
|
||||||
if (slot === null) return [];
|
if (slot === null) return [];
|
||||||
|
|
||||||
// Filter basierend auf der Quelle (i-Feld)
|
// Primärer Filter: Exakte CableLineX Übereinstimmung (X = slot + 1)
|
||||||
return allMessages.filter((msg: Meldung) => {
|
const primaryIdentifier = `CableLine${slot + 1}`;
|
||||||
// Verschiedene mögliche Slot-Identifikationen
|
|
||||||
const slotIdentifiers = [
|
console.log(
|
||||||
`CableLine${slot + 1}`,
|
`🔍 Filtere Nachrichten für Slot ${slot} (${primaryIdentifier}):`
|
||||||
|
);
|
||||||
|
console.log(`📥 Gesamt Nachrichten: ${allMessages.length}`);
|
||||||
|
|
||||||
|
// Debug: Zeige alle verfügbaren Quellen
|
||||||
|
const allSources = [...new Set(allMessages.map((msg) => msg.i))];
|
||||||
|
console.log(`📋 Alle verfügbaren Quellen:`, allSources);
|
||||||
|
|
||||||
|
// Filter basierend auf der Quelle (i-Feld) - EXAKTE Übereinstimmung
|
||||||
|
const filtered = allMessages.filter((msg: Meldung) => {
|
||||||
|
// Exakte Übereinstimmung: msg.i sollte genau "CableLineX" sein
|
||||||
|
const isExactMatch = msg.i === primaryIdentifier;
|
||||||
|
|
||||||
|
// Fallback: Falls die Quelle mehr Informationen enthält (z.B. "CableLine1_Sensor")
|
||||||
|
const isPartialMatch =
|
||||||
|
msg.i.startsWith(primaryIdentifier) &&
|
||||||
|
(msg.i === primaryIdentifier ||
|
||||||
|
msg.i.charAt(primaryIdentifier.length).match(/[^0-9]/));
|
||||||
|
|
||||||
|
const isMatch = isExactMatch || isPartialMatch;
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
console.log(`✅ Gefunden: "${msg.i}" -> ${msg.m}`);
|
||||||
|
}
|
||||||
|
return isMatch;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`📤 Gefilterte Nachrichten für ${primaryIdentifier}: ${filtered.length}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Falls keine Nachrichten mit CableLineX gefunden, versuche alternative Identifikatoren
|
||||||
|
if (filtered.length === 0) {
|
||||||
|
console.log(
|
||||||
|
`⚠️ Keine Nachrichten für ${primaryIdentifier} gefunden. Versuche alternative Identifikatoren...`
|
||||||
|
);
|
||||||
|
|
||||||
|
const alternativeIdentifiers = [
|
||||||
`Slot${slot + 1}`,
|
`Slot${slot + 1}`,
|
||||||
`KÜ${slot + 1}`,
|
`KÜ${slot + 1}`,
|
||||||
`Kue${slot + 1}`,
|
`Kue${slot + 1}`,
|
||||||
@@ -45,8 +82,29 @@ const Report: React.FC = () => {
|
|||||||
`Line${slot + 1}`,
|
`Line${slot + 1}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
return slotIdentifiers.some((identifier) => msg.i.includes(identifier));
|
const alternativeFiltered = allMessages.filter((msg: Meldung) => {
|
||||||
});
|
return alternativeIdentifiers.some((identifier) => {
|
||||||
|
const isExactMatch = msg.i === identifier;
|
||||||
|
const isPartialMatch =
|
||||||
|
msg.i.startsWith(identifier) &&
|
||||||
|
(msg.i === identifier ||
|
||||||
|
msg.i.charAt(identifier.length).match(/[^0-9]/));
|
||||||
|
const isMatch = isExactMatch || isPartialMatch;
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
console.log(`🔄 Alternative gefunden: "${msg.i}" -> ${msg.m}`);
|
||||||
|
}
|
||||||
|
return isMatch;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`📤 Alternative gefilterte Nachrichten: ${alternativeFiltered.length}`
|
||||||
|
);
|
||||||
|
return alternativeFiltered;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@@ -107,25 +165,15 @@ const Report: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex flex-col">
|
<div className="w-full h-full flex flex-col p-4">
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h4 className="text-lg font-semibold">
|
|
||||||
Meldungen für KÜ {slotNumber !== null ? slotNumber + 1 : "-"}
|
|
||||||
</h4>
|
|
||||||
<button
|
|
||||||
onClick={loadMessages}
|
|
||||||
className="px-3 py-1 bg-littwin-blue text-white rounded text-sm hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
Aktualisieren
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{filteredMessages.length === 0 ? (
|
{filteredMessages.length === 0 ? (
|
||||||
<div className="text-center text-gray-500 py-8">
|
<div className="text-center text-gray-500 ">
|
||||||
Keine Meldungen im gewählten Zeitraum gefunden.
|
Keine Meldungen für CableLine
|
||||||
|
{slotNumber !== null ? slotNumber + 1 : "-"} im gewählten Zeitraum
|
||||||
|
gefunden.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 overflow-auto max-h-[80vh]">
|
<div className="flex-1 overflow-auto ">
|
||||||
<table className="min-w-full border text-sm">
|
<table className="min-w-full border text-sm">
|
||||||
<thead className="bg-gray-100 text-left sticky top-0 z-10">
|
<thead className="bg-gray-100 text-left sticky top-0 z-10">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -165,7 +213,7 @@ const Report: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-4 text-sm text-gray-500 text-center">
|
<div className="mt-4 text-sm text-gray-500 text-center mt-4">
|
||||||
{filteredMessages.length} Meldung(en) gefunden
|
{filteredMessages.length} Meldung(en) gefunden
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.667",
|
"version": "1.6.668",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.667",
|
"version": "1.6.668",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "^5.1.0",
|
"@fontsource/roboto": "^5.1.0",
|
||||||
"@headlessui/react": "^2.2.4",
|
"@headlessui/react": "^2.2.4",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.667",
|
"version": "1.6.668",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user