@xiwebdesign/xicms-core 7.18.0 → 7.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/shop/OrdersTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { collection, limit, onSnapshot, orderBy, query } from \"firebase/firestore\";\nimport type { Timestamp } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { formatCents, type Order } from \"../../schema/shop\";\nimport { Badge, Button, Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { Dialog, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { ORDER_STATUS_LABELS, centsToEuroInput, eurosToCents, shopCallable } from \"./lib\";\n\ntype StoredOrder = Order & {\n id: string;\n createdAt?: Timestamp;\n paidAt?: Timestamp;\n shippedAt?: Timestamp;\n refundedAt?: Timestamp;\n mail?: { confirmationSentAt?: Timestamp; error?: string };\n /** Runtime-velden die de Functions erbovenop schrijven (niet in OrderSchema). */\n provider?: \"stripe\" | \"mollie\";\n mollie?: { paymentId?: string };\n};\n\nconst FILTERS = [\n { value: \"all\", label: \"Alles\" },\n { value: \"pending\", label: \"Wacht op betaling\" },\n { value: \"paid\", label: \"Betaald\" },\n { value: \"shipped\", label: \"Verzonden\" },\n { value: \"cancelled\", label: \"Geannuleerd\" },\n { value: \"refunded\", label: \"Terugbetaald\" },\n { value: \"donation\", label: \"Donaties\" },\n];\n\nfunction fmtDate(t?: Timestamp): string {\n if (!t?.toDate) return \"—\";\n return t.toDate().toLocaleString(\"nl-NL\", { dateStyle: \"short\", timeStyle: \"short\" });\n}\n\n/** Btw-component uit een incl.-prijs: totaal × tarief / (100 + tarief). */\nfunction vatOf(cents: number, rate: number): number {\n return Math.round((cents * rate) / (100 + rate));\n}\n\nexport function OrdersTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [orders, setOrders] = useState<StoredOrder[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [filter, setFilter] = useState(\"all\");\n const [search, setSearch] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [invoice, setInvoice] = useState<{ url: string; number: string } | null>(null);\n const [shipOpen, setShipOpen] = useState(false);\n const [mailOpen, setMailOpen] = useState(false);\n const [mailSubject, setMailSubject] = useState(\"\");\n const [mailBody, setMailBody] = useState(\"\");\n const [mailCopy, setMailCopy] = useState(true);\n const [refundOpen, setRefundOpen] = useState(false);\n const [refundInput, setRefundInput] = useState(\"\");\n const [noteInput, setNoteInput] = useState(\"\");\n const [trackingUrl, setTrackingUrl] = useState(\"\");\n const [notify, setNotify] = useState(true);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n return onSnapshot(\n query(collection(db, \"sites\", siteId, \"orders\"), orderBy(\"createdAt\", \"desc\"), limit(300)),\n (snap) => setOrders(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Order) }) as StoredOrder)),\n );\n }, [siteId]);\n\n /** Dashboard-tegels: vandaag / deze week / omzet (betaald + verzonden). */\n const stats = useMemo(() => {\n const now = new Date();\n const startToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const startWeek = startToday - ((now.getDay() + 6) % 7) * 86_400_000;\n const paidish = orders.filter((o) => [\"paid\", \"shipped\"].includes(o.status));\n return {\n today: paidish.filter((o) => (o.createdAt?.toMillis?.() ?? 0) >= startToday).length,\n week: paidish.filter((o) => (o.createdAt?.toMillis?.() ?? 0) >= startWeek).length,\n openPaid: orders.filter((o) => o.status === \"paid\").length,\n revenueCents: paidish.reduce((n, o) => n + o.amount.totalCents, 0),\n };\n }, [orders]);\n\n const visible = useMemo(() => {\n let list = orders;\n if (filter === \"donation\") list = list.filter((o) => o.items.some((i) => i.kind === \"donation\"));\n else if (filter !== \"all\") list = list.filter((o) => o.status === filter);\n const needle = search.trim().toLowerCase();\n if (needle) {\n list = list.filter(\n (o) =>\n o.number.toLowerCase().includes(needle) ||\n o.customer.name.toLowerCase().includes(needle) ||\n o.customer.email.toLowerCase().includes(needle),\n );\n }\n return list;\n }, [orders, filter, search]);\n\n const current = orders.find((o) => o.id === selected) ?? null;\n // Notitieveld volgt de geselecteerde order (en niet andersom: typen mag niet worden overschreven).\n useEffect(() => {\n const o = orders.find((x) => x.id === selected);\n setNoteInput(o?.adminNote ?? \"\");\n }, [selected, orders]);\n\n async function setStatus(order: StoredOrder, status: \"shipped\" | \"cancelled\") {\n setBusy(true);\n try {\n await shopCallable<Record<string, unknown>, { ok: boolean }>(\"updateOrderStatus\")({\n siteId,\n orderId: order.id,\n status,\n notify,\n trackingUrl: status === \"shipped\" && trackingUrl.trim() ? trackingUrl.trim() : undefined,\n });\n toast({\n title: status === \"shipped\" ? \"Gemarkeerd als verzonden\" : \"Bestelling geannuleerd\",\n description: notify ? \"De klant heeft een e-mail ontvangen.\" : undefined,\n tone: \"success\",\n });\n setShipOpen(false);\n setTrackingUrl(\"\");\n } catch (e) {\n toast({ title: \"Status wijzigen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n /** Nog openstaand bedrag (totaal minus wat er al terug is). */\n function openstaand(order: StoredOrder): number {\n return Math.max(0, order.amount.totalCents - (order.refundedCents ?? 0));\n }\n\n async function refund(order: StoredOrder, bedragCents?: number) {\n const rest = openstaand(order);\n const bedrag = bedragCents ?? rest;\n // De provider bij naam noemen: dit beloofde altijd \"via Stripe\", ook op een Mollie-winkel.\n const providerNaam = (order.provider ?? (order.mollie?.paymentId ? \"mollie\" : \"stripe\")) === \"mollie\" ? \"Mollie\" : \"Stripe\";\n const volledig = bedrag >= rest;\n const ok = await confirm({\n title: volledig ? `Bestelling ${order.number} terugbetalen?` : `${formatCents(bedrag)} terugbetalen?`,\n description: volledig\n ? `Het openstaande bedrag (${formatCents(rest)}) wordt via ${providerNaam} teruggestort; de klant krijgt een e-mail.`\n : `Er wordt ${formatCents(bedrag)} van ${formatCents(order.amount.totalCents)} via ${providerNaam} teruggestort. De bestelling blijft verder gewoon staan.`,\n tone: \"danger\",\n confirmLabel: \"Terugbetalen\",\n });\n if (!ok) return;\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; orderId: string; amountCents?: number }, { ok: boolean }>(\"refundOrder\")({\n siteId,\n orderId: order.id,\n ...(volledig ? {} : { amountCents: bedrag }),\n });\n toast({ title: volledig ? \"Terugbetaling gestart\" : `${formatCents(bedrag)} teruggestort`, tone: \"success\" });\n setRefundOpen(false);\n setRefundInput(\"\");\n } catch (e) {\n toast({ title: \"Terugbetalen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n /** Klant rechtstreeks mailen (huisstijl + kopie naar de winkel + log bij de order). */\n async function mailKlant(order: StoredOrder) {\n if (!mailSubject.trim() || !mailBody.trim()) return;\n setBusy(true);\n try {\n await shopCallable<Record<string, unknown>, { ok: boolean; to: string }>(\"sendCustomerMail\")({\n siteId,\n orderId: order.id,\n subject: mailSubject.trim(),\n body: mailBody.trim(),\n copyToShop: mailCopy,\n });\n toast({ title: \"Bericht verstuurd\", description: order.customer.email, tone: \"success\" });\n setMailOpen(false);\n setMailSubject(\"\");\n setMailBody(\"\");\n } catch (e) {\n toast({ title: \"Versturen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n /** Interne notitie opslaan (debounced via de knop; klant ziet hem nooit). */\n async function saveNote(order: StoredOrder) {\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; orderId: string; note: string }, { ok: boolean }>(\"setOrderNote\")({\n siteId,\n orderId: order.id,\n note: noteInput,\n });\n toast({ title: \"Notitie opgeslagen\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function resendMail(order: StoredOrder) {\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; orderId: string }, { ok: boolean }>(\"resendOrderMail\")({ siteId, orderId: order.id });\n toast({ title: \"Bevestigingsmail opnieuw verstuurd\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Mail versturen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function signedUrl(order: StoredOrder, path: string): Promise<string | null> {\n try {\n const res = await shopCallable<{ siteId: string; orderId: string; path: string }, { url: string }>(\"getOrderFileUrl\")({\n siteId,\n orderId: order.id,\n path,\n });\n return res.url;\n } catch (e) {\n toast({ title: \"Bestand ophalen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n return null;\n }\n }\n\n async function download(order: StoredOrder, path: string) {\n setBusy(true);\n const url = await signedUrl(order, path);\n setBusy(false);\n if (url) window.open(url, \"_blank\", \"noopener\");\n }\n\n /** Factuur inline tonen in een dialoog (met download/openen-in-tab erin). */\n async function viewInvoice(order: StoredOrder, path: string) {\n setBusy(true);\n const url = await signedUrl(order, path);\n setBusy(false);\n if (url) setInvoice({ url, number: order.number });\n }\n\n function exportCsv() {\n const rows = [\n [\"nummer\", \"datum\", \"status\", \"naam\", \"email\", \"items\", \"subtotaal\", \"korting\", \"verzendkosten\", \"totaal\", \"btw\"],\n ...orders.map((o) => {\n const vat =\n o.items.reduce((n, i) => n + vatOf(i.unitPriceCents * i.qty, i.vatRate ?? 21), 0) +\n (o.amount.shippingCents > 0 ? vatOf(o.amount.shippingCents, 21) : 0);\n return [\n o.number,\n fmtDate(o.createdAt),\n o.status,\n o.customer.name,\n o.customer.email,\n o.items.map((i) => `${i.qty}x ${i.name}${i.variantKey ? ` (${i.variantKey})` : \"\"}`).join(\" | \"),\n (o.amount.itemsCents / 100).toFixed(2),\n ((o.amount.discountCents ?? 0) / 100).toFixed(2),\n (o.amount.shippingCents / 100).toFixed(2),\n (o.amount.totalCents / 100).toFixed(2),\n (vat / 100).toFixed(2),\n ];\n }),\n ];\n const csv = rows.map((r) => r.map((c) => `\"${String(c).replaceAll('\"', '\"\"')}\"`).join(\";\")).join(\"\\n\");\n const blob = new Blob([\"\" + csv], { type: \"text/csv;charset=utf-8\" });\n const a = document.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = `bestellingen-${siteId}.csv`;\n a.click();\n URL.revokeObjectURL(a.href);\n }\n\n return (\n <div className=\"grid gap-4\">\n {/* Dashboard-tegels */}\n <div className=\"grid grid-cols-2 gap-3 md:grid-cols-4\">\n {[\n { label: \"Vandaag\", value: String(stats.today) },\n { label: \"Deze week\", value: String(stats.week) },\n { label: \"Te verzenden\", value: String(stats.openPaid) },\n { label: \"Omzet (betaald)\", value: formatCents(stats.revenueCents) },\n ].map((s) => (\n <Card key={s.label} className=\"py-3\">\n <p className=\"text-xs text-slate-500\">{s.label}</p>\n <p className=\"text-xl font-bold\">{s.value}</p>\n </Card>\n ))}\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-[340px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold\">Bestellingen</h3>\n <Button size=\"sm\" variant=\"secondary\" onClick={exportCsv} disabled={orders.length === 0}>\n CSV\n </Button>\n </div>\n <Input placeholder=\"Zoek op nummer, naam of e-mail…\" value={search} onChange={(e) => setSearch(e.target.value)} className=\"mb-2\" />\n <Select value={filter} onValueChange={setFilter} options={FILTERS} className=\"mb-2\" />\n <ul className=\"grid max-h-[60vh] gap-1 overflow-y-auto\">\n {visible.map((o) => {\n const st = ORDER_STATUS_LABELS[o.status] ?? { label: o.status, tone: \"slate\" as const };\n return (\n <li key={o.id}>\n <button\n onClick={() => {\n setSelected(o.id);\n setShipOpen(false);\n }}\n className={`grid w-full gap-0.5 rounded-lg px-3 py-2 text-left text-sm ${\n o.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n <span className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium\">{o.number}</span>\n <Badge tone={st.tone}>{st.label}</Badge>\n </span>\n <span className=\"flex items-center justify-between gap-2 text-xs text-slate-500\">\n <span className=\"truncate\">{o.customer.name}</span>\n <span>{formatCents(o.amount.totalCents)}</span>\n </span>\n <span className=\"text-xs text-slate-400\">{fmtDate(o.createdAt)}</span>\n </button>\n </li>\n );\n })}\n {visible.length === 0 && <li className=\"px-3 py-2 text-sm text-slate-500\">Geen bestellingen.</li>}\n </ul>\n </Card>\n\n {current ? (\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"text-base font-semibold\">\n {current.number}{\" \"}\n <Badge tone={(ORDER_STATUS_LABELS[current.status] ?? { tone: \"slate\" as const }).tone}>\n {(ORDER_STATUS_LABELS[current.status] ?? { label: current.status }).label}\n </Badge>\n </h3>\n <span className=\"text-sm text-slate-500\">Geplaatst: {fmtDate(current.createdAt)}</span>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Klant</h4>\n <p className=\"text-sm\">\n {current.customer.name}\n <br />\n <button\n type=\"button\"\n className=\"text-brand-700 underline\"\n title=\"Mail deze klant vanuit de winkel (in huisstijl, met een kopie bij de bestelling)\"\n onClick={() => { setMailSubject(`Over je bestelling ${current.number}`); setMailOpen(true); }}\n >\n {current.customer.email}\n </button>\n {current.customer.phone && (\n <>\n <br />\n {current.customer.phone}\n </>\n )}\n </p>\n </div>\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Verzendadres</h4>\n {current.shipping ? (\n <p className=\"text-sm\">\n {current.shipping.address}\n <br />\n {current.shipping.zip} {current.shipping.city} ({current.shipping.country})\n </p>\n ) : (\n <p className=\"text-sm text-slate-500\">Geen verzending (donatie).</p>\n )}\n {current.trackingUrl && (\n <a className=\"text-sm text-brand-700 underline\" href={current.trackingUrl} target=\"_blank\" rel=\"noreferrer\">\n Track & trace ↗\n </a>\n )}\n </div>\n </div>\n\n {current.note && (\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Opmerking van de klant</h4>\n <p className=\"whitespace-pre-wrap text-sm\">{current.note}</p>\n </div>\n )}\n\n {/* Interne notitie: alleen voor de winkelier (\"belt terug over maat\", \"wacht op\n levering leverancier\"). Bewust naast — niet in — de klantopmerking. */}\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Interne notitie</h4>\n <Textarea\n rows={2}\n value={noteInput}\n placeholder=\"Alleen zichtbaar in het beheer…\"\n onChange={(e) => setNoteInput(e.target.value)}\n />\n {noteInput !== (current.adminNote ?? \"\") && (\n <Button size=\"sm\" variant=\"secondary\" className=\"mt-2\" disabled={busy} onClick={() => void saveNote(current)}>\n Notitie opslaan\n </Button>\n )}\n </div>\n\n {/* Wat is er al naar deze klant gestuurd? Zonder dit spoor verdwijnt elke afspraak. */}\n {(current.messages?.length ?? 0) > 0 && (\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Verstuurde berichten</h4>\n <ul className=\"grid gap-2\">\n {current.messages!.map((m, i) => (\n <li key={i} className=\"rounded-lg border border-slate-200 p-2 text-sm\">\n <p className=\"font-medium text-slate-800\">{m.subject}</p>\n <p className=\"text-xs text-slate-400\">{new Date(m.at).toLocaleString(\"nl-NL\", { dateStyle: \"short\", timeStyle: \"short\" })}</p>\n <p className=\"mt-1 whitespace-pre-wrap text-slate-600\">{m.body}</p>\n </li>\n ))}\n </ul>\n </div>\n )}\n\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Regels</h4>\n <table className=\"w-full text-sm\">\n <tbody>\n {current.items.map((i, idx) => (\n <tr key={idx} className=\"border-t border-slate-100 align-top\">\n <td className=\"py-1 pr-2\">\n {i.qty}× {i.name}\n {i.variantKey && <span className=\"text-slate-500\"> ({i.variantKey.split(\"|\").join(\" · \")})</span>}\n {i.kind === \"donation\" && <Badge tone=\"amber\">donatie</Badge>}\n {i.personalization &&\n Object.entries(i.personalization).map(([k, v]) => (\n <span key={k} className=\"block text-xs text-slate-600\">\n ↳ {v.text ?? v.fileName ?? \"eigen ontwerp\"}\n {v.imagePath && (\n <button\n type=\"button\"\n className=\"ml-1 text-brand-700 underline\"\n disabled={busy}\n onClick={() => download(current, v.imagePath as string)}\n >\n download\n </button>\n )}\n </span>\n ))}\n </td>\n <td className=\"py-1 text-right\">{formatCents(i.unitPriceCents * i.qty)}</td>\n </tr>\n ))}\n {(current.amount.discountCents ?? 0) > 0 && (\n <tr className=\"border-t border-slate-100 text-slate-500\">\n <td className=\"py-1 pr-2\">Korting{current.discount?.code ? ` (${current.discount.code})` : \"\"}</td>\n <td className=\"py-1 text-right\">−{formatCents(current.amount.discountCents ?? 0)}</td>\n </tr>\n )}\n <tr className=\"border-t border-slate-100 text-slate-500\">\n <td className=\"py-1 pr-2\">Verzendkosten</td>\n <td className=\"py-1 text-right\">\n {current.amount.shippingCents === 0 ? \"gratis\" : formatCents(current.amount.shippingCents)}\n </td>\n </tr>\n <tr className=\"border-t border-slate-200 font-semibold\">\n <td className=\"py-1 pr-2\">Totaal</td>\n <td className=\"py-1 text-right\">{formatCents(current.amount.totalCents)}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n {current.mail?.error && (\n <p className=\"rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700\">\n Bevestigingsmail mislukt: {current.mail.error}\n </p>\n )}\n\n {/* Verzenden: track & trace + klant informeren */}\n {shipOpen && current.status === \"paid\" && (\n <div className=\"grid gap-3 rounded-lg border border-slate-200 p-3\">\n <Input\n placeholder=\"Track & trace-link (optioneel, https://…)\"\n value={trackingUrl}\n onChange={(e) => setTrackingUrl(e.target.value)}\n />\n <div className=\"flex items-center gap-4\">\n <Switch checked={notify} onCheckedChange={setNotify} label=\"Klant per e-mail informeren\" />\n <Button size=\"sm\" disabled={busy} onClick={() => setStatus(current, \"shipped\")}>\n Bevestig verzending\n </Button>\n </div>\n </div>\n )}\n\n <div className=\"flex flex-wrap items-center gap-2 border-t border-slate-100 pt-4\">\n {current.status === \"paid\" && !shipOpen && (\n <Button size=\"sm\" onClick={() => setShipOpen(true)}>Markeer als verzonden</Button>\n )}\n {[\"pending\", \"paid\"].includes(current.status) && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={busy}\n onClick={async () => {\n const ok = await confirm({\n title: `Bestelling ${current.number} annuleren?`,\n description: notify ? \"De klant ontvangt een e-mail.\" : undefined,\n });\n if (ok) void setStatus(current, \"cancelled\");\n }}\n >\n Annuleer\n </Button>\n )}\n <Button size=\"sm\" variant=\"secondary\" disabled={busy} onClick={() => { setMailSubject(`Over je bestelling ${current.number}`); setMailOpen(true); }}>\n ✉ Klant mailen\n </Button>\n {(current.status === \"paid\" || current.status === \"shipped\") && (\n <>\n <Button size=\"sm\" variant=\"secondary\" disabled={busy} onClick={() => resendMail(current)}>\n Mail opnieuw versturen\n </Button>\n <Button size=\"sm\" variant=\"danger\" disabled={busy || openstaand(current) === 0} onClick={() => { setRefundInput(centsToEuroInput(openstaand(current))); setRefundOpen(true); }}>\n Terugbetalen\n </Button>\n </>\n )}\n {current.invoicePath && (\n <Button size=\"sm\" variant=\"secondary\" disabled={busy} onClick={() => viewInvoice(current, current.invoicePath as string)}>\n Factuur bekijken\n </Button>\n )}\n {current.stripe?.paymentIntentId && (\n <a\n className=\"ml-auto text-sm text-brand-700 underline\"\n href={`https://dashboard.stripe.com/payments/${current.stripe.paymentIntentId}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n Bekijk in Stripe ↗\n </a>\n )}\n </div>\n </div>\n </Card>\n ) : (\n <Card>\n <p className=\"text-sm text-slate-500\">Selecteer een bestelling.</p>\n </Card>\n )}\n </div>\n\n {/* Klant mailen — huisstijl, kopie naar de winkel, en het bericht komt bij de order te staan. */}\n <Dialog open={mailOpen} onOpenChange={setMailOpen}>\n <DialogContent title={current ? `Mail ${current.customer.name}` : \"Klant mailen\"} width=\"min(92vw, 34rem)\">\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-500\">\n Gaat naar <strong>{current?.customer.email}</strong> in de huisstijl van je site. Antwoorden\n komen binnen op je notificatie-adres.\n </p>\n <Field label=\"Onderwerp\">\n <Input value={mailSubject} onChange={(e) => setMailSubject(e.target.value)} />\n </Field>\n <Field label=\"Bericht\">\n <Textarea rows={7} value={mailBody} onChange={(e) => setMailBody(e.target.value)} placeholder=\"Beste …\" />\n </Field>\n <Switch checked={mailCopy} onCheckedChange={setMailCopy} label=\"Kopie naar mijzelf\" />\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild>\n <Button variant=\"secondary\">Annuleren</Button>\n </DialogClose>\n <Button disabled={busy || !mailSubject.trim() || !mailBody.trim()} onClick={() => current && void mailKlant(current)}>\n {busy ? \"Bezig…\" : \"Versturen\"}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Terugbetalen — geheel of gedeeltelijk. */}\n <Dialog open={refundOpen} onOpenChange={setRefundOpen}>\n <DialogContent title={current ? `Terugbetalen — ${current.number}` : \"Terugbetalen\"} width=\"min(92vw, 26rem)\">\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-500\">\n Openstaand: <strong>{current ? formatCents(openstaand(current)) : \"—\"}</strong>\n {current && (current.refundedCents ?? 0) > 0 && (\n <> (er is al {formatCents(current.refundedCents as number)} terugbetaald)</>\n )}\n </p>\n <Field label=\"Bedrag (€)\" hint=\"Laat het volledige bedrag staan voor een hele terugbetaling\">\n <Input value={refundInput} onChange={(e) => setRefundInput(e.target.value)} className=\"w-36\" />\n </Field>\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild>\n <Button variant=\"secondary\">Annuleren</Button>\n </DialogClose>\n <Button\n variant=\"danger\"\n disabled={busy}\n onClick={() => {\n if (!current) return;\n const cents = eurosToCents(refundInput);\n if (cents === null || cents <= 0) {\n toast({ title: \"Vul een geldig bedrag in\", tone: \"error\" });\n return;\n }\n void refund(current, Math.min(cents, openstaand(current)));\n }}\n >\n {busy ? \"Bezig…\" : \"Terugbetalen\"}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n\n <Dialog open={!!invoice} onOpenChange={(o) => !o && setInvoice(null)}>\n {invoice && (\n <DialogContent title={`Factuur ${invoice.number}`} width=\"min(92vw, 48rem)\">\n <iframe\n title={`Factuur ${invoice.number}`}\n src={invoice.url}\n className=\"h-[70vh] w-full rounded-lg border border-slate-200 bg-white\"\n />\n <div className=\"mt-3 flex items-center gap-2\">\n <a href={invoice.url} target=\"_blank\" rel=\"noreferrer\" download={`factuur-${invoice.number}.pdf`}>\n <Button size=\"sm\" variant=\"secondary\">Downloaden</Button>\n </a>\n <a href={invoice.url} target=\"_blank\" rel=\"noreferrer\" className=\"text-sm text-brand-700 underline\">\n Openen in nieuw tabblad ↗\n </a>\n <DialogClose asChild>\n <Button size=\"sm\" variant=\"ghost\" className=\"ml-auto\">Sluiten</Button>\n </DialogClose>\n </div>\n </DialogContent>\n )}\n </Dialog>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/shop/OrdersTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { collection, getDocs, limit, onSnapshot, orderBy, query, where } from \"firebase/firestore\";\nimport type { Timestamp } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { formatCents, type Order } from \"../../schema/shop\";\nimport { Badge, Button, Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { Dialog, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { ORDER_STATUS_LABELS, centsToEuroInput, eurosToCents, shopCallable } from \"./lib\";\n\n/** Minimale productvorm voor de keuzelijst van een handmatige bestelling. */\ntype ProductKeuze = { id: string; name: string; options?: { name: string }[]; variants?: { key: string }[] };\n\ntype StoredOrder = Order & {\n id: string;\n createdAt?: Timestamp;\n paidAt?: Timestamp;\n shippedAt?: Timestamp;\n refundedAt?: Timestamp;\n mail?: { confirmationSentAt?: Timestamp; error?: string };\n /** Runtime-velden die de Functions erbovenop schrijven (niet in OrderSchema). */\n provider?: \"stripe\" | \"mollie\";\n mollie?: { paymentId?: string };\n};\n\nconst FILTERS = [\n { value: \"all\", label: \"Alles\" },\n { value: \"pending\", label: \"Wacht op betaling\" },\n { value: \"paid\", label: \"Betaald\" },\n { value: \"shipped\", label: \"Verzonden\" },\n { value: \"cancelled\", label: \"Geannuleerd\" },\n { value: \"refunded\", label: \"Terugbetaald\" },\n { value: \"donation\", label: \"Donaties\" },\n];\n\nfunction fmtDate(t?: Timestamp): string {\n if (!t?.toDate) return \"—\";\n return t.toDate().toLocaleString(\"nl-NL\", { dateStyle: \"short\", timeStyle: \"short\" });\n}\n\n/** Btw-component uit een incl.-prijs: totaal × tarief / (100 + tarief). */\nfunction vatOf(cents: number, rate: number): number {\n return Math.round((cents * rate) / (100 + rate));\n}\n\nexport function OrdersTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [orders, setOrders] = useState<StoredOrder[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [filter, setFilter] = useState(\"all\");\n const [search, setSearch] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [invoice, setInvoice] = useState<{ url: string; number: string } | null>(null);\n const [shipOpen, setShipOpen] = useState(false);\n const [mailOpen, setMailOpen] = useState(false);\n const [mailSubject, setMailSubject] = useState(\"\");\n const [mailBody, setMailBody] = useState(\"\");\n const [mailCopy, setMailCopy] = useState(true);\n const [refundOpen, setRefundOpen] = useState(false);\n const [refundInput, setRefundInput] = useState(\"\");\n const [noteInput, setNoteInput] = useState(\"\");\n const [manualOpen, setManualOpen] = useState(false);\n const [manual, setManual] = useState({ name: \"\", email: \"\", phone: \"\", address: \"\", zip: \"\", city: \"\", shipping: \"\", note: \"\", paid: true, notify: false });\n const [manualLines, setManualLines] = useState<{ productId: string; variantKey: string; qty: number }[]>([]);\n const [producten, setProducten] = useState<ProductKeuze[]>([]);\n\n // Producten pas ophalen als het handmatige formulier opengaat: de bestellijst zelf heeft ze niet nodig.\n useEffect(() => {\n if (!manualOpen || producten.length > 0) return;\n const db = getDb();\n if (!db) return;\n void getDocs(query(collection(db, \"sites\", siteId, \"products\"), where(\"status\", \"==\", \"active\"))).then((snap) =>\n setProducten(\n snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as Omit<ProductKeuze, \"id\">) }))\n .sort((a, b) => a.name.localeCompare(b.name, \"nl\")),\n ),\n );\n }, [manualOpen, producten.length, siteId]);\n const [trackingUrl, setTrackingUrl] = useState(\"\");\n const [notify, setNotify] = useState(true);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n return onSnapshot(\n query(collection(db, \"sites\", siteId, \"orders\"), orderBy(\"createdAt\", \"desc\"), limit(300)),\n (snap) => setOrders(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Order) }) as StoredOrder)),\n );\n }, [siteId]);\n\n /** Dashboard-tegels: vandaag / deze week / omzet (betaald + verzonden). */\n const stats = useMemo(() => {\n const now = new Date();\n const startToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const startWeek = startToday - ((now.getDay() + 6) % 7) * 86_400_000;\n const paidish = orders.filter((o) => [\"paid\", \"shipped\"].includes(o.status));\n return {\n today: paidish.filter((o) => (o.createdAt?.toMillis?.() ?? 0) >= startToday).length,\n week: paidish.filter((o) => (o.createdAt?.toMillis?.() ?? 0) >= startWeek).length,\n openPaid: orders.filter((o) => o.status === \"paid\").length,\n revenueCents: paidish.reduce((n, o) => n + o.amount.totalCents, 0),\n };\n }, [orders]);\n\n const visible = useMemo(() => {\n let list = orders;\n if (filter === \"donation\") list = list.filter((o) => o.items.some((i) => i.kind === \"donation\"));\n else if (filter !== \"all\") list = list.filter((o) => o.status === filter);\n const needle = search.trim().toLowerCase();\n if (needle) {\n list = list.filter(\n (o) =>\n o.number.toLowerCase().includes(needle) ||\n o.customer.name.toLowerCase().includes(needle) ||\n o.customer.email.toLowerCase().includes(needle),\n );\n }\n return list;\n }, [orders, filter, search]);\n\n const current = orders.find((o) => o.id === selected) ?? null;\n // Notitieveld volgt de geselecteerde order (en niet andersom: typen mag niet worden overschreven).\n useEffect(() => {\n const o = orders.find((x) => x.id === selected);\n setNoteInput(o?.adminNote ?? \"\");\n }, [selected, orders]);\n\n async function setStatus(order: StoredOrder, status: \"shipped\" | \"cancelled\") {\n setBusy(true);\n try {\n await shopCallable<Record<string, unknown>, { ok: boolean }>(\"updateOrderStatus\")({\n siteId,\n orderId: order.id,\n status,\n notify,\n trackingUrl: status === \"shipped\" && trackingUrl.trim() ? trackingUrl.trim() : undefined,\n });\n toast({\n title: status === \"shipped\" ? \"Gemarkeerd als verzonden\" : \"Bestelling geannuleerd\",\n description: notify ? \"De klant heeft een e-mail ontvangen.\" : undefined,\n tone: \"success\",\n });\n setShipOpen(false);\n setTrackingUrl(\"\");\n } catch (e) {\n toast({ title: \"Status wijzigen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n /** Nog openstaand bedrag (totaal minus wat er al terug is). */\n function openstaand(order: StoredOrder): number {\n return Math.max(0, order.amount.totalCents - (order.refundedCents ?? 0));\n }\n\n async function refund(order: StoredOrder, bedragCents?: number) {\n const rest = openstaand(order);\n const bedrag = bedragCents ?? rest;\n // De provider bij naam noemen: dit beloofde altijd \"via Stripe\", ook op een Mollie-winkel.\n const providerNaam = (order.provider ?? (order.mollie?.paymentId ? \"mollie\" : \"stripe\")) === \"mollie\" ? \"Mollie\" : \"Stripe\";\n const volledig = bedrag >= rest;\n const ok = await confirm({\n title: volledig ? `Bestelling ${order.number} terugbetalen?` : `${formatCents(bedrag)} terugbetalen?`,\n description: volledig\n ? `Het openstaande bedrag (${formatCents(rest)}) wordt via ${providerNaam} teruggestort; de klant krijgt een e-mail.`\n : `Er wordt ${formatCents(bedrag)} van ${formatCents(order.amount.totalCents)} via ${providerNaam} teruggestort. De bestelling blijft verder gewoon staan.`,\n tone: \"danger\",\n confirmLabel: \"Terugbetalen\",\n });\n if (!ok) return;\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; orderId: string; amountCents?: number }, { ok: boolean }>(\"refundOrder\")({\n siteId,\n orderId: order.id,\n ...(volledig ? {} : { amountCents: bedrag }),\n });\n toast({ title: volledig ? \"Terugbetaling gestart\" : `${formatCents(bedrag)} teruggestort`, tone: \"success\" });\n setRefundOpen(false);\n setRefundInput(\"\");\n } catch (e) {\n toast({ title: \"Terugbetalen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n /** Klant rechtstreeks mailen (huisstijl + kopie naar de winkel + log bij de order). */\n async function mailKlant(order: StoredOrder) {\n if (!mailSubject.trim() || !mailBody.trim()) return;\n setBusy(true);\n try {\n await shopCallable<Record<string, unknown>, { ok: boolean; to: string }>(\"sendCustomerMail\")({\n siteId,\n orderId: order.id,\n subject: mailSubject.trim(),\n body: mailBody.trim(),\n copyToShop: mailCopy,\n });\n toast({ title: \"Bericht verstuurd\", description: order.customer.email, tone: \"success\" });\n setMailOpen(false);\n setMailSubject(\"\");\n setMailBody(\"\");\n } catch (e) {\n toast({ title: \"Versturen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n /** Handmatige bestelling aanmaken (telefonisch/markt/overschrijving). */\n async function createManual() {\n const regels = manualLines.filter((l) => l.productId && l.qty > 0);\n if (!manual.name.trim() || !manual.email.trim() || regels.length === 0) {\n toast({ title: \"Vul naam, e-mail en minstens één product in\", tone: \"error\" });\n return;\n }\n setBusy(true);\n try {\n const res = await shopCallable<Record<string, unknown>, { id: string }>(\"createManualOrder\")({\n siteId,\n items: regels.map((l) => ({ productId: l.productId, variantKey: l.variantKey || undefined, qty: l.qty })),\n customer: { name: manual.name.trim(), email: manual.email.trim(), phone: manual.phone.trim() || undefined },\n shipping: manual.address.trim()\n ? { address: manual.address.trim(), zip: manual.zip.trim(), city: manual.city.trim(), country: \"NL\" }\n : undefined,\n shippingCents: eurosToCents(manual.shipping) ?? 0,\n note: manual.note.trim() || undefined,\n alBetaald: manual.paid,\n notify: manual.notify,\n });\n toast({ title: \"Bestelling aangemaakt\", description: manual.paid ? \"Geboekt als betaald.\" : \"Staat open.\", tone: \"success\" });\n setManualOpen(false);\n setManualLines([]);\n setManual({ name: \"\", email: \"\", phone: \"\", address: \"\", zip: \"\", city: \"\", shipping: \"\", note: \"\", paid: true, notify: false });\n setSelected(res.id);\n } catch (e) {\n toast({ title: \"Aanmaken mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n /** Openstaande bestelling handmatig op betaald zetten (overschrijving ontvangen). */\n async function markPaid(order: StoredOrder) {\n const ok = await confirm({\n title: `Bestelling ${order.number} als betaald boeken?`,\n description: \"Gebruik dit als je het geld buiten de webwinkel om hebt ontvangen (overschrijving, contant, pin). De voorraad wordt afgeboekt en de factuur wordt gemaakt.\",\n confirmLabel: \"Als betaald boeken\",\n });\n if (!ok) return;\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; orderId: string; notify: boolean }, { ok: boolean }>(\"markOrderPaidManually\")({\n siteId,\n orderId: order.id,\n notify,\n });\n toast({ title: \"Geboekt als betaald\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n /** Interne notitie opslaan (debounced via de knop; klant ziet hem nooit). */\n async function saveNote(order: StoredOrder) {\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; orderId: string; note: string }, { ok: boolean }>(\"setOrderNote\")({\n siteId,\n orderId: order.id,\n note: noteInput,\n });\n toast({ title: \"Notitie opgeslagen\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function resendMail(order: StoredOrder) {\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; orderId: string }, { ok: boolean }>(\"resendOrderMail\")({ siteId, orderId: order.id });\n toast({ title: \"Bevestigingsmail opnieuw verstuurd\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Mail versturen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function signedUrl(order: StoredOrder, path: string): Promise<string | null> {\n try {\n const res = await shopCallable<{ siteId: string; orderId: string; path: string }, { url: string }>(\"getOrderFileUrl\")({\n siteId,\n orderId: order.id,\n path,\n });\n return res.url;\n } catch (e) {\n toast({ title: \"Bestand ophalen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n return null;\n }\n }\n\n async function download(order: StoredOrder, path: string) {\n setBusy(true);\n const url = await signedUrl(order, path);\n setBusy(false);\n if (url) window.open(url, \"_blank\", \"noopener\");\n }\n\n /** Factuur inline tonen in een dialoog (met download/openen-in-tab erin). */\n async function viewInvoice(order: StoredOrder, path: string) {\n setBusy(true);\n const url = await signedUrl(order, path);\n setBusy(false);\n if (url) setInvoice({ url, number: order.number });\n }\n\n function exportCsv() {\n const rows = [\n [\"nummer\", \"datum\", \"status\", \"naam\", \"email\", \"items\", \"subtotaal\", \"korting\", \"verzendkosten\", \"totaal\", \"btw\"],\n ...orders.map((o) => {\n const vat =\n o.items.reduce((n, i) => n + vatOf(i.unitPriceCents * i.qty, i.vatRate ?? 21), 0) +\n (o.amount.shippingCents > 0 ? vatOf(o.amount.shippingCents, 21) : 0);\n return [\n o.number,\n fmtDate(o.createdAt),\n o.status,\n o.customer.name,\n o.customer.email,\n o.items.map((i) => `${i.qty}x ${i.name}${i.variantKey ? ` (${i.variantKey})` : \"\"}`).join(\" | \"),\n (o.amount.itemsCents / 100).toFixed(2),\n ((o.amount.discountCents ?? 0) / 100).toFixed(2),\n (o.amount.shippingCents / 100).toFixed(2),\n (o.amount.totalCents / 100).toFixed(2),\n (vat / 100).toFixed(2),\n ];\n }),\n ];\n const csv = rows.map((r) => r.map((c) => `\"${String(c).replaceAll('\"', '\"\"')}\"`).join(\";\")).join(\"\\n\");\n const blob = new Blob([\"\" + csv], { type: \"text/csv;charset=utf-8\" });\n const a = document.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = `bestellingen-${siteId}.csv`;\n a.click();\n URL.revokeObjectURL(a.href);\n }\n\n return (\n <div className=\"grid gap-4\">\n {/* Dashboard-tegels */}\n <div className=\"grid grid-cols-2 gap-3 md:grid-cols-4\">\n {[\n { label: \"Vandaag\", value: String(stats.today) },\n { label: \"Deze week\", value: String(stats.week) },\n { label: \"Te verzenden\", value: String(stats.openPaid) },\n { label: \"Omzet (betaald)\", value: formatCents(stats.revenueCents) },\n ].map((s) => (\n <Card key={s.label} className=\"py-3\">\n <p className=\"text-xs text-slate-500\">{s.label}</p>\n <p className=\"text-xl font-bold\">{s.value}</p>\n </Card>\n ))}\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-[340px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold\">Bestellingen</h3>\n <div className=\"flex gap-2\">\n <Button size=\"sm\" onClick={() => setManualOpen(true)}>+ Handmatig</Button>\n <Button size=\"sm\" variant=\"secondary\" onClick={exportCsv} disabled={orders.length === 0}>\n CSV\n </Button>\n </div>\n </div>\n <Input placeholder=\"Zoek op nummer, naam of e-mail…\" value={search} onChange={(e) => setSearch(e.target.value)} className=\"mb-2\" />\n <Select value={filter} onValueChange={setFilter} options={FILTERS} className=\"mb-2\" />\n <ul className=\"grid max-h-[60vh] gap-1 overflow-y-auto\">\n {visible.map((o) => {\n const st = ORDER_STATUS_LABELS[o.status] ?? { label: o.status, tone: \"slate\" as const };\n return (\n <li key={o.id}>\n <button\n onClick={() => {\n setSelected(o.id);\n setShipOpen(false);\n }}\n className={`grid w-full gap-0.5 rounded-lg px-3 py-2 text-left text-sm ${\n o.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n <span className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium\">{o.number}</span>\n <Badge tone={st.tone}>{st.label}</Badge>\n </span>\n <span className=\"flex items-center justify-between gap-2 text-xs text-slate-500\">\n <span className=\"truncate\">{o.customer.name}</span>\n <span>{formatCents(o.amount.totalCents)}</span>\n </span>\n <span className=\"text-xs text-slate-400\">{fmtDate(o.createdAt)}</span>\n </button>\n </li>\n );\n })}\n {visible.length === 0 && <li className=\"px-3 py-2 text-sm text-slate-500\">Geen bestellingen.</li>}\n </ul>\n </Card>\n\n {current ? (\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"text-base font-semibold\">\n {current.number}{\" \"}\n <Badge tone={(ORDER_STATUS_LABELS[current.status] ?? { tone: \"slate\" as const }).tone}>\n {(ORDER_STATUS_LABELS[current.status] ?? { label: current.status }).label}\n </Badge>\n </h3>\n <span className=\"text-sm text-slate-500\">Geplaatst: {fmtDate(current.createdAt)}</span>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Klant</h4>\n <p className=\"text-sm\">\n {current.customer.name}\n <br />\n <button\n type=\"button\"\n className=\"text-brand-700 underline\"\n title=\"Mail deze klant vanuit de winkel (in huisstijl, met een kopie bij de bestelling)\"\n onClick={() => { setMailSubject(`Over je bestelling ${current.number}`); setMailOpen(true); }}\n >\n {current.customer.email}\n </button>\n {current.customer.phone && (\n <>\n <br />\n {current.customer.phone}\n </>\n )}\n </p>\n </div>\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Verzendadres</h4>\n {current.shipping ? (\n <p className=\"text-sm\">\n {current.shipping.address}\n <br />\n {current.shipping.zip} {current.shipping.city} ({current.shipping.country})\n </p>\n ) : (\n <p className=\"text-sm text-slate-500\">Geen verzending (donatie).</p>\n )}\n {current.trackingUrl && (\n <a className=\"text-sm text-brand-700 underline\" href={current.trackingUrl} target=\"_blank\" rel=\"noreferrer\">\n Track & trace ↗\n </a>\n )}\n </div>\n </div>\n\n {current.note && (\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Opmerking van de klant</h4>\n <p className=\"whitespace-pre-wrap text-sm\">{current.note}</p>\n </div>\n )}\n\n {/* Interne notitie: alleen voor de winkelier (\"belt terug over maat\", \"wacht op\n levering leverancier\"). Bewust naast — niet in — de klantopmerking. */}\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Interne notitie</h4>\n <Textarea\n rows={2}\n value={noteInput}\n placeholder=\"Alleen zichtbaar in het beheer…\"\n onChange={(e) => setNoteInput(e.target.value)}\n />\n {noteInput !== (current.adminNote ?? \"\") && (\n <Button size=\"sm\" variant=\"secondary\" className=\"mt-2\" disabled={busy} onClick={() => void saveNote(current)}>\n Notitie opslaan\n </Button>\n )}\n </div>\n\n {/* Wat is er al naar deze klant gestuurd? Zonder dit spoor verdwijnt elke afspraak. */}\n {(current.messages?.length ?? 0) > 0 && (\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Verstuurde berichten</h4>\n <ul className=\"grid gap-2\">\n {current.messages!.map((m, i) => (\n <li key={i} className=\"rounded-lg border border-slate-200 p-2 text-sm\">\n <p className=\"font-medium text-slate-800\">{m.subject}</p>\n <p className=\"text-xs text-slate-400\">{new Date(m.at).toLocaleString(\"nl-NL\", { dateStyle: \"short\", timeStyle: \"short\" })}</p>\n <p className=\"mt-1 whitespace-pre-wrap text-slate-600\">{m.body}</p>\n </li>\n ))}\n </ul>\n </div>\n )}\n\n <div>\n <h4 className=\"mb-1 text-sm font-semibold\">Regels</h4>\n <table className=\"w-full text-sm\">\n <tbody>\n {current.items.map((i, idx) => (\n <tr key={idx} className=\"border-t border-slate-100 align-top\">\n <td className=\"py-1 pr-2\">\n {i.qty}× {i.name}\n {i.variantKey && <span className=\"text-slate-500\"> ({i.variantKey.split(\"|\").join(\" · \")})</span>}\n {i.kind === \"donation\" && <Badge tone=\"amber\">donatie</Badge>}\n {i.personalization &&\n Object.entries(i.personalization).map(([k, v]) => (\n <span key={k} className=\"block text-xs text-slate-600\">\n ↳ {v.text ?? v.fileName ?? \"eigen ontwerp\"}\n {v.imagePath && (\n <button\n type=\"button\"\n className=\"ml-1 text-brand-700 underline\"\n disabled={busy}\n onClick={() => download(current, v.imagePath as string)}\n >\n download\n </button>\n )}\n </span>\n ))}\n </td>\n <td className=\"py-1 text-right\">{formatCents(i.unitPriceCents * i.qty)}</td>\n </tr>\n ))}\n {(current.amount.discountCents ?? 0) > 0 && (\n <tr className=\"border-t border-slate-100 text-slate-500\">\n <td className=\"py-1 pr-2\">Korting{current.discount?.code ? ` (${current.discount.code})` : \"\"}</td>\n <td className=\"py-1 text-right\">−{formatCents(current.amount.discountCents ?? 0)}</td>\n </tr>\n )}\n <tr className=\"border-t border-slate-100 text-slate-500\">\n <td className=\"py-1 pr-2\">Verzendkosten</td>\n <td className=\"py-1 text-right\">\n {current.amount.shippingCents === 0 ? \"gratis\" : formatCents(current.amount.shippingCents)}\n </td>\n </tr>\n <tr className=\"border-t border-slate-200 font-semibold\">\n <td className=\"py-1 pr-2\">Totaal</td>\n <td className=\"py-1 text-right\">{formatCents(current.amount.totalCents)}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n {current.mail?.error ? (\n <p className=\"rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700\">\n Bevestigingsmail mislukt: {current.mail.error}\n </p>\n ) : current.mail?.confirmationSentAt ? (\n /* Geslaagde verzending werd wél vastgelegd maar nergens getoond: je zag alleen\n iets bij een FOUT, en moest dus maar aannemen dat de mail was aangekomen. */\n <p className=\"text-xs text-slate-400\">\n Bevestigingsmail verstuurd op {fmtDate(current.mail.confirmationSentAt)}\n </p>\n ) : null}\n\n {/* Verzenden: track & trace + klant informeren */}\n {shipOpen && current.status === \"paid\" && (\n <div className=\"grid gap-3 rounded-lg border border-slate-200 p-3\">\n <Input\n placeholder=\"Track & trace-link (optioneel, https://…)\"\n value={trackingUrl}\n onChange={(e) => setTrackingUrl(e.target.value)}\n />\n <div className=\"flex items-center gap-4\">\n <Switch checked={notify} onCheckedChange={setNotify} label=\"Klant per e-mail informeren\" />\n <Button size=\"sm\" disabled={busy} onClick={() => setStatus(current, \"shipped\")}>\n Bevestig verzending\n </Button>\n </div>\n </div>\n )}\n\n <div className=\"flex flex-wrap items-center gap-2 border-t border-slate-100 pt-4\">\n {current.status === \"paid\" && !shipOpen && (\n <Button size=\"sm\" onClick={() => setShipOpen(true)}>Markeer als verzonden</Button>\n )}\n {/* Geld buiten de winkel om ontvangen (overschrijving/contant/pin). */}\n {current.status === \"pending\" && (\n <Button size=\"sm\" disabled={busy} onClick={() => void markPaid(current)}>\n Als betaald boeken\n </Button>\n )}\n {[\"pending\", \"paid\"].includes(current.status) && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={busy}\n onClick={async () => {\n const ok = await confirm({\n title: `Bestelling ${current.number} annuleren?`,\n description: notify ? \"De klant ontvangt een e-mail.\" : undefined,\n });\n if (ok) void setStatus(current, \"cancelled\");\n }}\n >\n Annuleer\n </Button>\n )}\n <Button size=\"sm\" variant=\"secondary\" disabled={busy} onClick={() => { setMailSubject(`Over je bestelling ${current.number}`); setMailOpen(true); }}>\n ✉ Klant mailen\n </Button>\n {(current.status === \"paid\" || current.status === \"shipped\") && (\n <>\n <Button size=\"sm\" variant=\"secondary\" disabled={busy} onClick={() => resendMail(current)}>\n Mail opnieuw versturen\n </Button>\n <Button size=\"sm\" variant=\"danger\" disabled={busy || openstaand(current) === 0} onClick={() => { setRefundInput(centsToEuroInput(openstaand(current))); setRefundOpen(true); }}>\n Terugbetalen\n </Button>\n </>\n )}\n {current.invoicePath && (\n <Button size=\"sm\" variant=\"secondary\" disabled={busy} onClick={() => viewInvoice(current, current.invoicePath as string)}>\n Factuur bekijken\n </Button>\n )}\n {current.stripe?.paymentIntentId && (\n <a\n className=\"ml-auto text-sm text-brand-700 underline\"\n href={`https://dashboard.stripe.com/payments/${current.stripe.paymentIntentId}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n Bekijk in Stripe ↗\n </a>\n )}\n </div>\n </div>\n </Card>\n ) : (\n <Card>\n <p className=\"text-sm text-slate-500\">Selecteer een bestelling.</p>\n </Card>\n )}\n </div>\n\n {/* Handmatige bestelling: telefonisch, op de markt, of per bankoverschrijving. */}\n <Dialog open={manualOpen} onOpenChange={setManualOpen}>\n <DialogContent title=\"Bestelling handmatig aanmaken\" width=\"min(92vw, 44rem)\">\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-500\">\n Voor een bestelling die buiten de webwinkel om binnenkwam. Prijzen en btw komen uit je\n productenlijst, dus de omzet en de voorraad blijven kloppen.\n </p>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <Field label=\"Naam\"><Input value={manual.name} onChange={(e) => setManual((p) => ({ ...p, name: e.target.value }))} /></Field>\n <Field label=\"E-mail\"><Input value={manual.email} onChange={(e) => setManual((p) => ({ ...p, email: e.target.value }))} /></Field>\n <Field label=\"Telefoon (optioneel)\"><Input value={manual.phone} onChange={(e) => setManual((p) => ({ ...p, phone: e.target.value }))} /></Field>\n </div>\n\n <div>\n <p className=\"mb-1 text-sm font-medium\">Producten</p>\n <div className=\"grid gap-2\">\n {manualLines.map((l, i) => {\n const prod = producten.find((p) => p.id === l.productId);\n const varianten = (prod?.options?.length ?? 0) > 0 ? (prod?.variants ?? []) : [];\n return (\n <div key={i} className=\"grid grid-cols-[1fr_150px_70px_auto] items-center gap-2\">\n <Select\n value={l.productId}\n onValueChange={(v) => setManualLines((p) => p.map((x, j) => (j === i ? { ...x, productId: v, variantKey: \"\" } : x)))}\n placeholder=\"Kies product…\"\n options={producten.map((p) => ({ value: p.id, label: p.name }))}\n />\n {varianten.length > 0 ? (\n <Select\n value={l.variantKey}\n onValueChange={(v) => setManualLines((p) => p.map((x, j) => (j === i ? { ...x, variantKey: v } : x)))}\n placeholder=\"Uitvoering…\"\n options={varianten.map((v) => ({ value: v.key, label: v.key.split(\"|\").join(\", \") }))}\n />\n ) : (\n <span className=\"text-xs text-slate-400\">—</span>\n )}\n <Input\n type=\"number\"\n value={String(l.qty)}\n onChange={(e) => setManualLines((p) => p.map((x, j) => (j === i ? { ...x, qty: Math.max(1, Number(e.target.value) || 1) } : x)))}\n />\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setManualLines((p) => p.filter((_, j) => j !== i))}>✕</Button>\n </div>\n );\n })}\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setManualLines((p) => [...p, { productId: \"\", variantKey: \"\", qty: 1 }])}>\n + Regel\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-3 md:grid-cols-4\">\n <Field label=\"Adres\"><Input value={manual.address} onChange={(e) => setManual((p) => ({ ...p, address: e.target.value }))} /></Field>\n <Field label=\"Postcode\"><Input value={manual.zip} onChange={(e) => setManual((p) => ({ ...p, zip: e.target.value }))} /></Field>\n <Field label=\"Plaats\"><Input value={manual.city} onChange={(e) => setManual((p) => ({ ...p, city: e.target.value }))} /></Field>\n <Field label=\"Verzendkosten (€)\"><Input value={manual.shipping} onChange={(e) => setManual((p) => ({ ...p, shipping: e.target.value }))} placeholder=\"0,00\" /></Field>\n </div>\n <Field label=\"Notitie (optioneel)\">\n <Input value={manual.note} onChange={(e) => setManual((p) => ({ ...p, note: e.target.value }))} placeholder=\"bv. telefonisch besteld, contant betaald\" />\n </Field>\n <div className=\"flex flex-wrap gap-4\">\n <Switch checked={manual.paid} onCheckedChange={(v) => setManual((p) => ({ ...p, paid: v }))} label=\"Al betaald (voorraad afboeken + factuur maken)\" />\n <Switch checked={manual.notify} onCheckedChange={(v) => setManual((p) => ({ ...p, notify: v }))} label=\"Bevestigingsmail naar de klant\" />\n </div>\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild><Button variant=\"secondary\">Annuleren</Button></DialogClose>\n <Button disabled={busy} onClick={() => void createManual()}>{busy ? \"Bezig…\" : \"Bestelling aanmaken\"}</Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Klant mailen — huisstijl, kopie naar de winkel, en het bericht komt bij de order te staan. */}\n <Dialog open={mailOpen} onOpenChange={setMailOpen}>\n <DialogContent title={current ? `Mail ${current.customer.name}` : \"Klant mailen\"} width=\"min(92vw, 34rem)\">\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-500\">\n Gaat naar <strong>{current?.customer.email}</strong> in de huisstijl van je site. Antwoorden\n komen binnen op je notificatie-adres.\n </p>\n <Field label=\"Onderwerp\">\n <Input value={mailSubject} onChange={(e) => setMailSubject(e.target.value)} />\n </Field>\n <Field label=\"Bericht\">\n <Textarea rows={7} value={mailBody} onChange={(e) => setMailBody(e.target.value)} placeholder=\"Beste …\" />\n </Field>\n <Switch checked={mailCopy} onCheckedChange={setMailCopy} label=\"Kopie naar mijzelf\" />\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild>\n <Button variant=\"secondary\">Annuleren</Button>\n </DialogClose>\n <Button disabled={busy || !mailSubject.trim() || !mailBody.trim()} onClick={() => current && void mailKlant(current)}>\n {busy ? \"Bezig…\" : \"Versturen\"}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Terugbetalen — geheel of gedeeltelijk. */}\n <Dialog open={refundOpen} onOpenChange={setRefundOpen}>\n <DialogContent title={current ? `Terugbetalen — ${current.number}` : \"Terugbetalen\"} width=\"min(92vw, 26rem)\">\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-500\">\n Openstaand: <strong>{current ? formatCents(openstaand(current)) : \"—\"}</strong>\n {current && (current.refundedCents ?? 0) > 0 && (\n <> (er is al {formatCents(current.refundedCents as number)} terugbetaald)</>\n )}\n </p>\n <Field label=\"Bedrag (€)\" hint=\"Laat het volledige bedrag staan voor een hele terugbetaling\">\n <Input value={refundInput} onChange={(e) => setRefundInput(e.target.value)} className=\"w-36\" />\n </Field>\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild>\n <Button variant=\"secondary\">Annuleren</Button>\n </DialogClose>\n <Button\n variant=\"danger\"\n disabled={busy}\n onClick={() => {\n if (!current) return;\n const cents = eurosToCents(refundInput);\n if (cents === null || cents <= 0) {\n toast({ title: \"Vul een geldig bedrag in\", tone: \"error\" });\n return;\n }\n void refund(current, Math.min(cents, openstaand(current)));\n }}\n >\n {busy ? \"Bezig…\" : \"Terugbetalen\"}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n\n <Dialog open={!!invoice} onOpenChange={(o) => !o && setInvoice(null)}>\n {invoice && (\n <DialogContent title={`Factuur ${invoice.number}`} width=\"min(92vw, 48rem)\">\n <iframe\n title={`Factuur ${invoice.number}`}\n src={invoice.url}\n className=\"h-[70vh] w-full rounded-lg border border-slate-200 bg-white\"\n />\n <div className=\"mt-3 flex items-center gap-2\">\n <a href={invoice.url} target=\"_blank\" rel=\"noreferrer\" download={`factuur-${invoice.number}.pdf`}>\n <Button size=\"sm\" variant=\"secondary\">Downloaden</Button>\n </a>\n <a href={invoice.url} target=\"_blank\" rel=\"noreferrer\" className=\"text-sm text-brand-700 underline\">\n Openen in nieuw tabblad ↗\n </a>\n <DialogClose asChild>\n <Button size=\"sm\" variant=\"ghost\" className=\"ml-auto\">Sluiten</Button>\n </DialogClose>\n </div>\n </DialogContent>\n )}\n </Dialog>\n </div>\n );\n}\n"],"mappings":";AAqXU,SA+EY,UA9EV,KADF;AAnXV,SAAS,WAAW,SAAS,gBAAgB;AAC7C,SAAS,YAAY,SAAS,OAAO,YAAY,SAAS,OAAO,aAAa;AAE9E,SAAS,aAAa;AACtB,SAAS,mBAA+B;AACxC,SAAS,OAAO,QAAQ,MAAM,OAAO,OAAO,gBAAgB;AAC5D,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,QAAQ,eAAe,mBAAmB;AACnD,SAAS,qBAAqB,kBAAkB,cAAc,oBAAoB;AAiBlF,MAAM,UAAU;AAAA,EACd,EAAE,OAAO,OAAO,OAAO,QAAQ;AAAA,EAC/B,EAAE,OAAO,WAAW,OAAO,oBAAoB;AAAA,EAC/C,EAAE,OAAO,QAAQ,OAAO,UAAU;AAAA,EAClC,EAAE,OAAO,WAAW,OAAO,YAAY;AAAA,EACvC,EAAE,OAAO,aAAa,OAAO,cAAc;AAAA,EAC3C,EAAE,OAAO,YAAY,OAAO,eAAe;AAAA,EAC3C,EAAE,OAAO,YAAY,OAAO,WAAW;AACzC;AAEA,SAAS,QAAQ,GAAuB;AACtC,MAAI,CAAC,GAAG,OAAQ,QAAO;AACvB,SAAO,EAAE,OAAO,EAAE,eAAe,SAAS,EAAE,WAAW,SAAS,WAAW,QAAQ,CAAC;AACtF;AAGA,SAAS,MAAM,OAAe,MAAsB;AAClD,SAAO,KAAK,MAAO,QAAQ,QAAS,MAAM,KAAK;AACjD;AAEO,SAAS,UAAU,EAAE,OAAO,GAAuB;AACxD,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,UAAU,WAAW;AAC3B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,CAAC,CAAC;AACtD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAwB,IAAI;AAC5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE;AACvC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAiD,IAAI;AACnF,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,EAAE;AACjD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,EAAE;AAC3C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,IAAI;AAC7C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,EAAE;AACjD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,EAAE;AAC7C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE,MAAM,IAAI,OAAO,IAAI,OAAO,IAAI,SAAS,IAAI,KAAK,IAAI,MAAM,IAAI,UAAU,IAAI,MAAM,IAAI,MAAM,MAAM,QAAQ,MAAM,CAAC;AAC1J,QAAM,CAAC,aAAa,cAAc,IAAI,SAAmE,CAAC,CAAC;AAC3G,QAAM,CAAC,WAAW,YAAY,IAAI,SAAyB,CAAC,CAAC;AAG7D,YAAU,MAAM;AACd,QAAI,CAAC,cAAc,UAAU,SAAS,EAAG;AACzC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI;AACT,SAAK,QAAQ,MAAM,WAAW,IAAI,SAAS,QAAQ,UAAU,GAAG,MAAM,UAAU,MAAM,QAAQ,CAAC,CAAC,EAAE;AAAA,MAAK,CAAC,SACtG;AAAA,QACE,KAAK,KACF,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA+B,EAAE,EACpE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,MAAM,IAAI,CAAC;AAAA,MACtD;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,UAAU,QAAQ,MAAM,CAAC;AACzC,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,EAAE;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AAEzC,YAAU,MAAM;AACd,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI;AACT,WAAO;AAAA,MACL,MAAM,WAAW,IAAI,SAAS,QAAQ,QAAQ,GAAG,QAAQ,aAAa,MAAM,GAAG,MAAM,GAAG,CAAC;AAAA,MACzF,CAAC,SAAS,UAAU,KAAK,KAAK,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAAY,EAAiB,CAAC;AAAA,IACjG;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,QAAM,QAAQ,QAAQ,MAAM;AAC1B,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,aAAa,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,SAAS,GAAG,IAAI,QAAQ,CAAC,EAAE,QAAQ;AACtF,UAAM,YAAY,cAAe,IAAI,OAAO,IAAI,KAAK,IAAK;AAC1D,UAAM,UAAU,OAAO,OAAO,CAAC,MAAM,CAAC,QAAQ,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC;AAC3E,WAAO;AAAA,MACL,OAAO,QAAQ,OAAO,CAAC,OAAO,EAAE,WAAW,WAAW,KAAK,MAAM,UAAU,EAAE;AAAA,MAC7E,MAAM,QAAQ,OAAO,CAAC,OAAO,EAAE,WAAW,WAAW,KAAK,MAAM,SAAS,EAAE;AAAA,MAC3E,UAAU,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAAA,MACpD,cAAc,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,YAAY,CAAC;AAAA,IACnE;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,UAAU,QAAQ,MAAM;AAC5B,QAAI,OAAO;AACX,QAAI,WAAW,WAAY,QAAO,KAAK,OAAO,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,CAAC;AAAA,aACtF,WAAW,MAAO,QAAO,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM;AACxE,UAAM,SAAS,OAAO,KAAK,EAAE,YAAY;AACzC,QAAI,QAAQ;AACV,aAAO,KAAK;AAAA,QACV,CAAC,MACC,EAAE,OAAO,YAAY,EAAE,SAAS,MAAM,KACtC,EAAE,SAAS,KAAK,YAAY,EAAE,SAAS,MAAM,KAC7C,EAAE,SAAS,MAAM,YAAY,EAAE,SAAS,MAAM;AAAA,MAClD;AAAA,IACF;AACA,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,QAAQ,MAAM,CAAC;AAE3B,QAAM,UAAU,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ,KAAK;AAEzD,YAAU,MAAM;AACd,UAAM,IAAI,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ;AAC9C,iBAAa,GAAG,aAAa,EAAE;AAAA,EACjC,GAAG,CAAC,UAAU,MAAM,CAAC;AAErB,iBAAe,UAAU,OAAoB,QAAiC;AAC5E,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAuD,mBAAmB,EAAE;AAAA,QAChF;AAAA,QACA,SAAS,MAAM;AAAA,QACf;AAAA,QACA;AAAA,QACA,aAAa,WAAW,aAAa,YAAY,KAAK,IAAI,YAAY,KAAK,IAAI;AAAA,MACjF,CAAC;AACD,YAAM;AAAA,QACJ,OAAO,WAAW,YAAY,6BAA6B;AAAA,QAC3D,aAAa,SAAS,yCAAyC;AAAA,QAC/D,MAAM;AAAA,MACR,CAAC;AACD,kBAAY,KAAK;AACjB,qBAAe,EAAE;AAAA,IACnB,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,2BAA2B,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAAA,IACpH,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAGA,WAAS,WAAW,OAA4B;AAC9C,WAAO,KAAK,IAAI,GAAG,MAAM,OAAO,cAAc,MAAM,iBAAiB,EAAE;AAAA,EACzE;AAEA,iBAAe,OAAO,OAAoB,aAAsB;AAC9D,UAAM,OAAO,WAAW,KAAK;AAC7B,UAAM,SAAS,eAAe;AAE9B,UAAM,gBAAgB,MAAM,aAAa,MAAM,QAAQ,YAAY,WAAW,eAAe,WAAW,WAAW;AACnH,UAAM,WAAW,UAAU;AAC3B,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO,WAAW,cAAc,MAAM,MAAM,mBAAmB,GAAG,YAAY,MAAM,CAAC;AAAA,MACrF,aAAa,WACT,2BAA2B,YAAY,IAAI,CAAC,eAAe,YAAY,+CACvE,YAAY,YAAY,MAAM,CAAC,QAAQ,YAAY,MAAM,OAAO,UAAU,CAAC,QAAQ,YAAY;AAAA,MACnG,MAAM;AAAA,MACN,cAAc;AAAA,IAChB,CAAC;AACD,QAAI,CAAC,GAAI;AACT,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAyF,aAAa,EAAE;AAAA,QAC5G;AAAA,QACA,SAAS,MAAM;AAAA,QACf,GAAI,WAAW,CAAC,IAAI,EAAE,aAAa,OAAO;AAAA,MAC5C,CAAC;AACD,YAAM,EAAE,OAAO,WAAW,0BAA0B,GAAG,YAAY,MAAM,CAAC,iBAAiB,MAAM,UAAU,CAAC;AAC5G,oBAAc,KAAK;AACnB,qBAAe,EAAE;AAAA,IACnB,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,wBAAwB,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAAA,IACjH,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAGA,iBAAe,UAAU,OAAoB;AAC3C,QAAI,CAAC,YAAY,KAAK,KAAK,CAAC,SAAS,KAAK,EAAG;AAC7C,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAmE,kBAAkB,EAAE;AAAA,QAC3F;AAAA,QACA,SAAS,MAAM;AAAA,QACf,SAAS,YAAY,KAAK;AAAA,QAC1B,MAAM,SAAS,KAAK;AAAA,QACpB,YAAY;AAAA,MACd,CAAC;AACD,YAAM,EAAE,OAAO,qBAAqB,aAAa,MAAM,SAAS,OAAO,MAAM,UAAU,CAAC;AACxF,kBAAY,KAAK;AACjB,qBAAe,EAAE;AACjB,kBAAY,EAAE;AAAA,IAChB,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,qBAAqB,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAAA,IAC9G,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAGA,iBAAe,eAAe;AAC5B,UAAM,SAAS,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC;AACjE,QAAI,CAAC,OAAO,KAAK,KAAK,KAAK,CAAC,OAAO,MAAM,KAAK,KAAK,OAAO,WAAW,GAAG;AACtE,YAAM,EAAE,OAAO,qDAA+C,MAAM,QAAQ,CAAC;AAC7E;AAAA,IACF;AACA,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,MAAM,MAAM,aAAsD,mBAAmB,EAAE;AAAA,QAC3F;AAAA,QACA,OAAO,OAAO,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,YAAY,EAAE,cAAc,QAAW,KAAK,EAAE,IAAI,EAAE;AAAA,QACxG,UAAU,EAAE,MAAM,OAAO,KAAK,KAAK,GAAG,OAAO,OAAO,MAAM,KAAK,GAAG,OAAO,OAAO,MAAM,KAAK,KAAK,OAAU;AAAA,QAC1G,UAAU,OAAO,QAAQ,KAAK,IAC1B,EAAE,SAAS,OAAO,QAAQ,KAAK,GAAG,KAAK,OAAO,IAAI,KAAK,GAAG,MAAM,OAAO,KAAK,KAAK,GAAG,SAAS,KAAK,IAClG;AAAA,QACJ,eAAe,aAAa,OAAO,QAAQ,KAAK;AAAA,QAChD,MAAM,OAAO,KAAK,KAAK,KAAK;AAAA,QAC5B,WAAW,OAAO;AAAA,QAClB,QAAQ,OAAO;AAAA,MACjB,CAAC;AACD,YAAM,EAAE,OAAO,yBAAyB,aAAa,OAAO,OAAO,yBAAyB,eAAe,MAAM,UAAU,CAAC;AAC5H,oBAAc,KAAK;AACnB,qBAAe,CAAC,CAAC;AACjB,gBAAU,EAAE,MAAM,IAAI,OAAO,IAAI,OAAO,IAAI,SAAS,IAAI,KAAK,IAAI,MAAM,IAAI,UAAU,IAAI,MAAM,IAAI,MAAM,MAAM,QAAQ,MAAM,CAAC;AAC/H,kBAAY,IAAI,EAAE;AAAA,IACpB,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,oBAAoB,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAAA,IAC7G,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAGA,iBAAe,SAAS,OAAoB;AAC1C,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO,cAAc,MAAM,MAAM;AAAA,MACjC,aAAa;AAAA,MACb,cAAc;AAAA,IAChB,CAAC;AACD,QAAI,CAAC,GAAI;AACT,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAoF,uBAAuB,EAAE;AAAA,QACjH;AAAA,QACA,SAAS,MAAM;AAAA,QACf;AAAA,MACF,CAAC;AACD,YAAM,EAAE,OAAO,uBAAuB,MAAM,UAAU,CAAC;AAAA,IACzD,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,WAAW,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAAA,IACpG,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAGA,iBAAe,SAAS,OAAoB;AAC1C,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAiF,cAAc,EAAE;AAAA,QACrG;AAAA,QACA,SAAS,MAAM;AAAA,QACf,MAAM;AAAA,MACR,CAAC;AACD,YAAM,EAAE,OAAO,sBAAsB,MAAM,UAAU,CAAC;AAAA,IACxD,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,mBAAmB,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAAA,IAC5G,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,iBAAe,WAAW,OAAoB;AAC5C,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAmE,iBAAiB,EAAE,EAAE,QAAQ,SAAS,MAAM,GAAG,CAAC;AACzH,YAAM,EAAE,OAAO,sCAAsC,MAAM,UAAU,CAAC;AAAA,IACxE,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,0BAA0B,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAAA,IACnH,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,iBAAe,UAAU,OAAoB,MAAsC;AACjF,QAAI;AACF,YAAM,MAAM,MAAM,aAAiF,iBAAiB,EAAE;AAAA,QACpH;AAAA,QACA,SAAS,MAAM;AAAA,QACf;AAAA,MACF,CAAC;AACD,aAAO,IAAI;AAAA,IACb,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,2BAA2B,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAClH,aAAO;AAAA,IACT;AAAA,EACF;AAEA,iBAAe,SAAS,OAAoB,MAAc;AACxD,YAAQ,IAAI;AACZ,UAAM,MAAM,MAAM,UAAU,OAAO,IAAI;AACvC,YAAQ,KAAK;AACb,QAAI,IAAK,QAAO,KAAK,KAAK,UAAU,UAAU;AAAA,EAChD;AAGA,iBAAe,YAAY,OAAoB,MAAc;AAC3D,YAAQ,IAAI;AACZ,UAAM,MAAM,MAAM,UAAU,OAAO,IAAI;AACvC,YAAQ,KAAK;AACb,QAAI,IAAK,YAAW,EAAE,KAAK,QAAQ,MAAM,OAAO,CAAC;AAAA,EACnD;AAEA,WAAS,YAAY;AACnB,UAAM,OAAO;AAAA,MACX,CAAC,UAAU,SAAS,UAAU,QAAQ,SAAS,SAAS,aAAa,WAAW,iBAAiB,UAAU,KAAK;AAAA,MAChH,GAAG,OAAO,IAAI,CAAC,MAAM;AACnB,cAAM,MACJ,EAAE,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,CAAC,KAC/E,EAAE,OAAO,gBAAgB,IAAI,MAAM,EAAE,OAAO,eAAe,EAAE,IAAI;AACpE,eAAO;AAAA,UACL,EAAE;AAAA,UACF,QAAQ,EAAE,SAAS;AAAA,UACnB,EAAE;AAAA,UACF,EAAE,SAAS;AAAA,UACX,EAAE,SAAS;AAAA,UACX,EAAE,MAAM,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,KAAK,EAAE,IAAI,GAAG,EAAE,aAAa,KAAK,EAAE,UAAU,MAAM,EAAE,EAAE,EAAE,KAAK,KAAK;AAAA,WAC9F,EAAE,OAAO,aAAa,KAAK,QAAQ,CAAC;AAAA,YACnC,EAAE,OAAO,iBAAiB,KAAK,KAAK,QAAQ,CAAC;AAAA,WAC9C,EAAE,OAAO,gBAAgB,KAAK,QAAQ,CAAC;AAAA,WACvC,EAAE,OAAO,aAAa,KAAK,QAAQ,CAAC;AAAA,WACpC,MAAM,KAAK,QAAQ,CAAC;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AACA,UAAM,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI;AACrG,UAAM,OAAO,IAAI,KAAK,CAAC,WAAM,GAAG,GAAG,EAAE,MAAM,yBAAyB,CAAC;AACrE,UAAM,IAAI,SAAS,cAAc,GAAG;AACpC,MAAE,OAAO,IAAI,gBAAgB,IAAI;AACjC,MAAE,WAAW,gBAAgB,MAAM;AACnC,MAAE,MAAM;AACR,QAAI,gBAAgB,EAAE,IAAI;AAAA,EAC5B;AAEA,SACE,qBAAC,SAAI,WAAU,cAEb;AAAA,wBAAC,SAAI,WAAU,yCACZ;AAAA,MACC,EAAE,OAAO,WAAW,OAAO,OAAO,MAAM,KAAK,EAAE;AAAA,MAC/C,EAAE,OAAO,aAAa,OAAO,OAAO,MAAM,IAAI,EAAE;AAAA,MAChD,EAAE,OAAO,gBAAgB,OAAO,OAAO,MAAM,QAAQ,EAAE;AAAA,MACvD,EAAE,OAAO,mBAAmB,OAAO,YAAY,MAAM,YAAY,EAAE;AAAA,IACrE,EAAE,IAAI,CAAC,MACL,qBAAC,QAAmB,WAAU,QAC5B;AAAA,0BAAC,OAAE,WAAU,0BAA0B,YAAE,OAAM;AAAA,MAC/C,oBAAC,OAAE,WAAU,qBAAqB,YAAE,OAAM;AAAA,SAFjC,EAAE,KAGb,CACD,GACH;AAAA,IAEA,qBAAC,SAAI,WAAU,uCACb;AAAA,2BAAC,QAAK,WAAU,SACd;AAAA,6BAAC,SAAI,WAAU,gDACb;AAAA,8BAAC,QAAG,WAAU,yBAAwB,0BAAY;AAAA,UAClD,qBAAC,SAAI,WAAU,cACb;AAAA,gCAAC,UAAO,MAAK,MAAK,SAAS,MAAM,cAAc,IAAI,GAAG,yBAAW;AAAA,YACjE,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAS,WAAW,UAAU,OAAO,WAAW,GAAG,iBAEzF;AAAA,aACF;AAAA,WACF;AAAA,QACA,oBAAC,SAAM,aAAY,wCAAkC,OAAO,QAAQ,UAAU,CAAC,MAAM,UAAU,EAAE,OAAO,KAAK,GAAG,WAAU,QAAO;AAAA,QACjI,oBAAC,UAAO,OAAO,QAAQ,eAAe,WAAW,SAAS,SAAS,WAAU,QAAO;AAAA,QACpF,qBAAC,QAAG,WAAU,2CACX;AAAA,kBAAQ,IAAI,CAAC,MAAM;AAClB,kBAAM,KAAK,oBAAoB,EAAE,MAAM,KAAK,EAAE,OAAO,EAAE,QAAQ,MAAM,QAAiB;AACtF,mBACE,oBAAC,QACC;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,MAAM;AACb,8BAAY,EAAE,EAAE;AAChB,8BAAY,KAAK;AAAA,gBACnB;AAAA,gBACA,WAAW,8DACT,EAAE,OAAO,WAAW,+BAA+B,oBACrD;AAAA,gBAEA;AAAA,uCAAC,UAAK,WAAU,2CACd;AAAA,wCAAC,UAAK,WAAU,eAAe,YAAE,QAAO;AAAA,oBACxC,oBAAC,SAAM,MAAM,GAAG,MAAO,aAAG,OAAM;AAAA,qBAClC;AAAA,kBACA,qBAAC,UAAK,WAAU,kEACd;AAAA,wCAAC,UAAK,WAAU,YAAY,YAAE,SAAS,MAAK;AAAA,oBAC5C,oBAAC,UAAM,sBAAY,EAAE,OAAO,UAAU,GAAE;AAAA,qBAC1C;AAAA,kBACA,oBAAC,UAAK,WAAU,0BAA0B,kBAAQ,EAAE,SAAS,GAAE;AAAA;AAAA;AAAA,YACjE,KAnBO,EAAE,EAoBX;AAAA,UAEJ,CAAC;AAAA,UACA,QAAQ,WAAW,KAAK,oBAAC,QAAG,WAAU,oCAAmC,gCAAkB;AAAA,WAC9F;AAAA,SACF;AAAA,MAEC,UACC,oBAAC,QACC,+BAAC,SAAI,WAAU,cACb;AAAA,6BAAC,SAAI,WAAU,qDACb;AAAA,+BAAC,QAAG,WAAU,2BACX;AAAA,oBAAQ;AAAA,YAAQ;AAAA,YACjB,oBAAC,SAAM,OAAO,oBAAoB,QAAQ,MAAM,KAAK,EAAE,MAAM,QAAiB,GAAG,MAC7E,+BAAoB,QAAQ,MAAM,KAAK,EAAE,OAAO,QAAQ,OAAO,GAAG,OACtE;AAAA,aACF;AAAA,UACA,qBAAC,UAAK,WAAU,0BAAyB;AAAA;AAAA,YAAY,QAAQ,QAAQ,SAAS;AAAA,aAAE;AAAA,WAClF;AAAA,QAEA,qBAAC,SAAI,WAAU,6BACb;AAAA,+BAAC,SACC;AAAA,gCAAC,QAAG,WAAU,8BAA6B,mBAAK;AAAA,YAChD,qBAAC,OAAE,WAAU,WACV;AAAA,sBAAQ,SAAS;AAAA,cAClB,oBAAC,QAAG;AAAA,cACJ;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,WAAU;AAAA,kBACV,OAAM;AAAA,kBACN,SAAS,MAAM;AAAE,mCAAe,sBAAsB,QAAQ,MAAM,EAAE;AAAG,gCAAY,IAAI;AAAA,kBAAG;AAAA,kBAE3F,kBAAQ,SAAS;AAAA;AAAA,cACpB;AAAA,cACC,QAAQ,SAAS,SAChB,iCACE;AAAA,oCAAC,QAAG;AAAA,gBACH,QAAQ,SAAS;AAAA,iBACpB;AAAA,eAEJ;AAAA,aACF;AAAA,UACA,qBAAC,SACC;AAAA,gCAAC,QAAG,WAAU,8BAA6B,0BAAY;AAAA,YACtD,QAAQ,WACP,qBAAC,OAAE,WAAU,WACV;AAAA,sBAAQ,SAAS;AAAA,cAClB,oBAAC,QAAG;AAAA,cACH,QAAQ,SAAS;AAAA,cAAI;AAAA,cAAE,QAAQ,SAAS;AAAA,cAAK;AAAA,cAAG,QAAQ,SAAS;AAAA,cAAQ;AAAA,eAC5E,IAEA,oBAAC,OAAE,WAAU,0BAAyB,wCAA0B;AAAA,YAEjE,QAAQ,eACP,oBAAC,OAAE,WAAU,oCAAmC,MAAM,QAAQ,aAAa,QAAO,UAAS,KAAI,cAAa,kCAE5G;AAAA,aAEJ;AAAA,WACF;AAAA,QAEC,QAAQ,QACP,qBAAC,SACC;AAAA,8BAAC,QAAG,WAAU,8BAA6B,oCAAsB;AAAA,UACjE,oBAAC,OAAE,WAAU,+BAA+B,kBAAQ,MAAK;AAAA,WAC3D;AAAA,QAKF,qBAAC,SACC;AAAA,8BAAC,QAAG,WAAU,8BAA6B,6BAAe;AAAA,UAC1D;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,OAAO;AAAA,cACP,aAAY;AAAA,cACZ,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;AAAA;AAAA,UAC9C;AAAA,UACC,eAAe,QAAQ,aAAa,OACnC,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,WAAU,QAAO,UAAU,MAAM,SAAS,MAAM,KAAK,SAAS,OAAO,GAAG,6BAE9G;AAAA,WAEJ;AAAA,SAGE,QAAQ,UAAU,UAAU,KAAK,KACjC,qBAAC,SACC;AAAA,8BAAC,QAAG,WAAU,8BAA6B,kCAAoB;AAAA,UAC/D,oBAAC,QAAG,WAAU,cACX,kBAAQ,SAAU,IAAI,CAAC,GAAG,MACzB,qBAAC,QAAW,WAAU,kDACpB;AAAA,gCAAC,OAAE,WAAU,8BAA8B,YAAE,SAAQ;AAAA,YACrD,oBAAC,OAAE,WAAU,0BAA0B,cAAI,KAAK,EAAE,EAAE,EAAE,eAAe,SAAS,EAAE,WAAW,SAAS,WAAW,QAAQ,CAAC,GAAE;AAAA,YAC1H,oBAAC,OAAE,WAAU,2CAA2C,YAAE,MAAK;AAAA,eAHxD,CAIT,CACD,GACH;AAAA,WACF;AAAA,QAGF,qBAAC,SACC;AAAA,8BAAC,QAAG,WAAU,8BAA6B,oBAAM;AAAA,UACjD,oBAAC,WAAM,WAAU,kBACf,+BAAC,WACE;AAAA,oBAAQ,MAAM,IAAI,CAAC,GAAG,QACrB,qBAAC,QAAa,WAAU,uCACtB;AAAA,mCAAC,QAAG,WAAU,aACX;AAAA,kBAAE;AAAA,gBAAI;AAAA,gBAAG,EAAE;AAAA,gBACX,EAAE,cAAc,qBAAC,UAAK,WAAU,kBAAiB;AAAA;AAAA,kBAAG,EAAE,WAAW,MAAM,GAAG,EAAE,KAAK,QAAK;AAAA,kBAAE;AAAA,mBAAC;AAAA,gBACzF,EAAE,SAAS,cAAc,oBAAC,SAAM,MAAK,SAAQ,qBAAO;AAAA,gBACpD,EAAE,mBACD,OAAO,QAAQ,EAAE,eAAe,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAC1C,qBAAC,UAAa,WAAU,gCAA+B;AAAA;AAAA,kBAClD,EAAE,QAAQ,EAAE,YAAY;AAAA,kBAC1B,EAAE,aACD;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV,UAAU;AAAA,sBACV,SAAS,MAAM,SAAS,SAAS,EAAE,SAAmB;AAAA,sBACvD;AAAA;AAAA,kBAED;AAAA,qBAVO,CAYX,CACD;AAAA,iBACL;AAAA,cACA,oBAAC,QAAG,WAAU,mBAAmB,sBAAY,EAAE,iBAAiB,EAAE,GAAG,GAAE;AAAA,iBAtBhE,GAuBT,CACD;AAAA,aACC,QAAQ,OAAO,iBAAiB,KAAK,KACrC,qBAAC,QAAG,WAAU,4CACZ;AAAA,mCAAC,QAAG,WAAU,aAAY;AAAA;AAAA,gBAAQ,QAAQ,UAAU,OAAO,KAAK,QAAQ,SAAS,IAAI,MAAM;AAAA,iBAAG;AAAA,cAC9F,qBAAC,QAAG,WAAU,mBAAkB;AAAA;AAAA,gBAAE,YAAY,QAAQ,OAAO,iBAAiB,CAAC;AAAA,iBAAE;AAAA,eACnF;AAAA,YAEF,qBAAC,QAAG,WAAU,4CACZ;AAAA,kCAAC,QAAG,WAAU,aAAY,2BAAa;AAAA,cACvC,oBAAC,QAAG,WAAU,mBACX,kBAAQ,OAAO,kBAAkB,IAAI,WAAW,YAAY,QAAQ,OAAO,aAAa,GAC3F;AAAA,eACF;AAAA,YACA,qBAAC,QAAG,WAAU,2CACZ;AAAA,kCAAC,QAAG,WAAU,aAAY,oBAAM;AAAA,cAChC,oBAAC,QAAG,WAAU,mBAAmB,sBAAY,QAAQ,OAAO,UAAU,GAAE;AAAA,eAC1E;AAAA,aACF,GACF;AAAA,WACF;AAAA,QAEC,QAAQ,MAAM,QACb,qBAAC,OAAE,WAAU,2DAA0D;AAAA;AAAA,UAC1C,QAAQ,KAAK;AAAA,WAC1C,IACE,QAAQ,MAAM;AAAA;AAAA;AAAA,UAGhB,qBAAC,OAAE,WAAU,0BAAyB;AAAA;AAAA,YACL,QAAQ,QAAQ,KAAK,kBAAkB;AAAA,aACxE;AAAA,YACE;AAAA,QAGH,YAAY,QAAQ,WAAW,UAC9B,qBAAC,SAAI,WAAU,qDACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,aAAY;AAAA,cACZ,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,eAAe,EAAE,OAAO,KAAK;AAAA;AAAA,UAChD;AAAA,UACA,qBAAC,SAAI,WAAU,2BACb;AAAA,gCAAC,UAAO,SAAS,QAAQ,iBAAiB,WAAW,OAAM,+BAA8B;AAAA,YACzF,oBAAC,UAAO,MAAK,MAAK,UAAU,MAAM,SAAS,MAAM,UAAU,SAAS,SAAS,GAAG,iCAEhF;AAAA,aACF;AAAA,WACF;AAAA,QAGF,qBAAC,SAAI,WAAU,oEACZ;AAAA,kBAAQ,WAAW,UAAU,CAAC,YAC7B,oBAAC,UAAO,MAAK,MAAK,SAAS,MAAM,YAAY,IAAI,GAAG,mCAAqB;AAAA,UAG1E,QAAQ,WAAW,aAClB,oBAAC,UAAO,MAAK,MAAK,UAAU,MAAM,SAAS,MAAM,KAAK,SAAS,OAAO,GAAG,gCAEzE;AAAA,UAED,CAAC,WAAW,MAAM,EAAE,SAAS,QAAQ,MAAM,KAC1C;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,UAAU;AAAA,cACV,SAAS,YAAY;AACnB,sBAAM,KAAK,MAAM,QAAQ;AAAA,kBACvB,OAAO,cAAc,QAAQ,MAAM;AAAA,kBACnC,aAAa,SAAS,kCAAkC;AAAA,gBAC1D,CAAC;AACD,oBAAI,GAAI,MAAK,UAAU,SAAS,WAAW;AAAA,cAC7C;AAAA,cACD;AAAA;AAAA,UAED;AAAA,UAEF,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,UAAU,MAAM,SAAS,MAAM;AAAE,2BAAe,sBAAsB,QAAQ,MAAM,EAAE;AAAG,wBAAY,IAAI;AAAA,UAAG,GAAG,iCAErJ;AAAA,WACE,QAAQ,WAAW,UAAU,QAAQ,WAAW,cAChD,iCACE;AAAA,gCAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,UAAU,MAAM,SAAS,MAAM,WAAW,OAAO,GAAG,oCAE1F;AAAA,YACA,oBAAC,UAAO,MAAK,MAAK,SAAQ,UAAS,UAAU,QAAQ,WAAW,OAAO,MAAM,GAAG,SAAS,MAAM;AAAE,6BAAe,iBAAiB,WAAW,OAAO,CAAC,CAAC;AAAG,4BAAc,IAAI;AAAA,YAAG,GAAG,0BAEhL;AAAA,aACF;AAAA,UAED,QAAQ,eACP,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,UAAU,MAAM,SAAS,MAAM,YAAY,SAAS,QAAQ,WAAqB,GAAG,8BAE1H;AAAA,UAED,QAAQ,QAAQ,mBACf;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,MAAM,yCAAyC,QAAQ,OAAO,eAAe;AAAA,cAC7E,QAAO;AAAA,cACP,KAAI;AAAA,cACL;AAAA;AAAA,UAED;AAAA,WAEJ;AAAA,SACF,GACF,IAEA,oBAAC,QACC,8BAAC,OAAE,WAAU,0BAAyB,uCAAyB,GACjE;AAAA,OAEJ;AAAA,IAGA,oBAAC,UAAO,MAAM,YAAY,cAAc,eACtC,8BAAC,iBAAc,OAAM,iCAAgC,OAAM,oBACzD,+BAAC,SAAI,WAAU,cACb;AAAA,0BAAC,OAAE,WAAU,0BAAyB,iKAGtC;AAAA,MACA,qBAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,SAAM,OAAM,QAAO,8BAAC,SAAM,OAAO,OAAO,MAAM,UAAU,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,OAAO,MAAM,EAAE,GAAG,GAAE;AAAA,QACtH,oBAAC,SAAM,OAAM,UAAS,8BAAC,SAAM,OAAO,OAAO,OAAO,UAAU,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,EAAE,OAAO,MAAM,EAAE,GAAG,GAAE;AAAA,QAC1H,oBAAC,SAAM,OAAM,wBAAuB,8BAAC,SAAM,OAAO,OAAO,OAAO,UAAU,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,EAAE,OAAO,MAAM,EAAE,GAAG,GAAE;AAAA,SAC1I;AAAA,MAEA,qBAAC,SACC;AAAA,4BAAC,OAAE,WAAU,4BAA2B,uBAAS;AAAA,QACjD,qBAAC,SAAI,WAAU,cACZ;AAAA,sBAAY,IAAI,CAAC,GAAG,MAAM;AACzB,kBAAM,OAAO,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS;AACvD,kBAAM,aAAa,MAAM,SAAS,UAAU,KAAK,IAAK,MAAM,YAAY,CAAC,IAAK,CAAC;AAC/E,mBACE,qBAAC,SAAY,WAAU,2DACrB;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAO,EAAE;AAAA,kBACT,eAAe,CAAC,MAAM,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,MAAO,MAAM,IAAI,EAAE,GAAG,GAAG,WAAW,GAAG,YAAY,GAAG,IAAI,CAAE,CAAC;AAAA,kBACnH,aAAY;AAAA,kBACZ,SAAS,UAAU,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,KAAK,EAAE;AAAA;AAAA,cAChE;AAAA,cACC,UAAU,SAAS,IAClB;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAO,EAAE;AAAA,kBACT,eAAe,CAAC,MAAM,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,MAAO,MAAM,IAAI,EAAE,GAAG,GAAG,YAAY,EAAE,IAAI,CAAE,CAAC;AAAA,kBACpG,aAAY;AAAA,kBACZ,SAAS,UAAU,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI,EAAE,EAAE;AAAA;AAAA,cACtF,IAEA,oBAAC,UAAK,WAAU,0BAAyB,oBAAC;AAAA,cAE5C;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,OAAO,OAAO,EAAE,GAAG;AAAA,kBACnB,UAAU,CAAC,MAAM,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,MAAO,MAAM,IAAI,EAAE,GAAG,GAAG,KAAK,KAAK,IAAI,GAAG,OAAO,EAAE,OAAO,KAAK,KAAK,CAAC,EAAE,IAAI,CAAE,CAAC;AAAA;AAAA,cACjI;AAAA,cACA,oBAAC,UAAO,MAAK,MAAK,SAAQ,SAAQ,SAAS,MAAM,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,GAAG,oBAAC;AAAA,iBAtB9F,CAuBV;AAAA,UAEJ,CAAC;AAAA,UACD,oBAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,SAAS,MAAM,eAAe,CAAC,MAAM,CAAC,GAAG,GAAG,EAAE,WAAW,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC,CAAC,GAAG,qBAE/H;AAAA,WACF;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,SAAM,OAAM,SAAQ,8BAAC,SAAM,OAAO,OAAO,SAAS,UAAU,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,SAAS,EAAE,OAAO,MAAM,EAAE,GAAG,GAAE;AAAA,QAC7H,oBAAC,SAAM,OAAM,YAAW,8BAAC,SAAM,OAAO,OAAO,KAAK,UAAU,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,KAAK,EAAE,OAAO,MAAM,EAAE,GAAG,GAAE;AAAA,QACxH,oBAAC,SAAM,OAAM,UAAS,8BAAC,SAAM,OAAO,OAAO,MAAM,UAAU,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,OAAO,MAAM,EAAE,GAAG,GAAE;AAAA,QACxH,oBAAC,SAAM,OAAM,0BAAoB,8BAAC,SAAM,OAAO,OAAO,UAAU,UAAU,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,UAAU,EAAE,OAAO,MAAM,EAAE,GAAG,aAAY,QAAO,GAAE;AAAA,SAChK;AAAA,MACA,oBAAC,SAAM,OAAM,uBACX,8BAAC,SAAM,OAAO,OAAO,MAAM,UAAU,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,OAAO,MAAM,EAAE,GAAG,aAAY,4CAA2C,GACzJ;AAAA,MACA,qBAAC,SAAI,WAAU,wBACb;AAAA,4BAAC,UAAO,SAAS,OAAO,MAAM,iBAAiB,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,EAAE,GAAG,OAAM,kDAAiD;AAAA,QACpJ,oBAAC,UAAO,SAAS,OAAO,QAAQ,iBAAiB,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,EAAE,GAAG,OAAM,kCAAiC;AAAA,SAC1I;AAAA,MACA,qBAAC,SAAI,WAAU,0BACb;AAAA,4BAAC,eAAY,SAAO,MAAC,8BAAC,UAAO,SAAQ,aAAY,uBAAS,GAAS;AAAA,QACnE,oBAAC,UAAO,UAAU,MAAM,SAAS,MAAM,KAAK,aAAa,GAAI,iBAAO,gBAAW,uBAAsB;AAAA,SACvG;AAAA,OACF,GACF,GACF;AAAA,IAGA,oBAAC,UAAO,MAAM,UAAU,cAAc,aACpC,8BAAC,iBAAc,OAAO,UAAU,QAAQ,QAAQ,SAAS,IAAI,KAAK,gBAAgB,OAAM,oBACtF,+BAAC,SAAI,WAAU,cACb;AAAA,2BAAC,OAAE,WAAU,0BAAyB;AAAA;AAAA,QAC1B,oBAAC,YAAQ,mBAAS,SAAS,OAAM;AAAA,QAAS;AAAA,SAEtD;AAAA,MACA,oBAAC,SAAM,OAAM,aACX,8BAAC,SAAM,OAAO,aAAa,UAAU,CAAC,MAAM,eAAe,EAAE,OAAO,KAAK,GAAG,GAC9E;AAAA,MACA,oBAAC,SAAM,OAAM,WACX,8BAAC,YAAS,MAAM,GAAG,OAAO,UAAU,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK,GAAG,aAAY,gBAAU,GAC1G;AAAA,MACA,oBAAC,UAAO,SAAS,UAAU,iBAAiB,aAAa,OAAM,sBAAqB;AAAA,MACpF,qBAAC,SAAI,WAAU,0BACb;AAAA,4BAAC,eAAY,SAAO,MAClB,8BAAC,UAAO,SAAQ,aAAY,uBAAS,GACvC;AAAA,QACA,oBAAC,UAAO,UAAU,QAAQ,CAAC,YAAY,KAAK,KAAK,CAAC,SAAS,KAAK,GAAG,SAAS,MAAM,WAAW,KAAK,UAAU,OAAO,GAChH,iBAAO,gBAAW,aACrB;AAAA,SACF;AAAA,OACF,GACF,GACF;AAAA,IAGA,oBAAC,UAAO,MAAM,YAAY,cAAc,eACtC,8BAAC,iBAAc,OAAO,UAAU,uBAAkB,QAAQ,MAAM,KAAK,gBAAgB,OAAM,oBACzF,+BAAC,SAAI,WAAU,cACb;AAAA,2BAAC,OAAE,WAAU,0BAAyB;AAAA;AAAA,QACxB,oBAAC,YAAQ,oBAAU,YAAY,WAAW,OAAO,CAAC,IAAI,UAAI;AAAA,QACrE,YAAY,QAAQ,iBAAiB,KAAK,KACzC,iCAAE;AAAA;AAAA,UAAY,YAAY,QAAQ,aAAuB;AAAA,UAAE;AAAA,WAAc;AAAA,SAE7E;AAAA,MACA,oBAAC,SAAM,OAAM,mBAAa,MAAK,+DAC7B,8BAAC,SAAM,OAAO,aAAa,UAAU,CAAC,MAAM,eAAe,EAAE,OAAO,KAAK,GAAG,WAAU,QAAO,GAC/F;AAAA,MACA,qBAAC,SAAI,WAAU,0BACb;AAAA,4BAAC,eAAY,SAAO,MAClB,8BAAC,UAAO,SAAQ,aAAY,uBAAS,GACvC;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,UAAU;AAAA,YACV,SAAS,MAAM;AACb,kBAAI,CAAC,QAAS;AACd,oBAAM,QAAQ,aAAa,WAAW;AACtC,kBAAI,UAAU,QAAQ,SAAS,GAAG;AAChC,sBAAM,EAAE,OAAO,4BAA4B,MAAM,QAAQ,CAAC;AAC1D;AAAA,cACF;AACA,mBAAK,OAAO,SAAS,KAAK,IAAI,OAAO,WAAW,OAAO,CAAC,CAAC;AAAA,YAC3D;AAAA,YAEC,iBAAO,gBAAW;AAAA;AAAA,QACrB;AAAA,SACF;AAAA,OACF,GACF,GACF;AAAA,IAEA,oBAAC,UAAO,MAAM,CAAC,CAAC,SAAS,cAAc,CAAC,MAAM,CAAC,KAAK,WAAW,IAAI,GAChE,qBACC,qBAAC,iBAAc,OAAO,WAAW,QAAQ,MAAM,IAAI,OAAM,oBACvD;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,WAAW,QAAQ,MAAM;AAAA,UAChC,KAAK,QAAQ;AAAA,UACb,WAAU;AAAA;AAAA,MACZ;AAAA,MACA,qBAAC,SAAI,WAAU,gCACb;AAAA,4BAAC,OAAE,MAAM,QAAQ,KAAK,QAAO,UAAS,KAAI,cAAa,UAAU,WAAW,QAAQ,MAAM,QACxF,8BAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,wBAAU,GAClD;AAAA,QACA,oBAAC,OAAE,MAAM,QAAQ,KAAK,QAAO,UAAS,KAAI,cAAa,WAAU,oCAAmC,4CAEpG;AAAA,QACA,oBAAC,eAAY,SAAO,MAClB,8BAAC,UAAO,MAAK,MAAK,SAAQ,SAAQ,WAAU,WAAU,qBAAO,GAC/D;AAAA,SACF;AAAA,OACF,GAEJ;AAAA,KACF;AAEJ;","names":[]}
@@ -9,17 +9,13 @@ type StoredCategory = ShopCategory & {
9
9
  export declare function ProductsTab({ siteId }: {
10
10
  siteId: string;
11
11
  }): import("react").JSX.Element;
12
- /**
13
- * Het volledige productformulier. Sinds 7.8 ook buiten dit tabblad in gebruik: het tandwiel bij een
14
- * product op de pagina opent hetzelfde formulier in een zijpaneel. Bewust hergebruik en geen tweede,
15
- * uitgeklede versie — twee formulieren voor één product lopen gegarandeerd uit elkaar, en dan hangt
16
- * het van je route af welke velden je kunt bereiken.
17
- */
18
- export declare function ProductForm({ siteId, product, categories, onDeleted, onToast, confirm, }: {
12
+ export declare function ProductForm({ siteId, product, categories, onDeleted, onDuplicated, onToast, confirm, }: {
19
13
  siteId: string;
20
14
  product: StoredProduct;
21
15
  categories: StoredCategory[];
22
16
  onDeleted: () => void;
17
+ /** Optioneel: na dupliceren de kopie selecteren (het zijpaneel doet dit niet). */
18
+ onDuplicated?: (id: string) => void;
23
19
  onToast: (t: {
24
20
  title: string;
25
21
  description?: string;