120 lines
4.2 KiB
TypeScript
120 lines
4.2 KiB
TypeScript
"use client";
|
|
import { use, useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import Shell from "../../components/shell";
|
|
import LogoImg from "../../components/logo-img";
|
|
import { formatEuro } from "@/lib/vouchers";
|
|
import { getVoucherStatus } from "@/lib/voucher-status";
|
|
|
|
type Voucher = {
|
|
id: number;
|
|
name: string;
|
|
anbieter: string;
|
|
wertCents: number;
|
|
restCents: number;
|
|
ablaufdatum: string | null;
|
|
codeText: string | null;
|
|
kategorie: string | null;
|
|
notiz: string | null;
|
|
status: string;
|
|
createdBy?: string | null;
|
|
providerDomain?: string | null;
|
|
logoUrl?: string | null;
|
|
contacts?: { contact: { id: number; name: string; email: string | null; telegramChatId: string | null } }[];
|
|
};
|
|
|
|
function Row({ label, children }: { label: string; children: React.ReactNode }) {
|
|
return (
|
|
<div className="grid grid-cols-[120px_1fr] gap-3 py-2.5">
|
|
<dt className="text-sm text-muted">{label}</dt>
|
|
<dd className="text-sm font-medium">{children}</dd>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function VoucherDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
|
const { id } = use(params);
|
|
const [v, setV] = useState<Voucher | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState("");
|
|
|
|
useEffect(() => {
|
|
fetch(`/api/vouchers/${id}`)
|
|
.then((r) => (r.ok ? r.json() : Promise.reject(new Error("not found"))))
|
|
.then((d) => setV(d))
|
|
.catch(() => setError("Gutschein nicht gefunden"))
|
|
.finally(() => setLoading(false));
|
|
}, [id]);
|
|
|
|
async function redeem() {
|
|
const res = await fetch(`/api/vouchers/${id}`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ action: "redeem" }),
|
|
});
|
|
if (res.ok) setV(await res.json());
|
|
}
|
|
|
|
if (loading)
|
|
return (
|
|
<Shell>
|
|
<div className="animate-pulse rounded-card border border-line bg-white p-6">
|
|
<div className="h-6 w-1/2 rounded bg-line" />
|
|
<div className="mt-2 h-4 w-1/3 rounded bg-wash" />
|
|
</div>
|
|
</Shell>
|
|
);
|
|
if (error || !v)
|
|
return (
|
|
<Shell>
|
|
<div className="rounded-card border border-line bg-white p-8 text-center">
|
|
<p className="text-sm text-muted">{error}</p>
|
|
<Link href="/" className="mt-4 inline-block text-sm font-semibold text-brand-700 underline">
|
|
Zurück zur Liste
|
|
</Link>
|
|
</div>
|
|
</Shell>
|
|
);
|
|
|
|
const status = getVoucherStatus({
|
|
status: v.status,
|
|
ablaufdatum: v.ablaufdatum ? new Date(v.ablaufdatum) : null,
|
|
});
|
|
|
|
return (
|
|
<Shell>
|
|
<Link href="/" className="text-sm font-medium text-muted hover:text-ink">
|
|
← Zurück
|
|
</Link>
|
|
<div className="mt-2 flex items-center gap-3">
|
|
<LogoImg logoUrl={v.logoUrl ?? null} name={v.anbieter} size={52} />
|
|
<h1 className="text-2xl font-semibold tracking-tight">{v.name}</h1>
|
|
</div>
|
|
<section aria-label="Details" className="mt-4 divide-y divide-line rounded-card border border-line bg-white px-5 py-2">
|
|
<Row label="Anbieter">{v.anbieter}</Row>
|
|
<Row label="Wert">{formatEuro(v.wertCents)}</Row>
|
|
<Row label="Restguthaben">{formatEuro(v.restCents)}</Row>
|
|
<Row label="Status">{status === "aktiv" ? "Aktiv" : status === "abgelaufen" ? "Abgelaufen" : "Eingelöst"}</Row>
|
|
<Row label="Angelegt von">{v.createdBy ? v.createdBy : "Nicht erfasst"}</Row>
|
|
{v.ablaufdatum && (
|
|
<Row label="Ablaufdatum">{new Date(v.ablaufdatum).toLocaleDateString("de-DE")}</Row>
|
|
)}
|
|
{v.codeText && <Row label="Code">{v.codeText}</Row>}
|
|
{v.kategorie && <Row label="Kategorie">{v.kategorie}</Row>}
|
|
{v.notiz && <Row label="Notiz">{v.notiz}</Row>}
|
|
{v.contacts && v.contacts.length > 0 && (
|
|
<Row label="Empfänger">{v.contacts.map((c) => c.contact.name).join(", ")}</Row>
|
|
)}
|
|
</section>
|
|
{v.status !== "eingeloest" && (
|
|
<button
|
|
onClick={redeem}
|
|
className="mt-4 h-12 w-full rounded-field bg-brand-600 text-sm font-semibold text-white transition-colors hover:bg-brand-700 active:scale-[0.99] sm:w-auto sm:px-8"
|
|
>
|
|
Einlösen
|
|
</button>
|
|
)}
|
|
</Shell>
|
|
);
|
|
}
|