feat: modernize main list pages from tables to card-based rows
New shared ListItemCard component: icon avatar, title/subtitle, trailing badges/actions, chevron affordance when linked to a detail page. Applied to the 9 primary data-list pages (Bienenstaende, Voelker, Koeniginnen, Honigchargen, Aufgaben, Lager, Produkte, Inventar, Wiederkehrende Aufgaben), replacing the plain HTML tables with the same rounded-card list aesthetic already used elsewhere in the app (inspections, treatments, varroa, etc.), for a consistent look across the whole product. Admin/settings tables (Benutzer, Webhooks, Audit-Log, Import) intentionally left as tables per user's scoping choice.
This commit is contained in:
1 parent
faefd47e4b
commit
c586bff18f
10 files changed
+242
-362
No files matched your search
@@ -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<ApiaryStatus | "">("");
|
||||
@@ -92,32 +85,22 @@ export default function ApiariesPage() {
|
||||
) : null}
|
||||
|
||||
{apiaries && apiaries.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Standort</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{apiaries.map((apiary) => (
|
||||
<TableRow key={apiary.id}>
|
||||
<TableCell>
|
||||
<Link className="underline" href={`/apiaries/${apiary.id}`}>
|
||||
{apiary.name}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>{apiary.location ?? "—"}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={apiary.status === "ACTIVE" ? "default" : "secondary"}>
|
||||
{apiary.status === "ACTIVE" ? "Aktiv" : "Inaktiv"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex flex-col gap-2">
|
||||
{apiaries.map((apiary) => (
|
||||
<ListItemCard
|
||||
key={apiary.id}
|
||||
href={`/apiaries/${apiary.id}`}
|
||||
icon={MapPin}
|
||||
title={apiary.name}
|
||||
subtitle={apiary.location ?? "—"}
|
||||
trailing={
|
||||
<Badge variant={apiary.status === "ACTIVE" ? "default" : "secondary"}>
|
||||
{apiary.status === "ACTIVE" ? "Aktiv" : "Inaktiv"}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Code</TableHead>
|
||||
<TableHead>Typ</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{colonies.map((colony) => (
|
||||
<TableRow key={colony.id}>
|
||||
<TableCell>
|
||||
<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={statusTone(colony.status)}>{colony.status}</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex flex-col gap-2">
|
||||
{colonies.map((colony) => (
|
||||
<ListItemCard
|
||||
key={colony.id}
|
||||
href={`/colonies/${colony.id}`}
|
||||
icon={Layers}
|
||||
title={<span className="font-mono">{colony.code}</span>}
|
||||
subtitle={colony.colonyType}
|
||||
trailing={<Badge variant={statusTone(colony.status)}>{colony.status}</Badge>}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Inventarnummer</TableHead>
|
||||
<TableHead>Bezeichnung</TableHead>
|
||||
<TableHead>Standort</TableHead>
|
||||
<TableHead>Zustand</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{equipmentList.map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell>
|
||||
<Link
|
||||
className="font-mono font-medium text-primary underline-offset-4 hover:underline"
|
||||
href={`/equipment/${item.id}`}
|
||||
>
|
||||
{item.inventoryNumber}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell>{item.location ?? "—"}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={statusTone(item.condition)}>{item.condition}</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex flex-col gap-2">
|
||||
{equipmentList.map((item) => (
|
||||
<ListItemCard
|
||||
key={item.id}
|
||||
href={`/equipment/${item.id}`}
|
||||
icon={Wrench}
|
||||
gradient="var(--gradient-sage)"
|
||||
title={item.name}
|
||||
subtitle={`${item.inventoryNumber} · ${item.location ?? "—"}`}
|
||||
trailing={<Badge variant={statusTone(item.condition)}>{item.condition}</Badge>}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Chargennummer</TableHead>
|
||||
<TableHead>Sorte</TableHead>
|
||||
<TableHead>Menge</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{honeyBatches.map((honeyBatch) => (
|
||||
<TableRow key={honeyBatch.id}>
|
||||
<TableCell>
|
||||
<Link
|
||||
className="font-mono font-medium text-primary underline-offset-4 hover:underline"
|
||||
href={`/honey-batches/${honeyBatch.id}`}
|
||||
>
|
||||
{honeyBatch.batchNumber}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>{honeyBatch.honeyType ?? "—"}</TableCell>
|
||||
<TableCell>
|
||||
{honeyBatch.amount !== null ? `${honeyBatch.amount} kg` : "—"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={statusTone(honeyBatch.status)}>{honeyBatch.status}</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex flex-col gap-2">
|
||||
{honeyBatches.map((honeyBatch) => (
|
||||
<ListItemCard
|
||||
key={honeyBatch.id}
|
||||
href={`/honey-batches/${honeyBatch.id}`}
|
||||
icon={Droplet}
|
||||
title={<span className="font-mono">{honeyBatch.batchNumber}</span>}
|
||||
subtitle={`${honeyBatch.honeyType ?? "—"} · ${
|
||||
honeyBatch.amount !== null ? `${honeyBatch.amount} kg` : "—"
|
||||
}`}
|
||||
trailing={
|
||||
<Badge variant={statusTone(honeyBatch.status)}>{honeyBatch.status}</Badge>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>SKU</TableHead>
|
||||
<TableHead>Verkaufseinheit</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{products.map((product) => (
|
||||
<TableRow key={product.id}>
|
||||
<TableCell className="font-medium">{product.name}</TableCell>
|
||||
<TableCell className="font-mono">{product.sku ?? "—"}</TableCell>
|
||||
<TableCell>{product.salesUnit ?? "—"}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={product.status === "ACTIVE" ? "default" : "secondary"}>
|
||||
{product.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex flex-col gap-2">
|
||||
{products.map((product) => (
|
||||
<ListItemCard
|
||||
key={product.id}
|
||||
icon={ShoppingBag}
|
||||
title={product.name}
|
||||
subtitle={`${product.sku ?? "—"} · ${product.salesUnit ?? "—"}`}
|
||||
trailing={
|
||||
<Badge variant={product.status === "ACTIVE" ? "default" : "secondary"}>
|
||||
{product.status}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Code</TableHead>
|
||||
<TableHead>Zucht</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{queens.map((queen) => (
|
||||
<TableRow key={queen.id}>
|
||||
<TableCell>
|
||||
<Link className="underline" href={`/queens/${queen.id}`}>
|
||||
{queen.code}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>{queen.breed ?? "—"}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={queen.status === "ACTIVE" ? "default" : "secondary"}>
|
||||
{queen.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex flex-col gap-2">
|
||||
{queens.map((queen) => (
|
||||
<ListItemCard
|
||||
key={queen.id}
|
||||
href={`/queens/${queen.id}`}
|
||||
icon={Crown}
|
||||
gradient="var(--gradient-sage)"
|
||||
title={queen.code}
|
||||
subtitle={queen.breed ?? "—"}
|
||||
trailing={
|
||||
<Badge variant={queen.status === "ACTIVE" ? "default" : "secondary"}>
|
||||
{queen.status}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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<TaskTemplateRecurrenceType, string> = {
|
||||
@@ -76,43 +69,32 @@ export default function TaskTemplatesPage() {
|
||||
) : null}
|
||||
|
||||
{templates && templates.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Titel</TableHead>
|
||||
<TableHead>Wiederholung</TableHead>
|
||||
<TableHead>Nächste Fälligkeit</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Aktion</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{templates.map((template) => (
|
||||
<TableRow key={template.id}>
|
||||
<TableCell className="font-medium">{template.title}</TableCell>
|
||||
<TableCell>{describeRecurrence(template)}</TableCell>
|
||||
<TableCell>{formatDate(template.nextDueDate)}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={template.active ? "default" : "outline"}>
|
||||
{template.active ? "Aktiv" : "Inaktiv"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={!template.active || deactivateMutation.isPending}
|
||||
onClick={() => deactivateMutation.mutate(template.id)}
|
||||
>
|
||||
Deaktivieren
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex flex-col gap-2">
|
||||
{templates.map((template) => (
|
||||
<ListItemCard
|
||||
key={template.id}
|
||||
icon={Repeat}
|
||||
gradient="var(--gradient-sky)"
|
||||
title={template.title}
|
||||
subtitle={`${describeRecurrence(template)} · Nächste Fälligkeit: ${formatDate(template.nextDueDate)}`}
|
||||
trailing={
|
||||
<>
|
||||
<Badge variant={template.active ? "default" : "outline"}>
|
||||
{template.active ? "Aktiv" : "Inaktiv"}
|
||||
</Badge>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={!template.active || deactivateMutation.isPending}
|
||||
onClick={() => deactivateMutation.mutate(template.id)}
|
||||
>
|
||||
Deaktivieren
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
|
||||
@@ -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 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Code</TableHead>
|
||||
<TableHead>Typ</TableHead>
|
||||
<TableHead>Standort</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">QR-Code</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{containers.map((container) => (
|
||||
<TableRow key={container.id}>
|
||||
<TableCell className="font-mono font-medium">{container.code}</TableCell>
|
||||
<TableCell>{container.containerType ?? "—"}</TableCell>
|
||||
<TableCell>{container.location ?? "—"}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-col gap-2">
|
||||
{containers.map((container) => (
|
||||
<ListItemCard
|
||||
key={container.id}
|
||||
icon={Warehouse}
|
||||
gradient="var(--gradient-sky)"
|
||||
title={<span className="font-mono">{container.code}</span>}
|
||||
subtitle={`${container.containerType ?? "—"} · ${container.location ?? "—"}`}
|
||||
trailing={
|
||||
<>
|
||||
<Badge variant={container.status === "FILLED" ? "default" : "secondary"}>
|
||||
{container.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<QrCodeDialog entityType="storage-containers" id={container.id} label="QR" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
@@ -96,35 +80,28 @@ export default function StoragePage() {
|
||||
) : null}
|
||||
|
||||
{stockItems && stockItems.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Kategorie</TableHead>
|
||||
<TableHead>Bestand</TableHead>
|
||||
<TableHead>Aktion</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{stockItems.map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell>{item.category}</TableCell>
|
||||
<TableCell>
|
||||
{item.quantity} {item.unit}
|
||||
<div className="flex flex-col gap-2">
|
||||
{stockItems.map((item) => (
|
||||
<ListItemCard
|
||||
key={item.id}
|
||||
icon={Package}
|
||||
gradient="var(--gradient-sky)"
|
||||
title={item.name}
|
||||
subtitle={item.category}
|
||||
trailing={
|
||||
<>
|
||||
<span className="text-sm tabular-nums">
|
||||
{item.quantity} {item.unit}
|
||||
</span>
|
||||
{item.minThreshold !== null && item.quantity < item.minThreshold ? (
|
||||
<Badge variant="destructive" className="ml-2">
|
||||
Niedrig
|
||||
</Badge>
|
||||
<Badge variant="destructive">Niedrig</Badge>
|
||||
) : null}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<RecordMovementDialog stockItemId={item.id} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -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 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Titel</TableHead>
|
||||
<TableHead>Priorität</TableHead>
|
||||
<TableHead>Fällig</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Aktion</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{visibleTasks.map((task) => (
|
||||
<TableRow key={task.id}>
|
||||
<TableCell>{task.title}</TableCell>
|
||||
<TableCell>{task.priority}</TableCell>
|
||||
<TableCell>
|
||||
{task.dueDate ? new Date(task.dueDate).toLocaleDateString("de-DE") : "—"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-col gap-2">
|
||||
{visibleTasks.map((task) => (
|
||||
<ListItemCard
|
||||
key={task.id}
|
||||
icon={ListTodo}
|
||||
gradient="var(--gradient-sky)"
|
||||
title={task.title}
|
||||
subtitle={`${task.priority} · Fällig: ${
|
||||
task.dueDate ? new Date(task.dueDate).toLocaleDateString("de-DE") : "—"
|
||||
}`}
|
||||
trailing={
|
||||
<>
|
||||
<Badge variant={task.status === "DONE" ? "secondary" : "default"}>
|
||||
{task.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<TaskEditDialog task={task} />
|
||||
{task.status !== "DONE" && task.status !== "CANCELLED" ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={completeMutation.isPending}
|
||||
onClick={() => completeMutation.mutate(task.id)}
|
||||
>
|
||||
Erledigen
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TaskEditDialog task={task} />
|
||||
{task.status !== "DONE" && task.status !== "CANCELLED" ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={completeMutation.isPending}
|
||||
onClick={() => completeMutation.mutate(task.id)}
|
||||
>
|
||||
Erledigen
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user