feat: voucher crud and list
This commit is contained in:
1 parent
73295653ee
commit
660d6cff51
7 files changed
+358
No files matched your search
@@ -0,0 +1,95 @@
|
||||
import Link from "next/link";
|
||||
import { Suspense } from "react";
|
||||
import { getDb } from "@/lib/db";
|
||||
import { getVoucherStatus } from "@/lib/voucher-status";
|
||||
import { formatEuro } from "@/lib/vouchers";
|
||||
|
||||
const FILTERS = [
|
||||
{ key: "", label: "Alle" },
|
||||
{ key: "aktiv", label: "Aktiv" },
|
||||
{ key: "bald", label: "Bald ablaufend" },
|
||||
{ key: "abgelaufen", label: "Abgelaufen" },
|
||||
{ key: "eingeloest", label: "Eingelöst" },
|
||||
] as const;
|
||||
|
||||
function Skeleton() {
|
||||
return (
|
||||
<ul aria-label="lädt">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<li key={i} className="animate-pulse rounded border p-4">
|
||||
<div className="h-4 w-1/3 bg-gray-200" />
|
||||
<div className="mt-2 h-3 w-1/4 bg-gray-100" />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
async function VoucherList({ filter }: { filter: string }) {
|
||||
const db = getDb();
|
||||
const all = await db.voucher.findMany({ orderBy: { createdAt: "desc" } });
|
||||
const now = Date.now();
|
||||
const in14d = now + 14 * 24 * 3600 * 1000;
|
||||
const filtered = all.filter((v) => {
|
||||
const s = getVoucherStatus(v);
|
||||
if (filter === "eingeloest") return s === "eingeloest";
|
||||
if (filter === "abgelaufen") return s === "abgelaufen";
|
||||
if (filter === "bald")
|
||||
return s === "aktiv" && v.ablaufdatum && +new Date(v.ablaufdatum) >= now && +new Date(v.ablaufdatum) <= in14d;
|
||||
if (filter === "aktiv") return s === "aktiv";
|
||||
return true;
|
||||
});
|
||||
if (filtered.length === 0) {
|
||||
return (
|
||||
<div className="rounded border p-8 text-center">
|
||||
<p>Keine Gutscheine gefunden.</p>
|
||||
<Link href="/vouchers/new" className="mt-4 inline-block rounded bg-black px-4 py-2 text-white">
|
||||
Gutschein anlegen
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ul className="space-y-2">
|
||||
{filtered.map((v) => (
|
||||
<li key={v.id} className="rounded border p-4">
|
||||
<Link href={`/vouchers/${v.id}`} className="font-medium underline">
|
||||
{v.name}
|
||||
</Link>
|
||||
<p className="text-sm text-gray-600">
|
||||
{v.anbieter} · {formatEuro(v.restCents)} / {formatEuro(v.wertCents)} · {getVoucherStatus(v)}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function Home({ searchParams }: { searchParams: Promise<{ filter?: string }> }) {
|
||||
const { filter = "" } = await searchParams;
|
||||
return (
|
||||
<main className="mx-auto max-w-2xl space-y-4 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold">Gutscheine</h1>
|
||||
<Link href="/vouchers/new" className="rounded bg-black px-4 py-2 text-white">
|
||||
Neu
|
||||
</Link>
|
||||
</div>
|
||||
<nav className="flex gap-2" aria-label="Filter">
|
||||
{FILTERS.map((f) => (
|
||||
<Link
|
||||
key={f.key}
|
||||
href={f.key ? `/?filter=${f.key}` : "/"}
|
||||
aria-current={filter === f.key ? "page" : undefined}
|
||||
className={`rounded border px-3 py-1 text-sm ${filter === f.key ? "bg-black text-white" : ""}`}
|
||||
>
|
||||
{f.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<Suspense fallback={<Skeleton />}>
|
||||
<VoucherList filter={filter} />
|
||||
</Suspense>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user