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:
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user