fix(frontend): zirkuläre CSS-Variablen endgültig behoben - kein @theme-Namespace mehr
CI / backend-tests (push) Failing after 2m8s
CI / frontend-build (push) Successful in 21s

Vorheriger Fix (--mabea-* Zwischen-Variablen) hat den Zirkel nur eine Ebene verschoben statt aufgelöst: da @theme weiterhin --color-primary selbst neu definierte (höherer Layer als global.css), zeigte --mabea-primary auf --color-primary, welches wiederum auf --mabea-primary zeigte - derselbe Zirkel, nur indirekt. Coming from CSS-Spec: eine zirkuläre var()-Kette macht die Property zur "guaranteed-invalid value", unabhängig davon über wie viele Zwischenschritte sie läuft.

Echter Fix: kein @theme-Mapping auf --color-* mehr, damit keine Namenskollision mit global.css überhaupt erst entstehen kann. Alle betroffenen Tailwind-Klassen (bg-primary, text-danger, border-l-warning, bg-surface-sunken, text-muted-foreground, ring-primary, etc.) auf Arbitrary-Value-Syntax umgestellt (z.B. bg-[var(--color-primary)]) - referenziert global.css-Variablen direkt, ohne je einen neuen Property-Namen im Theme-Layer zu belegen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-07 14:35:48 +02:00
co-authored by Claude Sonnet 5
parent adb4aeef46
commit e6eda26eb2
14 changed files with 122 additions and 94 deletions
+60
View File
@@ -6698,3 +6698,63 @@ Keine Commits in dieser Session.
- frontend/src/auth/AuthContext.tsx | 15 +++++++++++++-- - frontend/src/auth/AuthContext.tsx | 15 +++++++++++++--
--- ---
## 2026-09-07 14:19 14:23 (3m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
- adb4aee fix(frontend): kritischer Bug - zirkuläre CSS-Variablen brachen Farben app-weit
### Geänderte Dateien
- DEVLOG.md | 26 ++++++++++++++++++++++++++
- frontend/src/styles/tailwind.css | 61 +++++++++++++++++++++++++++++++++++++++++++++----------------
---
## 2026-09-07 14:24 14:24 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 26 ++++++++++++++++++++++++++
- frontend/src/styles/tailwind.css | 61 +++++++++++++++++++++++++++++++++++++++++++++----------------
---
## 2026-09-07 14:25 14:25 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 26 ++++++++++++++++++++++++++
- frontend/src/styles/tailwind.css | 61 +++++++++++++++++++++++++++++++++++++++++++++----------------
---
## 2026-09-07 14:29 14:29 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 26 ++++++++++++++++++++++++++
- frontend/src/styles/tailwind.css | 61 +++++++++++++++++++++++++++++++++++++++++++++----------------
---
## 2026-09-07 14:29 14:30 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 26 ++++++++++++++++++++++++++
- frontend/src/styles/tailwind.css | 61 +++++++++++++++++++++++++++++++++++++++++++++----------------
---
@@ -17,10 +17,10 @@ const VARIANT: Record<Readiness, "success" | "warning" | "danger" | "neutral"> =
}; };
const PUNKT_FARBE: Record<Readiness, string> = { const PUNKT_FARBE: Record<Readiness, string> = {
ready: "bg-success", ready: "bg-[var(--color-success)]",
limited: "bg-warning", limited: "bg-[var(--color-warning)]",
"not-ready": "bg-danger", "not-ready": "bg-[var(--color-danger)]",
unknown: "bg-muted-foreground", unknown: "bg-[var(--color-text-muted)]",
}; };
interface Props { interface Props {
+5 -5
View File
@@ -6,11 +6,11 @@ import { cn } from "../../lib/utils";
const badgeVariants = cva("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", { const badgeVariants = cva("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", {
variants: { variants: {
variant: { variant: {
neutral: "bg-surface-sunken text-muted-foreground", neutral: "bg-[var(--color-surface-sunken)] text-[var(--color-text-muted)]",
success: "bg-success-bg text-success", success: "bg-[var(--color-success-bg)] text-[var(--color-success)]",
warning: "bg-warning-bg text-warning", warning: "bg-[var(--color-warning-bg)] text-[var(--color-warning)]",
danger: "bg-danger-bg text-danger", danger: "bg-[var(--color-danger-bg)] text-[var(--color-danger)]",
primary: "bg-primary-soft text-primary", primary: "bg-[var(--color-primary-soft)] text-[var(--color-primary)]",
}, },
}, },
defaultVariants: { defaultVariants: {
+6 -5
View File
@@ -5,14 +5,15 @@ import { forwardRef, type ButtonHTMLAttributes } from "react";
import { cn } from "../../lib/utils"; import { cn } from "../../lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50", "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] disabled:pointer-events-none disabled:opacity-50",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-primary text-white hover:bg-primary-hover", default: "bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-hover)]",
secondary: "bg-surface-sunken text-foreground border border-border hover:bg-border/40", secondary:
ghost: "hover:bg-surface-sunken text-foreground", "bg-[var(--color-surface-sunken)] text-[var(--color-text)] border border-[var(--color-border)] hover:bg-[var(--color-border)]/40",
danger: "bg-danger text-white hover:opacity-90", ghost: "hover:bg-[var(--color-surface-sunken)] text-[var(--color-text)]",
danger: "bg-[var(--color-danger)] text-white hover:opacity-90",
}, },
size: { size: {
default: "h-9 px-4 py-2", default: "h-9 px-4 py-2",
+5 -2
View File
@@ -6,7 +6,7 @@ export const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>((
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-[14px] border border-border bg-surface text-foreground", "rounded-[14px] border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text)]",
"shadow-[0_1px_3px_rgba(0,0,0,.08),0_4px_12px_rgba(0,0,0,.05)]", "shadow-[0_1px_3px_rgba(0,0,0,.08),0_4px_12px_rgba(0,0,0,.05)]",
// hover-lift über box-shadow statt transform: TileFrame wird von // hover-lift über box-shadow statt transform: TileFrame wird von
// react-grid-layout per inline transform positioniert, ein Tailwind- // react-grid-layout per inline transform positioniert, ein Tailwind-
@@ -28,7 +28,10 @@ export const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadi
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<h3 <h3
ref={ref} ref={ref}
className={cn("min-w-0 truncate text-sm font-semibold tracking-wide text-muted-foreground uppercase", className)} className={cn(
"min-w-0 truncate text-sm font-semibold tracking-wide text-[var(--color-text-muted)] uppercase",
className
)}
{...props} {...props}
/> />
) )
@@ -216,7 +216,7 @@ export function DashboardPage() {
<div> <div>
<h1 className="text-2xl font-bold tracking-tight">Dashboard</h1> <h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
{zuletztAktualisiert && ( {zuletztAktualisiert && (
<p className="text-xs text-muted-foreground"> <p className="text-xs text-[var(--color-text-muted)]">
Aktualisiert um {zuletztAktualisiert.toLocaleTimeString("de-DE")} Aktualisiert um {zuletztAktualisiert.toLocaleTimeString("de-DE")}
</p> </p>
)} )}
@@ -252,17 +252,17 @@ export function DashboardPage() {
{laedt && ( {laedt && (
<div className="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div className="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => ( {Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="h-40 animate-pulse rounded-[14px] border border-border bg-surface-sunken" /> <div key={i} className="h-40 animate-pulse rounded-[14px] border border-[var(--color-border)] bg-[var(--color-surface-sunken)]" />
))} ))}
</div> </div>
)} )}
{bearbeitenModus && layout.ausgeblendet.length > 0 && ( {bearbeitenModus && layout.ausgeblendet.length > 0 && (
<div className="mt-3 rounded-md border border-border bg-surface-sunken px-4 py-3 text-sm"> <div className="mt-3 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-sunken)] px-4 py-3 text-sm">
Ausgeblendet:{" "} Ausgeblendet:{" "}
{layout.ausgeblendet.map((id, i) => ( {layout.ausgeblendet.map((id, i) => (
<span key={id}> <span key={id}>
{i > 0 && ", "} {i > 0 && ", "}
<button className="text-primary underline underline-offset-2" onClick={() => ausblendenUmschalten(id)}> <button className="text-[var(--color-primary)] underline underline-offset-2" onClick={() => ausblendenUmschalten(id)}>
{TITEL[id]} wieder einblenden {TITEL[id]} wieder einblenden
</button> </button>
</span> </span>
@@ -270,7 +270,7 @@ export function DashboardPage() {
</div> </div>
)} )}
{bearbeitenModus && ( {bearbeitenModus && (
<p className="mt-2 text-xs text-muted-foreground"> <p className="mt-2 text-xs text-[var(--color-text-muted)]">
Kachel ziehen zum Verschieben, an der unteren rechten Ecke ziehen zum Vergrößern/Verkleinern. Kachel ziehen zum Verschieben, an der unteren rechten Ecke ziehen zum Vergrößern/Verkleinern.
</p> </p>
)} )}
+3 -3
View File
@@ -7,9 +7,9 @@ import { cn } from "../../lib/utils";
export type TileAkzent = "neutral" | "warning" | "danger"; export type TileAkzent = "neutral" | "warning" | "danger";
const AKZENT_KLASSE: Record<TileAkzent, string> = { const AKZENT_KLASSE: Record<TileAkzent, string> = {
neutral: "border-l-border", neutral: "border-l-[var(--color-border)]",
warning: "border-l-warning", warning: "border-l-[var(--color-warning)]",
danger: "border-l-danger", danger: "border-l-[var(--color-danger)]",
}; };
interface Props extends HTMLAttributes<HTMLDivElement> { interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -14,7 +14,7 @@ function AblaufdatenTileKomponente({ ablaufdaten }: Props) {
const [alleAnzeigen, setAlleAnzeigen] = useState(false); const [alleAnzeigen, setAlleAnzeigen] = useState(false);
if (ablaufdaten.length === 0) { if (ablaufdaten.length === 0) {
return <p className="text-sm text-muted-foreground">Keine bald ablaufenden Chargen.</p>; return <p className="text-sm text-[var(--color-text-muted)]">Keine bald ablaufenden Chargen.</p>;
} }
const sichtbare = alleAnzeigen ? ablaufdaten : ablaufdaten.slice(0, ANZAHL_OHNE_AUFKLAPPEN); const sichtbare = alleAnzeigen ? ablaufdaten : ablaufdaten.slice(0, ANZAHL_OHNE_AUFKLAPPEN);
@@ -25,14 +25,14 @@ function AblaufdatenTileKomponente({ ablaufdaten }: Props) {
{sichtbare.map((a) => ( {sichtbare.map((a) => (
<li <li
key={`${a.objekt_id}-${a.material_id}`} key={`${a.objekt_id}-${a.material_id}`}
className="flex items-center gap-3 border-b border-border py-2 last:border-0" className="flex items-center gap-3 border-b border-[var(--color-border)] py-2 last:border-0"
> >
<InventoryStatus status={a.status} /> <InventoryStatus status={a.status} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<Link to={`/akte/objekt/${a.objekt_id}`} className="block truncate font-medium"> <Link to={`/akte/objekt/${a.objekt_id}`} className="block truncate font-medium">
{a.objekt_name} {a.material_name} {a.objekt_name} {a.material_name}
</Link> </Link>
<span className="text-xs text-muted-foreground">{a.ablaufdatum}</span> <span className="text-xs text-[var(--color-text-muted)]">{a.ablaufdatum}</span>
</div> </div>
<span className="shrink-0 tabular-nums text-sm font-semibold">{a.verbleibende_tage} Tage</span> <span className="shrink-0 tabular-nums text-sm font-semibold">{a.verbleibende_tage} Tage</span>
</li> </li>
@@ -40,7 +40,7 @@ function AblaufdatenTileKomponente({ ablaufdaten }: Props) {
</ul> </ul>
{ablaufdaten.length > ANZAHL_OHNE_AUFKLAPPEN && ( {ablaufdaten.length > ANZAHL_OHNE_AUFKLAPPEN && (
<button <button
className="mt-2 text-sm font-medium text-primary underline-offset-2 hover:underline" className="mt-2 text-sm font-medium text-[var(--color-primary)] underline-offset-2 hover:underline"
onClick={() => setAlleAnzeigen(!alleAnzeigen)} onClick={() => setAlleAnzeigen(!alleAnzeigen)}
> >
{alleAnzeigen ? "Weniger anzeigen" : `Alle anzeigen (${ablaufdaten.length})`} {alleAnzeigen ? "Weniger anzeigen" : `Alle anzeigen (${ablaufdaten.length})`}
@@ -39,10 +39,10 @@ function EinsatzbereitschaftTileKomponente({ bereitschaft }: Props) {
<> <>
<p className="leading-none"> <p className="leading-none">
<span className="text-4xl font-bold tabular-nums">{bereitschaft.einsatzbereit}</span> <span className="text-4xl font-bold tabular-nums">{bereitschaft.einsatzbereit}</span>
<span className="text-sm font-semibold text-muted-foreground">/{bereitschaft.gesamt} einsatzbereit</span> <span className="text-sm font-semibold text-[var(--color-text-muted)]">/{bereitschaft.gesamt} einsatzbereit</span>
</p> </p>
<div className="mt-3 flex h-2.5 w-full overflow-hidden rounded-full bg-surface-sunken"> <div className="mt-3 flex h-2.5 w-full overflow-hidden rounded-full bg-[var(--color-surface-sunken)]">
{segmente.map( {segmente.map(
(s) => (s) =>
s.anzahl > 0 && ( s.anzahl > 0 && (
@@ -59,14 +59,14 @@ function EinsatzbereitschaftTileKomponente({ bereitschaft }: Props) {
{segmente.map((s) => ( {segmente.map((s) => (
<div key={s.label} className="flex items-center gap-1.5 text-sm"> <div key={s.label} className="flex items-center gap-1.5 text-sm">
<span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: s.farbVar }} /> <span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: s.farbVar }} />
<span className="text-muted-foreground">{s.label}</span> <span className="text-[var(--color-text-muted)]">{s.label}</span>
<span className="ml-auto font-semibold tabular-nums">{s.anzahl}</span> <span className="ml-auto font-semibold tabular-nums">{s.anzahl}</span>
</div> </div>
))} ))}
</div> </div>
{bereitschaft.nie_kontrolliert_gesamt > bereitschaft.unbekannt && ( {bereitschaft.nie_kontrolliert_gesamt > bereitschaft.unbekannt && (
<p className="mt-3 text-xs text-muted-foreground"> <p className="mt-3 text-xs text-[var(--color-text-muted)]">
Davon {bereitschaft.nie_kontrolliert_gesamt} insgesamt noch nie kontrolliert (auch bei Objekten mit weiteren Davon {bereitschaft.nie_kontrolliert_gesamt} insgesamt noch nie kontrolliert (auch bei Objekten mit weiteren
Problemen). Problemen).
</p> </p>
@@ -75,7 +75,7 @@ function EinsatzbereitschaftTileKomponente({ bereitschaft }: Props) {
{bereitschaft.details.length > 0 && ( {bereitschaft.details.length > 0 && (
<> <>
<button <button
className="mt-3 text-sm font-medium text-primary underline-offset-2 hover:underline" className="mt-3 text-sm font-medium text-[var(--color-primary)] underline-offset-2 hover:underline"
onClick={() => setDetailsOffen(!detailsOffen)} onClick={() => setDetailsOffen(!detailsOffen)}
> >
{detailsOffen ? "Details ausblenden" : "Details anzeigen"} {detailsOffen ? "Details ausblenden" : "Details anzeigen"}
@@ -83,13 +83,13 @@ function EinsatzbereitschaftTileKomponente({ bereitschaft }: Props) {
{detailsOffen && ( {detailsOffen && (
<ul className="mt-2"> <ul className="mt-2">
{bereitschaft.details.map((d) => ( {bereitschaft.details.map((d) => (
<li key={d.objekt_id} className="flex items-center justify-between gap-2 border-b border-border py-2 last:border-0"> <li key={d.objekt_id} className="flex items-center justify-between gap-2 border-b border-[var(--color-border)] py-2 last:border-0">
<Link to={`/objekte/${d.objekt_id}/kontrolle`} className="truncate font-medium"> <Link to={`/objekte/${d.objekt_id}/kontrolle`} className="truncate font-medium">
{d.name} ({d.code}) {d.name} ({d.code})
</Link> </Link>
<span className="flex shrink-0 items-center gap-2"> <span className="flex shrink-0 items-center gap-2">
<ReadinessBadge status={readinessVonGruenden(d.gruende)} /> <ReadinessBadge status={readinessVonGruenden(d.gruende)} />
<span className="hidden text-xs text-muted-foreground sm:inline"> <span className="hidden text-xs text-[var(--color-text-muted)] sm:inline">
{d.gruende.map((g) => GRUND_TEXT[g] ?? g).join(", ")} {d.gruende.map((g) => GRUND_TEXT[g] ?? g).join(", ")}
</span> </span>
</span> </span>
@@ -10,7 +10,7 @@ function KennzahlenTileKomponente({ kennzahlen }: Props) {
return ( return (
<div> <div>
<p className="text-4xl font-bold tabular-nums leading-none">{kennzahlen.anzahl_offener_fehlbestaende}</p> <p className="text-4xl font-bold tabular-nums leading-none">{kennzahlen.anzahl_offener_fehlbestaende}</p>
<p className="mb-3 text-xs uppercase tracking-wide text-muted-foreground">Offene Fehlbestände</p> <p className="mb-3 text-xs uppercase tracking-wide text-[var(--color-text-muted)]">Offene Fehlbestände</p>
<ul className="card-list"> <ul className="card-list">
<li style={{ padding: "0.3rem 0" }}>Genehmigte Mindermengen: {kennzahlen.anzahl_genehmigter_mindermengen}</li> <li style={{ padding: "0.3rem 0" }}>Genehmigte Mindermengen: {kennzahlen.anzahl_genehmigter_mindermengen}</li>
<li style={{ padding: "0.3rem 0" }}> <li style={{ padding: "0.3rem 0" }}>
@@ -16,12 +16,12 @@ function MaengelTileKomponente({ maengel, maengelNeuAnzahl, maengelKritischAnzah
return ( return (
<> <>
<p className="text-4xl font-bold tabular-nums leading-none">{maengelOffenListe.length}</p> <p className="text-4xl font-bold tabular-nums leading-none">{maengelOffenListe.length}</p>
<p className="mb-2 text-xs uppercase tracking-wide text-muted-foreground">Offen</p> <p className="mb-2 text-xs uppercase tracking-wide text-[var(--color-text-muted)]">Offen</p>
{maengelNeuAnzahl > 0 && ( {maengelNeuAnzahl > 0 && (
<p className="font-semibold text-warning">{maengelNeuAnzahl} neu gemeldet</p> <p className="font-semibold text-[var(--color-warning)]">{maengelNeuAnzahl} neu gemeldet</p>
)} )}
{maengelKritischAnzahl > 0 && ( {maengelKritischAnzahl > 0 && (
<p className="text-danger">{maengelKritischAnzahl} kritisch (blockiert Einsatzbereitschaft)</p> <p className="text-[var(--color-danger)]">{maengelKritischAnzahl} kritisch (blockiert Einsatzbereitschaft)</p>
)} )}
{maengelOffenListe.length > 0 && ( {maengelOffenListe.length > 0 && (
<> <>
@@ -14,7 +14,7 @@ function PrueftermineTileKomponente({ prueftermine }: Props) {
const [alleAnzeigen, setAlleAnzeigen] = useState(false); const [alleAnzeigen, setAlleAnzeigen] = useState(false);
if (prueftermine.length === 0) { if (prueftermine.length === 0) {
return <p className="text-sm text-muted-foreground">Keine anstehenden oder überfälligen Geräteprüfungen.</p>; return <p className="text-sm text-[var(--color-text-muted)]">Keine anstehenden oder überfälligen Geräteprüfungen.</p>;
} }
const sichtbare = alleAnzeigen ? prueftermine : prueftermine.slice(0, ANZAHL_OHNE_AUFKLAPPEN); const sichtbare = alleAnzeigen ? prueftermine : prueftermine.slice(0, ANZAHL_OHNE_AUFKLAPPEN);
@@ -25,14 +25,14 @@ function PrueftermineTileKomponente({ prueftermine }: Props) {
{sichtbare.map((p) => ( {sichtbare.map((p) => (
<li <li
key={`${p.typ}-${p.objekt_id}-${p.faelligkeitsdatum}`} key={`${p.typ}-${p.objekt_id}-${p.faelligkeitsdatum}`}
className="flex items-center gap-3 border-b border-border py-2 last:border-0" className="flex items-center gap-3 border-b border-[var(--color-border)] py-2 last:border-0"
> >
<InspectionStatus status={p.status} /> <InspectionStatus status={p.status} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<Link to={`/akte/objekt/${p.objekt_id}`} className="block truncate font-medium"> <Link to={`/akte/objekt/${p.objekt_id}`} className="block truncate font-medium">
{p.bezeichnung} {p.bezeichnung}
</Link> </Link>
<span className="text-xs text-muted-foreground">{p.faelligkeitsdatum}</span> <span className="text-xs text-[var(--color-text-muted)]">{p.faelligkeitsdatum}</span>
</div> </div>
<span className="shrink-0 tabular-nums text-sm font-semibold">{p.verbleibende_tage} Tage</span> <span className="shrink-0 tabular-nums text-sm font-semibold">{p.verbleibende_tage} Tage</span>
</li> </li>
@@ -40,7 +40,7 @@ function PrueftermineTileKomponente({ prueftermine }: Props) {
</ul> </ul>
{prueftermine.length > ANZAHL_OHNE_AUFKLAPPEN && ( {prueftermine.length > ANZAHL_OHNE_AUFKLAPPEN && (
<button <button
className="mt-2 text-sm font-medium text-primary underline-offset-2 hover:underline" className="mt-2 text-sm font-medium text-[var(--color-primary)] underline-offset-2 hover:underline"
onClick={() => setAlleAnzeigen(!alleAnzeigen)} onClick={() => setAlleAnzeigen(!alleAnzeigen)}
> >
{alleAnzeigen ? "Weniger anzeigen" : `Alle anzeigen (${prueftermine.length})`} {alleAnzeigen ? "Weniger anzeigen" : `Alle anzeigen (${prueftermine.length})`}
@@ -13,7 +13,7 @@ function QualifikationTileKomponente({ qualifikationsablaeufe }: Props) {
const [alleAnzeigen, setAlleAnzeigen] = useState(false); const [alleAnzeigen, setAlleAnzeigen] = useState(false);
if (qualifikationsablaeufe.length === 0) { if (qualifikationsablaeufe.length === 0) {
return <p className="text-sm text-muted-foreground">Keine bald ablaufenden Qualifikationen.</p>; return <p className="text-sm text-[var(--color-text-muted)]">Keine bald ablaufenden Qualifikationen.</p>;
} }
const sichtbare = alleAnzeigen ? qualifikationsablaeufe : qualifikationsablaeufe.slice(0, ANZAHL_OHNE_AUFKLAPPEN); const sichtbare = alleAnzeigen ? qualifikationsablaeufe : qualifikationsablaeufe.slice(0, ANZAHL_OHNE_AUFKLAPPEN);
@@ -22,11 +22,11 @@ function QualifikationTileKomponente({ qualifikationsablaeufe }: Props) {
<> <>
<ul> <ul>
{sichtbare.map((q) => ( {sichtbare.map((q) => (
<li key={q.benutzer_qualifikation_id} className="flex items-center gap-3 border-b border-border py-2 last:border-0"> <li key={q.benutzer_qualifikation_id} className="flex items-center gap-3 border-b border-[var(--color-border)] py-2 last:border-0">
<FristBadge status={q.status} /> <FristBadge status={q.status} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<span className="block truncate font-medium">{q.benutzer_name}</span> <span className="block truncate font-medium">{q.benutzer_name}</span>
<span className="text-xs text-muted-foreground">{q.qualifikationstyp_name}</span> <span className="text-xs text-[var(--color-text-muted)]">{q.qualifikationstyp_name}</span>
</div> </div>
<span className="shrink-0 tabular-nums text-sm font-semibold">{q.verbleibende_tage} Tage</span> <span className="shrink-0 tabular-nums text-sm font-semibold">{q.verbleibende_tage} Tage</span>
</li> </li>
@@ -34,7 +34,7 @@ function QualifikationTileKomponente({ qualifikationsablaeufe }: Props) {
</ul> </ul>
{qualifikationsablaeufe.length > ANZAHL_OHNE_AUFKLAPPEN && ( {qualifikationsablaeufe.length > ANZAHL_OHNE_AUFKLAPPEN && (
<button <button
className="mt-2 text-sm font-medium text-primary underline-offset-2 hover:underline" className="mt-2 text-sm font-medium text-[var(--color-primary)] underline-offset-2 hover:underline"
onClick={() => setAlleAnzeigen(!alleAnzeigen)} onClick={() => setAlleAnzeigen(!alleAnzeigen)}
> >
{alleAnzeigen ? "Weniger anzeigen" : `Alle anzeigen (${qualifikationsablaeufe.length})`} {alleAnzeigen ? "Weniger anzeigen" : `Alle anzeigen (${qualifikationsablaeufe.length})`}
+11 -47
View File
@@ -10,50 +10,14 @@
@import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities); @import "tailwindcss/utilities.css" layer(utilities);
/* Zwischen-Variablen mit eigenem Namen: @theme unten braucht dieselben Namen /* KEIN @theme-Mapping auf --color-primary/--color-danger/etc.! Das wurde
wie Tailwinds Farb-Namespace (--color-primary etc.), das sind aber EXAKT zweimal versucht (auch über Zwischen-Variablen) und ist beide Male an
dieselben Namen wie in global.css. "--color-primary: var(--color-primary)" zirkulären CSS-Custom-Property-Referenzen gescheitert: sobald Tailwinds
ist eine zirkuläre Referenz auf sich selbst (gleiche Custom Property, Theme-Namespace denselben Property-Namen wie global.css belegt, gewinnt der
gleiches Element) - laut CSS-Spec wird das zur "guaranteed-invalid value", höher priorisierte Theme-Layer die Kaskade für GENAU diesen Namen - jede
die Property bricht dadurch überall weg, nicht nur für Tailwind-Utilities, var()-Kette, die (auch indirekt) wieder bei diesem Namen landet, wird zur
sondern auch für jede alte global.css-Regel wie .btn-primary, die dieselbe "guaranteed-invalid value" und bricht app-weit, nicht nur für Tailwind-
Property liest. Über diesen Umweg mit eigenem Namen keine Kollision mehr. */ Utilities. Statt eigener Theme-Tokens werden bestehende global.css-Variablen
@layer global-css { deshalb überall direkt per Tailwind-Arbitrary-Value-Syntax genutzt, z. B.
:root { `bg-[var(--color-primary)]` statt `bg-primary` - keine Namenskollision mehr
--mabea-bg: var(--color-bg); möglich, da kein neuer Property-Name im Theme-Layer definiert wird. */
--mabea-surface: var(--color-surface);
--mabea-surface-sunken: var(--color-surface-sunken);
--mabea-border: var(--color-border);
--mabea-border-strong: var(--color-border-strong);
--mabea-text: var(--color-text);
--mabea-text-muted: var(--color-text-muted);
--mabea-primary: var(--color-primary);
--mabea-primary-hover: var(--color-primary-hover);
--mabea-primary-soft: var(--color-primary-soft);
--mabea-danger: var(--color-danger);
--mabea-danger-bg: var(--color-danger-bg);
--mabea-warning: var(--color-warning);
--mabea-warning-bg: var(--color-warning-bg);
--mabea-success: var(--color-success);
--mabea-success-bg: var(--color-success-bg);
}
}
@theme {
--color-background: var(--mabea-bg);
--color-surface: var(--mabea-surface);
--color-surface-sunken: var(--mabea-surface-sunken);
--color-border: var(--mabea-border);
--color-border-strong: var(--mabea-border-strong);
--color-foreground: var(--mabea-text);
--color-muted-foreground: var(--mabea-text-muted);
--color-primary: var(--mabea-primary);
--color-primary-hover: var(--mabea-primary-hover);
--color-primary-soft: var(--mabea-primary-soft);
--color-danger: var(--mabea-danger);
--color-danger-bg: var(--mabea-danger-bg);
--color-warning: var(--mabea-warning);
--color-warning-bg: var(--mabea-warning-bg);
--color-success: var(--mabea-success);
--color-success-bg: var(--mabea-success-bg);
}