feat(frontend): Leerer-Zustand-Komponente für alle Listenseiten

Neue gemeinsame EmptyState-Komponente (Icon in Kreis, Titel, Beschreibung,
gestrichelter Rahmen) und Einbindung auf allen 9 Listenseiten mit Tabellen
(Völker, Bienenstände, Königinnen, Aufgaben, Honigchargen, Benutzer,
Inventar, Produkte, Lager mit Behältern und Artikeln).

Bug behoben: Bei leerem Datensatz wurde bisher nur eine Tabelle mit
Kopfzeile und keinerlei Inhalt oder Erklärung angezeigt – wirkte wie ein
Ladefehler statt eines normalen "noch keine Daten"-Zustands. Besonders
für neurodivergente Nutzer wichtig, da Unklarheit über den Systemzustand
("ist das kaputt oder einfach leer?") unnötige kognitive Last erzeugt.
This commit is contained in:
Nick Szaunig committed 2026-08-23 21:03:59 +02:00
1 parent fc88c272ae
commit ebfbd2ef6d
10 files changed
+121 -11

No files matched your search

@@ -10,6 +10,7 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import {
Select,
SelectContent,
@@ -77,7 +78,15 @@ export default function ApiariesPage() {
</p>
) : null}
{apiaries ? (
{apiaries && apiaries.length === 0 ? (
<EmptyState
icon={MapPin}
title="Noch keine Bienenstände"
description="Lege deinen ersten Bienenstand an, um Völker zuzuordnen."
/>
) : null}
{apiaries && apiaries.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
@@ -11,6 +11,7 @@ import { statusTone } from "@/lib/status-tone";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import {
Select,
SelectContent,
@@ -94,7 +95,15 @@ export default function ColoniesPage() {
</p>
) : null}
{colonies ? (
{colonies && colonies.length === 0 ? (
<EmptyState
icon={Layers}
title="Noch keine Völker"
description="Lege dein erstes Volk an, um den Überblick zu behalten."
/>
) : null}
{colonies && colonies.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
@@ -8,6 +8,7 @@ import { statusTone } from "@/lib/status-tone";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import {
Table,
TableBody,
@@ -44,7 +45,15 @@ export default function EquipmentPage() {
</p>
) : null}
{equipmentList ? (
{equipmentList && equipmentList.length === 0 ? (
<EmptyState
icon={Wrench}
title="Noch kein Inventar"
description="Erfasse deinen ersten Gegenstand, um den Zustand im Blick zu behalten."
/>
) : null}
{equipmentList && equipmentList.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
@@ -11,6 +11,7 @@ import { statusTone } from "@/lib/status-tone";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import {
Select,
SelectContent,
@@ -95,7 +96,15 @@ export default function HoneyBatchesPage() {
</p>
) : null}
{honeyBatches ? (
{honeyBatches && honeyBatches.length === 0 ? (
<EmptyState
icon={Droplet}
title="Noch keine Honigchargen"
description="Lege eine Charge an, sobald du geschleudert hast."
/>
) : null}
{honeyBatches && honeyBatches.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
@@ -7,6 +7,7 @@ import { fetchProducts } from "@/lib/products/api";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import {
Table,
TableBody,
@@ -42,7 +43,15 @@ export default function ProductsPage() {
</p>
) : null}
{products ? (
{products && products.length === 0 ? (
<EmptyState
icon={ShoppingBag}
title="Noch keine Produkte"
description="Lege dein erstes verkaufsfertiges Produkt an."
/>
) : null}
{products && products.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
+10 -1
View File
@@ -9,6 +9,7 @@ import { QueenStatus } from "@/lib/queens/types";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import {
Select,
SelectContent,
@@ -82,7 +83,15 @@ export default function QueensPage() {
</p>
) : null}
{queens ? (
{queens && queens.length === 0 ? (
<EmptyState
icon={Crown}
title="Noch keine Königinnen"
description="Lege deine erste Königin an, um Zuchtlinien zu verfolgen."
/>
) : null}
{queens && queens.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
@@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ConfirmDialog } from "@/components/confirm-dialog";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import {
Table,
TableBody,
@@ -62,7 +63,11 @@ export default function UsersPage() {
</p>
) : null}
{users ? (
{users && users.length === 0 ? (
<EmptyState icon={Users} title="Noch keine Benutzer" description="Lege den ersten Zugang an." />
) : null}
{users && users.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
@@ -1,10 +1,11 @@
"use client";
import { Warehouse } from "lucide-react";
import { Package, Warehouse } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { fetchStockItems, fetchStorageContainers } from "@/lib/storage/api";
import { QrCodeDialog } from "@/components/qr-code-dialog";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import { Badge } from "@/components/ui/badge";
import {
Table,
@@ -40,7 +41,15 @@ export default function StoragePage() {
/>
<h2 className="text-lg font-semibold tracking-tight">Lagerbehälter</h2>
{containersLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
{containers ? (
{containers && containers.length === 0 ? (
<EmptyState
icon={Warehouse}
title="Noch keine Lagerbehälter"
description="Lege deinen ersten Behälter an."
/>
) : null}
{containers && containers.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
@@ -78,7 +87,15 @@ export default function StoragePage() {
<CreateStockItemDialog />
</div>
{itemsLoading ? <p className="text-sm text-muted-foreground">Lädt…</p> : null}
{stockItems ? (
{stockItems && stockItems.length === 0 ? (
<EmptyState
icon={Package}
title="Noch keine Lagerartikel"
description="Lege deinen ersten Artikel an, um den Bestand zu verfolgen."
/>
) : null}
{stockItems && stockItems.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
+14 -1
View File
@@ -9,6 +9,7 @@ import { TaskPriority, TaskStatus } from "@/lib/tasks/types";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { PageHeader } from "@/components/page-header";
import { EmptyState } from "@/components/empty-state";
import {
Select,
SelectContent,
@@ -122,7 +123,19 @@ export default function TasksPage() {
</p>
) : null}
{tasks ? (
{tasks && visibleTasks.length === 0 ? (
<EmptyState
icon={ListTodo}
title="Keine Aufgaben"
description={
onlyOverdue
? "Keine überfälligen Aufgaben – gut gemacht!"
: "Lege deine erste Aufgabe an, um den Überblick zu behalten."
}
/>
) : null}
{tasks && visibleTasks.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
@@ -0,0 +1,21 @@
import { LucideIcon } from "lucide-react";
interface EmptyStateProps {
icon: LucideIcon;
title: string;
description?: string;
}
export function EmptyState({ icon: Icon, title, description }: EmptyStateProps) {
return (
<div className="flex flex-col items-center gap-3 rounded-2xl border border-dashed border-border py-12 text-center">
<span className="flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground">
<Icon className="size-6" />
</span>
<div>
<p className="font-medium">{title}</p>
{description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
</div>
</div>
);
}