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
@@ -19,7 +19,7 @@ export default function ApiaryDetailPage() {
|
||||
}
|
||||
if (isError || !apiary) {
|
||||
return (
|
||||
<p role="alert" className="p-4 text-sm text-red-600 sm:p-6">
|
||||
<p role="alert" className="p-4 text-sm text-destructive sm:p-6">
|
||||
Bienenstand konnte nicht geladen werden.
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -88,7 +88,7 @@ export default function NewApiaryPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function ApiariesPage() {
|
||||
return (
|
||||
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold">Bienenstände</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Bienenstände</h1>
|
||||
<Button nativeButton={false} render={<Link href="/apiaries/new" />}>
|
||||
+ Neuer Bienenstand
|
||||
</Button>
|
||||
@@ -66,7 +66,7 @@ export default function ApiariesPage() {
|
||||
|
||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
{isError ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Bienenstände konnten nicht geladen werden.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function ColonyDetailPage() {
|
||||
}
|
||||
if (isError || !colony) {
|
||||
return (
|
||||
<p role="alert" className="p-4 text-sm text-red-600 sm:p-6">
|
||||
<p role="alert" className="p-4 text-sm text-destructive sm:p-6">
|
||||
Volk konnte nicht geladen werden.
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function NewColonyPage() {
|
||||
</Select>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { fetchColonies } from "@/lib/colonies/api";
|
||||
import { ColonyStatus } from "@/lib/colonies/types";
|
||||
import { exportColoniesCsv } from "@/lib/export/api";
|
||||
import { statusTone } from "@/lib/status-tone";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -48,7 +49,7 @@ export default function ColoniesPage() {
|
||||
return (
|
||||
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold">Völker</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Völker</h1>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -82,7 +83,7 @@ export default function ColoniesPage() {
|
||||
|
||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
{isError ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Völker konnten nicht geladen werden.
|
||||
</p>
|
||||
) : null}
|
||||
@@ -100,15 +101,16 @@ export default function ColoniesPage() {
|
||||
{colonies.map((colony) => (
|
||||
<TableRow key={colony.id}>
|
||||
<TableCell>
|
||||
<Link className="underline" href={`/colonies/${colony.id}`}>
|
||||
<Link
|
||||
className="font-mono font-medium text-primary underline-offset-4 hover:underline"
|
||||
href={`/colonies/${colony.id}`}
|
||||
>
|
||||
{colony.code}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>{colony.colonyType}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={colony.status === "ACTIVE" ? "default" : "secondary"}>
|
||||
{colony.status}
|
||||
</Badge>
|
||||
<Badge variant={statusTone(colony.status)}>{colony.status}</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -151,7 +151,7 @@ export default function NewExtractionPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -183,7 +183,7 @@ export default function NewFeedingPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function HarvestDetailPage() {
|
||||
}
|
||||
if (isError || !harvest) {
|
||||
return (
|
||||
<p role="alert" className="p-4 text-sm text-red-600 sm:p-6">
|
||||
<p role="alert" className="p-4 text-sm text-destructive sm:p-6">
|
||||
Ernte konnte nicht geladen werden.
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -176,7 +176,7 @@ export default function NewHarvestPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function AddMeasurementDialog({ honeyBatchId }: { honeyBatchId: string })
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function HoneyBatchDetailPage() {
|
||||
}
|
||||
if (isError || !honeyBatch) {
|
||||
return (
|
||||
<p role="alert" className="p-4 text-sm text-red-600 sm:p-6">
|
||||
<p role="alert" className="p-4 text-sm text-destructive sm:p-6">
|
||||
Honigcharge konnte nicht geladen werden.
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -122,7 +122,7 @@ export function CreateHoneyBatchDialog() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { fetchHoneyBatches } from "@/lib/honey-batches/api";
|
||||
import { HoneyBatchStatus } from "@/lib/honey-batches/types";
|
||||
import { exportHoneyBatchesCsv } from "@/lib/export/api";
|
||||
import { statusTone } from "@/lib/status-tone";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -49,7 +50,7 @@ export default function HoneyBatchesPage() {
|
||||
return (
|
||||
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold">Honigchargen</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Honigchargen</h1>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -83,7 +84,7 @@ export default function HoneyBatchesPage() {
|
||||
|
||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
{isError ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Honigchargen konnten nicht geladen werden.
|
||||
</p>
|
||||
) : null}
|
||||
@@ -102,7 +103,10 @@ export default function HoneyBatchesPage() {
|
||||
{honeyBatches.map((honeyBatch) => (
|
||||
<TableRow key={honeyBatch.id}>
|
||||
<TableCell>
|
||||
<Link className="underline" href={`/honey-batches/${honeyBatch.id}`}>
|
||||
<Link
|
||||
className="font-mono font-medium text-primary underline-offset-4 hover:underline"
|
||||
href={`/honey-batches/${honeyBatch.id}`}
|
||||
>
|
||||
{honeyBatch.batchNumber}
|
||||
</Link>
|
||||
</TableCell>
|
||||
@@ -111,7 +115,7 @@ export default function HoneyBatchesPage() {
|
||||
{honeyBatch.amount !== null ? `${honeyBatch.amount} kg` : "—"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge>{honeyBatch.status}</Badge>
|
||||
<Badge variant={statusTone(honeyBatch.status)}>{honeyBatch.status}</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
|
||||
@@ -194,7 +194,7 @@ export default function NewInspectionPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -2,14 +2,53 @@
|
||||
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { Hexagon, MoreHorizontal } from "lucide-react";
|
||||
import { useAuth } from "@/lib/auth/auth-context";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { BottomNav } from "./bottom-nav";
|
||||
import { QuickAddMenu } from "./quick-add-menu";
|
||||
|
||||
const NAV_LINK_CLASS =
|
||||
"shrink-0 rounded-md px-2.5 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary";
|
||||
|
||||
const PRIMARY_LINKS = [
|
||||
{ label: "Dashboard", href: "/dashboard" },
|
||||
{ label: "Bienenstände", href: "/apiaries" },
|
||||
{ label: "Völker", href: "/colonies" },
|
||||
{ label: "Königinnen", href: "/queens" },
|
||||
{ label: "Aufgaben", href: "/tasks" },
|
||||
{ label: "Honigchargen", href: "/honey-batches" },
|
||||
{ label: "Lager", href: "/storage" },
|
||||
{ label: "Suche", href: "/search" },
|
||||
];
|
||||
|
||||
const ADMIN_LINKS = [
|
||||
{ label: "Benutzer", href: "/settings/users" },
|
||||
{ label: "Audit-Log", href: "/settings/audit-log" },
|
||||
];
|
||||
|
||||
function NavLink({ href, label, pathname }: { href: string; label: string; pathname: string }) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
aria-current={pathname.startsWith(href) ? "page" : undefined}
|
||||
className={NAV_LINK_CLASS}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AppLayout({ children }: { children: ReactNode }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { user, isLoading, logout } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -24,53 +63,33 @@ export default function AppLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<header className="flex items-center justify-between gap-3 border-b px-4 py-3">
|
||||
<nav className="flex min-w-0 items-center gap-4 max-sm:gap-2">
|
||||
<span className="shrink-0 font-semibold">ApiCore</span>
|
||||
<div className="hidden min-w-0 items-center gap-4 overflow-x-auto whitespace-nowrap sm:flex">
|
||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/dashboard">
|
||||
Dashboard
|
||||
<header className="flex items-center justify-between gap-3 border-b bg-card px-4 py-2.5">
|
||||
<nav className="flex min-w-0 items-center gap-3 max-sm:gap-2">
|
||||
<Link href="/dashboard" className="flex shrink-0 items-center gap-1.5 font-heading font-semibold">
|
||||
<Hexagon className="size-5 fill-primary/15 text-primary" strokeWidth={2.25} />
|
||||
ApiCore
|
||||
</Link>
|
||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/apiaries">
|
||||
Bienenstände
|
||||
</Link>
|
||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/colonies">
|
||||
Völker
|
||||
</Link>
|
||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/queens">
|
||||
Königinnen
|
||||
</Link>
|
||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/tasks">
|
||||
Aufgaben
|
||||
</Link>
|
||||
<Link
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
href="/honey-batches"
|
||||
>
|
||||
Honigchargen
|
||||
</Link>
|
||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/storage">
|
||||
Lager
|
||||
</Link>
|
||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/search">
|
||||
Suche
|
||||
</Link>
|
||||
{user.role === "ADMIN" ? (
|
||||
<Link
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
href="/settings/users"
|
||||
>
|
||||
Benutzer
|
||||
</Link>
|
||||
) : null}
|
||||
{user.role === "ADMIN" ? (
|
||||
<Link
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
href="/settings/audit-log"
|
||||
>
|
||||
Audit-Log
|
||||
</Link>
|
||||
) : null}
|
||||
<div className="hidden min-w-0 items-center gap-1 overflow-x-auto whitespace-nowrap sm:flex">
|
||||
{PRIMARY_LINKS.map((item) => (
|
||||
<NavLink key={item.href} {...item} pathname={pathname} />
|
||||
))}
|
||||
{user.role === "ADMIN" ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button variant="ghost" size="sm" className="gap-1 text-muted-foreground" />}
|
||||
>
|
||||
<MoreHorizontal className="size-4" />
|
||||
Mehr
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
{ADMIN_LINKS.map((item) => (
|
||||
<DropdownMenuItem key={item.href} render={<Link href={item.href} />}>
|
||||
{item.label}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
</div>
|
||||
</nav>
|
||||
<div className="flex shrink-0 items-center gap-3 text-sm">
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function QueenDetailPage() {
|
||||
}
|
||||
if (isError || !queen) {
|
||||
return (
|
||||
<p role="alert" className="p-4 text-sm text-red-600 sm:p-6">
|
||||
<p role="alert" className="p-4 text-sm text-destructive sm:p-6">
|
||||
Königin konnte nicht geladen werden.
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function NewQueenPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function QueensPage() {
|
||||
return (
|
||||
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold">Königinnen</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Königinnen</h1>
|
||||
<Button nativeButton={false} render={<Link href="/queens/new" />}>
|
||||
+ Neue Königin
|
||||
</Button>
|
||||
@@ -70,7 +70,7 @@ export default function QueensPage() {
|
||||
|
||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
{isError ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Königinnen konnten nicht geladen werden.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function SearchPage() {
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||
<h1 className="text-xl font-semibold">Suche</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Suche</h1>
|
||||
<Input
|
||||
className="h-11 max-w-lg"
|
||||
placeholder="Bienenstand, Volk, Königin, Aufgabe, Charge…"
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function AuditLogPage() {
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||
<h1 className="text-xl font-semibold">Audit-Log</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Audit-Log</h1>
|
||||
|
||||
<form
|
||||
className="flex flex-wrap items-end gap-3"
|
||||
@@ -70,7 +70,7 @@ export default function AuditLogPage() {
|
||||
|
||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
{isError ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Audit-Log konnte nicht geladen werden.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -110,7 +110,7 @@ export function CreateUserDialog() {
|
||||
</Select>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -31,13 +31,13 @@ export default function UsersPage() {
|
||||
return (
|
||||
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold">Benutzerverwaltung</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Benutzerverwaltung</h1>
|
||||
<CreateUserDialog />
|
||||
</div>
|
||||
|
||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
{isError ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Benutzer konnten nicht geladen werden.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -115,7 +115,7 @@ export function CreateStockItemDialog() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -95,7 +95,7 @@ export function CreateStorageContainerDialog() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function StoragePage() {
|
||||
<main className="flex flex-col gap-8 p-4 sm:p-6">
|
||||
<section className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold">Lagerbehälter</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Lagerbehälter</h1>
|
||||
<CreateStorageContainerDialog />
|
||||
</div>
|
||||
{containersLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
@@ -63,7 +63,7 @@ export default function StoragePage() {
|
||||
|
||||
<section className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold">Lagerartikel</h2>
|
||||
<h2 className="text-2xl font-semibold tracking-tight">Lagerartikel</h2>
|
||||
<CreateStockItemDialog />
|
||||
</div>
|
||||
{itemsLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function RecordMovementDialog({ stockItemId }: { stockItemId: string }) {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -104,7 +104,7 @@ export default function NewTaskPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function TasksPage() {
|
||||
return (
|
||||
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold">Aufgaben</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Aufgaben</h1>
|
||||
<Button nativeButton={false} render={<Link href="/tasks/new" />}>
|
||||
+ Neue Aufgabe
|
||||
</Button>
|
||||
@@ -110,7 +110,7 @@ export default function TasksPage() {
|
||||
|
||||
{isLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
|
||||
{isError ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Aufgaben konnten nicht geladen werden.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -153,7 +153,7 @@ export default function NewTreatmentPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -173,7 +173,7 @@ export default function NewVarroaMeasurementPage() {
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
Reference in new issue
Block a user