@xiwebdesign/xicms-core 7.17.1 → 7.19.1

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, Input } 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, shopCallable } from \"./lib\";\n\ntype StoredOrder = Order & {\n id: string;\n createdAt?: Timestamp;\n paidAt?: Timestamp;\n shippedAt?: Timestamp;\n mail?: { confirmationSentAt?: Timestamp; error?: 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 [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\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 async function refund(order: StoredOrder) {\n const ok = await confirm({\n title: `Bestelling ${order.number} terugbetalen?`,\n description: `Het volledige bedrag (${formatCents(order.amount.totalCents)}) wordt via Stripe teruggestort; de klant krijgt een e-mail.`,\n tone: \"danger\",\n confirmLabel: \"Terugbetalen\",\n });\n if (!ok) return;\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; orderId: string }, { ok: boolean }>(\"refundOrder\")({ siteId, orderId: order.id });\n toast({ title: \"Terugbetaling gestart\", tone: \"success\" });\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 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 <a className=\"text-brand-700 underline\" href={`mailto:${current.customer.email}`}>{current.customer.email}</a>\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 <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 {(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} onClick={() => refund(current)}>\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 <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 )}\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":"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","names":[]}
@@ -15,11 +15,13 @@ export declare function ProductsTab({ siteId }: {
15
15
  * uitgeklede versie — twee formulieren voor één product lopen gegarandeerd uit elkaar, en dan hangt
16
16
  * het van je route af welke velden je kunt bereiken.
17
17
  */
18
- export declare function ProductForm({ siteId, product, categories, onDeleted, onToast, confirm, }: {
18
+ export declare function ProductForm({ siteId, product, categories, onDeleted, onDuplicated, onToast, confirm, }: {
19
19
  siteId: string;
20
20
  product: StoredProduct;
21
21
  categories: StoredCategory[];
22
22
  onDeleted: () => void;
23
+ /** Optioneel: na dupliceren de kopie selecteren (het zijpaneel doet dit niet). */
24
+ onDuplicated?: (id: string) => void;
23
25
  onToast: (t: {
24
26
  title: string;
25
27
  description?: string;
@@ -61,7 +61,8 @@ function ProductsTab({ siteId }) {
61
61
  trackStock: false,
62
62
  status: "draft",
63
63
  order: products.length,
64
- featured: false
64
+ featured: false,
65
+ personalizationEnabled: true
65
66
  };
66
67
  try {
67
68
  const id = await saveProductDoc(siteId, void 0, nieuw);
@@ -106,6 +107,7 @@ function ProductsTab({ siteId }) {
106
107
  product: current,
107
108
  categories: cats,
108
109
  onDeleted: () => setSelected(null),
110
+ onDuplicated: (id) => setSelected(id),
109
111
  onToast: toast,
110
112
  confirm
111
113
  },
@@ -118,10 +120,18 @@ function ProductForm({
118
120
  product,
119
121
  categories,
120
122
  onDeleted,
123
+ onDuplicated,
121
124
  onToast,
122
125
  confirm
123
126
  }) {
124
127
  const [def, setDef] = useState(product);
128
+ const vuil = JSON.stringify(def) !== JSON.stringify(product);
129
+ useEffect(() => {
130
+ if (!vuil) return;
131
+ const waarschuw = (e) => e.preventDefault();
132
+ window.addEventListener("beforeunload", waarschuw);
133
+ return () => window.removeEventListener("beforeunload", waarschuw);
134
+ }, [vuil]);
125
135
  const [priceInput, setPriceInput] = useState(centsToEuroInput(product.priceCents));
126
136
  const [compareInput, setCompareInput] = useState(centsToEuroInput(product.compareAtCents));
127
137
  const variantKeys = useMemo(() => allVariantKeys(def.options ?? []), [def.options]);
@@ -144,6 +154,22 @@ function ProductForm({
144
154
  return { ...p, variants };
145
155
  });
146
156
  }
157
+ async function dupliceer() {
158
+ try {
159
+ const kopie = {
160
+ ...def,
161
+ name: `${def.name} (kopie)`,
162
+ slug: `${def.slug}-kopie`.slice(0, 120),
163
+ status: "draft",
164
+ order: (def.order ?? 0) + 1
165
+ };
166
+ const id = await saveProductDoc(siteId, void 0, kopie);
167
+ onToast({ title: "Product gedupliceerd", description: "De kopie staat als concept klaar.", tone: "success" });
168
+ onDuplicated?.(id);
169
+ } catch (e) {
170
+ onToast({ title: "Dupliceren mislukt", description: e instanceof Error ? e.message : void 0, tone: "error" });
171
+ }
172
+ }
147
173
  async function save() {
148
174
  const db = getDb();
149
175
  if (!db) return;
@@ -224,7 +250,8 @@ function ProductForm({
224
250
  /* @__PURE__ */ jsx(Field, { label: "Omschrijving", children: /* @__PURE__ */ jsx(Textarea, { rows: 4, value: def.description ?? "", onChange: (e) => setDef((p) => ({ ...p, description: e.target.value })) }) }),
225
251
  /* @__PURE__ */ jsx(Field, { label: "Afbeeldingen", hint: "Eerste afbeelding is het hoofdbeeld", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
226
252
  (def.images ?? []).map((url, i) => /* @__PURE__ */ jsxs("div", { className: "relative", children: [
227
- /* @__PURE__ */ jsx("img", { src: url, alt: "", className: "h-16 w-16 rounded-lg object-cover" }),
253
+ /* @__PURE__ */ jsx("img", { src: url, alt: "", className: `h-16 w-16 rounded-lg object-cover ${i === 0 ? "ring-2 ring-brand-500" : ""}` }),
254
+ i === 0 && /* @__PURE__ */ jsx("span", { className: "absolute -bottom-2 left-1/2 -translate-x-1/2 rounded-full bg-brand-500 px-1.5 text-[10px] text-white", children: "hoofd" }),
228
255
  /* @__PURE__ */ jsx(
229
256
  "button",
230
257
  {
@@ -234,6 +261,21 @@ function ProductForm({
234
261
  onClick: () => setDef((p) => ({ ...p, images: (p.images ?? []).filter((_, j) => j !== i) })),
235
262
  children: "\u2715"
236
263
  }
264
+ ),
265
+ i > 0 && /* @__PURE__ */ jsx(
266
+ "button",
267
+ {
268
+ type: "button",
269
+ "aria-label": "Naar voren verplaatsen",
270
+ title: i === 1 ? "Maak hoofdbeeld" : "Naar voren",
271
+ className: "absolute -left-2 -top-2 grid h-5 w-5 place-items-center rounded-full bg-slate-700 text-xs text-white",
272
+ onClick: () => setDef((p) => {
273
+ const imgs = [...p.images ?? []];
274
+ [imgs[i - 1], imgs[i]] = [imgs[i], imgs[i - 1]];
275
+ return { ...p, images: imgs };
276
+ }),
277
+ children: "\u2190"
278
+ }
237
279
  )
238
280
  ] }, url + i)),
239
281
  /* @__PURE__ */ jsx(
@@ -390,94 +432,124 @@ function ProductForm({
390
432
  variantKeys.length,
391
433
  " combinaties \u2014 te veel om per stuk te tonen. Beperk het aantal optie-waarden."
392
434
  ] }),
393
- /* @__PURE__ */ jsx(
394
- Field,
395
- {
396
- label: "Personalisatie (eigen tekst of afbeelding)",
397
- hint: "Max. 3 velden. De klant vult dit in op de productpagina; de meerprijs telt automatisch mee.",
398
- children: /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
399
- (def.personalization ?? []).map((f, i) => {
400
- const setPers = (patch) => setDef((p) => {
401
- const personalization = [...p.personalization ?? []];
402
- personalization[i] = { ...personalization[i], ...patch };
403
- return { ...p, personalization };
404
- });
405
- return /* @__PURE__ */ jsxs("div", { className: "grid gap-2 rounded-lg border border-slate-100 p-2 md:grid-cols-[1fr_130px_90px_110px_auto]", children: [
406
- /* @__PURE__ */ jsx(
407
- Input,
408
- {
409
- placeholder: 'Label (bv. "Naam van het kindje")',
410
- value: f.label,
411
- onChange: (e) => setPers({
412
- label: e.target.value,
413
- key: f.key || e.target.value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40) || `veld${i + 1}`
414
- })
415
- }
416
- ),
417
- /* @__PURE__ */ jsx(
418
- Select,
419
- {
420
- value: f.type,
421
- onValueChange: (v) => setPers({ type: v }),
422
- options: [
423
- { value: "text", label: "Tekst" },
424
- { value: "image", label: "Afbeelding" }
425
- ]
426
- }
427
- ),
428
- f.type === "text" ? /* @__PURE__ */ jsx(
429
- Input,
430
- {
431
- type: "number",
432
- title: "Max. tekens",
433
- min: 1,
434
- max: 200,
435
- value: f.maxLength ?? 40,
436
- onChange: (e) => setPers({ maxLength: Math.max(1, Math.min(200, Number(e.target.value) || 40)) })
437
- }
438
- ) : /* @__PURE__ */ jsx("span", { className: "self-center text-xs text-slate-400", children: "jpg/png/webp" }),
439
- /* @__PURE__ */ jsx(
440
- "input",
441
- {
442
- className: "rounded-lg border border-slate-200 px-2 py-1",
443
- placeholder: "Meerprijs \u20AC",
444
- defaultValue: f.priceCents ? centsToEuroInput(f.priceCents) : "",
445
- onChange: (e) => setPers({ priceCents: eurosToCents(e.target.value) ?? 0 })
446
- }
447
- ),
448
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
449
- /* @__PURE__ */ jsx(Switch, { checked: f.required === true, onCheckedChange: (v) => setPers({ required: v }), label: "Verplicht" }),
435
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-slate-200 p-3", children: [
436
+ /* @__PURE__ */ jsx(
437
+ Switch,
438
+ {
439
+ checked: def.personalizationEnabled !== false,
440
+ onCheckedChange: (v) => setDef((p) => ({
441
+ ...p,
442
+ personalizationEnabled: v,
443
+ // Bij het aanzetten meteen één leeg veld klaarzetten, anders staat de klant voor
444
+ // een lege sectie en lijkt de schakelaar niets te doen.
445
+ personalization: v && (p.personalization ?? []).length === 0 ? [{ key: "naam", label: "Naam", type: "text", required: false, maxLength: 20, priceCents: 0 }] : p.personalization
446
+ })),
447
+ label: "Persoonlijk maken aanzetten voor dit product"
448
+ }
449
+ ),
450
+ def.personalizationEnabled === false && (def.personalization?.length ?? 0) > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-slate-500", children: [
451
+ "Staat uit \u2014 de klant ziet geen invulveld. Je ",
452
+ def.personalization.length === 1 ? "instelling blijft" : "instellingen blijven",
453
+ " bewaard voor als je het weer aanzet."
454
+ ] }),
455
+ def.personalizationEnabled !== false && /* @__PURE__ */ jsx(
456
+ Field,
457
+ {
458
+ label: "Personalisatie (eigen tekst of afbeelding)",
459
+ hint: "Max. 3 velden. De klant vult dit in op de productpagina; de meerprijs telt automatisch mee.",
460
+ children: /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
461
+ (def.personalization ?? []).map((f, i) => {
462
+ const setPers = (patch) => setDef((p) => {
463
+ const personalization = [...p.personalization ?? []];
464
+ personalization[i] = { ...personalization[i], ...patch };
465
+ return { ...p, personalization };
466
+ });
467
+ return /* @__PURE__ */ jsxs("div", { className: "grid gap-2 rounded-lg border border-slate-100 p-2 md:grid-cols-[1fr_130px_90px_110px_auto]", children: [
450
468
  /* @__PURE__ */ jsx(
451
- Button,
469
+ Input,
452
470
  {
453
- size: "sm",
454
- variant: "ghost",
455
- onClick: () => setDef((p) => ({ ...p, personalization: (p.personalization ?? []).filter((_, j) => j !== i) })),
456
- children: "\u2715"
471
+ placeholder: 'Label (bv. "Naam van het kindje")',
472
+ value: f.label,
473
+ onChange: (e) => setPers({
474
+ label: e.target.value,
475
+ key: f.key || e.target.value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40) || `veld${i + 1}`
476
+ })
477
+ }
478
+ ),
479
+ /* @__PURE__ */ jsx(
480
+ Select,
481
+ {
482
+ value: f.type,
483
+ onValueChange: (v) => setPers({ type: v }),
484
+ options: [
485
+ { value: "text", label: "Tekst" },
486
+ { value: "image", label: "Afbeelding" }
487
+ ]
488
+ }
489
+ ),
490
+ f.type === "text" ? /* @__PURE__ */ jsx(
491
+ Input,
492
+ {
493
+ type: "number",
494
+ title: "Max. tekens",
495
+ min: 1,
496
+ max: 200,
497
+ value: f.maxLength ?? 40,
498
+ onChange: (e) => setPers({ maxLength: Math.max(1, Math.min(200, Number(e.target.value) || 40)) })
499
+ }
500
+ ) : /* @__PURE__ */ jsx("span", { className: "self-center text-xs text-slate-400", children: "jpg/png/webp" }),
501
+ /* @__PURE__ */ jsx(
502
+ "input",
503
+ {
504
+ className: "rounded-lg border border-slate-200 px-2 py-1",
505
+ placeholder: "Meerprijs \u20AC",
506
+ defaultValue: f.priceCents ? centsToEuroInput(f.priceCents) : "",
507
+ onChange: (e) => setPers({ priceCents: eurosToCents(e.target.value) ?? 0 })
508
+ }
509
+ ),
510
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
511
+ /* @__PURE__ */ jsx(Switch, { checked: f.required === true, onCheckedChange: (v) => setPers({ required: v }), label: "Verplicht" }),
512
+ /* @__PURE__ */ jsx(
513
+ Button,
514
+ {
515
+ size: "sm",
516
+ variant: "ghost",
517
+ onClick: () => setDef((p) => ({ ...p, personalization: (p.personalization ?? []).filter((_, j) => j !== i) })),
518
+ children: "\u2715"
519
+ }
520
+ )
521
+ ] }),
522
+ /* @__PURE__ */ jsx(
523
+ "input",
524
+ {
525
+ className: "col-span-full rounded-lg border border-slate-200 px-2 py-1 text-sm",
526
+ placeholder: "Uitleg voor de klant (optioneel), bv. 'Max. 12 tekens \u2014 komt op de voorkant'",
527
+ value: f.helpText ?? "",
528
+ onChange: (e) => setPers({ helpText: e.target.value })
457
529
  }
458
530
  )
459
- ] })
460
- ] }, i);
461
- }),
462
- (def.personalization ?? []).length < 3 && /* @__PURE__ */ jsx(
463
- Button,
464
- {
465
- size: "sm",
466
- variant: "secondary",
467
- className: "w-fit",
468
- onClick: () => setDef((p) => ({
469
- ...p,
470
- personalization: [
471
- ...p.personalization ?? [],
472
- { key: `veld${(p.personalization ?? []).length + 1}`, label: "", type: "text", required: false, maxLength: 40, priceCents: 0 }
473
- ]
474
- })),
475
- children: "+ Personalisatie-veld"
476
- }
477
- )
478
- ] })
479
- }
480
- ),
531
+ ] }, i);
532
+ }),
533
+ (def.personalization ?? []).length < 3 && /* @__PURE__ */ jsx(
534
+ Button,
535
+ {
536
+ size: "sm",
537
+ variant: "secondary",
538
+ className: "w-fit",
539
+ onClick: () => setDef((p) => ({
540
+ ...p,
541
+ personalization: [
542
+ ...p.personalization ?? [],
543
+ { key: `veld${(p.personalization ?? []).length + 1}`, label: "", type: "text", required: false, maxLength: 40, priceCents: 0 }
544
+ ]
545
+ })),
546
+ children: "+ Personalisatie-veld"
547
+ }
548
+ )
549
+ ] })
550
+ }
551
+ )
552
+ ] }),
481
553
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [
482
554
  /* @__PURE__ */ jsx(Field, { label: "Status", children: /* @__PURE__ */ jsx(
483
555
  Select,
@@ -512,9 +584,13 @@ function ProductForm({
512
584
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-slate-100 pt-4", children: [
513
585
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
514
586
  /* @__PURE__ */ jsx(Button, { onClick: save, children: "Opslaan" }),
515
- /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: formatCents(def.priceCents) })
587
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: formatCents(def.priceCents) }),
588
+ vuil && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-amber-700", children: "\u2022 niet opgeslagen" })
516
589
  ] }),
517
- /* @__PURE__ */ jsx(Button, { variant: "danger", onClick: remove, children: "Verwijderen" })
590
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
591
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: dupliceer, children: "Dupliceren" }),
592
+ /* @__PURE__ */ jsx(Button, { variant: "danger", onClick: remove, children: "Verwijderen" })
593
+ ] })
518
594
  ] })
519
595
  ] }) });
520
596
  }