diff --git a/ApiCore-Frontend/src/app/(app)/dashboard/page.tsx b/ApiCore-Frontend/src/app/(app)/dashboard/page.tsx index 17f6491..0bd03ec 100644 --- a/ApiCore-Frontend/src/app/(app)/dashboard/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/dashboard/page.tsx @@ -10,11 +10,14 @@ import { type LucideIcon, MapPin, Layers, + PackageSearch, + ShieldAlert, } from "lucide-react"; +import { useAuth } from "@/lib/auth/auth-context"; import { fetchDashboardAlerts, fetchDashboardSummary } from "@/lib/dashboard/api"; -import { DashboardAlert } from "@/lib/dashboard/types"; -import { Badge } from "@/components/ui/badge"; +import { DashboardAlert, DashboardAlertType } from "@/lib/dashboard/types"; import { Card, CardContent } from "@/components/ui/card"; +import { HoneycombPattern } from "@/components/honeycomb-pattern"; function alertHref(alert: DashboardAlert): string | null { if (alert.relatedType === "Colony") return `/colonies/${alert.relatedId}`; @@ -23,26 +26,43 @@ function alertHref(alert: DashboardAlert): string | null { return null; } -const ALERT_STYLES: Record = { - OVERDUE_TASK: "border-destructive/30 bg-destructive/5", - CONSPICUOUS_COLONY: "border-warning/40 bg-warning/10", - LOW_STOCK: "border-warning/40 bg-warning/10", -}; +function greeting(): string { + const hour = new Date().getHours(); + if (hour < 11) return "Guten Morgen"; + if (hour < 18) return "Guten Tag"; + return "Guten Abend"; +} interface StatTile { label: string; value: number; icon: LucideIcon; - tone: "primary" | "secondary" | "warning"; + gradient: string; } -const TONE_CLASSES: Record = { - primary: "bg-primary/10 text-primary", - secondary: "bg-secondary/10 text-secondary", - warning: "bg-warning/15 text-warning-foreground", +const ALERT_META: Record< + DashboardAlertType, + { label: string; icon: LucideIcon; className: string } +> = { + OVERDUE_TASK: { + label: "Aufgabe überfällig", + icon: ListTodo, + className: "border-destructive/30 bg-destructive/5", + }, + COLONY_ATTENTION: { + label: "Volk auffällig", + icon: ShieldAlert, + className: "border-warning/40 bg-warning/10", + }, + LOW_STOCK: { + label: "Bestand niedrig", + icon: PackageSearch, + className: "border-warning/40 bg-warning/10", + }, }; export default function DashboardPage() { + const { user } = useAuth(); const { data: summary, isLoading: summaryLoading } = useQuery({ queryKey: ["dashboard", "summary"], queryFn: fetchDashboardSummary, @@ -54,25 +74,62 @@ export default function DashboardPage() { const tiles: StatTile[] = summary ? [ - { 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: "Aktive Bienenstände", + value: summary.apiariesActive, + icon: MapPin, + gradient: "var(--gradient-honey)", + }, + { + label: "Aktive Völker", + value: summary.coloniesActive, + icon: Layers, + gradient: "var(--gradient-honey)", + }, + { + label: "Aktive Königinnen", + value: summary.queensActive, + icon: Crown, + gradient: "var(--gradient-sage)", + }, + { + label: "Offene Aufgaben", + value: summary.openTasks, + icon: ListTodo, + gradient: "var(--gradient-sky)", + }, + { + label: "Honigchargen", + value: summary.honeyBatchesTotal, + icon: Droplet, + gradient: "var(--gradient-honey)", + }, { label: "Artikel unter Mindestbestand", value: summary.stockItemsLowCount, icon: AlertTriangle, - tone: summary.stockItemsLowCount > 0 ? "warning" : "secondary", + gradient: + summary.stockItemsLowCount > 0 + ? "linear-gradient(135deg, oklch(0.72 0.15 70) 0%, oklch(0.56 0.15 50) 100%)" + : "var(--gradient-sage)", }, ] : []; return (
-
-

Dashboard

-

Überblick über deine Imkerei

+
+ +
+

+ {greeting()} + {user ? `, ${user.email.split("@")[0]}` : ""} +

+

Überblick über deine Imkerei

+
{summaryLoading ?

Lädt…

: null} @@ -80,12 +137,13 @@ export default function DashboardPage() { {tiles.map((tile) => { const Icon = tile.icon; return ( - +
- +

{tile.value}

@@ -98,7 +156,7 @@ export default function DashboardPage() {
-

Hinweise

+

Hinweise

{alertsLoading ?

Lädt…

: null} {!alertsLoading && (!alerts || alerts.length === 0) ? ( @@ -110,20 +168,23 @@ export default function DashboardPage() { ) : null} {alerts?.map((alert) => { const href = alertHref(alert); + const meta = ALERT_META[alert.type]; + const AlertIcon = meta?.icon ?? AlertTriangle; return (
- {alert.message} -
- {alert.type} - {href ? ( - - Ansehen - - ) : null} -
+ + {alert.message} + {href ? ( + + Ansehen + + ) : null}
); })} diff --git a/ApiCore-Frontend/src/app/(app)/layout.tsx b/ApiCore-Frontend/src/app/(app)/layout.tsx index faeb296..8ff827e 100644 --- a/ApiCore-Frontend/src/app/(app)/layout.tsx +++ b/ApiCore-Frontend/src/app/(app)/layout.tsx @@ -30,12 +30,17 @@ export default function AppLayout({ children }: { children: ReactNode }) {
-
+
- + + + ApiCore
diff --git a/ApiCore-Frontend/src/app/(app)/sidebar.tsx b/ApiCore-Frontend/src/app/(app)/sidebar.tsx index 13b2ea6..64db8fd 100644 --- a/ApiCore-Frontend/src/app/(app)/sidebar.tsx +++ b/ApiCore-Frontend/src/app/(app)/sidebar.tsx @@ -93,13 +93,18 @@ function SidebarLink({ item, pathname }: { item: NavItem; pathname: string }) { href={item.href} aria-current={isActive ? "page" : undefined} className={cn( - "flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-150", + "group relative flex items-center gap-2.5 rounded-xl px-3 py-2 text-sm font-medium transition-all duration-150", isActive - ? "bg-primary/10 text-primary" - : "text-muted-foreground hover:bg-accent hover:text-accent-foreground", + ? "bg-primary text-primary-foreground shadow-[var(--shadow-soft)]" + : "text-muted-foreground hover:bg-accent/70 hover:text-foreground", )} > - + {item.label} ); @@ -110,19 +115,24 @@ export function Sidebar({ user }: { user: AuthUser }) { const groups = user.role === "ADMIN" ? [...NAV_GROUPS, ADMIN_GROUP] : NAV_GROUPS; return ( -