feat(admin): Listen mit Status-Badges, Leerzustaenden und klaren Aktionen

Neue gemeinsame Bausteine in components/admin-list.tsx: Seitenkopf mit
Hauptaktion, Suchleiste, Status-Badge (gleiche Form wie der Bestellstatus,
wartende Eintraege mit pulsierendem Punkt), Leerzustand mit Wabe und
naechstem Schritt sowie Zeilenaktionen mit einheitlichem Unterstrich-
Hover (Loeschen rot, Sperren/Zurueckziehen gedaempft).

Umgestellt: Bewertungen (SVG-Sterne statt Unicode), Benutzer, Gutscheine
(neu: Status "Abgelaufen"), Versand, Ernten, Chargen (Anlegeformulare auf
admin-form), Firmenanfragen (Status als Badge, beschriftete Auswahl) und
Audit-Log. Zahlen und Daten in Tabellen mit tabular-nums.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-25 20:51:50 +02:00
1 parent 95b4b5953d
commit a09057d960
12 files changed
+440 -300

No files matched your search

+22 -21
View File
@@ -1,8 +1,15 @@
import Link from "next/link";
import { prisma } from "@/lib/prisma";
import { BackLink } from "@/components/back-link";
import { ConfirmForm } from "@/components/confirm-form";
import { formatPrice, formatDate } from "@/lib/format";
import {
AdminActionButton,
AdminActionLink,
AdminBadge,
AdminEmptyState,
AdminPageHeader,
AdminRowActions,
} from "@/components/admin-list";
import { deleteDiscountCodeAction } from "../actions";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
@@ -10,17 +17,13 @@ import { getDictionary } from "@/i18n/get-dictionary";
export default async function AdminGutscheinePage() {
const t = getDictionary(await getLocale());
const codes = await prisma.discountCode.findMany({ orderBy: { createdAt: "desc" } });
const now = new Date();
const newVoucher = { href: "/admin/gutscheine/neu", label: t.adminVouchersPage.newVoucher };
return (
<div>
<BackLink href="/admin" label={t.adminVouchersPage.backToDashboard} />
<div className="flex items-center justify-between mb-8">
<h1 className="text-2xl text-forest">{t.adminVouchersPage.title}</h1>
<Link href="/admin/gutscheine/neu" className="bg-forest text-cream px-5 py-2 text-sm">
{t.adminVouchersPage.newVoucher}
</Link>
</div>
<AdminPageHeader title={t.adminVouchersPage.title} action={newVoucher} />
<div className="overflow-x-auto bg-background border border-ink/10">
<table className="w-full text-sm">
@@ -45,7 +48,7 @@ export default async function AdminGutscheinePage() {
</td>
<td className="p-4">{code.minOrderCents > 0 ? formatPrice(code.minOrderCents) : "—"}</td>
<td className="p-4">{code.minQuantity !== null ? code.minQuantity : "—"}</td>
<td className="p-4">
<td className="p-4 tabular-nums">
{code.usedCount}
{code.maxUses !== null ? ` / ${code.maxUses}` : ""}
</td>
@@ -54,33 +57,31 @@ export default async function AdminGutscheinePage() {
{code.validUntil ? formatDate(code.validUntil) : t.adminVouchersPage.unlimited}
</td>
<td className="p-4">
{code.isActive ? (
<span className="text-forest">{t.adminVouchersPage.statusActive}</span>
{!code.isActive ? (
<AdminBadge tone="neutral">{t.adminVouchersPage.statusInactive}</AdminBadge>
) : code.validUntil && code.validUntil < now ? (
<AdminBadge tone="pending">{t.adminVouchersPage.statusExpired}</AdminBadge>
) : (
<span className="text-ink/65">{t.adminVouchersPage.statusInactive}</span>
<AdminBadge tone="positive">{t.adminVouchersPage.statusActive}</AdminBadge>
)}
</td>
<td className="p-4">
<div className="flex items-center gap-3 whitespace-nowrap">
<Link href={`/admin/gutscheine/${code.id}`} className="text-forest underline">
{t.adminVouchersPage.edit}
</Link>
<AdminRowActions>
<AdminActionLink href={`/admin/gutscheine/${code.id}`}>{t.adminVouchersPage.edit}</AdminActionLink>
<ConfirmForm
action={deleteDiscountCodeAction}
confirmMessage={t.adminVouchersPage.deleteConfirm(code.code)}
>
<input type="hidden" name="id" value={code.id} />
<button type="submit" className="text-red-700 underline">
{t.adminVouchersPage.delete}
</button>
<AdminActionButton tone="danger">{t.adminVouchersPage.delete}</AdminActionButton>
</ConfirmForm>
</div>
</AdminRowActions>
</td>
</tr>
))}
</tbody>
</table>
{codes.length === 0 ? <p className="p-6 text-ink/65">{t.adminVouchersPage.noVouchersYet}</p> : null}
{codes.length === 0 ? <AdminEmptyState message={t.adminVouchersPage.noVouchersYet} action={newVoucher} /> : null}
</div>
</div>
);