96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
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>
|
|
);
|
|
}
|