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