Compare commits

10 Commits

Author SHA1 Message Date
ISA
001b237dd7 style: dark mode Modal KÜ Einstellungen 2025-09-08 15:38:55 +02:00
ISA
af21b180f1 WIP: dark mode Modale 2025-09-08 15:33:26 +02:00
ISA
fefff9419d WIP: dark mode Berichte 2025-09-08 15:22:06 +02:00
ISA
27c60c6742 WIP: dark mode Modale 2025-09-08 15:12:38 +02:00
ISA
c8ec763aac WIP: dark mode Baugrüppenträger sttus 2025-09-08 15:04:52 +02:00
ISA
d163df0d96 WIP: dark mode 2025-09-08 15:01:34 +02:00
ISA
12d3a17f60 fix: TDR 2 Minuten eingestellt laut eingabe 2025-09-08 13:31:32 +02:00
ISA
f3339ccafd fix: TDR 2 Minuten eingestellt laut eingaben 2025-09-08 13:30:23 +02:00
ISA
fab8a02ce9 fix: TDR 2 Minuten eingestellt laut eingaben 2025-09-08 13:28:59 +02:00
ISA
eb0585072d WIP: dark mode 2025-09-08 13:14:04 +02:00
37 changed files with 579 additions and 274 deletions

View File

@@ -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.869 NEXT_PUBLIC_APP_VERSION=1.6.879
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)

View File

@@ -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.869 NEXT_PUBLIC_APP_VERSION=1.6.879
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,3 +1,53 @@
## [1.6.879] 2025-09-08
- WIP: dark mode Modale
---
## [1.6.878] 2025-09-08
- WIP: dark mode Berichte
---
## [1.6.877] 2025-09-08
- WIP: dark mode Modale
---
## [1.6.876] 2025-09-08
- WIP: dark mode Baugrüppenträger sttus
---
## [1.6.875] 2025-09-08
- WIP: dark mode
---
## [1.6.874] 2025-09-08
- fix: TDR 2 Minuten eingestellt laut eingabe
---
## [1.6.873] 2025-09-08
- fix: TDR 2 Minuten eingestellt laut eingaben
---
## [1.6.872] 2025-09-08
- fix: TDR 2 Minuten eingestellt laut eingaben
---
## [1.6.871] 2025-09-08
- WIP: dark mode
---
## [1.6.870] 2025-09-08
- fix: Beim Aufruf der TDR-Detailseite erscheint im Hintergrund auf der KÜ ein Schleifenwiderstand von 0 KOhm. In der Daten Javascriptdatei steht jedoch der richtige Wert.
---
## [1.6.869] 2025-09-08 ## [1.6.869] 2025-09-08
- fix: Beim Ausführen einer TDR-Messung (Klick auf blauen Button in der TDR-Detailseite) erscheint keine Rückmeldung. Dort müsste ein Hinweis erscheinen “TDR-Messung wird ausgeführt und kann bis zu zwei Minuten dauern” - fix: Beim Ausführen einer TDR-Messung (Klick auf blauen Button in der TDR-Detailseite) erscheint keine Rückmeldung. Dort müsste ein Hinweis erscheinen “TDR-Messung wird ausgeführt und kann bis zu zwei Minuten dauern”

View File

@@ -55,45 +55,57 @@ function Footer() {
}, [showSlider]); }, [showSlider]);
return ( return (
<footer className="relative bg-gray-300 p-4 xl:p-2 2xl:p-4 overflow-hidden text-black laptop:h-[5vh] "> <footer className="relative bg-[var(--color-surface-alt)] border-t border-base p-4 xl:p-2 2xl:p-4 overflow-hidden text-[var(--color-fg)] laptop:h-[5vh] theme-transition">
<div className="container mx-auto flex justify-between"> <div className="container mx-auto flex justify-between">
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2 items-center">
<Icon <Icon
icon="material-symbols:factory-outline" icon="material-symbols:factory-outline"
className="text-xl text-blue-400" className="text-xl text-accent"
/> />
<p className="text-sm">Littwin Systemtechnik GmbH & Co. KG</p> <p className="text-sm text-fg-muted">
Littwin Systemtechnik GmbH & Co. KG
</p>
</div> </div>
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2 items-center">
<Icon icon="charm:phone" className="text-xl text-blue-400" /> <Icon icon="charm:phone" className="text-xl text-accent" />
<p className="text-sm">Telefon: 04402 972577-0</p> <p className="text-sm text-fg-muted">Telefon: 04402 972577-0</p>
</div> </div>
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2 items-center">
<Icon icon="mdi:email-outline" className="text-xl text-blue-400" /> <Icon icon="mdi:email-outline" className="text-xl text-accent" />
<p className="text-sm">kontakt@littwin-systemtechnik.de</p> <p className="text-sm text-fg-muted">
kontakt@littwin-systemtechnik.de
</p>
</div> </div>
<div <div
className="flex flex-row space-x-2 cursor-pointer" className="flex flex-row space-x-2 cursor-pointer items-center group"
onClick={() => setShowSlider(true)} onClick={() => setShowSlider(true)}
> >
<Icon icon="bi:book" className="text-xl text-blue-400" /> <Icon
<p className="text-sm">Handbücher</p> icon="bi:book"
className="text-xl text-accent group-hover:brightness-110 transition"
/>
<p className="text-sm text-fg-muted group-hover:text-[var(--color-fg)] transition">
Handbücher
</p>
</div> </div>
</div> </div>
<div <div
ref={sliderRef} ref={sliderRef}
className={`fixed top-0 right-0 w-64 h-full bg-white shadow-lg transform transition-transform duration-300 ${ className={`fixed top-0 right-0 w-64 h-full bg-[var(--color-surface)] border-l border-base shadow-lg transform transition-transform duration-300 ${
showSlider ? "translate-x-0" : "translate-x-full" showSlider ? "translate-x-0" : "translate-x-full"
}`} }`}
> >
<div className="p-4 flex justify-between items-center border-b"> <div className="p-4 flex justify-between items-center border-b border-base">
<h3 className="text-lg font-semibold">PDF Handbücher</h3> <h3 className="text-base font-semibold text-[var(--color-fg)]">
PDF Handbücher
</h3>
<button <button
className="text-gray-500 hover:text-gray-800" className="text-[var(--color-muted)] hover:text-[var(--color-fg)] transition"
onClick={() => setShowSlider(false)} onClick={() => setShowSlider(false)}
aria-label="Schließen"
> >
<Icon icon="carbon:close" className="text-2xl" /> <Icon icon="carbon:close" className="text-xl" />
</button> </button>
</div> </div>
@@ -102,7 +114,7 @@ function Footer() {
{pdfFiles.map((fileName) => ( {pdfFiles.map((fileName) => (
<li <li
key={fileName} key={fileName}
className="cursor-pointer text-blue-500 hover:underline mb-2" className="cursor-pointer text-accent hover:underline mb-2 text-sm"
onClick={() => loadPDF(fileName)} onClick={() => loadPDF(fileName)}
> >
{fileName} {fileName}

View File

@@ -109,21 +109,43 @@ function Header() {
}, [deviceName, dispatch]); }, [deviceName, dispatch]);
//---------------------------------------------------------------- //----------------------------------------------------------------
// Dark/Light Mode Toggle // Dark/Light Mode Toggle (persisted)
const [isDark, setIsDark] = useState(false); const [isDark, setIsDark] = useState(false);
// Initial state from html class / localStorage (set by _document script before hydration)
useEffect(() => { useEffect(() => {
if (typeof window !== "undefined") { if (typeof window === "undefined") return;
const html = document.documentElement; const html = document.documentElement;
if (isDark) { const stored = localStorage.getItem("theme");
html.classList.add("dark"); const active = stored ? stored === "dark" : html.classList.contains("dark");
} else { setIsDark(active);
html.classList.remove("dark"); }, []);
}
useEffect(() => {
if (typeof window === "undefined") return;
const html = document.documentElement;
if (isDark) {
html.classList.add("dark");
localStorage.setItem("theme", "dark");
} else {
html.classList.remove("dark");
localStorage.setItem("theme", "light");
} }
}, [isDark]); }, [isDark]);
// Keyboard shortcut Alt + D to toggle theme
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.altKey && (e.key === "d" || e.key === "D")) {
e.preventDefault();
setIsDark((d) => !d);
}
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, []);
return ( return (
<header className="bg-gray-300 dark:bg-gray-800 flex justify-between items-center w-full h-[13vh] laptop:h-[10vh] relative text-black dark:text-white "> <header className="bg-[var(--color-surface)] dark:bg-[var(--color-surface)]/90 backdrop-blur flex justify-between items-center w-full h-[13vh] laptop:h-[10vh] relative text-[var(--color-fg)] dark:text-[var(--color-fg)] shadow-sm border-b border-[var(--color-border)]">
<div <div
className="absolute transform -translate-y-1/2 className="absolute transform -translate-y-1/2
left-[8%] sm:left-[8%] md:left-[8%] lg:left-[8%] xl:left-[6%] 2xl:left-[2%] laptop:left-[4%] laptop: left-[8%] sm:left-[8%] md:left-[8%] lg:left-[8%] xl:left-[6%] 2xl:left-[2%] laptop:left-[4%] laptop:
@@ -154,10 +176,10 @@ function Header() {
priority priority
/> />
<div className="flex flex-col leading-tight whitespace-nowrap"> <div className="flex flex-col leading-tight whitespace-nowrap">
<h2 className="text-xl laptop:text-base xl:text-lg font-bold text-gray-900 dark:text-gray-100"> <h2 className="text-xl laptop:text-base xl:text-lg font-bold text-[var(--color-fg)]">
Meldestation Meldestation
</h2> </h2>
<p className="text-gray-600 dark:text-gray-300 text-lg laptop:text-sm xl:text-base truncate max-w-[20vw]"> <p className="text-[var(--color-fg-muted)] text-lg laptop:text-sm xl:text-base truncate max-w-[20vw]">
{deviceName} {deviceName}
</p> </p>
</div> </div>
@@ -169,7 +191,7 @@ function Header() {
<button <button
aria-label={isDark ? "Light Mode" : "Dark Mode"} aria-label={isDark ? "Light Mode" : "Dark Mode"}
onClick={() => setIsDark((d) => !d)} onClick={() => setIsDark((d) => !d)}
className="rounded-full p-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition" className="rounded-full p-2 bg-[var(--color-surface-alt)]/80 hover:bg-[var(--color-surface-alt)] dark:bg-[var(--color-surface-alt)]/60 dark:hover:bg-[var(--color-surface-alt)] transition border border-[var(--color-border)]"
title={isDark ? "Light Mode" : "Dark Mode"} title={isDark ? "Light Mode" : "Dark Mode"}
> >
{isDark ? ( {isDark ? (
@@ -192,7 +214,7 @@ function Header() {
<button <button
onClick={handleLogout} onClick={handleLogout}
aria-label="Abmelden" aria-label="Abmelden"
className="bg-littwin-blue text-white px-4 py-2 rounded" className="px-4 py-2 rounded bg-[var(--color-accent)] text-white hover:brightness-110 shadow-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-ring)] focus:ring-offset-2 focus:ring-offset-[var(--color-background)] transition"
> >
Abmelden Abmelden
</button> </button>
@@ -202,7 +224,7 @@ function Header() {
{/* Warnhinweis, wenn der Admin angemeldet ist */} {/* Warnhinweis, wenn der Admin angemeldet ist */}
{isAdminLoggedIn && ( {isAdminLoggedIn && (
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-full xl:w-1/4 2xl:w-1/4 h-8 bg-yellow-400 text-center py-2 text-black font-bold"> <div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-full xl:w-1/4 2xl:w-1/4 h-8 bg-[var(--color-warning)] text-center py-2 text-black font-bold tracking-wide">
Admin-Modus aktiv Admin-Modus aktiv
</div> </div>
)} )}

View File

@@ -37,8 +37,8 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
return ( return (
<div <div
className={`bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 shadow-md border border-gray-200 dark:border-gray-700 p-3 rounded-lg laptop:p-1 xl:p-1 ${ className={`text-[var(--color-fg)] bg-[var(--color-surface)] dark:bg-[var(--color-surface)] shadow-sm border border-[var(--color-border)] p-3 rounded-lg laptop:p-1 xl:p-1 ${
loading ? "cursor-wait" : "" loading ? "cursor-wait opacity-70" : ""
}`} }`}
> >
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center"> <h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
@@ -54,24 +54,24 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
loading ? "cursor-wait" : "" loading ? "cursor-wait" : ""
}`} }`}
> >
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-b items-center"> <thead className="bg-[var(--color-surface-alt)]/60 dark:bg-[var(--color-surface-alt)]/30 text-[var(--color-fg)] border-b border-[var(--color-border)] items-center">
<tr> <tr>
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
Eingang Eingang
</th> </th>
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
Messwert Messwert
</th> </th>
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
Einheit Einheit
</th> </th>
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
Bezeichnung Bezeichnung
</th> </th>
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
Einstellungen Einstellungen
</th> </th>
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
Messkurve Messkurve
</th> </th>
</tr> </tr>
@@ -92,12 +92,12 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
loading loading
? "cursor-wait" ? "cursor-wait"
: analogInput.id === activeId : analogInput.id === activeId
? "bg-blue-100 dark:bg-gray-700 dark:text-white" ? "bg-[var(--color-accent-soft)] dark:bg-[var(--color-surface-alt)]/60 text-[var(--color-fg)]"
: "hover:bg-gray-100 dark:hover:bg-gray-800" : "hover:bg-[var(--color-surface-alt)]/70 dark:hover:bg-[var(--color-surface-alt)]/30"
}`} }`}
> >
<td <td
className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100" className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]"
onClick={() => handleSelect(analogInput.id!, analogInput)} onClick={() => handleSelect(analogInput.id!, analogInput)}
> >
<div className="flex items-center gap-1 "> <div className="flex items-center gap-1 ">
@@ -109,7 +109,7 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
</div> </div>
</td> </td>
<td <td
className="border p-2 text-right bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100" className="border p-2 text-right bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]"
onClick={() => handleSelect(analogInput.id!, analogInput)} onClick={() => handleSelect(analogInput.id!, analogInput)}
> >
{typeof analogInput.value === "number" {typeof analogInput.value === "number"
@@ -118,19 +118,19 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
</td> </td>
<td <td
className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100" className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]"
onClick={() => handleSelect(analogInput.id!, analogInput)} onClick={() => handleSelect(analogInput.id!, analogInput)}
> >
{analogInput.unit || "-"} {analogInput.unit || "-"}
</td> </td>
<td <td
className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100" className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]"
onClick={() => handleSelect(analogInput.id!, analogInput)} onClick={() => handleSelect(analogInput.id!, analogInput)}
> >
{analogInput.label || "----"} {analogInput.label || "----"}
</td> </td>
<td className="border p-2 text-center bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border p-2 text-center bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
<button <button
onClick={() => { onClick={() => {
handleSelect(analogInput.id!, analogInput); handleSelect(analogInput.id!, analogInput);
@@ -141,7 +141,7 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
<Icon icon={settingsIcon} className="text-xl" /> <Icon icon={settingsIcon} className="text-xl" />
</button> </button>
</td> </td>
<td className="border p-2 text-center bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border p-2 text-center bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
<button <button
onClick={() => { onClick={() => {
handleSelect(analogInput.id!, analogInput); handleSelect(analogInput.id!, analogInput);

View File

@@ -36,8 +36,8 @@ function AnalogInputsView() {
}`} }`}
> >
<div className="grid grid-cols-1 gap-4 justify-items-start"> <div className="grid grid-cols-1 gap-4 justify-items-start">
<div className="bg-white dark:bg-gray-900 rounded-lg p-4 max-w-3xl text-gray-900 dark:text-gray-100"> <div className="rounded-lg p-4 max-w-3xl text-[var(--color-fg)] bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border border-[var(--color-border)] shadow-sm">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100"> <h2 className="text-xl font-semibold mb-4 text-[var(--color-fg)] tracking-wide">
Messwerteingänge Messwerteingänge
</h2> </h2>
<AnalogInputsTable loading={loading} /> <AnalogInputsTable loading={loading} />

View File

@@ -43,7 +43,7 @@ const Baugruppentraeger: React.FC = () => {
baugruppen.push( baugruppen.push(
<div <div
key={i} key={i}
className="flex bg-white shadow-md rounded-lg mb-4 xl:mb-0 lg:mb-0 border border-gray-200 w-full laptop:scale-y-75 xl:scale-y-90" className="flex card mb-4 xl:mb-0 lg:mb-0 w-full laptop:scale-y-75 xl:scale-y-90"
> >
<div className="flex gap-1"> <div className="flex gap-1">
{slots.map((version, index) => { {slots.map((version, index) => {

View File

@@ -25,7 +25,7 @@ const DashboardView: React.FC = () => {
}, [dispatch]); }, [dispatch]);
//------------------------------------- //-------------------------------------
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 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"> <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 bg-[var(--color-background)] text-[var(--color-fg)]">
{/* Header */} {/* Header */}
<div className="flex justify-between items-center w-full lg:w-2/3"> <div className="flex justify-between items-center w-full lg:w-2/3">
<div className="flex justify-between gap-1"> <div className="flex justify-between gap-1">
@@ -33,7 +33,7 @@ const DashboardView: React.FC = () => {
icon="ri:calendar-schedule-line" icon="ri:calendar-schedule-line"
className="text-littwin-blue text-4xl xl:text-2xl" className="text-littwin-blue text-4xl xl:text-2xl"
/> />
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-100 xl:text-base"> <h1 className="text-xl font-bold xl:text-base text-[var(--color-fg)] tracking-wide">
Letzten 20 Meldungen Letzten 20 Meldungen
</h1> </h1>
</div> </div>

View File

@@ -48,22 +48,22 @@ export default function Last20MessagesTable({ className }: Props) {
return ( return (
<div className={`flex flex-col gap-3 p-4 ${className}`}> <div className={`flex flex-col gap-3 p-4 ${className}`}>
<div className="overflow-auto max-h-[80vh]"> <div className="overflow-auto max-h-[80vh]">
<table className="min-w-full border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <table className="min-w-full border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 text-left sticky top-0 z-10"> <thead className="text-left sticky top-0 z-10 bg-[var(--color-surface-alt)]/70 dark:bg-[var(--color-surface-alt)]/25 text-[var(--color-fg)]">
<tr> <tr>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
Prio Prio
</th> </th>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
Zeitstempel Zeitstempel
</th> </th>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
Quelle Quelle
</th> </th>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
Meldung Meldung
</th> </th>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
Status Status
</th> </th>
</tr> </tr>
@@ -72,24 +72,24 @@ export default function Last20MessagesTable({ className }: Props) {
{filteredMessages.slice(0, 20).map((msg, index) => ( {filteredMessages.slice(0, 20).map((msg, index) => (
<tr <tr
key={index} key={index}
className="hover:bg-gray-100 dark:hover:bg-gray-800" className="hover:bg-[var(--color-surface-alt)]/70 dark:hover:bg-[var(--color-surface-alt)]/30 transition"
> >
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
<div <div
className="w-4 h-4 rounded" className="w-4 h-4 rounded"
style={{ backgroundColor: msg.c }} style={{ backgroundColor: msg.c }}
></div> ></div>
</td> </td>
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
{msg.t} {msg.t}
</td> </td>
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
{msg.i} {msg.i}
</td> </td>
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
{msg.m} {msg.m}
</td> </td>
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
{msg.v} {msg.v}
</td> </td>
</tr> </tr>
@@ -97,7 +97,7 @@ export default function Last20MessagesTable({ className }: Props) {
</tbody> </tbody>
</table> </table>
{messages.length === 0 && ( {messages.length === 0 && (
<div className="mt-4 text-center text-gray-500 italic dark:text-gray-400"> <div className="mt-4 text-center italic text-[var(--color-fg-muted)]">
Keine Meldungen im gewählten Zeitraum vorhanden. Keine Meldungen im gewählten Zeitraum vorhanden.
</div> </div>
)} )}

View File

@@ -38,7 +38,7 @@ const NetworkInfo: React.FC = () => {
return ( return (
<div className="w-full flex-direction: row flex"> <div className="w-full flex-direction: row flex">
<div className=" flex-grow flex justify-between items-center mt-1 bg-white dark:bg-gray-800 p-2 rounded-lg shadow-md border border-gray-200 dark:border-gray-700 laptop:m-0 laptop:scale-y-75 2xl:scale-y-75"> <div className=" flex-grow flex justify-between items-center mt-1 p-2 rounded-lg shadow-sm bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border border-[var(--color-border)] laptop:m-0 laptop:scale-y-75 2xl:scale-y-75">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<Image <Image
src="/images/IP-icon.svg" src="/images/IP-icon.svg"
@@ -49,12 +49,8 @@ const NetworkInfo: React.FC = () => {
priority priority
/> />
<div> <div>
<p className="text-xs text-gray-500 dark:text-gray-400"> <p className="text-xs text-[var(--color-fg-muted)]">IP-Adresse</p>
IP-Adresse <p className="text-sm font-medium text-[var(--color-fg)]">{ip}</p>
</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
{ip}
</p>
</div> </div>
</div> </div>
@@ -68,10 +64,8 @@ const NetworkInfo: React.FC = () => {
priority priority
/> />
<div> <div>
<p className="text-xs text-gray-500 dark:text-gray-400"> <p className="text-xs text-[var(--color-fg-muted)]">Subnet-Maske</p>
Subnet-Maske <p className="text-sm font-medium text-[var(--color-fg)]">
</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
{subnet} {subnet}
</p> </p>
</div> </div>
@@ -87,8 +81,8 @@ const NetworkInfo: React.FC = () => {
priority priority
/> />
<div> <div>
<p className="text-xs text-gray-500 dark:text-gray-400">Gateway</p> <p className="text-xs text-[var(--color-fg-muted)]">Gateway</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-200"> <p className="text-sm font-medium text-[var(--color-fg)]">
{gateway} {gateway}
</p> </p>
</div> </div>
@@ -97,8 +91,8 @@ const NetworkInfo: React.FC = () => {
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div className="text-xs font-bold text-littwin-blue">OPC-UA</div> <div className="text-xs font-bold text-littwin-blue">OPC-UA</div>
<div> <div>
<p className="text-xs text-gray-500 dark:text-gray-400">Status</p> <p className="text-xs text-[var(--color-fg-muted)]">Status</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-200"> <p className="text-sm font-medium text-[var(--color-fg)]">
{opcUaZustand} {opcUaZustand}
</p> </p>
</div> </div>

View File

@@ -4,9 +4,7 @@ import { Icon } from "@iconify/react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store"; import { RootState } from "../../../redux/store";
type VersionInfoProps = { type VersionInfoProps = { className?: string };
className?: string;
};
const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => { const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
const appVersion = const appVersion =
@@ -17,26 +15,26 @@ const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
); );
return ( return (
<div <div className={`card w-full p-3 laptop:p-2 ${className}`}>
className={`bg-gray-50 dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 w-full laptop:p-2 ${className}`} <h2 className="text-base font-semibold mb-2 text-[var(--color-fg)]">
>
<h2 className="text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">
Versionsinformationen Versionsinformationen
</h2> </h2>
<ul className="space-y-1">
<div className="flex flex-row p-2 space-x-2"> <li className="flex items-start gap-2">
<Icon icon="bx:code-block" className="text-xl text-blue-400" /> <Icon icon="bx:code-block" className="text-xl text-accent" />
<p className="text-sm text-gray-600 dark:text-gray-300"> <p className="text-sm text-fg-muted">
Applikationsversion: {appVersion} Applikationsversion:{" "}
</p> <span className="text-[var(--color-fg)]">{appVersion}</span>
</div> </p>
</li>
<div className="flex flex-row p-2 space-x-2"> <li className="flex items-start gap-2">
<Icon icon="mdi:web" className="text-xl text-blue-400" /> <Icon icon="mdi:web" className="text-xl text-accent" />
<p className="text-sm text-gray-600 dark:text-gray-300"> <p className="text-sm text-fg-muted">
Webversion: {webVersion} Webversion:{" "}
</p> <span className="text-[var(--color-fg)]">{webVersion}</span>
</div> </p>
</li>
</ul>
</div> </div>
); );
}; };

View File

@@ -30,7 +30,7 @@ export default function DigitalInputsWidget({
//console.log("DigitalInputs", inputs); //console.log("DigitalInputs", inputs);
return ( return (
<div className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 shadow-md border border-gray-200 dark:border-gray-700 p-3 rounded-lg w-full laptop:p-1 xl:p-1"> <div className="text-[var(--color-fg)] bg-[var(--color-surface)] dark:bg-[var(--color-surface)] shadow-sm border border-[var(--color-border)] p-3 rounded-lg w-full laptop:p-1 xl:p-1">
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center"> <h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
<Icon <Icon
icon={inputIcon} icon={inputIcon}
@@ -38,19 +38,19 @@ export default function DigitalInputsWidget({
/> />
Meldungseingänge {inputRange.start + 1} {inputRange.end} Meldungseingänge {inputRange.start + 1} {inputRange.end}
</h2> </h2>
<table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-white dark:bg-gray-900"> <table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-[var(--color-surface)]">
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-b"> <thead className="bg-[var(--color-surface-alt)]/60 dark:bg-[var(--color-surface-alt)]/25 text-[var(--color-fg)] border-b border-[var(--color-border)]">
<tr> <tr>
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
Eingang Eingang
</th> </th>
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
Zustand Zustand
</th> </th>
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
Bezeichnung Bezeichnung
</th> </th>
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
Aktion Aktion
</th> </th>
</tr> </tr>
@@ -59,9 +59,9 @@ export default function DigitalInputsWidget({
{inputs.map((input) => ( {inputs.map((input) => (
<tr <tr
key={input.id} key={input.id}
className="border-b hover:bg-gray-100 dark:hover:bg-gray-800" className="border-b border-[var(--color-border)] hover:bg-[var(--color-surface-alt)]/70 dark:hover:bg-[var(--color-surface-alt)]/30 transition"
> >
<td className="px-1 py-0 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="px-1 py-0 bg-[var(--color-surface)] text-[var(--color-fg)]">
<div className="flex items-center gap-1 "> <div className="flex items-center gap-1 ">
<Icon <Icon
icon={loginIcon} icon={loginIcon}
@@ -70,7 +70,7 @@ export default function DigitalInputsWidget({
{input.id} {input.id}
</div> </div>
</td> </td>
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
{input.eingangOffline ? ( {input.eingangOffline ? (
<div className="relative group inline-block"> <div className="relative group inline-block">
<span className="text-red-500 sm:text-sm md:text-base lg:text-lg xl:text-xl 2xl:text-2xl laptop:text-sm "> <span className="text-red-500 sm:text-sm md:text-base lg:text-lg xl:text-xl 2xl:text-2xl laptop:text-sm ">
@@ -91,10 +91,10 @@ export default function DigitalInputsWidget({
</div> </div>
)} )}
</td> </td>
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
{input.label} {input.label}
</td> </td>
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
<Icon <Icon
icon={settingsIcon} icon={settingsIcon}
className="text-gray-400 text-base laptop:text-sm xl:text-sm 2xl:text-lg cursor-pointer dark:text-gray-300 dark:hover:text-white" className="text-gray-400 text-base laptop:text-sm xl:text-sm 2xl:text-lg cursor-pointer dark:text-gray-300 dark:hover:text-white"

View File

@@ -66,7 +66,7 @@ export default function DigitalOutputsWidget({
}; };
return ( return (
<div className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 shadow-md border border-gray-200 dark:border-gray-700 p-3 rounded-lg w-full h-fit max-h-[400px] overflow-auto"> <div className="bg-[var(--color-surface)] text-[var(--color-fg)] shadow-md border border-base p-3 rounded-lg w-full h-fit max-h-[400px] overflow-auto">
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center"> <h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
<Icon <Icon
icon={outputIcon} icon={outputIcon}
@@ -74,19 +74,19 @@ export default function DigitalOutputsWidget({
/> />
Schaltausgänge Schaltausgänge
</h2> </h2>
<table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-white dark:bg-gray-900 rounded-lg"> <table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-[var(--color-surface)] rounded-lg">
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-b"> <thead className="bg-[var(--color-surface)] text-[var(--color-fg)] border-b border-base">
<tr> <tr>
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
Ausgang Ausgang
</th> </th>
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
Bezeichnung Bezeichnung
</th> </th>
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
Schalter Schalter
</th> </th>
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
Aktion Aktion
</th> </th>
</tr> </tr>
@@ -95,33 +95,33 @@ export default function DigitalOutputsWidget({
{digitalOutputs.map((output) => ( {digitalOutputs.map((output) => (
<tr <tr
key={output.id} key={output.id}
className="border-b hover:bg-gray-100 dark:hover:bg-gray-800" className="border-b border-base hover:bg-[var(--color-surface-alt)] transition-colors"
> >
<td className="flex items-center px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="flex items-center px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
<Icon <Icon
icon={outputIcon} icon={outputIcon}
className="text-gray-600 mr-1 text-base" className="text-[var(--color-muted)] mr-1 text-base"
/> />
{output.id} {output.id}
</td> </td>
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
{output.label} {output.label}
</td> </td>
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
<Icon <Icon
icon={switchIcon} icon={switchIcon}
className={`cursor-pointer text-base transition ${ className={`cursor-pointer text-base transition ${
output.status output.status
? "text-littwin-blue" ? "text-accent"
: "text-gray-500 scale-x-[-1]" : "text-[var(--color-muted)] scale-x-[-1]"
} dark:hover:text-littwin-blue`} } hover:text-accent`}
onClick={() => handleToggle(output.id)} onClick={() => handleToggle(output.id)}
/> />
</td> </td>
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
<Icon <Icon
icon={settingsIcon} icon={settingsIcon}
className="text-gray-400 text-base cursor-pointer dark:text-gray-300 dark:hover:text-white" className="text-[var(--color-muted)] text-base cursor-pointer hover:text-[var(--color-fg)]"
onClick={() => openOutputModal(output)} onClick={() => openOutputModal(output)}
/> />
</td> </td>

View File

@@ -119,19 +119,23 @@ function KabelueberwachungView() {
return ( return (
<div> <div>
<div className="mb-4"> <div className="mb-4">
{[1, 2, 3, 4].map((rack) => ( {[1, 2, 3, 4].map((rack) => {
<button const isActive = Number(activeRack) === Number(rack);
key={rack} return (
onClick={() => changeRack(rack)} <button
className={`mr-2 ${ key={rack}
Number(activeRack) === Number(rack) onClick={() => changeRack(rack)}
? "bg-littwin-blue text-white p-1 rounded-sm" aria-pressed={isActive}
: "bg-gray-300 p-1 text-sm" className={`mr-2 px-2 py-1 rounded-sm text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-accent/50 ${
}`} isActive
> ? "btn-primary"
Rack {rack} : "btn-muted text-fg opacity-90 hover:opacity-100"
</button> }`}
))} >
Rack {rack}
</button>
);
})}
</div> </div>
<div className="flex flex-row space-x-8 xl:space-x-0 2xl:space-x-8 qhd:space-x-16 ml-[5%] mt-[5%]"> <div className="flex flex-row space-x-8 xl:space-x-0 2xl:space-x-8 qhd:space-x-16 ml-[5%] mt-[5%]">
{( {(

View File

@@ -276,7 +276,9 @@ const TDRChartActionBar: React.FC = () => {
{tdrRunning && ( {tdrRunning && (
<div className="fixed inset-0 z-[1000] flex flex-col items-center justify-center bg-white/80 backdrop-blur-sm"> <div className="fixed inset-0 z-[1000] flex flex-col items-center justify-center bg-white/80 backdrop-blur-sm">
<div className="mb-4 text-center space-y-1"> <div className="mb-4 text-center space-y-1">
<p className="text-lg font-semibold">TDR Messung läuft</p> <p className="text-lg font-semibold">
TDR Messung läuft... kann bis zu zwei Minuten dauern
</p>
<p className="text-sm text-gray-700"> <p className="text-sm text-gray-700">
Bitte warten{" "} Bitte warten{" "}
{Math.min( {Math.min(

View File

@@ -66,7 +66,7 @@ export default function SlotActivityOverlay({
// Durations // Durations
const LOOP_MS = 2 * 60 * 1000; // ~2 min const LOOP_MS = 2 * 60 * 1000; // ~2 min
const TDR_MS = 30 * 1000; // ~30 s const TDR_MS = 110 * 1000; // ~2 min laut die Eigaben
const ALIGN_MS = 10 * 60 * 1000; // ~10 min const ALIGN_MS = 10 * 60 * 1000; // ~10 min
if (!loopActive && !tdrActive && !comparisonActive) return null; if (!loopActive && !tdrActive && !comparisonActive) return null;

View File

@@ -69,14 +69,20 @@ export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
}, },
}} }}
> >
<div className="p-2 flex justify-between items-center rounded-t-md"> <div className="p-2 flex justify-between items-center rounded-t-md bg-surface-alt border-b border-base">
<h2 className="text-base font-bold">Einstellungen {slot + 1}</h2> <h2 className="text-base font-bold text-fg">
<button onClick={onClose} className="text-2xl hover:text-gray-200"> Einstellungen {slot + 1}
</h2>
<button
onClick={onClose}
className="text-2xl text-fg-muted hover:text-fg transition-colors focus:outline-none focus:ring-2 focus:ring-accent/50 rounded"
aria-label="Modal schließen"
>
<i className="bi bi-x-circle-fill"></i> <i className="bi bi-x-circle-fill"></i>
</button> </button>
</div> </div>
<div className="flex justify-start bg-gray-100 space-x-2 p-2"> <div className="flex justify-start bg-surface-alt space-x-2 p-2 border-b border-base">
{[ {[
{ label: "Allgemein", key: "kue" as const }, { label: "Allgemein", key: "kue" as const },
{ label: "TDR ", key: "tdr" as const }, { label: "TDR ", key: "tdr" as const },
@@ -86,18 +92,17 @@ export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
<button <button
key={key} key={key}
onClick={() => setActiveTab(key)} onClick={() => setActiveTab(key)}
className={`px-4 py-1 rounded-t font-bold text-sm ${ className={`px-4 py-1 rounded-t font-semibold text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-accent/40 ${
activeTab === key activeTab === key
? "bg-white text-littwin-blue" ? "bg-surface text-accent shadow-sm"
: "text-gray-500 hover:text-black" : "text-fg-muted hover:text-fg"
}`} }`}
> >
{label} {label}
</button> </button>
))} ))}
</div> </div>
<div className="p-4 bg-surface rounded-b-md h-[20rem] laptop:h-[24rem] 2xl:h-[30rem] overflow-y-auto text-fg">
<div className="p-4 bg-white rounded-b-md h-[20rem] laptop:h-[24rem] 2xl:h-[30rem] overflow-y-auto">
{activeTab === "kue" && ( {activeTab === "kue" && (
<KueEinstellung <KueEinstellung
slot={slot} slot={slot}

View File

@@ -14,22 +14,22 @@ export default function MeldungenTabelle({
}) { }) {
return ( return (
<div className="overflow-auto max-h-[80vh]"> <div className="overflow-auto max-h-[80vh]">
<table className="min-w-full border"> <table className="min-w-full border border-base table-surface text-fg">
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 text-left sticky top-0 z-10"> <thead className="text-left sticky top-0 z-10 bg-surface-alt/90 backdrop-blur supports-[backdrop-filter]:bg-surface-alt/70">
<tr> <tr>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-base bg-surface-alt text-fg font-medium">
Prio Prio
</th> </th>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-base bg-surface-alt text-fg font-medium">
Zeitstempel Zeitstempel
</th> </th>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-base bg-surface-alt text-fg font-medium">
Quelle Quelle
</th> </th>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-base bg-surface-alt text-fg font-medium">
Meldung Meldung
</th> </th>
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <th className="p-2 border border-base bg-surface-alt text-fg font-medium">
Status Status
</th> </th>
</tr> </tr>
@@ -38,15 +38,15 @@ export default function MeldungenTabelle({
{messages.map((msg, index) => ( {messages.map((msg, index) => (
<tr <tr
key={index} key={index}
className="hover:bg-gray-100 dark:hover:bg-gray-700" className="transition-colors hover:bg-surface-alt/60"
> >
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border border-base p-2 bg-surface text-fg">
<div <div
className="w-4 h-4 rounded" className="w-4 h-4 rounded"
style={{ backgroundColor: msg.c }} style={{ backgroundColor: msg.c }}
></div> ></div>
</td> </td>
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border border-base p-2 bg-surface text-fg">
{new Date(msg.t).toLocaleString("de-DE", { {new Date(msg.t).toLocaleString("de-DE", {
day: "2-digit", day: "2-digit",
month: "2-digit", month: "2-digit",
@@ -56,13 +56,13 @@ export default function MeldungenTabelle({
second: "2-digit", second: "2-digit",
})} })}
</td> </td>
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border border-base p-2 bg-surface text-fg">
{msg.i} {msg.i}
</td> </td>
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border border-base p-2 bg-surface text-fg">
{msg.m} {msg.m}
</td> </td>
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"> <td className="border border-base p-2 bg-surface text-fg">
{msg.v} {msg.v}
</td> </td>
</tr> </tr>
@@ -70,7 +70,7 @@ export default function MeldungenTabelle({
</tbody> </tbody>
</table> </table>
{messages.length === 0 && ( {messages.length === 0 && (
<div className="mt-4 text-center text-gray-500 italic"> <div className="mt-4 text-center text-fg-muted italic">
Keine Meldungen im gewählten Zeitraum vorhanden. Keine Meldungen im gewählten Zeitraum vorhanden.
</div> </div>
)} )}

View File

@@ -60,14 +60,14 @@ export default function MeldungenView() {
/> />
<button <button
onClick={() => dispatch(getMessagesThunk({ fromDate, toDate }))} onClick={() => dispatch(getMessagesThunk({ fromDate, toDate }))}
className="bg-littwin-blue text-white px-4 py-2 rounded h-fit" className="btn-primary px-4 py-2 h-fit"
> >
Anzeigen Anzeigen
</button> </button>
<Listbox value={sourceFilter} onChange={setSourceFilter}> <Listbox value={sourceFilter} onChange={setSourceFilter}>
<div className="relative ml-6 w-64"> <div className="relative ml-6 w-64">
<Listbox.Button className="bg-white text-gray-900 w-full border px-4 py-2 rounded text-left flex justify-between items-center dark:bg-gray-900 dark:text-gray-100"> <Listbox.Button className="bg-[var(--color-surface)] text-[var(--color-fg)] w-full border border-base px-4 py-2 rounded text-left flex justify-between items-center">
<span>{sourceFilter}</span> <span>{sourceFilter}</span>
<svg <svg
className="w-5 h-5 text-gray-400" className="w-5 h-5 text-gray-400"
@@ -83,19 +83,19 @@ export default function MeldungenView() {
/> />
</svg> </svg>
</Listbox.Button> </Listbox.Button>
<Listbox.Options className="bg-white absolute z-50 mt-1 w-full border rounded dark:bg-gray-900"> <Listbox.Options className="bg-[var(--color-surface)] absolute z-50 mt-1 w-full border border-base rounded shadow-sm">
{sources.map((src) => ( {sources.map((src) => (
<Listbox.Option <Listbox.Option
key={src} key={src}
value={src} value={src}
className={({ selected, active, disabled }) => className={({ selected, active, disabled }) =>
`px-4 py-2 cursor-pointer text-gray-900 dark:text-gray-100 ${ `px-4 py-2 cursor-pointer text-[var(--color-fg)] transition-colors ${
selected disabled
? "bg-littwin-blue text-white" ? "opacity-50 text-[var(--color-muted)] cursor-not-allowed"
: selected
? "bg-accent text-white"
: active : active
? "bg-blue-100 dark:bg-gray-700 dark:text-white" ? "bg-[var(--color-surface-alt)]"
: disabled
? "opacity-50 text-gray-400 dark:text-gray-500 cursor-not-allowed"
: "" : ""
}` }`
} }

View File

@@ -10,14 +10,14 @@ import { useAdminAuth } from "./hooks/useAdminAuth";
const DatabaseSettings: React.FC = () => { const DatabaseSettings: React.FC = () => {
const { isAdminLoggedIn } = useAdminAuth(true); const { isAdminLoggedIn } = useAdminAuth(true);
return ( return (
<div className="p-6 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto rounded shadow text-gray-900 dark:text-gray-100"> <div className="p-6 bg-[var(--color-surface-alt)] max-w-5xl mr-auto rounded shadow text-[var(--color-fg)]">
<h2 className="text-lg font-bold mb-6">Datenbank Einstellungen</h2> <h2 className="text-lg font-bold mb-6">Datenbank Einstellungen</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<button <button
type="button" type="button"
onClick={handleClearMessages} onClick={handleClearMessages}
className="bg-littwin-blue text-white px-4 py-2 rounded shadow " className="btn-accent px-4 py-2 rounded shadow"
> >
Meldungen löschen Meldungen löschen
</button> </button>
@@ -25,7 +25,7 @@ const DatabaseSettings: React.FC = () => {
<button <button
type="button" type="button"
onClick={handleClearLogger} onClick={handleClearLogger}
className="bg-littwin-blue text-white px-4 py-2 rounded shadow " className="btn-accent px-4 py-2 rounded shadow"
> >
Messwerte Logger löschen Messwerte Logger löschen
</button> </button>
@@ -41,7 +41,7 @@ const DatabaseSettings: React.FC = () => {
<button <button
type="button" type="button"
onClick={handleClearDatabase} onClick={handleClearDatabase}
className="bg-littwin-blue text-white px-4 py-2 rounded shadow " className="btn-accent px-4 py-2 rounded shadow"
> >
Datenbank vollständig leeren Datenbank vollständig leeren
</button> </button>
@@ -49,7 +49,7 @@ const DatabaseSettings: React.FC = () => {
<button <button
type="button" type="button"
onClick={handleClearConfig} onClick={handleClearConfig}
className="bg-littwin-blue text-white px-4 py-2 rounded shadow " className="btn-accent px-4 py-2 rounded shadow"
> >
Konfiguration löschen Konfiguration löschen
</button> </button>

View File

@@ -63,8 +63,11 @@ const GeneralSettings: React.FC = () => {
setMac1(systemSettings.mac1 || ""); setMac1(systemSettings.mac1 || "");
}, [systemSettings]); }, [systemSettings]);
const inputCls =
"border border-base focus:border-accent rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none";
return ( return (
<div className="p-6 md:p-3 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto overflow-y-auto max-h-[calc(100vh-200px)] dark:text-gray-100 "> <div className="p-6 md:p-3 bg-[var(--color-surface-alt)] max-w-5xl mr-auto overflow-y-auto max-h-[calc(100vh-200px)] text-[var(--color-fg)]">
<h2 className="text-sm md:text-md font-bold mb-2"> <h2 className="text-sm md:text-md font-bold mb-2">
Allgemeine Einstellungen Allgemeine Einstellungen
</h2> </h2>
@@ -74,25 +77,18 @@ const GeneralSettings: React.FC = () => {
<label className="block text-xs md:text-sm font-medium">Name:</label> <label className="block text-xs md:text-sm font-medium">Name:</label>
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className={inputCls}
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
/> />
</div> </div>
{/* MAC Adresse */} {/* MAC Adresse */}
<div> <div>
<label className="block text-xs md:text-sm font-medium"> <label className="block text-xs md:text-sm font-medium">
MAC Adresse 1: MAC Adresse 1:
</label> </label>
<input <input type="text" className={inputCls} value={mac1} disabled />
type="text"
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
value={mac1}
disabled
/>
</div> </div>
{/* Systemzeit */} {/* Systemzeit */}
<div className="col-span-2"> <div className="col-span-2">
<label className="block text-xs md:text-sm font-medium mb-1"> <label className="block text-xs md:text-sm font-medium mb-1">
@@ -101,13 +97,13 @@ const GeneralSettings: React.FC = () => {
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className={inputCls}
value={systemUhr.replace(/\s*Uhr$/, "")} value={systemUhr.replace(/\s*Uhr$/, "")}
disabled disabled
/> />
<button <button
type="button" type="button"
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap" className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
onClick={() => handleSetDateTime()} onClick={() => handleSetDateTime()}
> >
Systemzeit übernehmen Systemzeit übernehmen
@@ -120,7 +116,7 @@ const GeneralSettings: React.FC = () => {
<label className="block text-xs md:text-sm font-medium">IP:</label> <label className="block text-xs md:text-sm font-medium">IP:</label>
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className={inputCls}
value={ip} value={ip}
onChange={(e) => setIp(e.target.value)} onChange={(e) => setIp(e.target.value)}
/> />
@@ -131,7 +127,7 @@ const GeneralSettings: React.FC = () => {
</label> </label>
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className={inputCls}
value={subnet} value={subnet}
onChange={(e) => setSubnet(e.target.value)} onChange={(e) => setSubnet(e.target.value)}
/> />
@@ -142,7 +138,7 @@ const GeneralSettings: React.FC = () => {
</label> </label>
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className={inputCls}
value={gateway} value={gateway}
onChange={(e) => setGateway(e.target.value)} onChange={(e) => setGateway(e.target.value)}
/> />
@@ -152,7 +148,7 @@ const GeneralSettings: React.FC = () => {
<div className="col-span-2 flex flex-wrap md:justify-between gap-1 mt-2"> <div className="col-span-2 flex flex-wrap md:justify-between gap-1 mt-2">
<button <button
type="button" type="button"
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap" className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
onClick={() => handleReboot()} onClick={() => handleReboot()}
> >
Neustart CPL Neustart CPL
@@ -160,7 +156,7 @@ const GeneralSettings: React.FC = () => {
{isAdminLoggedIn && ( {isAdminLoggedIn && (
<button <button
type="button" type="button"
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap" className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
onClick={() => { onClick={() => {
const confirmed = window.confirm( const confirmed = window.confirm(
"⚠️ Wollen Sie wirklich ein Firmwareupdate für alle KÜ-Module starten?" "⚠️ Wollen Sie wirklich ein Firmwareupdate für alle KÜ-Module starten?"

View File

@@ -27,7 +27,7 @@ const NTPSettings: React.FC = () => {
} }
return ( return (
<div className="p-6 md:p-3 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto text-gray-900 dark:text-gray-100"> <div className="p-6 md:p-3 bg-[var(--color-surface-alt)] max-w-5xl mr-auto text-[var(--color-fg)]">
<h2 className="text-sm md:text-md font-bold mb-4">NTP Einstellungen</h2> <h2 className="text-sm md:text-md font-bold mb-4">NTP Einstellungen</h2>
<div className="grid md:grid-cols-2 gap-3"> <div className="grid md:grid-cols-2 gap-3">
@@ -35,7 +35,7 @@ const NTPSettings: React.FC = () => {
<label className="block text-xs font-medium">NTP Server 1</label> <label className="block text-xs font-medium">NTP Server 1</label>
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
value={ntp1} value={ntp1}
onChange={(e) => setNtp1(e.target.value)} onChange={(e) => setNtp1(e.target.value)}
/> />
@@ -45,7 +45,7 @@ const NTPSettings: React.FC = () => {
<label className="block text-xs font-medium">NTP Server 2</label> <label className="block text-xs font-medium">NTP Server 2</label>
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
value={ntp2} value={ntp2}
onChange={(e) => setNtp2(e.target.value)} onChange={(e) => setNtp2(e.target.value)}
/> />
@@ -55,7 +55,7 @@ const NTPSettings: React.FC = () => {
<label className="block text-xs font-medium">NTP Server 3</label> <label className="block text-xs font-medium">NTP Server 3</label>
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
value={ntp3} value={ntp3}
onChange={(e) => setNtp3(e.target.value)} onChange={(e) => setNtp3(e.target.value)}
/> />
@@ -65,7 +65,7 @@ const NTPSettings: React.FC = () => {
<label className="block text-xs font-medium">Zeitzone</label> <label className="block text-xs font-medium">Zeitzone</label>
<input <input
type="text" type="text"
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
value={ntpTimezone} value={ntpTimezone}
onChange={(e) => setNtpTimezone(e.target.value)} onChange={(e) => setNtpTimezone(e.target.value)}
/> />

View File

@@ -21,7 +21,7 @@ export default function OPCUAInterfaceSettings() {
); );
return ( return (
<div className="p-6 md:p-3 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto text-gray-900 dark:text-gray-100 "> <div className="p-6 md:p-3 bg-[var(--color-surface-alt)] max-w-5xl mr-auto text-[var(--color-fg)]">
<div className="flex justify-between items-center mb-3"> <div className="flex justify-between items-center mb-3">
<Image <Image
src="/images/OPCUA.jpg" src="/images/OPCUA.jpg"
@@ -44,9 +44,11 @@ export default function OPCUAInterfaceSettings() {
<label className="mr-3 font-medium text-sm">Server Status:</label> <label className="mr-3 font-medium text-sm">Server Status:</label>
<button <button
onClick={() => dispatch(toggleOpcUaServer())} onClick={() => dispatch(toggleOpcUaServer())}
className={`px-3 py-1 rounded text-sm ${ className={`px-3 py-1 rounded text-sm font-medium transition-colors text-white ${
opcuaSettings.isEnabled ? "bg-littwin-blue" : "bg-gray-300" opcuaSettings.isEnabled
} text-white`} ? "bg-accent hover:brightness-110"
: "bg-[var(--color-muted)] hover:bg-[var(--color-fg)]/20"
}`}
> >
{opcuaSettings.isEnabled ? "Aktiviert" : "Deaktiviert"} {opcuaSettings.isEnabled ? "Aktiviert" : "Deaktiviert"}
</button> </button>
@@ -62,7 +64,7 @@ export default function OPCUAInterfaceSettings() {
<select <select
value={opcuaSettings.encryption} value={opcuaSettings.encryption}
onChange={(e) => dispatch(setOpcUaEncryption(e.target.value))} onChange={(e) => dispatch(setOpcUaEncryption(e.target.value))}
className="w-full p-1 border border-gray-300 rounded-md text-sm" className="w-full p-1 border border-base rounded-md text-sm bg-[var(--color-surface)] text-[var(--color-fg)]"
> >
<option value="None">Keine</option> <option value="None">Keine</option>
<option value="Basic256">Basic256</option> <option value="Basic256">Basic256</option>
@@ -74,7 +76,7 @@ export default function OPCUAInterfaceSettings() {
{/* ✅ OPCUA Zustand */} {/* ✅ OPCUA Zustand */}
<div className="mb-3"> <div className="mb-3">
<label className="block font-medium text-sm mb-1">OPCUA Zustand</label> <label className="block font-medium text-sm mb-1">OPCUA Zustand</label>
<div className="p-1 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-sm text-gray-900 dark:text-gray-100"> <div className="p-1 border border-base rounded-md bg-[var(--color-surface)] text-sm text-[var(--color-fg)]">
{opcuaSettings.opcUaZustand} {opcuaSettings.opcUaZustand}
</div> </div>
</div> </div>
@@ -85,7 +87,7 @@ export default function OPCUAInterfaceSettings() {
<div className="flex"> <div className="flex">
<input <input
type="text" type="text"
className="flex-grow p-1 border border-gray-300 dark:border-gray-700 rounded-l-md text-sm !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className="flex-grow p-1 border border-base rounded-l-md text-sm bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
value={nodesetName} value={nodesetName}
onChange={(e) => setNodesetName(e.target.value)} onChange={(e) => setNodesetName(e.target.value)}
disabled={opcuaSettings.isEnabled} // Disable input when server is enabled disabled={opcuaSettings.isEnabled} // Disable input when server is enabled
@@ -106,7 +108,7 @@ export default function OPCUAInterfaceSettings() {
<label className="block font-medium text-sm mb-1"> <label className="block font-medium text-sm mb-1">
Aktuelle OPC-Clients Aktuelle OPC-Clients
</label> </label>
<div className="p-1 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-sm text-gray-900 dark:text-gray-100"> <div className="p-1 border border-base rounded-md bg-[var(--color-surface)] text-sm text-[var(--color-fg)]">
{opcUaActiveClientCount} {opcUaActiveClientCount}
</div> </div>
</div> </div>
@@ -120,12 +122,12 @@ export default function OPCUAInterfaceSettings() {
{opcuaSettings.users.map((user) => ( {opcuaSettings.users.map((user) => (
<li <li
key={user.id} key={user.id}
className="p-1 bg-white shadow-sm rounded-md flex justify-between items-center text-sm" className="p-1 bg-[var(--color-surface)] border border-base rounded-md flex justify-between items-center text-sm text-[var(--color-fg)]"
> >
<span className="font-medium">{user.username}</span> <span className="font-medium">{user.username}</span>
<button <button
onClick={() => dispatch(removeOpcUaUser(user.id))} onClick={() => dispatch(removeOpcUaUser(user.id))}
className="text-red-500" className="text-danger hover:underline"
> >
Löschen Löschen
</button> </button>
@@ -140,18 +142,18 @@ export default function OPCUAInterfaceSettings() {
placeholder="Benutzername" placeholder="Benutzername"
value={newUsername} value={newUsername}
onChange={(e) => setNewUsername(e.target.value)} onChange={(e) => setNewUsername(e.target.value)}
className="p-1 border rounded flex-grow text-sm" className="p-1 border border-base rounded flex-grow text-sm bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] focus:outline-none focus:border-accent"
/> />
<input <input
type="password" type="password"
placeholder="Passwort" placeholder="Passwort"
value={newPassword} value={newPassword}
onChange={(e) => setNewPassword(e.target.value)} onChange={(e) => setNewPassword(e.target.value)}
className="p-1 border rounded flex-grow text-sm" className="p-1 border border-base rounded flex-grow text-sm bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] focus:outline-none focus:border-accent"
/> />
<button <button
onClick={handleAddUser} onClick={handleAddUser}
className="bg-littwin-blue text-white p-1 rounded text-sm" className="btn-primary p-1 rounded text-sm"
> >
Hinzufügen Hinzufügen
</button> </button>

View File

@@ -44,7 +44,7 @@ const UserManagementSettings: React.FC = () => {
}; };
return ( return (
<div className="p-6 md:p-3 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto text-gray-900 dark:text-gray-100"> <div className="p-6 md:p-3 bg-[var(--color-surface-alt)] max-w-5xl mr-auto text-[var(--color-fg)]">
<h2 className="text-sm md:text-md font-bold mb-4">Login Admin-Bereich</h2> <h2 className="text-sm md:text-md font-bold mb-4">Login Admin-Bereich</h2>
{/* Admin Login/Logout */} {/* Admin Login/Logout */}
@@ -52,7 +52,7 @@ const UserManagementSettings: React.FC = () => {
{isAdminLoggedIn ? ( {isAdminLoggedIn ? (
<button <button
type="button" type="button"
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap" className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
onClick={() => { onClick={() => {
try { try {
localStorage.removeItem("adminLoginTime"); localStorage.removeItem("adminLoginTime");
@@ -70,7 +70,7 @@ const UserManagementSettings: React.FC = () => {
<input <input
type="text" type="text"
placeholder="Benutzername" placeholder="Benutzername"
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
@@ -78,14 +78,14 @@ const UserManagementSettings: React.FC = () => {
<input <input
type="password" type="password"
placeholder="Passwort" placeholder="Passwort"
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500" className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
/> />
<button <button
type="button" type="button"
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap" className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
onClick={handleLogin} onClick={handleLogin}
> >
Admin anmelden Admin anmelden
@@ -96,9 +96,9 @@ const UserManagementSettings: React.FC = () => {
</div> </div>
{loginSuccess && ( {loginSuccess && (
<p className="text-green-600 text-xs mt-2">Login erfolgreich!</p> <p className="text-success text-xs mt-2">Login erfolgreich!</p>
)} )}
{error && <p className="text-red-500 text-xs mt-2">{error}</p>} {error && <p className="text-danger text-xs mt-2">{error}</p>}
{/* {/*
// Benutzerverwaltungstabelle (kommt später) // Benutzerverwaltungstabelle (kommt später)

View File

@@ -13,7 +13,7 @@ const ProgressModal: React.FC<Props> = ({ visible, progress, slot }) => {
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 "> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 ">
<div className="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 p-6 rounded shadow-md text-center w-80"> <div className="p-6 rounded shadow-sm text-center w-80 bg-[var(--color-surface)] dark:bg-[var(--color-surface)] text-[var(--color-fg)] border border-[var(--color-border)]">
{/* {/*
<h2 className="text-lg font-bold mb-4"> <h2 className="text-lg font-bold mb-4">
Firmwareupdate Firmwareupdate
@@ -26,9 +26,9 @@ const ProgressModal: React.FC<Props> = ({ visible, progress, slot }) => {
</h2> </h2>
Bitte Fenster nicht schließen Bitte Fenster nicht schließen
<h2></h2> <h2></h2>
<div className="w-full bg-gray-200 rounded-full h-4"> <div className="w-full h-4 rounded-full bg-[var(--color-surface-alt)]/80 dark:bg-[var(--color-surface-alt)]/30 border border-[var(--color-border)] overflow-hidden">
<div <div
className="bg-littwin-blue h-4 rounded-full transition-all duration-100" className="h-full rounded-full transition-all duration-200 bg-[var(--color-accent)]"
style={{ width: `${progress}%` }} style={{ width: `${progress}%` }}
></div> ></div>
</div> </div>

View File

@@ -454,7 +454,7 @@ export const DetailModal = ({
<div className="absolute top-0 right-0 flex gap-3"> <div className="absolute top-0 right-0 flex gap-3">
<button <button
onClick={toggleFullScreen} onClick={toggleFullScreen}
className="text-2xl text-gray-600 hover:text-gray-800" className="text-2xl text-[var(--color-fg-muted)] hover:text-[var(--color-fg)] transition"
> >
<i <i
className={ className={
@@ -467,7 +467,7 @@ export const DetailModal = ({
<button <button
onClick={handleClose} onClick={handleClose}
className="text-2xl text-gray-600 hover:text-gray-800" className="text-2xl text-[var(--color-fg-muted)] hover:text-[var(--color-danger)] transition"
> >
<i className="bi bi-x-circle-fill"></i> <i className="bi bi-x-circle-fill"></i>
</button> </button>
@@ -481,7 +481,7 @@ export const DetailModal = ({
isLoading={isLoading} isLoading={isLoading}
/> />
<div className="h-[85%] bg-white dark:bg-gray-800 rounded shadow border border-gray-200 dark:border-gray-700 p-2"> <div className="h-[85%] rounded shadow border p-2 bg-[var(--color-surface)] border-[var(--color-border)]">
<Line ref={chartRef} data={chartData} options={chartOptions} /> <Line ref={chartRef} data={chartData} options={chartOptions} />
</div> </div>
</div> </div>

View File

@@ -16,7 +16,7 @@ export const SystemOverviewGrid = ({ voltages, onOpenDetail }: Props) => {
return ( return (
<div <div
key={key} key={key}
className="p-4 border rounded shadow bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-900 dark:text-gray-100" className="p-4 border rounded shadow-sm bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border-[var(--color-border)] text-[var(--color-fg)] hover:bg-[var(--color-surface-alt)]/60 dark:hover:bg-[var(--color-surface-alt)]/30 transition"
> >
<h2 className="font-semibold">{key}</h2> <h2 className="font-semibold">{key}</h2>
<p> <p>

View File

@@ -71,8 +71,8 @@ const SystemPage = () => {
}; };
return ( return (
<div className="p-4 bg-white dark:bg-gray-900"> <div className="p-4 bg-[var(--color-background)] text-[var(--color-fg)]">
<h1 className="text-xl font-bold mb-4"> <h1 className="text-xl font-bold mb-4 tracking-wide">
System Spannungen & Temperaturen System Spannungen & Temperaturen
</h1> </h1>
@@ -80,7 +80,7 @@ const SystemPage = () => {
<div className="flex justify-center items-center h-[400px]"> <div className="flex justify-center items-center h-[400px]">
<div className="text-center"> <div className="text-center">
<ClipLoader size={50} color="#3B82F6" /> <ClipLoader size={50} color="#3B82F6" />
<p className="mt-4 text-gray-500"> <p className="mt-4 text-[var(--color-fg-muted)]">
Lade Systemdaten bitte warten Lade Systemdaten bitte warten
</p> </p>
</div> </div>

View File

@@ -36,12 +36,12 @@ const Navigation: React.FC<NavigationProps> = ({ className }) => {
]; ];
return ( return (
<aside className="bg-white dark:bg-gray-900 h-full"> <aside className="h-full bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border-r border-[var(--color-border)]">
<nav className={`h-full flex-shrink-0 mt-16 ${className || "w-48"}`}> <nav className={`h-full flex-shrink-0 mt-16 ${className || "w-48"}`}>
{menuItems.map((item) => ( {menuItems.map((item) => (
<div key={item.name}> <div key={item.name}>
{item.disabled ? ( {item.disabled ? (
<div className="block px-4 py-2 mb-4 font-bold whitespace-nowrap text-gray-400 dark:text-gray-600 cursor-not-allowed text-[1rem] sm:text-[1rem] md:text-[1rem] lg:text-[1rem] xl:text-sm 2xl:text-lg"> <div className="block px-4 py-2 mb-4 font-bold whitespace-nowrap text-[var(--color-fg-muted)] opacity-60 cursor-not-allowed text-[1rem] sm:text-[1rem] md:text-[1rem] lg:text-[1rem] xl:text-sm 2xl:text-lg">
{item.name} {item.name}
</div> </div>
) : ( ) : (
@@ -49,11 +49,13 @@ const Navigation: React.FC<NavigationProps> = ({ className }) => {
href={formatPath(item.path)} href={formatPath(item.path)}
prefetch={false} prefetch={false}
onClick={() => setActiveLink(item.path)} onClick={() => setActiveLink(item.path)}
className={`block px-4 py-2 mb-4 font-bold whitespace-nowrap transition duration-300 text-[1rem] sm:text-[1rem] md:text-[1rem] lg:text-[1rem] xl:text-sm 2xl:text-lg ${ className={`block px-4 py-2 mb-4 font-semibold whitespace-nowrap transition duration-200 rounded-r-full pr-6 relative text-[1rem] sm:text-[1rem] md:text-[1rem] lg:text-[1rem] xl:text-sm 2xl:text-lg
${
activeLink.startsWith(item.path) activeLink.startsWith(item.path)
? "bg-sky-500 text-white rounded-r-full xl:mr-4 xl:w-full dark:bg-sky-600 dark:text-white" ? "bg-[var(--color-accent)] text-white shadow-sm xl:mr-4 xl:w-full"
: "text-black hover:bg-gray-200 rounded-r-full dark:text-gray-200 dark:hover:bg-gray-800" : "text-[var(--color-fg-muted)] hover:text-[var(--color-fg)] hover:bg-[var(--color-surface-alt)]/80 dark:hover:bg-[var(--color-surface-alt)]/40"
}`} }
`}
> >
{item.name} {item.name}
</Link> </Link>

View File

@@ -2,7 +2,7 @@
"win_da_state": [ "win_da_state": [
1, 1,
1, 1,
1, 0,
1 1
], ],
"win_da_bezeichnung": [ "win_da_bezeichnung": [

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.869", "version": "1.6.879",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.869", "version": "1.6.879",
"dependencies": { "dependencies": {
"@fontsource/roboto": "^5.1.0", "@fontsource/roboto": "^5.1.0",
"@headlessui/react": "^2.2.4", "@headlessui/react": "^2.2.4",

View File

@@ -1,6 +1,6 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.869", "version": "1.6.879",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3000", "dev": "next dev -p 3000",

View File

@@ -148,11 +148,11 @@ function AppContent({
}, [pathname, dispatch]); }, [pathname, dispatch]);
return ( return (
<div className="flex flex-col h-screen overflow-hidden bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100"> <div className="flex flex-col h-screen overflow-hidden bg-[var(--color-background)] text-[var(--color-fg)]">
<Header /> <Header />
<div className="flex flex-grow w-full"> <div className="flex flex-grow w-full">
<Navigation className="w-56" /> <Navigation className="w-56" />
<main className="w-full flex-grow bg-white dark:bg-gray-900"> <main className="w-full flex-grow bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border-l border-[var(--color-border)]">
{sessionExpired && ( {sessionExpired && (
<div className="bg-red-500 text-white p-4 text-center"> <div className="bg-red-500 text-white p-4 text-center">
Ihre Sitzung ist abgelaufen oder die Verbindung ist Ihre Sitzung ist abgelaufen oder die Verbindung ist

View File

@@ -8,6 +8,13 @@ export default function Document() {
<link rel="icon" href="/favicon.png" type="image/png" /> <link rel="icon" href="/favicon.png" type="image/png" />
</Head> </Head>
<body> <body>
{/* Theme init (executed before React hydration) */}
<script
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `(() => { try { const ls = localStorage.getItem('theme'); const mql = window.matchMedia('(prefers-color-scheme: dark)'); const wantDark = ls === 'dark' || (!ls && mql.matches); if (wantDark) document.documentElement.classList.add('dark'); } catch(e) {} })();`,
}}
/>
<Main /> {/* Hier wird der Seiteninhalt eingebettet */} <Main /> {/* Hier wird der Seiteninhalt eingebettet */}
<NextScript /> {/* Fügt Next.js-Skripte für die Seite hinzu */} <NextScript /> {/* Fügt Next.js-Skripte für die Seite hinzu */}
</body> </body>

View File

@@ -3,45 +3,255 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
/* Light theme tokens */
:root { :root {
--background: #ffffff; --background: #f1f5f9; /* slate-100 */
--foreground: #171717; --foreground: #0f172a; /* slate-900 */
--littwin-blue: #00aeef; --littwin-blue: #00aeef;
--color-background: #f1f5f9;
--color-surface: #ffffff;
--color-surface-alt: #f8fafc; /* slate-50 */
--color-border: #e2e8f0; /* slate-200 */
--color-fg: #0f172a; /* slate-900 */
--color-fg-muted: #475569; /* slate-600 */
/* Alias for convenience (used in components) */
--color-muted: var(--color-fg-muted);
--color-accent: #00aeef;
--color-accent-soft: #e0f7ff;
--color-danger: #dc2626; /* red-600 */
--color-warning: #f59e0b; /* amber-500 */
--color-success: #16a34a; /* green-600 */
--color-info: #0ea5e9; /* sky-500 */
--color-ring: #38bdf8; /* sky-400 */
--color-input-bg: #ffffff;
--color-input-border: #cbd5e1; /* slate-300 */
} }
@media (prefers-color-scheme: dark) { /* Dark theme overrides (activated via .dark class on <html>) */
:root { .dark {
--background: #0a0a0a; --background: #0f1115;
--foreground: #ededed; --foreground: #e2e8f0;
} --color-background: #0f1115; /* near slate-950 */
--color-surface: #1c232d; /* custom dark surface */
--color-surface-alt: #243140; /* slightly elevated */
--color-border: #334155; /* slate-600 */
--color-fg: #e2e8f0; /* slate-200 */
--color-fg-muted: #94a3b8; /* slate-400 */
--color-muted: var(--color-fg-muted);
--color-accent: #00aeef;
--color-accent-soft: #06394d; /* soft accent background */
--color-danger: #ef4444; /* red-500 */
--color-warning: #fbbf24; /* amber-400 */
--color-success: #22c55e; /* green-500 */
--color-info: #38bdf8; /* sky-400 */
--color-ring: #0ea5e9;
--color-input-bg: #1c232d;
--color-input-border: #475569;
} }
body { body {
color: var(--foreground); color: var(--color-fg);
background: var(--background); background: var(--color-background);
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
accent-color: var(--color-accent);
}
/* Smooth theme transitions (respect reduced motion) */
@media (prefers-reduced-motion: no-preference) {
html,
body {
transition: background-color 0.25s ease, color 0.25s ease;
}
.theme-transition {
transition: background-color 0.25s ease, color 0.25s ease,
border-color 0.25s ease, box-shadow 0.25s ease;
}
} }
@layer utilities { @layer utilities {
.text-balance { .text-balance {
text-wrap: balance; text-wrap: balance;
} }
} /* Semantic shortcut utilities */
.bg-background {
background-color: var(--color-background);
}
.bg-surface {
background-color: var(--color-surface);
}
.bg-surface-alt {
background-color: var(--color-surface-alt);
}
.text-fg {
color: var(--color-fg);
}
.text-fg-muted {
color: var(--color-fg-muted);
}
.text-muted {
color: var(--color-muted);
}
.border-base {
border-color: var(--color-border);
}
.ring-accent {
--tw-ring-color: var(--color-ring);
}
.bg-accent {
background-color: var(--color-accent);
}
.bg-accent-soft {
background-color: var(--color-accent-soft);
}
.text-success {
color: var(--color-success);
}
.text-danger {
color: var(--color-danger);
}
@media (prefers-color-scheme: dark) { /* Component abstractions (no @apply to avoid processing issues in global layer) */
input { .card {
background-color: #333 !important; /* Dunkler Hintergrund im Darkmode */ background: var(--color-surface);
color: #fff !important; /* Weißer Text im Darkmode */ color: var(--color-fg);
border: 1px solid #555; /* Optional: etwas hellerer Rahmen */ border: 1px solid var(--color-border);
border-radius: 0.375rem; /* rounded-md */
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.card-elevated {
background: var(--color-surface);
color: var(--color-fg);
border: 1px solid var(--color-border);
border-radius: 0.375rem;
box-shadow: 0 4px 10px -2px rgba(0, 0, 0, 0.25),
0 2px 4px rgba(0, 0, 0, 0.15);
}
.table-surface {
background: var(--color-surface);
color: var(--color-fg);
border: 1px solid var(--color-border);
border-radius: 0.375rem;
overflow: hidden;
}
.table-head {
background: var(--color-surface-alt);
color: var(--color-fg);
font-weight: 500;
border-bottom: 1px solid var(--color-border);
}
.table-row-hover:hover {
background-color: color-mix(
in srgb,
var(--color-surface-alt) 85%,
transparent
);
}
.btn-accent {
background: var(--color-accent);
color: #fff;
border-radius: 0.375rem;
padding: 0.5rem 1rem;
font-weight: 500;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
transition: filter 0.2s ease, box-shadow 0.2s ease;
}
.btn-accent:hover {
filter: brightness(1.1);
}
.btn-accent:focus {
outline: 2px solid var(--color-ring);
outline-offset: 2px;
}
/* Button Variants */
.btn-primary {
@media (prefers-reduced-motion: no-preference) {
transition: background-color 0.2s ease, filter 0.2s ease;
}
background: var(--color-accent);
color: #fff;
border-radius: 0.375rem;
font-weight: 500;
padding: 0.5rem 1rem;
}
.btn-primary:hover {
filter: brightness(1.1);
}
.btn-primary:focus {
outline: 2px solid var(--color-ring);
outline-offset: 2px;
}
.btn-muted {
background: var(--color-muted);
color: var(--color-fg);
border-radius: 0.375rem;
font-weight: 500;
padding: 0.5rem 1rem;
}
.btn-muted:hover {
background: var(--color-fg);
color: var(--color-background);
}
.btn-outline {
background: transparent;
color: var(--color-fg);
border: 1px solid var(--color-border);
border-radius: 0.375rem;
font-weight: 500;
padding: 0.5rem 1rem;
}
.btn-outline:hover {
background: var(--color-surface-alt);
}
.btn-danger {
background: var(--color-danger);
color: #fff;
border-radius: 0.375rem;
font-weight: 500;
padding: 0.5rem 1rem;
}
.btn-danger:hover {
filter: brightness(1.1);
}
.input-base {
background: var(--color-input-bg);
color: var(--color-fg);
border: 1px solid var(--color-input-border);
border-radius: 0.375rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.input-base:focus {
border-color: var(--color-ring);
outline: 1px solid var(--color-ring);
outline-offset: 2px;
}
.input-base:disabled {
opacity: 0.6;
cursor: not-allowed;
} }
} }
@media (prefers-color-scheme: light) { /* Form elements use tokens */
input { input,
background-color: white !important; /* Heller Hintergrund im Lightmode */ select,
color: black !important; /* Schwarzer Text im Lightmode */ textarea {
border: 1px solid #ccc; /* Optional: heller Rahmen */ background-color: var(--color-input-bg) !important;
} color: var(--color-fg) !important;
border: 1px solid var(--color-input-border);
}
input::placeholder,
textarea::placeholder {
color: var(--color-muted);
opacity: 1; /* ensure consistent visibility */
}
input:focus,
select:focus,
textarea:focus {
outline: 2px solid var(--color-ring);
outline-offset: 2px;
border-color: var(--color-ring);
} }
.react-datepicker__day--selected, .react-datepicker__day--selected,

View File

@@ -1,6 +1,7 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: "class", // Ermöglicht zusätzlich zu .dark auch [data-theme="dark"] als Schalter
darkMode: ["class", '[data-theme="dark"]'],
content: [ content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}", "./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}",