style(frontend): grundlegendes Redesign (Design-System, Farben, Typografie)

Neues Design-System: warme Honig-/Wachs-Farbpalette (Amber-Primaer,
Salbeigruen-Sekundaer) statt Standard-Graustufen, Space Grotesk fuer
Ueberschriften, Inter fuer Fliesstext, IBM Plex Mono fuer Codes/IDs.
Card-Komponente mit weicher Schattierung statt Ring, neuer
Badge-Warning-Variante und statusTone()-Helper fuer semantische
Status-Farben (Voelker/Honigchargen). Navigation umgebaut: aktiver
Link-Zustand, Hexagon-Markenzeichen, "Mehr"-Dropdown fuer Admin-Links.
Dashboard-Kacheln mit Icons/Farbakzenten, Login-Seite mit dezentem
Waben-Hintergrundmuster als wiederkehrendes Signature-Element.
This commit is contained in:
Nick Szaunig committed 2026-08-22 10:45:41 +02:00
1 parent 26b2eb0829
commit e7348b0b68
38 files changed
+350 -201

No files matched your search

@@ -2,10 +2,19 @@
import Link from "next/link";
import { useQuery } from "@tanstack/react-query";
import {
AlertTriangle,
Crown,
Droplet,
ListTodo,
type LucideIcon,
MapPin,
Layers,
} from "lucide-react";
import { fetchDashboardAlerts, fetchDashboardSummary } from "@/lib/dashboard/api";
import { DashboardAlert } from "@/lib/dashboard/types";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent } from "@/components/ui/card";
function alertHref(alert: DashboardAlert): string | null {
if (alert.relatedType === "Colony") return `/colonies/${alert.relatedId}`;
@@ -14,6 +23,25 @@ function alertHref(alert: DashboardAlert): string | null {
return null;
}
const ALERT_STYLES: Record<string, string> = {
OVERDUE_TASK: "border-destructive/30 bg-destructive/5",
CONSPICUOUS_COLONY: "border-warning/40 bg-warning/10",
LOW_STOCK: "border-warning/40 bg-warning/10",
};
interface StatTile {
label: string;
value: number;
icon: LucideIcon;
tone: "primary" | "secondary" | "warning";
}
const TONE_CLASSES: Record<StatTile["tone"], string> = {
primary: "bg-primary/10 text-primary",
secondary: "bg-secondary/10 text-secondary",
warning: "bg-warning/15 text-warning-foreground",
};
export default function DashboardPage() {
const { data: summary, isLoading: summaryLoading } = useQuery({
queryKey: ["dashboard", "summary"],
@@ -24,58 +52,74 @@ export default function DashboardPage() {
queryFn: fetchDashboardAlerts,
});
const tiles = summary
const tiles: StatTile[] = summary
? [
{ label: "Aktive Bienenstände", value: summary.apiariesActive },
{ label: "Aktive Völker", value: summary.coloniesActive },
{ label: "Aktive Königinnen", value: summary.queensActive },
{ label: "Offene Aufgaben", value: summary.openTasks },
{ label: "Honigchargen", value: summary.honeyBatchesTotal },
{ label: "Artikel unter Mindestbestand", value: summary.stockItemsLowCount },
{ label: "Aktive Bienenstände", value: summary.apiariesActive, icon: MapPin, tone: "primary" },
{ label: "Aktive Völker", value: summary.coloniesActive, icon: Layers, tone: "primary" },
{ label: "Aktive Königinnen", value: summary.queensActive, icon: Crown, tone: "secondary" },
{ label: "Offene Aufgaben", value: summary.openTasks, icon: ListTodo, tone: "secondary" },
{ label: "Honigchargen", value: summary.honeyBatchesTotal, icon: Droplet, tone: "primary" },
{
label: "Artikel unter Mindestbestand",
value: summary.stockItemsLowCount,
icon: AlertTriangle,
tone: summary.stockItemsLowCount > 0 ? "warning" : "secondary",
},
]
: [];
return (
<main className="flex flex-col gap-6 p-4 sm:p-6">
<h1 className="text-xl font-semibold">Dashboard</h1>
<div>
<h1 className="text-2xl font-semibold tracking-tight">Dashboard</h1>
<p className="text-sm text-muted-foreground">Überblick über deine Imkerei</p>
</div>
{summaryLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
{tiles.map((tile) => (
<Card key={tile.label}>
<CardHeader className="pb-1">
<CardTitle className="text-sm font-normal text-muted-foreground">
{tile.label}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-semibold">{tile.value}</p>
</CardContent>
</Card>
))}
{tiles.map((tile) => {
const Icon = tile.icon;
return (
<Card key={tile.label}>
<CardContent className="flex flex-col gap-3">
<div
className={`flex size-9 items-center justify-center rounded-lg ${TONE_CLASSES[tile.tone]}`}
>
<Icon className="size-4.5" />
</div>
<div>
<p className="text-2xl font-semibold tabular-nums">{tile.value}</p>
<p className="text-xs text-muted-foreground">{tile.label}</p>
</div>
</CardContent>
</Card>
);
})}
</div>
<Card>
<CardHeader>
<CardTitle>Hinweise</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-2 text-sm">
{alertsLoading ? <p className="text-muted-foreground">Lädt…</p> : null}
<div>
<h2 className="mb-3 text-lg font-semibold tracking-tight">Hinweise</h2>
<div className="flex flex-col gap-2">
{alertsLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
{!alertsLoading && (!alerts || alerts.length === 0) ? (
<p className="text-muted-foreground">Keine offenen Hinweise.</p>
<Card>
<CardContent className="text-sm text-muted-foreground">
Keine offenen Hinweise.
</CardContent>
</Card>
) : null}
{alerts?.map((alert) => {
const href = alertHref(alert);
return (
<div
key={`${alert.type}-${alert.relatedId}`}
className="flex items-center justify-between rounded-md border p-3"
className={`flex items-center justify-between rounded-xl border p-3 text-sm ${ALERT_STYLES[alert.type] ?? "border-border bg-card"}`}
>
<span>{alert.message}</span>
<div className="flex items-center gap-2">
<Badge variant="secondary">{alert.type}</Badge>
{href ? (
<Link className="underline" href={href}>
<Link className="font-medium text-primary underline-offset-4 hover:underline" href={href}>
Ansehen
</Link>
) : null}
@@ -83,8 +127,8 @@ export default function DashboardPage() {
</div>
);
})}
</CardContent>
</Card>
</div>
</div>
</main>
);
}