Compare commits
10 Commits
a596422056
...
001b237dd7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
001b237dd7 | ||
|
|
af21b180f1 | ||
|
|
fefff9419d | ||
|
|
27c60c6742 | ||
|
|
c8ec763aac | ||
|
|
d163df0d96 | ||
|
|
12d3a17f60 | ||
|
|
f3339ccafd | ||
|
|
fab8a02ce9 | ||
|
|
eb0585072d |
@@ -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.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)
|
||||
|
||||
|
||||
@@ -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.869
|
||||
NEXT_PUBLIC_APP_VERSION=1.6.879
|
||||
NEXT_PUBLIC_CPL_MODE=production
|
||||
50
CHANGELOG.md
50
CHANGELOG.md
@@ -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
|
||||
|
||||
- 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”
|
||||
|
||||
@@ -55,45 +55,57 @@ function Footer() {
|
||||
}, [showSlider]);
|
||||
|
||||
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="flex flex-row space-x-2">
|
||||
<div className="flex flex-row space-x-2 items-center">
|
||||
<Icon
|
||||
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 className="flex flex-row space-x-2">
|
||||
<Icon icon="charm:phone" className="text-xl text-blue-400" />
|
||||
<p className="text-sm">Telefon: 04402 972577-0</p>
|
||||
<div className="flex flex-row space-x-2 items-center">
|
||||
<Icon icon="charm:phone" className="text-xl text-accent" />
|
||||
<p className="text-sm text-fg-muted">Telefon: 04402 972577-0</p>
|
||||
</div>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Icon icon="mdi:email-outline" className="text-xl text-blue-400" />
|
||||
<p className="text-sm">kontakt@littwin-systemtechnik.de</p>
|
||||
<div className="flex flex-row space-x-2 items-center">
|
||||
<Icon icon="mdi:email-outline" className="text-xl text-accent" />
|
||||
<p className="text-sm text-fg-muted">
|
||||
kontakt@littwin-systemtechnik.de
|
||||
</p>
|
||||
</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)}
|
||||
>
|
||||
<Icon icon="bi:book" className="text-xl text-blue-400" />
|
||||
<p className="text-sm">Handbücher</p>
|
||||
<Icon
|
||||
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
|
||||
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"
|
||||
}`}
|
||||
>
|
||||
<div className="p-4 flex justify-between items-center border-b">
|
||||
<h3 className="text-lg font-semibold">PDF Handbücher</h3>
|
||||
<div className="p-4 flex justify-between items-center border-b border-base">
|
||||
<h3 className="text-base font-semibold text-[var(--color-fg)]">
|
||||
PDF Handbücher
|
||||
</h3>
|
||||
<button
|
||||
className="text-gray-500 hover:text-gray-800"
|
||||
className="text-[var(--color-muted)] hover:text-[var(--color-fg)] transition"
|
||||
onClick={() => setShowSlider(false)}
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<Icon icon="carbon:close" className="text-2xl" />
|
||||
<Icon icon="carbon:close" className="text-xl" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -102,7 +114,7 @@ function Footer() {
|
||||
{pdfFiles.map((fileName) => (
|
||||
<li
|
||||
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)}
|
||||
>
|
||||
{fileName}
|
||||
|
||||
@@ -109,21 +109,43 @@ function Header() {
|
||||
}, [deviceName, dispatch]);
|
||||
//----------------------------------------------------------------
|
||||
|
||||
// Dark/Light Mode Toggle
|
||||
// Dark/Light Mode Toggle (persisted)
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
// Initial state from html class / localStorage (set by _document script before hydration)
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const html = document.documentElement;
|
||||
if (isDark) {
|
||||
html.classList.add("dark");
|
||||
} else {
|
||||
html.classList.remove("dark");
|
||||
}
|
||||
if (typeof window === "undefined") return;
|
||||
const html = document.documentElement;
|
||||
const stored = localStorage.getItem("theme");
|
||||
const active = stored ? stored === "dark" : html.classList.contains("dark");
|
||||
setIsDark(active);
|
||||
}, []);
|
||||
|
||||
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]);
|
||||
|
||||
// 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 (
|
||||
<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
|
||||
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:
|
||||
@@ -154,10 +176,10 @@ function Header() {
|
||||
priority
|
||||
/>
|
||||
<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
|
||||
</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}
|
||||
</p>
|
||||
</div>
|
||||
@@ -169,7 +191,7 @@ function Header() {
|
||||
<button
|
||||
aria-label={isDark ? "Light Mode" : "Dark Mode"}
|
||||
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"}
|
||||
>
|
||||
{isDark ? (
|
||||
@@ -192,7 +214,7 @@ function Header() {
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
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
|
||||
</button>
|
||||
@@ -202,7 +224,7 @@ function Header() {
|
||||
|
||||
{/* Warnhinweis, wenn der Admin angemeldet ist */}
|
||||
{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
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -37,8 +37,8 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
||||
|
||||
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 laptop:p-1 xl:p-1 ${
|
||||
loading ? "cursor-wait" : ""
|
||||
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 opacity-70" : ""
|
||||
}`}
|
||||
>
|
||||
<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" : ""
|
||||
}`}
|
||||
>
|
||||
<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>
|
||||
<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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</th>
|
||||
</tr>
|
||||
@@ -92,12 +92,12 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
||||
loading
|
||||
? "cursor-wait"
|
||||
: analogInput.id === activeId
|
||||
? "bg-blue-100 dark:bg-gray-700 dark:text-white"
|
||||
: "hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
? "bg-[var(--color-accent-soft)] dark:bg-[var(--color-surface-alt)]/60 text-[var(--color-fg)]"
|
||||
: "hover:bg-[var(--color-surface-alt)]/70 dark:hover:bg-[var(--color-surface-alt)]/30"
|
||||
}`}
|
||||
>
|
||||
<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)}
|
||||
>
|
||||
<div className="flex items-center gap-1 ">
|
||||
@@ -109,7 +109,7 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
||||
</div>
|
||||
</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)}
|
||||
>
|
||||
{typeof analogInput.value === "number"
|
||||
@@ -118,19 +118,19 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
||||
</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)}
|
||||
>
|
||||
{analogInput.unit || "-"}
|
||||
</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)}
|
||||
>
|
||||
{analogInput.label || "----"}
|
||||
</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
|
||||
onClick={() => {
|
||||
handleSelect(analogInput.id!, analogInput);
|
||||
@@ -141,7 +141,7 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
||||
<Icon icon={settingsIcon} className="text-xl" />
|
||||
</button>
|
||||
</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
|
||||
onClick={() => {
|
||||
handleSelect(analogInput.id!, analogInput);
|
||||
|
||||
@@ -36,8 +36,8 @@ function AnalogInputsView() {
|
||||
}`}
|
||||
>
|
||||
<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">
|
||||
<h2 className="text-xl font-semibold mb-4 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-[var(--color-fg)] tracking-wide">
|
||||
Messwerteingänge
|
||||
</h2>
|
||||
<AnalogInputsTable loading={loading} />
|
||||
|
||||
@@ -43,7 +43,7 @@ const Baugruppentraeger: React.FC = () => {
|
||||
baugruppen.push(
|
||||
<div
|
||||
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">
|
||||
{slots.map((version, index) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ const DashboardView: React.FC = () => {
|
||||
}, [dispatch]);
|
||||
//-------------------------------------
|
||||
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 */}
|
||||
<div className="flex justify-between items-center w-full lg:w-2/3">
|
||||
<div className="flex justify-between gap-1">
|
||||
@@ -33,7 +33,7 @@ const DashboardView: React.FC = () => {
|
||||
icon="ri:calendar-schedule-line"
|
||||
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
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -48,22 +48,22 @@ export default function Last20MessagesTable({ className }: Props) {
|
||||
return (
|
||||
<div className={`flex flex-col gap-3 p-4 ${className}`}>
|
||||
<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">
|
||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 text-left sticky top-0 z-10">
|
||||
<table className="min-w-full border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||
<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>
|
||||
<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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</th>
|
||||
</tr>
|
||||
@@ -72,24 +72,24 @@ export default function Last20MessagesTable({ className }: Props) {
|
||||
{filteredMessages.slice(0, 20).map((msg, index) => (
|
||||
<tr
|
||||
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
|
||||
className="w-4 h-4 rounded"
|
||||
style={{ backgroundColor: msg.c }}
|
||||
></div>
|
||||
</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}
|
||||
</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}
|
||||
</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}
|
||||
</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}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -97,7 +97,7 @@ export default function Last20MessagesTable({ className }: Props) {
|
||||
</tbody>
|
||||
</table>
|
||||
{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.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -38,7 +38,7 @@ const NetworkInfo: React.FC = () => {
|
||||
|
||||
return (
|
||||
<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">
|
||||
<Image
|
||||
src="/images/IP-icon.svg"
|
||||
@@ -49,12 +49,8 @@ const NetworkInfo: React.FC = () => {
|
||||
priority
|
||||
/>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
IP-Adresse
|
||||
</p>
|
||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
{ip}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--color-fg-muted)]">IP-Adresse</p>
|
||||
<p className="text-sm font-medium text-[var(--color-fg)]">{ip}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,10 +64,8 @@ const NetworkInfo: React.FC = () => {
|
||||
priority
|
||||
/>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Subnet-Maske
|
||||
</p>
|
||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
<p className="text-xs text-[var(--color-fg-muted)]">Subnet-Maske</p>
|
||||
<p className="text-sm font-medium text-[var(--color-fg)]">
|
||||
{subnet}
|
||||
</p>
|
||||
</div>
|
||||
@@ -87,8 +81,8 @@ const NetworkInfo: React.FC = () => {
|
||||
priority
|
||||
/>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Gateway</p>
|
||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
<p className="text-xs text-[var(--color-fg-muted)]">Gateway</p>
|
||||
<p className="text-sm font-medium text-[var(--color-fg)]">
|
||||
{gateway}
|
||||
</p>
|
||||
</div>
|
||||
@@ -97,8 +91,8 @@ const NetworkInfo: React.FC = () => {
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="text-xs font-bold text-littwin-blue">OPC-UA</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Status</p>
|
||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
<p className="text-xs text-[var(--color-fg-muted)]">Status</p>
|
||||
<p className="text-sm font-medium text-[var(--color-fg)]">
|
||||
{opcUaZustand}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -4,9 +4,7 @@ import { Icon } from "@iconify/react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../redux/store";
|
||||
|
||||
type VersionInfoProps = {
|
||||
className?: string;
|
||||
};
|
||||
type VersionInfoProps = { className?: string };
|
||||
|
||||
const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
|
||||
const appVersion =
|
||||
@@ -17,26 +15,26 @@ const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
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-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">
|
||||
<div className={`card w-full p-3 laptop:p-2 ${className}`}>
|
||||
<h2 className="text-base font-semibold mb-2 text-[var(--color-fg)]">
|
||||
Versionsinformationen
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-row p-2 space-x-2">
|
||||
<Icon icon="bx:code-block" className="text-xl text-blue-400" />
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Applikationsversion: {appVersion}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row p-2 space-x-2">
|
||||
<Icon icon="mdi:web" className="text-xl text-blue-400" />
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Webversion: {webVersion}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
<li className="flex items-start gap-2">
|
||||
<Icon icon="bx:code-block" className="text-xl text-accent" />
|
||||
<p className="text-sm text-fg-muted">
|
||||
Applikationsversion:{" "}
|
||||
<span className="text-[var(--color-fg)]">{appVersion}</span>
|
||||
</p>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<Icon icon="mdi:web" className="text-xl text-accent" />
|
||||
<p className="text-sm text-fg-muted">
|
||||
Webversion:{" "}
|
||||
<span className="text-[var(--color-fg)]">{webVersion}</span>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function DigitalInputsWidget({
|
||||
//console.log("DigitalInputs", inputs);
|
||||
|
||||
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">
|
||||
<Icon
|
||||
icon={inputIcon}
|
||||
@@ -38,19 +38,19 @@ export default function DigitalInputsWidget({
|
||||
/>
|
||||
Meldungseingänge {inputRange.start + 1} – {inputRange.end}
|
||||
</h2>
|
||||
<table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-white dark:bg-gray-900">
|
||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-b">
|
||||
<table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-[var(--color-surface)]">
|
||||
<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>
|
||||
<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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</th>
|
||||
</tr>
|
||||
@@ -59,9 +59,9 @@ export default function DigitalInputsWidget({
|
||||
{inputs.map((input) => (
|
||||
<tr
|
||||
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 ">
|
||||
<Icon
|
||||
icon={loginIcon}
|
||||
@@ -70,7 +70,7 @@ export default function DigitalInputsWidget({
|
||||
{input.id}
|
||||
</div>
|
||||
</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 ? (
|
||||
<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 ">
|
||||
@@ -91,10 +91,10 @@ export default function DigitalInputsWidget({
|
||||
</div>
|
||||
)}
|
||||
</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}
|
||||
</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={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"
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function DigitalOutputsWidget({
|
||||
};
|
||||
|
||||
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">
|
||||
<Icon
|
||||
icon={outputIcon}
|
||||
@@ -74,19 +74,19 @@ export default function DigitalOutputsWidget({
|
||||
/>
|
||||
Schaltausgänge
|
||||
</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">
|
||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-b">
|
||||
<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-[var(--color-surface)] text-[var(--color-fg)] border-b border-base">
|
||||
<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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</th>
|
||||
</tr>
|
||||
@@ -95,33 +95,33 @@ export default function DigitalOutputsWidget({
|
||||
{digitalOutputs.map((output) => (
|
||||
<tr
|
||||
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={outputIcon}
|
||||
className="text-gray-600 mr-1 text-base"
|
||||
className="text-[var(--color-muted)] mr-1 text-base"
|
||||
/>
|
||||
{output.id}
|
||||
</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}
|
||||
</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={switchIcon}
|
||||
className={`cursor-pointer text-base transition ${
|
||||
output.status
|
||||
? "text-littwin-blue"
|
||||
: "text-gray-500 scale-x-[-1]"
|
||||
} dark:hover:text-littwin-blue`}
|
||||
? "text-accent"
|
||||
: "text-[var(--color-muted)] scale-x-[-1]"
|
||||
} hover:text-accent`}
|
||||
onClick={() => handleToggle(output.id)}
|
||||
/>
|
||||
</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={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)}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -119,19 +119,23 @@ function KabelueberwachungView() {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
{[1, 2, 3, 4].map((rack) => (
|
||||
<button
|
||||
key={rack}
|
||||
onClick={() => changeRack(rack)}
|
||||
className={`mr-2 ${
|
||||
Number(activeRack) === Number(rack)
|
||||
? "bg-littwin-blue text-white p-1 rounded-sm"
|
||||
: "bg-gray-300 p-1 text-sm"
|
||||
}`}
|
||||
>
|
||||
Rack {rack}
|
||||
</button>
|
||||
))}
|
||||
{[1, 2, 3, 4].map((rack) => {
|
||||
const isActive = Number(activeRack) === Number(rack);
|
||||
return (
|
||||
<button
|
||||
key={rack}
|
||||
onClick={() => changeRack(rack)}
|
||||
aria-pressed={isActive}
|
||||
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"
|
||||
: "btn-muted text-fg opacity-90 hover:opacity-100"
|
||||
}`}
|
||||
>
|
||||
Rack {rack}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</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%]">
|
||||
{(
|
||||
|
||||
@@ -276,7 +276,9 @@ const TDRChartActionBar: React.FC = () => {
|
||||
{tdrRunning && (
|
||||
<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">
|
||||
<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">
|
||||
Bitte warten…{" "}
|
||||
{Math.min(
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function SlotActivityOverlay({
|
||||
|
||||
// Durations
|
||||
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
|
||||
|
||||
if (!loopActive && !tdrActive && !comparisonActive) return null;
|
||||
|
||||
@@ -69,14 +69,20 @@ export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="p-2 flex justify-between items-center rounded-t-md">
|
||||
<h2 className="text-base font-bold">Einstellungen KÜ {slot + 1}</h2>
|
||||
<button onClick={onClose} className="text-2xl hover:text-gray-200">
|
||||
<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 text-fg">
|
||||
Einstellungen KÜ {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>
|
||||
</button>
|
||||
</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: "TDR ", key: "tdr" as const },
|
||||
@@ -86,18 +92,17 @@ export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
|
||||
<button
|
||||
key={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
|
||||
? "bg-white text-littwin-blue"
|
||||
: "text-gray-500 hover:text-black"
|
||||
? "bg-surface text-accent shadow-sm"
|
||||
: "text-fg-muted hover:text-fg"
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-white rounded-b-md h-[20rem] laptop:h-[24rem] 2xl:h-[30rem] overflow-y-auto">
|
||||
<div className="p-4 bg-surface rounded-b-md h-[20rem] laptop:h-[24rem] 2xl:h-[30rem] overflow-y-auto text-fg">
|
||||
{activeTab === "kue" && (
|
||||
<KueEinstellung
|
||||
slot={slot}
|
||||
|
||||
@@ -14,22 +14,22 @@ export default function MeldungenTabelle({
|
||||
}) {
|
||||
return (
|
||||
<div className="overflow-auto max-h-[80vh]">
|
||||
<table className="min-w-full border">
|
||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 text-left sticky top-0 z-10">
|
||||
<table className="min-w-full border border-base table-surface text-fg">
|
||||
<thead className="text-left sticky top-0 z-10 bg-surface-alt/90 backdrop-blur supports-[backdrop-filter]:bg-surface-alt/70">
|
||||
<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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</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
|
||||
</th>
|
||||
</tr>
|
||||
@@ -38,15 +38,15 @@ export default function MeldungenTabelle({
|
||||
{messages.map((msg, index) => (
|
||||
<tr
|
||||
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
|
||||
className="w-4 h-4 rounded"
|
||||
style={{ backgroundColor: msg.c }}
|
||||
></div>
|
||||
</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", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
@@ -56,13 +56,13 @@ export default function MeldungenTabelle({
|
||||
second: "2-digit",
|
||||
})}
|
||||
</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}
|
||||
</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}
|
||||
</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}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -70,7 +70,7 @@ export default function MeldungenTabelle({
|
||||
</tbody>
|
||||
</table>
|
||||
{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.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -60,14 +60,14 @@ export default function MeldungenView() {
|
||||
/>
|
||||
<button
|
||||
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
|
||||
</button>
|
||||
|
||||
<Listbox value={sourceFilter} onChange={setSourceFilter}>
|
||||
<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>
|
||||
<svg
|
||||
className="w-5 h-5 text-gray-400"
|
||||
@@ -83,19 +83,19 @@ export default function MeldungenView() {
|
||||
/>
|
||||
</svg>
|
||||
</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) => (
|
||||
<Listbox.Option
|
||||
key={src}
|
||||
value={src}
|
||||
className={({ selected, active, disabled }) =>
|
||||
`px-4 py-2 cursor-pointer text-gray-900 dark:text-gray-100 ${
|
||||
selected
|
||||
? "bg-littwin-blue text-white"
|
||||
`px-4 py-2 cursor-pointer text-[var(--color-fg)] transition-colors ${
|
||||
disabled
|
||||
? "opacity-50 text-[var(--color-muted)] cursor-not-allowed"
|
||||
: selected
|
||||
? "bg-accent text-white"
|
||||
: active
|
||||
? "bg-blue-100 dark:bg-gray-700 dark:text-white"
|
||||
: disabled
|
||||
? "opacity-50 text-gray-400 dark:text-gray-500 cursor-not-allowed"
|
||||
? "bg-[var(--color-surface-alt)]"
|
||||
: ""
|
||||
}`
|
||||
}
|
||||
|
||||
@@ -10,14 +10,14 @@ import { useAdminAuth } from "./hooks/useAdminAuth";
|
||||
const DatabaseSettings: React.FC = () => {
|
||||
const { isAdminLoggedIn } = useAdminAuth(true);
|
||||
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>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
</button>
|
||||
@@ -25,7 +25,7 @@ const DatabaseSettings: React.FC = () => {
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
</button>
|
||||
@@ -41,7 +41,7 @@ const DatabaseSettings: React.FC = () => {
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
</button>
|
||||
@@ -49,7 +49,7 @@ const DatabaseSettings: React.FC = () => {
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
</button>
|
||||
|
||||
@@ -63,8 +63,11 @@ const GeneralSettings: React.FC = () => {
|
||||
setMac1(systemSettings.mac1 || "");
|
||||
}, [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 (
|
||||
<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">
|
||||
Allgemeine Einstellungen
|
||||
</h2>
|
||||
@@ -74,25 +77,18 @@ const GeneralSettings: React.FC = () => {
|
||||
<label className="block text-xs md:text-sm font-medium">Name:</label>
|
||||
<input
|
||||
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}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* MAC Adresse */}
|
||||
<div>
|
||||
<label className="block text-xs md:text-sm font-medium">
|
||||
MAC Adresse 1:
|
||||
</label>
|
||||
<input
|
||||
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
|
||||
/>
|
||||
<input type="text" className={inputCls} value={mac1} disabled />
|
||||
</div>
|
||||
|
||||
{/* Systemzeit */}
|
||||
<div className="col-span-2">
|
||||
<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">
|
||||
<input
|
||||
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$/, "")}
|
||||
disabled
|
||||
/>
|
||||
<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()}
|
||||
>
|
||||
Systemzeit übernehmen
|
||||
@@ -120,7 +116,7 @@ const GeneralSettings: React.FC = () => {
|
||||
<label className="block text-xs md:text-sm font-medium">IP:</label>
|
||||
<input
|
||||
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}
|
||||
onChange={(e) => setIp(e.target.value)}
|
||||
/>
|
||||
@@ -131,7 +127,7 @@ const GeneralSettings: React.FC = () => {
|
||||
</label>
|
||||
<input
|
||||
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}
|
||||
onChange={(e) => setSubnet(e.target.value)}
|
||||
/>
|
||||
@@ -142,7 +138,7 @@ const GeneralSettings: React.FC = () => {
|
||||
</label>
|
||||
<input
|
||||
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}
|
||||
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">
|
||||
<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()}
|
||||
>
|
||||
Neustart CPL
|
||||
@@ -160,7 +156,7 @@ const GeneralSettings: React.FC = () => {
|
||||
{isAdminLoggedIn && (
|
||||
<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={() => {
|
||||
const confirmed = window.confirm(
|
||||
"⚠️ Wollen Sie wirklich ein Firmwareupdate für alle KÜ-Module starten?"
|
||||
|
||||
@@ -27,7 +27,7 @@ const NTPSettings: React.FC = () => {
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
<input
|
||||
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}
|
||||
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>
|
||||
<input
|
||||
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}
|
||||
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>
|
||||
<input
|
||||
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}
|
||||
onChange={(e) => setNtp3(e.target.value)}
|
||||
/>
|
||||
@@ -65,7 +65,7 @@ const NTPSettings: React.FC = () => {
|
||||
<label className="block text-xs font-medium">Zeitzone</label>
|
||||
<input
|
||||
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}
|
||||
onChange={(e) => setNtpTimezone(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function OPCUAInterfaceSettings() {
|
||||
);
|
||||
|
||||
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">
|
||||
<Image
|
||||
src="/images/OPCUA.jpg"
|
||||
@@ -44,9 +44,11 @@ export default function OPCUAInterfaceSettings() {
|
||||
<label className="mr-3 font-medium text-sm">Server Status:</label>
|
||||
<button
|
||||
onClick={() => dispatch(toggleOpcUaServer())}
|
||||
className={`px-3 py-1 rounded text-sm ${
|
||||
opcuaSettings.isEnabled ? "bg-littwin-blue" : "bg-gray-300"
|
||||
} text-white`}
|
||||
className={`px-3 py-1 rounded text-sm font-medium transition-colors text-white ${
|
||||
opcuaSettings.isEnabled
|
||||
? "bg-accent hover:brightness-110"
|
||||
: "bg-[var(--color-muted)] hover:bg-[var(--color-fg)]/20"
|
||||
}`}
|
||||
>
|
||||
{opcuaSettings.isEnabled ? "Aktiviert" : "Deaktiviert"}
|
||||
</button>
|
||||
@@ -62,7 +64,7 @@ export default function OPCUAInterfaceSettings() {
|
||||
<select
|
||||
value={opcuaSettings.encryption}
|
||||
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="Basic256">Basic256</option>
|
||||
@@ -74,7 +76,7 @@ export default function OPCUAInterfaceSettings() {
|
||||
{/* ✅ OPCUA Zustand */}
|
||||
<div className="mb-3">
|
||||
<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}
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +87,7 @@ export default function OPCUAInterfaceSettings() {
|
||||
<div className="flex">
|
||||
<input
|
||||
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}
|
||||
onChange={(e) => setNodesetName(e.target.value)}
|
||||
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">
|
||||
Aktuelle OPC-Clients
|
||||
</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}
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,12 +122,12 @@ export default function OPCUAInterfaceSettings() {
|
||||
{opcuaSettings.users.map((user) => (
|
||||
<li
|
||||
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>
|
||||
<button
|
||||
onClick={() => dispatch(removeOpcUaUser(user.id))}
|
||||
className="text-red-500"
|
||||
className="text-danger hover:underline"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
@@ -140,18 +142,18 @@ export default function OPCUAInterfaceSettings() {
|
||||
placeholder="Benutzername"
|
||||
value={newUsername}
|
||||
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
|
||||
type="password"
|
||||
placeholder="Passwort"
|
||||
value={newPassword}
|
||||
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
|
||||
onClick={handleAddUser}
|
||||
className="bg-littwin-blue text-white p-1 rounded text-sm"
|
||||
className="btn-primary p-1 rounded text-sm"
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
|
||||
@@ -44,7 +44,7 @@ const UserManagementSettings: React.FC = () => {
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
{/* Admin Login/Logout */}
|
||||
@@ -52,7 +52,7 @@ const UserManagementSettings: React.FC = () => {
|
||||
{isAdminLoggedIn ? (
|
||||
<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={() => {
|
||||
try {
|
||||
localStorage.removeItem("adminLoginTime");
|
||||
@@ -70,7 +70,7 @@ const UserManagementSettings: React.FC = () => {
|
||||
<input
|
||||
type="text"
|
||||
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}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
@@ -78,14 +78,14 @@ const UserManagementSettings: React.FC = () => {
|
||||
<input
|
||||
type="password"
|
||||
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}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<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}
|
||||
>
|
||||
Admin anmelden
|
||||
@@ -96,9 +96,9 @@ const UserManagementSettings: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{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)
|
||||
|
||||
@@ -13,7 +13,7 @@ const ProgressModal: React.FC<Props> = ({ visible, progress, slot }) => {
|
||||
|
||||
return (
|
||||
<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">
|
||||
Firmwareupdate
|
||||
@@ -26,9 +26,9 @@ const ProgressModal: React.FC<Props> = ({ visible, progress, slot }) => {
|
||||
</h2>
|
||||
Bitte Fenster nicht schließen
|
||||
<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
|
||||
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}%` }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
@@ -454,7 +454,7 @@ export const DetailModal = ({
|
||||
<div className="absolute top-0 right-0 flex gap-3">
|
||||
<button
|
||||
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
|
||||
className={
|
||||
@@ -467,7 +467,7 @@ export const DetailModal = ({
|
||||
|
||||
<button
|
||||
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>
|
||||
</button>
|
||||
@@ -481,7 +481,7 @@ export const DetailModal = ({
|
||||
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} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const SystemOverviewGrid = ({ voltages, onOpenDetail }: Props) => {
|
||||
return (
|
||||
<div
|
||||
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>
|
||||
<p>
|
||||
|
||||
@@ -71,8 +71,8 @@ const SystemPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-white dark:bg-gray-900">
|
||||
<h1 className="text-xl font-bold mb-4">
|
||||
<div className="p-4 bg-[var(--color-background)] text-[var(--color-fg)]">
|
||||
<h1 className="text-xl font-bold mb-4 tracking-wide">
|
||||
System Spannungen & Temperaturen
|
||||
</h1>
|
||||
|
||||
@@ -80,7 +80,7 @@ const SystemPage = () => {
|
||||
<div className="flex justify-center items-center h-[400px]">
|
||||
<div className="text-center">
|
||||
<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
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -36,12 +36,12 @@ const Navigation: React.FC<NavigationProps> = ({ className }) => {
|
||||
];
|
||||
|
||||
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"}`}>
|
||||
{menuItems.map((item) => (
|
||||
<div key={item.name}>
|
||||
{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}
|
||||
</div>
|
||||
) : (
|
||||
@@ -49,11 +49,13 @@ const Navigation: React.FC<NavigationProps> = ({ className }) => {
|
||||
href={formatPath(item.path)}
|
||||
prefetch={false}
|
||||
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)
|
||||
? "bg-sky-500 text-white rounded-r-full xl:mr-4 xl:w-full dark:bg-sky-600 dark:text-white"
|
||||
: "text-black hover:bg-gray-200 rounded-r-full dark:text-gray-200 dark:hover:bg-gray-800"
|
||||
}`}
|
||||
? "bg-[var(--color-accent)] text-white shadow-sm xl:mr-4 xl:w-full"
|
||||
: "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}
|
||||
</Link>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"win_da_state": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"win_da_bezeichnung": [
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "cpl-v4",
|
||||
"version": "1.6.869",
|
||||
"version": "1.6.879",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cpl-v4",
|
||||
"version": "1.6.869",
|
||||
"version": "1.6.879",
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.1.0",
|
||||
"@headlessui/react": "^2.2.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cpl-v4",
|
||||
"version": "1.6.869",
|
||||
"version": "1.6.879",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3000",
|
||||
|
||||
@@ -148,11 +148,11 @@ function AppContent({
|
||||
}, [pathname, dispatch]);
|
||||
|
||||
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 />
|
||||
<div className="flex flex-grow w-full">
|
||||
<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 && (
|
||||
<div className="bg-red-500 text-white p-4 text-center">
|
||||
❌ Ihre Sitzung ist abgelaufen oder die Verbindung ist
|
||||
|
||||
@@ -8,6 +8,13 @@ export default function Document() {
|
||||
<link rel="icon" href="/favicon.png" type="image/png" />
|
||||
</Head>
|
||||
<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 */}
|
||||
<NextScript /> {/* Fügt Next.js-Skripte für die Seite hinzu */}
|
||||
</body>
|
||||
|
||||
@@ -3,45 +3,255 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Light theme tokens */
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--background: #f1f5f9; /* slate-100 */
|
||||
--foreground: #0f172a; /* slate-900 */
|
||||
--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) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
/* Dark theme overrides (activated via .dark class on <html>) */
|
||||
.dark {
|
||||
--background: #0f1115;
|
||||
--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 {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
color: var(--color-fg);
|
||||
background: var(--color-background);
|
||||
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 {
|
||||
.text-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) {
|
||||
input {
|
||||
background-color: #333 !important; /* Dunkler Hintergrund im Darkmode */
|
||||
color: #fff !important; /* Weißer Text im Darkmode */
|
||||
border: 1px solid #555; /* Optional: etwas hellerer Rahmen */
|
||||
/* Component abstractions (no @apply to avoid processing issues in global layer) */
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-fg);
|
||||
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) {
|
||||
input {
|
||||
background-color: white !important; /* Heller Hintergrund im Lightmode */
|
||||
color: black !important; /* Schwarzer Text im Lightmode */
|
||||
border: 1px solid #ccc; /* Optional: heller Rahmen */
|
||||
}
|
||||
/* Form elements use tokens */
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
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,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: "class",
|
||||
// Ermöglicht zusätzlich zu .dark auch [data-theme="dark"] als Schalter
|
||||
darkMode: ["class", '[data-theme="dark"]'],
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
|
||||
Reference in New Issue
Block a user