diff --git a/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx b/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx index d155a08..57b169a 100644 --- a/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx @@ -11,6 +11,7 @@ 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 { ListItemCard } from "@/components/list-item-card"; import { Select, SelectContent, @@ -18,14 +19,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; export default function ApiariesPage() { const [status, setStatus] = useState(""); @@ -92,32 +85,22 @@ export default function ApiariesPage() { ) : null} {apiaries && apiaries.length > 0 ? ( - - - - Name - Standort - Status - - - - {apiaries.map((apiary) => ( - - - - {apiary.name} - - - {apiary.location ?? "—"} - - - {apiary.status === "ACTIVE" ? "Aktiv" : "Inaktiv"} - - - - ))} - -
+
+ {apiaries.map((apiary) => ( + + {apiary.status === "ACTIVE" ? "Aktiv" : "Inaktiv"} + + } + /> + ))} +
) : null} ); diff --git a/ApiCore-Frontend/src/app/(app)/colonies/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/page.tsx index 40f34c4..4e9e97a 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/page.tsx @@ -12,6 +12,7 @@ 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 { ListItemCard } from "@/components/list-item-card"; import { Select, SelectContent, @@ -19,14 +20,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; const STATUS_OPTIONS: ColonyStatus[] = [ "ACTIVE", @@ -104,33 +97,18 @@ export default function ColoniesPage() { ) : null} {colonies && colonies.length > 0 ? ( - - - - Code - Typ - Status - - - - {colonies.map((colony) => ( - - - - {colony.code} - - - {colony.colonyType} - - {colony.status} - - - ))} - -
+
+ {colonies.map((colony) => ( + {colony.code}} + subtitle={colony.colonyType} + trailing={{colony.status}} + /> + ))} +
) : null} ); diff --git a/ApiCore-Frontend/src/app/(app)/equipment/page.tsx b/ApiCore-Frontend/src/app/(app)/equipment/page.tsx index d3cab4f..48cbec0 100644 --- a/ApiCore-Frontend/src/app/(app)/equipment/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/equipment/page.tsx @@ -9,14 +9,7 @@ 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, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; +import { ListItemCard } from "@/components/list-item-card"; export default function EquipmentPage() { const { data: equipmentList, isLoading, isError } = useQuery({ @@ -54,35 +47,19 @@ export default function EquipmentPage() { ) : null} {equipmentList && equipmentList.length > 0 ? ( - - - - Inventarnummer - Bezeichnung - Standort - Zustand - - - - {equipmentList.map((item) => ( - - - - {item.inventoryNumber} - - - {item.name} - {item.location ?? "—"} - - {item.condition} - - - ))} - -
+
+ {equipmentList.map((item) => ( + {item.condition}} + /> + ))} +
) : null} ); diff --git a/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx b/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx index d799f15..8ea9a07 100644 --- a/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useState } from "react"; -import Link from "next/link"; import { Droplet } from "lucide-react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { fetchHoneyBatches } from "@/lib/honey-batches/api"; @@ -12,6 +11,7 @@ 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 { ListItemCard } from "@/components/list-item-card"; import { Select, SelectContent, @@ -19,14 +19,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; import { CreateHoneyBatchDialog } from "./create-honey-batch-dialog"; const STATUS_OPTIONS: HoneyBatchStatus[] = [ @@ -105,37 +97,22 @@ export default function HoneyBatchesPage() { ) : null} {honeyBatches && honeyBatches.length > 0 ? ( - - - - Chargennummer - Sorte - Menge - Status - - - - {honeyBatches.map((honeyBatch) => ( - - - - {honeyBatch.batchNumber} - - - {honeyBatch.honeyType ?? "—"} - - {honeyBatch.amount !== null ? `${honeyBatch.amount} kg` : "—"} - - - {honeyBatch.status} - - - ))} - -
+
+ {honeyBatches.map((honeyBatch) => ( + {honeyBatch.batchNumber}} + subtitle={`${honeyBatch.honeyType ?? "—"} · ${ + honeyBatch.amount !== null ? `${honeyBatch.amount} kg` : "—" + }`} + trailing={ + {honeyBatch.status} + } + /> + ))} +
) : null} ); diff --git a/ApiCore-Frontend/src/app/(app)/products/page.tsx b/ApiCore-Frontend/src/app/(app)/products/page.tsx index 62d41de..509d8a1 100644 --- a/ApiCore-Frontend/src/app/(app)/products/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/products/page.tsx @@ -8,14 +8,7 @@ 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, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; +import { ListItemCard } from "@/components/list-item-card"; export default function ProductsPage() { const { data: products, isLoading, isError } = useQuery({ @@ -52,30 +45,21 @@ export default function ProductsPage() { ) : null} {products && products.length > 0 ? ( - - - - Name - SKU - Verkaufseinheit - Status - - - - {products.map((product) => ( - - {product.name} - {product.sku ?? "—"} - {product.salesUnit ?? "—"} - - - {product.status} - - - - ))} - -
+
+ {products.map((product) => ( + + {product.status} + + } + /> + ))} +
) : null} ); diff --git a/ApiCore-Frontend/src/app/(app)/queens/page.tsx b/ApiCore-Frontend/src/app/(app)/queens/page.tsx index 895d5cd..67a0ef0 100644 --- a/ApiCore-Frontend/src/app/(app)/queens/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/queens/page.tsx @@ -10,6 +10,7 @@ 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 { ListItemCard } from "@/components/list-item-card"; import { Select, SelectContent, @@ -17,14 +18,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; const STATUS_OPTIONS: QueenStatus[] = [ "ACTIVE", @@ -92,32 +85,23 @@ export default function QueensPage() { ) : null} {queens && queens.length > 0 ? ( - - - - Code - Zucht - Status - - - - {queens.map((queen) => ( - - - - {queen.code} - - - {queen.breed ?? "—"} - - - {queen.status} - - - - ))} - -
+
+ {queens.map((queen) => ( + + {queen.status} + + } + /> + ))} +
) : null} ); diff --git a/ApiCore-Frontend/src/app/(app)/settings/task-templates/page.tsx b/ApiCore-Frontend/src/app/(app)/settings/task-templates/page.tsx index cdb43b1..ce83e7b 100644 --- a/ApiCore-Frontend/src/app/(app)/settings/task-templates/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/settings/task-templates/page.tsx @@ -8,14 +8,7 @@ import { TaskTemplate, TaskTemplateRecurrenceType } from "@/lib/task-templates/t import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { PageHeader } from "@/components/page-header"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; +import { ListItemCard } from "@/components/list-item-card"; import { CreateTaskTemplateDialog } from "./create-task-template-dialog"; const RECURRENCE_LABELS: Record = { @@ -76,43 +69,32 @@ export default function TaskTemplatesPage() { ) : null} {templates && templates.length > 0 ? ( -
- - - - Titel - Wiederholung - Nächste Fälligkeit - Status - Aktion - - - - {templates.map((template) => ( - - {template.title} - {describeRecurrence(template)} - {formatDate(template.nextDueDate)} - - - {template.active ? "Aktiv" : "Inaktiv"} - - - - - - - ))} - -
+
+ {templates.map((template) => ( + + + {template.active ? "Aktiv" : "Inaktiv"} + + + + } + /> + ))}
) : null} diff --git a/ApiCore-Frontend/src/app/(app)/storage/page.tsx b/ApiCore-Frontend/src/app/(app)/storage/page.tsx index 8299e70..4045bb3 100644 --- a/ApiCore-Frontend/src/app/(app)/storage/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/storage/page.tsx @@ -6,15 +6,8 @@ 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 { ListItemCard } from "@/components/list-item-card"; import { Badge } from "@/components/ui/badge"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; import { CreateStorageContainerDialog } from "./create-storage-container-dialog"; import { CreateStockItemDialog } from "./create-stock-item-dialog"; import { RecordMovementDialog } from "./record-movement-dialog"; @@ -50,34 +43,25 @@ export default function StoragePage() { ) : null} {containers && containers.length > 0 ? ( - - - - Code - Typ - Standort - Status - QR-Code - - - - {containers.map((container) => ( - - {container.code} - {container.containerType ?? "—"} - {container.location ?? "—"} - +
+ {containers.map((container) => ( + {container.code}} + subtitle={`${container.containerType ?? "—"} · ${container.location ?? "—"}`} + trailing={ + <> {container.status} - - - - - ))} - -
+ + } + /> + ))} +
) : null} @@ -96,35 +80,28 @@ export default function StoragePage() { ) : null} {stockItems && stockItems.length > 0 ? ( - - - - Name - Kategorie - Bestand - Aktion - - - - {stockItems.map((item) => ( - - {item.name} - {item.category} - - {item.quantity} {item.unit} +
+ {stockItems.map((item) => ( + + + {item.quantity} {item.unit} + {item.minThreshold !== null && item.quantity < item.minThreshold ? ( - - Niedrig - + Niedrig ) : null} - - - - - ))} - -
+ + } + /> + ))} + ) : null} diff --git a/ApiCore-Frontend/src/app/(app)/tasks/page.tsx b/ApiCore-Frontend/src/app/(app)/tasks/page.tsx index 4dc4eb8..07bb541 100644 --- a/ApiCore-Frontend/src/app/(app)/tasks/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/tasks/page.tsx @@ -10,6 +10,7 @@ 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 { ListItemCard } from "@/components/list-item-card"; import { Select, SelectContent, @@ -17,14 +18,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; import { TaskEditDialog } from "./task-edit-dialog"; const STATUS_OPTIONS: TaskStatus[] = ["OPEN", "IN_PROGRESS", "DONE", "CANCELLED"]; @@ -137,48 +130,37 @@ export default function TasksPage() { ) : null} {tasks && visibleTasks.length > 0 ? ( - - - - Titel - Priorität - Fällig - Status - Aktion - - - - {visibleTasks.map((task) => ( - - {task.title} - {task.priority} - - {task.dueDate ? new Date(task.dueDate).toLocaleDateString("de-DE") : "—"} - - +
+ {visibleTasks.map((task) => ( + {task.status} - - -
- - {task.status !== "DONE" && task.status !== "CANCELLED" ? ( - - ) : null} -
-
- - ))} - -
+ + {task.status !== "DONE" && task.status !== "CANCELLED" ? ( + + ) : null} + + } + /> + ))} + ) : null} ); diff --git a/ApiCore-Frontend/src/components/list-item-card.tsx b/ApiCore-Frontend/src/components/list-item-card.tsx new file mode 100644 index 0000000..8cdaaa2 --- /dev/null +++ b/ApiCore-Frontend/src/components/list-item-card.tsx @@ -0,0 +1,56 @@ +import { ReactNode } from "react"; +import Link from "next/link"; +import { ChevronRight, type LucideIcon } from "lucide-react"; +import { Card, CardContent } from "@/components/ui/card"; + +interface ListItemCardProps { + href?: string; + icon: LucideIcon; + gradient?: string; + title: ReactNode; + subtitle?: ReactNode; + trailing?: ReactNode; + className?: string; +} + +export function ListItemCard({ + href, + icon: Icon, + gradient = "var(--gradient-honey)", + title, + subtitle, + trailing, + className, +}: ListItemCardProps) { + const body = ( + + +
+ +
+
+

{title}

+ {subtitle ? ( +

{subtitle}

+ ) : null} +
+ {trailing ?
{trailing}
: null} + {href ? ( + + ) : null} +
+
+ ); + + if (href) { + return ( + + {body} + + ); + } + return body; +}