style: Detailansicht Modal dark mode
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.898
|
NEXT_PUBLIC_APP_VERSION=1.6.899
|
||||||
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.898
|
NEXT_PUBLIC_APP_VERSION=1.6.899
|
||||||
NEXT_PUBLIC_CPL_MODE=production
|
NEXT_PUBLIC_CPL_MODE=production
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
## [1.6.899] – 2025-09-10
|
||||||
|
|
||||||
|
- style: AnalogInputsChartModal dark mode
|
||||||
|
|
||||||
|
---
|
||||||
## [1.6.898] – 2025-09-10
|
## [1.6.898] – 2025-09-10
|
||||||
|
|
||||||
- style: AnalogInputsSettingsModal dark mode
|
- style: AnalogInputsSettingsModal dark mode
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ import {
|
|||||||
Legend,
|
Legend,
|
||||||
Filler,
|
Filler,
|
||||||
TimeScale,
|
TimeScale,
|
||||||
|
type ChartDataset,
|
||||||
|
type ChartOptions,
|
||||||
|
type ChartData,
|
||||||
|
type Chart,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
|
|
||||||
import "chartjs-adapter-date-fns";
|
import "chartjs-adapter-date-fns";
|
||||||
@@ -65,7 +69,7 @@ type ReduxDataEntry = {
|
|||||||
m?: number; // aktueller Messwert (optional, falls vorhanden)
|
m?: number; // aktueller Messwert (optional, falls vorhanden)
|
||||||
};
|
};
|
||||||
|
|
||||||
const chartOptions = {
|
const chartOptions: ChartOptions<"line"> = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
@@ -78,9 +82,11 @@ const chartOptions = {
|
|||||||
mode: "index" as const,
|
mode: "index" as const,
|
||||||
intersect: false,
|
intersect: false,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
label: function (ctx: any) {
|
label: function (ctx: any) {
|
||||||
return `Messwert: ${ctx.parsed.y}`;
|
return `Messwert: ${ctx.parsed.y}`;
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
title: function (items: any[]) {
|
title: function (items: any[]) {
|
||||||
const date = items[0].parsed.x;
|
const date = items[0].parsed.x;
|
||||||
return `Zeitpunkt: ${new Date(date).toLocaleString("de-DE")}`;
|
return `Zeitpunkt: ${new Date(date).toLocaleString("de-DE")}`;
|
||||||
@@ -140,13 +146,13 @@ export const DetailModal = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
// Stable empty reference to avoid React-Redux dev warning about selector returning new [] each call
|
// Stable empty reference to avoid React-Redux dev warning about selector returning new [] each call
|
||||||
const EMPTY_REDUX_DATA: ReadonlyArray<ReduxDataEntry> = Object.freeze([]);
|
const EMPTY_REDUX_DATA: ReadonlyArray<ReduxDataEntry> = Object.freeze([]);
|
||||||
const chartRef = useRef<any>(null);
|
const chartRef = useRef<Chart<"line"> | null>(null);
|
||||||
const [chartData, setChartData] = useState<any>({
|
const [chartData, setChartData] = useState<ChartData<"line">>({
|
||||||
datasets: [],
|
datasets: [],
|
||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [shouldUpdateChart, setShouldUpdateChart] = useState(false);
|
const [shouldUpdateChart, setShouldUpdateChart] = useState(false);
|
||||||
const [forceUpdate, setForceUpdate] = useState(0); // Für periodische UI-Updates
|
// const [forceUpdate, setForceUpdate] = useState(0); // Für periodische UI-Updates (derzeit nicht benötigt)
|
||||||
|
|
||||||
const reduxData = useSelector((state: RootState) => {
|
const reduxData = useSelector((state: RootState) => {
|
||||||
switch (selectedKey) {
|
switch (selectedKey) {
|
||||||
@@ -221,11 +227,8 @@ export const DetailModal = ({
|
|||||||
// Periodische UI-Updates alle 2 Sekunden während Wartezeit
|
// Periodische UI-Updates alle 2 Sekunden während Wartezeit
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && (!chartData.datasets || chartData.datasets.length === 0)) {
|
if (isOpen && (!chartData.datasets || chartData.datasets.length === 0)) {
|
||||||
const interval = setInterval(() => {
|
// Optional: periodische Re-Renders wurden deaktiviert, da nicht mehr notwendig
|
||||||
setForceUpdate((prev) => prev + 1); // Force re-render für cursor-wait Update
|
// (kann wieder aktiviert werden falls Cursor-Animation erwünscht ist)
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}
|
}
|
||||||
}, [isOpen, chartData.datasets]);
|
}, [isOpen, chartData.datasets]);
|
||||||
|
|
||||||
@@ -274,7 +277,12 @@ export const DetailModal = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chartRef.current && selectedKey) {
|
if (chartRef.current && selectedKey) {
|
||||||
chartRef.current.options.plugins.title.text = `Verlauf ${selectedKey}`;
|
const opts = chartRef.current.options as ChartOptions<"line"> & {
|
||||||
|
plugins?: { title?: { text?: string } };
|
||||||
|
};
|
||||||
|
if (opts.plugins?.title) {
|
||||||
|
opts.plugins.title.text = `Verlauf ${selectedKey}`;
|
||||||
|
}
|
||||||
chartRef.current.update("none");
|
chartRef.current.update("none");
|
||||||
}
|
}
|
||||||
}, [selectedKey]);
|
}, [selectedKey]);
|
||||||
@@ -290,14 +298,13 @@ export const DetailModal = ({
|
|||||||
if (chartRef.current && isLoading) {
|
if (chartRef.current && isLoading) {
|
||||||
const chartInstance = chartRef.current;
|
const chartInstance = chartRef.current;
|
||||||
// Save previous callback to restore later
|
// Save previous callback to restore later
|
||||||
const prevCallback = chartInstance.options.animation?.onComplete;
|
const animation: any = chartInstance.options.animation || {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
chartInstance.options.animation = {
|
const prevCallback = animation.onComplete;
|
||||||
...chartInstance.options.animation,
|
animation.onComplete = () => {
|
||||||
onComplete: () => {
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
if (typeof prevCallback === "function") prevCallback();
|
if (typeof prevCallback === "function") prevCallback();
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
chartInstance.options.animation = animation;
|
||||||
chartInstance.update();
|
chartInstance.update();
|
||||||
}
|
}
|
||||||
}, [chartData, isLoading]);
|
}, [chartData, isLoading]);
|
||||||
@@ -338,7 +345,7 @@ export const DetailModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create datasets array for multiple lines
|
// Create datasets array for multiple lines
|
||||||
const datasets = [];
|
const datasets: ChartDataset<"line">[] = [];
|
||||||
|
|
||||||
// Check which data fields are available and create datasets accordingly
|
// Check which data fields are available and create datasets accordingly
|
||||||
const hasMinimum = filtered.some(
|
const hasMinimum = filtered.some(
|
||||||
@@ -416,7 +423,8 @@ export const DetailModal = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const newChartData = {
|
const newChartData: ChartData<"line"> = {
|
||||||
|
labels: [],
|
||||||
datasets: datasets,
|
datasets: datasets,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -428,7 +436,13 @@ export const DetailModal = ({
|
|||||||
setChartData({ datasets: [] });
|
setChartData({ datasets: [] });
|
||||||
setShouldUpdateChart(false); // Reset flag
|
setShouldUpdateChart(false); // Reset flag
|
||||||
}
|
}
|
||||||
}, [reduxData, selectedKey, shouldUpdateChart]);
|
}, [
|
||||||
|
reduxData,
|
||||||
|
selectedKey,
|
||||||
|
shouldUpdateChart,
|
||||||
|
pickerVonDatum,
|
||||||
|
pickerBisDatum,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!isOpen || !selectedKey) return null;
|
if (!isOpen || !selectedKey) return null;
|
||||||
|
|
||||||
@@ -442,19 +456,25 @@ export const DetailModal = ({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`bg-white p-6 rounded-xl overflow-auto shadow-2xl transition-all duration-300 ${
|
role="dialog"
|
||||||
isFullScreen ? "w-[95vw] h-[90vh]" : "w-[50%] h-[60%]"
|
aria-modal="true"
|
||||||
|
className={`bg-[var(--color-surface)] text-fg border border-base rounded-xl shadow-xl flex flex-col overflow-hidden transition-all duration-300 ${
|
||||||
|
isFullScreen
|
||||||
|
? "w-[90vw] h-[90vh]"
|
||||||
|
: "w-[70rem] max-w-[95vw] h-[40rem]"
|
||||||
} ${!hasChartData ? "cursor-wait" : ""}`}
|
} ${!hasChartData ? "cursor-wait" : ""}`}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
{/* Header */}
|
||||||
<h2 className="text-xl font-semibold">
|
<header className="flex items-center justify-between px-6 py-4 border-b border-base select-none bg-[var(--color-surface)]">
|
||||||
|
<h2 className="text-base font-bold tracking-wide">
|
||||||
Detailansicht: {selectedKey}
|
Detailansicht: {selectedKey}
|
||||||
</h2>
|
</h2>
|
||||||
|
<div className="flex items-center gap-3 text-lg">
|
||||||
<div className="absolute top-0 right-0 flex gap-3">
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullScreen}
|
onClick={toggleFullScreen}
|
||||||
className="text-2xl text-[var(--color-fg-muted)] hover:text-[var(--color-fg)] transition"
|
className="icon-btn text-[1.4rem] hover:text-fg transition"
|
||||||
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={
|
className={
|
||||||
@@ -462,29 +482,36 @@ export const DetailModal = ({
|
|||||||
? "bi bi-fullscreen-exit"
|
? "bi bi-fullscreen-exit"
|
||||||
: "bi bi-arrows-fullscreen"
|
: "bi bi-arrows-fullscreen"
|
||||||
}
|
}
|
||||||
></i>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className="text-2xl text-[var(--color-fg-muted)] hover:text-[var(--color-danger)] transition"
|
className="icon-btn text-[1.4rem] transition"
|
||||||
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
<i className="bi bi-x-circle-fill" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="flex-1 min-h-0 flex flex-col px-6 pt-4 pb-5 bg-[var(--color-surface)] overflow-hidden">
|
||||||
|
<div className="mb-3">
|
||||||
<SystemChartActionBar
|
<SystemChartActionBar
|
||||||
zeitraum={zeitraum}
|
zeitraum={zeitraum}
|
||||||
setZeitraum={setZeitraum}
|
setZeitraum={setZeitraum}
|
||||||
onFetchData={handleFetchData}
|
onFetchData={handleFetchData}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
className="mb-0"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div className="h-[85%] rounded shadow border p-2 bg-[var(--color-surface)] border-[var(--color-border)]">
|
<div className="flex-1 min-h-0 rounded-lg border border-base bg-[var(--color-surface-alt)] px-3 py-2 shadow-inner">
|
||||||
<Line ref={chartRef} data={chartData} options={chartOptions} />
|
<Line ref={chartRef} data={chartData} options={chartOptions} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Optional Footer (currently empty, reserved for future) */}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const SystemChartActionBar: React.FC<Props> = ({
|
|||||||
<label className="font-medium text-sm">Zeitraum:</label>
|
<label className="font-medium text-sm">Zeitraum:</label>
|
||||||
<Listbox value={zeitraum} onChange={setZeitraum}>
|
<Listbox value={zeitraum} onChange={setZeitraum}>
|
||||||
<div className="relative w-48">
|
<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">
|
<Listbox.Button className="w-full border border-base px-3 py-1 rounded text-left bg-[var(--color-surface-alt)] text-fg flex justify-between items-center text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)]/40 transition">
|
||||||
<span>
|
<span>
|
||||||
{
|
{
|
||||||
{ DIA0: "Alle Messwerte", DIA1: "Stündlich", DIA2: "Täglich" }[
|
{ DIA0: "Alle Messwerte", DIA1: "Stündlich", DIA2: "Täglich" }[
|
||||||
@@ -39,7 +39,7 @@ const SystemChartActionBar: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5 text-gray-400"
|
className="w-5 h-5 text-[var(--color-fg-muted)]"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
>
|
>
|
||||||
@@ -50,20 +50,18 @@ const SystemChartActionBar: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
<Listbox.Options className="absolute z-50 mt-1 w-full border rounded bg-white shadow max-h-60 overflow-auto text-sm">
|
<Listbox.Options className="absolute z-50 mt-1 w-full border border-base rounded bg-[var(--color-surface)] text-fg shadow-lg max-h-60 overflow-auto text-sm focus:outline-none">
|
||||||
{["DIA0", "DIA1", "DIA2"].map((option) => (
|
{["DIA0", "DIA1", "DIA2"].map((option) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={option}
|
key={option}
|
||||||
value={option}
|
value={option}
|
||||||
className={({ selected, active }) =>
|
className={({ selected, active }) => {
|
||||||
`px-4 py-1 cursor-pointer ${
|
const base = "px-4 py-1 cursor-pointer text-sm";
|
||||||
selected
|
if (selected) return `${base} bg-littwin-blue text-white`; // selected highlight
|
||||||
? "bg-littwin-blue text-white"
|
if (active)
|
||||||
: active
|
return `${base} bg-[var(--color-surface-alt)] text-fg`;
|
||||||
? "bg-gray-200"
|
return `${base} text-fg`;
|
||||||
: ""
|
}}
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.898",
|
"version": "1.6.899",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.898",
|
"version": "1.6.899",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.0",
|
"@emotion/react": "^11.13.0",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.898",
|
"version": "1.6.899",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3000",
|
"dev": "next dev -p 3000",
|
||||||
|
|||||||
Reference in New Issue
Block a user