@xiwebdesign/xicms-core 7.19.1 → 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.
@@ -190,7 +190,14 @@ function SettingsPanel({ siteId }) {
190
190
  }
191
191
  ) }),
192
192
  /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
193
- /* @__PURE__ */ jsx(Field, { label: "Primaire kleur", children: /* @__PURE__ */ jsx(Input, { value: theme.primary, onChange: (e) => setS({ ...s, theme: { ...theme, primary: e.target.value } }) }) }),
193
+ /* @__PURE__ */ jsx(
194
+ Field,
195
+ {
196
+ label: "Primaire kleur",
197
+ hint: "Wordt ook gebruikt in je e-mails en op de factuur-PDF",
198
+ children: /* @__PURE__ */ jsx(Input, { value: theme.primary, onChange: (e) => setS({ ...s, theme: { ...theme, primary: e.target.value } }) })
199
+ }
200
+ ),
194
201
  /* @__PURE__ */ jsx(Field, { label: "Accentkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.accent ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, accent: e.target.value } }) }) }),
195
202
  /* @__PURE__ */ jsx(Field, { label: "Achtergrondkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.background ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, background: e.target.value } }), placeholder: "#ffffff" }) }),
196
203
  /* @__PURE__ */ jsx(Field, { label: "Tekstkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.foreground ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } }), placeholder: "#0f172a" }) })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/settings/SettingsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { doc, onSnapshot } from \"firebase/firestore\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { SiteSettingsSchema, type SiteSettings } from \"../../schema/settings\";\nimport { useModules } from \"../../modules/context\";\nimport { useSiteConfig } from \"../../config\";\nimport type { ModulesSiteConfig } from \"../../modules/types\";\nimport { builtinThemePresets } from \"../../theme/presets\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { ModuleSettingsForm } from \"./moduleSettingsForm\";\nimport { SnapshotsCard } from \"./SnapshotsCard\";\nimport { MailSettings } from \"./MailSettings\";\n\nexport function SettingsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { defs } = useModules();\n const cfg = useSiteConfig();\n const [s, setS] = useState<SiteSettings>({});\n const [loaded, setLoaded] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [restoring, setRestoring] = useState(false);\n const restoreInput = useRef<HTMLInputElement>(null);\n\n async function downloadBackup(silent = false): Promise<boolean> {\n const app = getFirebaseApp();\n if (!app) return false;\n setExporting(true);\n try {\n const fn = callable<{ siteId: string }, Record<string, unknown>>(app, \"exportSite\");\n const res = await fn({ siteId });\n const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `xicms-backup-${siteId}-${new Date().toISOString().slice(0, 10)}.json`;\n a.click();\n URL.revokeObjectURL(url);\n if (!silent) toast({ title: \"Back-up gedownload\", tone: \"success\" });\n return true;\n } catch (e) {\n toast({ title: \"Back-up mislukt\", description: (e as Error).message, tone: \"error\" });\n return false;\n } finally {\n setExporting(false);\n }\n }\n\n async function restoreFromFile(file: File) {\n let backup: Record<string, unknown>;\n try {\n backup = JSON.parse(await file.text()) as Record<string, unknown>;\n } catch {\n toast({ title: \"Ongeldig bestand\", description: \"Dit is geen geldig back-upbestand.\", tone: \"error\" });\n return;\n }\n if (backup._format !== \"xicms-export\") {\n toast({ title: \"Ongeldig bestand\", description: \"Dit lijkt geen XiCMS-back-up.\", tone: \"error\" });\n return;\n }\n if (backup.siteId && backup.siteId !== siteId) {\n const ok = await confirm({\n title: \"Back-up van een andere site\",\n description: `Deze back-up is van “${String(backup.siteId)}”, niet van deze site (${siteId}). Toch terugzetten?`,\n confirmLabel: \"Toch terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n }\n const ok = await confirm({\n title: \"Back-up terugzetten?\",\n description: `Dit VERVANGT alle huidige pagina's, nieuws, media en instellingen door de inhoud van de back-up${backup.exportedAt ? ` van ${new Date(String(backup.exportedAt)).toLocaleString(\"nl-NL\")}` : \"\"}. We maken eerst automatisch een back-up van de huidige stand.`,\n confirmLabel: \"Terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n setRestoring(true);\n try {\n // Veiligheid: eerst de huidige stand wegschrijven (als download) vóór we overschrijven.\n // Lukt die back-up niet, dan NIET doorgaan met de destructieve restore (anders ben je de\n // huidige stand kwijt zonder vangnet). De gebruiker kan het daarna opnieuw proberen.\n if (!(await downloadBackup(true))) {\n toast({ title: \"Terugzetten afgebroken\", description: \"De veiligheidsback-up van de huidige stand mislukte; er is niets overschreven.\", tone: \"error\" });\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = callable(app, \"restoreSite\");\n await fn({ siteId, backup });\n toast({ title: \"Back-up teruggezet\", description: \"Herlaad de pagina om alles bij te werken.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Terugzetten mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setRestoring(false);\n }\n }\n\n /**\n * Instellingen was één scroll van negen kaarten — van sitenaam tot back-up, alles onder elkaar.\n * Dat leest niet als \"waar stel ik mijn huisstijl in?\" maar als \"zoek het maar\". De kaarten zijn\n * nu per onderwerp gegroepeerd; je ziet er één tegelijk. De staat zit in `s` hierboven, dus\n * wisselen van sectie raakt niets kwijt en één \"Opslaan\" bewaart nog steeds het geheel.\n */\n const [sectie, setSectie] = useState<string>(\"algemeen\");\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoaded(true);\n return;\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"settings\", \"site\"), (snap) => {\n const uitDb = snap.exists() ? (snap.data() as SiteSettings) : {};\n // Sitenaam, domein en contact-e-mail stonden hier leeg, en dat las als \"niet ingevuld\".\n // Ze wáren ingevuld, alleen ergens anders: in `content/site.config.ts`, waar ze bij het\n // bouwen van de site zijn gezet. Het instellingen-document kent ze pas zodra iemand hier\n // op Opslaan drukt, en dat had nog nooit iemand gedaan.\n //\n // Terugvallen op de site-config maakt het eerlijk: je ziet de waarde die de site\n // daadwerkelijk gebruikt, en kunt hem van daaruit aanpassen. Wat in het document staat wint\n // altijd — anders zou een bewust leeggemaakt veld telkens terugkomen.\n setS({\n ...uitDb,\n name: uitDb.name ?? cfg.name,\n domain: uitDb.domain ?? cfg.domain,\n contactEmail: uitDb.contactEmail ?? cfg.contactEmail,\n });\n setLoaded(true);\n });\n }, [siteId, cfg.name, cfg.domain, cfg.contactEmail]);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = SiteSettingsSchema.safeParse(s);\n if (!parsed.success) {\n toast({ title: \"Ongeldige instellingen\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n // Via de callable i.p.v. rechtstreeks: die valideert server-side, leidt de publieke subset\n // zelf af (zodat `site` en `public` niet uit elkaar kunnen lopen) én duwt de cache-tag door,\n // waardoor de wijziging binnen seconden live staat i.p.v. na het ISR-vangnet.\n try {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase is niet geconfigureerd.\");\n const fn = callable(app, \"saveSiteSettings\");\n const res = await fn({ siteId, settings: parsed.data });\n toast({ title: \"Opgeslagen\", description: (res.data as { message?: string })?.message ?? \"Wijzigingen zijn live.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n if (!loaded) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n const forms = s.forms ?? { mode: \"xicms\" as const };\n const theme = s.theme ?? { primary: \"#4f46e5\" };\n // Zelfde terugval als XiCMSProvider: `mode` wint, anders de oude boolean.\n const donkerModus: \"light\" | \"dark\" | \"auto\" = theme.dark?.mode ?? (theme.dark?.auto ? \"auto\" : \"light\");\n const footer = s.footer ?? {};\n\n // Hulpfuncties voor de module-instellingen.\n const moduleState = (id: string, defaultEnabled: boolean) => {\n const raw = (s.modules as ModulesSiteConfig | undefined)?.[id];\n if (typeof raw === \"boolean\") return { enabled: raw, config: {} as Record<string, unknown> };\n return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };\n };\n const setModule = (id: string, next: { enabled: boolean; config: Record<string, unknown> }) =>\n setS({ ...s, modules: { ...(s.modules as ModulesSiteConfig | undefined), [id]: next } });\n\n const SECTIES: { sleutel: string; label: string }[] = [\n { sleutel: \"algemeen\", label: \"Algemeen\" },\n { sleutel: \"huisstijl\", label: \"Huisstijl\" },\n { sleutel: \"seo\", label: \"SEO\" },\n { sleutel: \"footer\", label: \"Footer\" },\n { sleutel: \"modules\", label: \"Modules\" },\n { sleutel: \"mail\", label: \"E-mail\" },\n { sleutel: \"backup\", label: \"Back-up\" },\n ];\n\n return (\n /* Een formulier leest niet op 1300px breed; een invoerveld hoort niet breder te zijn dan de\n tekst die erin komt. Vandaar een leesbreedte in plaats van het hele werkblad vullen. */\n <div className=\"grid max-w-4xl gap-4\">\n <nav aria-label=\"Onderdelen van de instellingen\" className=\"flex flex-wrap gap-1\">\n {SECTIES.map((x) => (\n <button\n key={x.sleutel}\n type=\"button\"\n aria-current={sectie === x.sleutel ? \"page\" : undefined}\n onClick={() => setSectie(x.sleutel)}\n className=\"rounded-lg px-3 py-1.5 text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none aria-[current=page]:bg-slate-200 aria-[current=page]:text-slate-900\"\n >\n {x.label}\n </button>\n ))}\n </nav>\n\n {sectie === \"algemeen\" && (\n <>\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Algemeen</h3>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Sitenaam\"><Input value={s.name ?? \"\"} onChange={(e) => setS({ ...s, name: e.target.value })} /></Field>\n <Field label=\"Domein\"><Input value={s.domain ?? \"\"} onChange={(e) => setS({ ...s, domain: e.target.value })} /></Field>\n <Field label=\"Contact-e-mail\"><Input value={s.contactEmail ?? \"\"} onChange={(e) => setS({ ...s, contactEmail: e.target.value })} /></Field>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"huisstijl\" && (\n <>\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Huisstijl</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Thema-preset\" hint=\"Vult kleuren, lettertype en afronding in één keer in.\">\n <Select\n value=\"\"\n placeholder=\"Kies een preset…\"\n onValueChange={(id) => {\n const preset = builtinThemePresets.find((p) => p.id === id);\n if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });\n }}\n options={builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))}\n />\n </Field>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Primaire kleur\"><Input value={theme.primary} onChange={(e) => setS({ ...s, theme: { ...theme, primary: e.target.value } })} /></Field>\n <Field label=\"Accentkleur\"><Input value={theme.accent ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, accent: e.target.value } })} /></Field>\n <Field label=\"Achtergrondkleur\"><Input value={theme.background ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, background: e.target.value } })} placeholder=\"#ffffff\" /></Field>\n <Field label=\"Tekstkleur\"><Input value={theme.foreground ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } })} placeholder=\"#0f172a\" /></Field>\n </div>\n {/* Donkere modus. Het schema en de rendering ondersteunden dit al, maar er was geen knop:\n donker aanzetten kon alleen door site.config.ts te bewerken — dus niet door de klant\n zelf. De kleuren verschijnen pas als ze ergens toe doen; bij \"altijd licht\" zou een\n donkere achtergrondkleur alleen verwarren. */}\n <Field\n label=\"Donkere modus\"\n hint={\n donkerModus === \"auto\"\n ? \"Volgt de voorkeur van de bezoeker: wie zijn telefoon of computer op donker heeft staan, ziet de site donker.\"\n : donkerModus === \"dark\"\n ? \"De site is voor iedereen donker, ongeacht de voorkeur van de bezoeker.\"\n : \"De site is voor iedereen licht.\"\n }\n >\n <Select\n value={donkerModus}\n onValueChange={(v) =>\n setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), mode: v as \"light\" | \"dark\" | \"auto\" } } })\n }\n options={[\n { value: \"light\", label: \"Altijd licht\" },\n { value: \"dark\", label: \"Altijd donker\" },\n { value: \"auto\", label: \"Automatisch — volg de bezoeker\" },\n ]}\n />\n </Field>\n {donkerModus !== \"light\" && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <Field label=\"Donkere achtergrond\">\n <Input\n value={theme.dark?.background ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), background: e.target.value } } })}\n placeholder=\"#0f172a\"\n />\n </Field>\n <Field label=\"Donkere tekstkleur\">\n <Input\n value={theme.dark?.foreground ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), foreground: e.target.value } } })}\n placeholder=\"#e2e8f0\"\n />\n </Field>\n {/* Een merkkleur die op wit werkt is op zwart vaak te donker; daarom een eigen waarde. */}\n <Field label=\"Donkere merkkleur\" hint=\"Laat leeg om dezelfde te gebruiken.\">\n <Input\n value={theme.dark?.primary ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), primary: e.target.value } } })}\n placeholder={theme.primary}\n />\n </Field>\n </div>\n )}\n <Field label=\"Logo-URL (e-mail & factuur)\" hint=\"Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.\">\n <Input value={theme.logo ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, logo: e.target.value } })} placeholder=\"https://…/logo.png\" />\n </Field>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"seo\" && (\n <>\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">SEO (standaard)</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Standaard-omschrijving\">\n <Input value={s.seoDefaults?.description ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } })} />\n </Field>\n <Field label=\"OG-afbeelding (URL)\">\n <Input value={s.seoDefaults?.ogImage ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } })} />\n </Field>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"footer\" && (\n <>\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Footer</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Kolommen met links, social-links en copyright. Wijzigingen zijn direct live.</p>\n <div className=\"grid gap-4\">\n <div>\n <span className=\"text-sm font-medium\">Kolommen</span>\n <ArrayEditor\n items={footer.columns ?? []}\n onChange={(columns) => setS({ ...s, footer: { ...footer, columns } })}\n newItem={() => ({ heading: \"\", links: [] })}\n addLabel=\"+ Kolom\"\n emptyLabel=\"Nog geen kolommen.\"\n renderRow={(col, update) => (\n <div className=\"grid gap-2\">\n <Field label=\"Kop\"><Input value={col.heading ?? \"\"} onChange={(e) => update({ heading: e.target.value })} /></Field>\n <ArrayEditor\n items={col.links ?? []}\n onChange={(links) => update({ links })}\n newItem={() => ({ label: \"\", href: \"\" })}\n addLabel=\"+ Link\"\n renderRow={(link, updL) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label} placeholder=\"Label\" onChange={(e) => updL({ label: e.target.value })} />\n <Input value={link.href} placeholder=\"/pad of https://…\" onChange={(e) => updL({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n )}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Social-links</span>\n <ArrayEditor\n items={footer.social ?? []}\n onChange={(social) => setS({ ...s, footer: { ...footer, social } })}\n newItem={() => ({ platform: \"\", href: \"\" })}\n addLabel=\"+ Social-link\"\n renderRow={(link, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.platform} placeholder=\"bv. Instagram\" onChange={(e) => update({ platform: e.target.value })} />\n <Input value={link.href} placeholder=\"https://…\" onChange={(e) => update({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Copyright (leeg = automatisch)\">\n <Input value={footer.copyright ?? \"\"} onChange={(e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } })} />\n </Field>\n <label className=\"flex items-center gap-2 self-end text-sm\">\n <Switch checked={footer.showAdminLink !== false} onCheckedChange={(v) => setS({ ...s, footer: { ...footer, showAdminLink: v } })} />\n “Beheer”-link tonen\n </label>\n </div>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"modules\" && (\n <>\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Modules</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel.</p>\n <div className=\"grid gap-3\">\n {defs\n .filter((d) => !d.core)\n .map((d) => {\n const st = moduleState(d.id, d.defaultEnabled ?? true);\n return (\n <div key={d.id} className=\"rounded-lg border border-slate-200 p-3\">\n <label className=\"flex items-center justify-between gap-2\">\n <span>\n <strong className=\"text-sm\">{d.label}</strong>\n {d.description && <span className=\"block text-xs text-slate-500\">{d.description}</span>}\n </span>\n <Switch checked={st.enabled} onCheckedChange={(v) => setModule(d.id, { enabled: v, config: st.config })} />\n </label>\n {st.enabled && d.settingsSchema && (\n <div className=\"mt-3\">\n <ModuleSettingsForm\n schema={d.settingsSchema}\n value={st.config}\n onChange={(config) => setModule(d.id, { enabled: st.enabled, config })}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"mail\" && (\n <>\n {/* Site-brede mailprovider + opmaak (formulieren én shop). Zie docs/SHARED-SETTINGS.md. */}\n <MailSettings siteId={siteId} />\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">E-mailteksten</h3>\n <p className=\"text-sm text-slate-600\">\n De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder{\" \"}\n <strong>Formulieren</strong>, webwinkel-mails onder <strong>Webwinkel → E-mails</strong>. Provider en\n opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in.\n </p>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Formulier-afzender</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n De afzender-identiteit die formulier-notificaties gebruiken. Kies de XiCMS-afzender\n (werkt direct) of het eigen domein van de klant (vereist domeinverificatie). De provider\n waarmee daadwerkelijk verstuurd wordt, stel je hierboven bij <strong>E-mail</strong> in.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzender-modus\">\n <Select\n value={forms.mode}\n onValueChange={(v) => setS({ ...s, forms: { ...forms, mode: v as \"xicms\" | \"client\" } })}\n options={[\n { value: \"xicms\", label: \"XiCMS-domein (aanbevolen)\" },\n { value: \"client\", label: \"Eigen domein van de klant\" },\n ]}\n />\n </Field>\n <Field label=\"Van (from) e-mail\" hint=\"Moet een geverifieerd domein zijn.\">\n <Input value={forms.fromEmail ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } })} />\n </Field>\n <Field label=\"Van (naam)\"><Input value={forms.fromName ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } })} /></Field>\n <Field label=\"Notificaties naar\" hint=\"Meestal het mailadres van de klant.\">\n <Input value={forms.notifyTo ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } })} />\n </Field>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"backup\" && (\n <>\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Back-up &amp; herstel</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij)\n op in één bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden\n zelf blijven in de cloud staan — de back-up bevat de teksten, instellingen en verwijzingen.\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"secondary\" onClick={() => void downloadBackup()} disabled={exporting || restoring}>\n {exporting ? \"Bezig…\" : \"Back-up downloaden\"}\n </Button>\n <Button variant=\"secondary\" onClick={() => restoreInput.current?.click()} disabled={restoring || exporting}>\n {restoring ? \"Terugzetten…\" : \"Back-up terugzetten…\"}\n </Button>\n <input\n ref={restoreInput}\n type=\"file\"\n accept=\"application/json,.json\"\n hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void restoreFromFile(f);\n e.target.value = \"\";\n }}\n />\n </div>\n <p className=\"mt-2 text-xs text-slate-400\">\n Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een\n back-up van de huidige stand, zodat je nooit iets kwijtraakt.\n </p>\n </Card>\n\n <SnapshotsCard siteId={siteId} />\n </>\n )}\n\n {/* Sticky: één formulier, één opslaan-knop — die moet bereikbaar blijven vanuit elke sectie,\n ook onderaan een lange (Huisstijl, Footer). */}\n <div className=\"sticky bottom-0 -mx-4 flex justify-end border-t px-4 py-3\" style={{ borderColor: \"var(--color-border)\", background: \"var(--admin-canvas)\" }}>\n <Button onClick={save}>Instellingen opslaan</Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["ok"]}
1
+ {"version":3,"sources":["../../../src/admin/settings/SettingsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { doc, onSnapshot } from \"firebase/firestore\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { SiteSettingsSchema, type SiteSettings } from \"../../schema/settings\";\nimport { useModules } from \"../../modules/context\";\nimport { useSiteConfig } from \"../../config\";\nimport type { ModulesSiteConfig } from \"../../modules/types\";\nimport { builtinThemePresets } from \"../../theme/presets\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { ModuleSettingsForm } from \"./moduleSettingsForm\";\nimport { SnapshotsCard } from \"./SnapshotsCard\";\nimport { MailSettings } from \"./MailSettings\";\n\nexport function SettingsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { defs } = useModules();\n const cfg = useSiteConfig();\n const [s, setS] = useState<SiteSettings>({});\n const [loaded, setLoaded] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [restoring, setRestoring] = useState(false);\n const restoreInput = useRef<HTMLInputElement>(null);\n\n async function downloadBackup(silent = false): Promise<boolean> {\n const app = getFirebaseApp();\n if (!app) return false;\n setExporting(true);\n try {\n const fn = callable<{ siteId: string }, Record<string, unknown>>(app, \"exportSite\");\n const res = await fn({ siteId });\n const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `xicms-backup-${siteId}-${new Date().toISOString().slice(0, 10)}.json`;\n a.click();\n URL.revokeObjectURL(url);\n if (!silent) toast({ title: \"Back-up gedownload\", tone: \"success\" });\n return true;\n } catch (e) {\n toast({ title: \"Back-up mislukt\", description: (e as Error).message, tone: \"error\" });\n return false;\n } finally {\n setExporting(false);\n }\n }\n\n async function restoreFromFile(file: File) {\n let backup: Record<string, unknown>;\n try {\n backup = JSON.parse(await file.text()) as Record<string, unknown>;\n } catch {\n toast({ title: \"Ongeldig bestand\", description: \"Dit is geen geldig back-upbestand.\", tone: \"error\" });\n return;\n }\n if (backup._format !== \"xicms-export\") {\n toast({ title: \"Ongeldig bestand\", description: \"Dit lijkt geen XiCMS-back-up.\", tone: \"error\" });\n return;\n }\n if (backup.siteId && backup.siteId !== siteId) {\n const ok = await confirm({\n title: \"Back-up van een andere site\",\n description: `Deze back-up is van “${String(backup.siteId)}”, niet van deze site (${siteId}). Toch terugzetten?`,\n confirmLabel: \"Toch terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n }\n const ok = await confirm({\n title: \"Back-up terugzetten?\",\n description: `Dit VERVANGT alle huidige pagina's, nieuws, media en instellingen door de inhoud van de back-up${backup.exportedAt ? ` van ${new Date(String(backup.exportedAt)).toLocaleString(\"nl-NL\")}` : \"\"}. We maken eerst automatisch een back-up van de huidige stand.`,\n confirmLabel: \"Terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n setRestoring(true);\n try {\n // Veiligheid: eerst de huidige stand wegschrijven (als download) vóór we overschrijven.\n // Lukt die back-up niet, dan NIET doorgaan met de destructieve restore (anders ben je de\n // huidige stand kwijt zonder vangnet). De gebruiker kan het daarna opnieuw proberen.\n if (!(await downloadBackup(true))) {\n toast({ title: \"Terugzetten afgebroken\", description: \"De veiligheidsback-up van de huidige stand mislukte; er is niets overschreven.\", tone: \"error\" });\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = callable(app, \"restoreSite\");\n await fn({ siteId, backup });\n toast({ title: \"Back-up teruggezet\", description: \"Herlaad de pagina om alles bij te werken.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Terugzetten mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setRestoring(false);\n }\n }\n\n /**\n * Instellingen was één scroll van negen kaarten — van sitenaam tot back-up, alles onder elkaar.\n * Dat leest niet als \"waar stel ik mijn huisstijl in?\" maar als \"zoek het maar\". De kaarten zijn\n * nu per onderwerp gegroepeerd; je ziet er één tegelijk. De staat zit in `s` hierboven, dus\n * wisselen van sectie raakt niets kwijt en één \"Opslaan\" bewaart nog steeds het geheel.\n */\n const [sectie, setSectie] = useState<string>(\"algemeen\");\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoaded(true);\n return;\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"settings\", \"site\"), (snap) => {\n const uitDb = snap.exists() ? (snap.data() as SiteSettings) : {};\n // Sitenaam, domein en contact-e-mail stonden hier leeg, en dat las als \"niet ingevuld\".\n // Ze wáren ingevuld, alleen ergens anders: in `content/site.config.ts`, waar ze bij het\n // bouwen van de site zijn gezet. Het instellingen-document kent ze pas zodra iemand hier\n // op Opslaan drukt, en dat had nog nooit iemand gedaan.\n //\n // Terugvallen op de site-config maakt het eerlijk: je ziet de waarde die de site\n // daadwerkelijk gebruikt, en kunt hem van daaruit aanpassen. Wat in het document staat wint\n // altijd — anders zou een bewust leeggemaakt veld telkens terugkomen.\n setS({\n ...uitDb,\n name: uitDb.name ?? cfg.name,\n domain: uitDb.domain ?? cfg.domain,\n contactEmail: uitDb.contactEmail ?? cfg.contactEmail,\n });\n setLoaded(true);\n });\n }, [siteId, cfg.name, cfg.domain, cfg.contactEmail]);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = SiteSettingsSchema.safeParse(s);\n if (!parsed.success) {\n toast({ title: \"Ongeldige instellingen\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n // Via de callable i.p.v. rechtstreeks: die valideert server-side, leidt de publieke subset\n // zelf af (zodat `site` en `public` niet uit elkaar kunnen lopen) én duwt de cache-tag door,\n // waardoor de wijziging binnen seconden live staat i.p.v. na het ISR-vangnet.\n try {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase is niet geconfigureerd.\");\n const fn = callable(app, \"saveSiteSettings\");\n const res = await fn({ siteId, settings: parsed.data });\n toast({ title: \"Opgeslagen\", description: (res.data as { message?: string })?.message ?? \"Wijzigingen zijn live.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n if (!loaded) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n const forms = s.forms ?? { mode: \"xicms\" as const };\n const theme = s.theme ?? { primary: \"#4f46e5\" };\n // Zelfde terugval als XiCMSProvider: `mode` wint, anders de oude boolean.\n const donkerModus: \"light\" | \"dark\" | \"auto\" = theme.dark?.mode ?? (theme.dark?.auto ? \"auto\" : \"light\");\n const footer = s.footer ?? {};\n\n // Hulpfuncties voor de module-instellingen.\n const moduleState = (id: string, defaultEnabled: boolean) => {\n const raw = (s.modules as ModulesSiteConfig | undefined)?.[id];\n if (typeof raw === \"boolean\") return { enabled: raw, config: {} as Record<string, unknown> };\n return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };\n };\n const setModule = (id: string, next: { enabled: boolean; config: Record<string, unknown> }) =>\n setS({ ...s, modules: { ...(s.modules as ModulesSiteConfig | undefined), [id]: next } });\n\n const SECTIES: { sleutel: string; label: string }[] = [\n { sleutel: \"algemeen\", label: \"Algemeen\" },\n { sleutel: \"huisstijl\", label: \"Huisstijl\" },\n { sleutel: \"seo\", label: \"SEO\" },\n { sleutel: \"footer\", label: \"Footer\" },\n { sleutel: \"modules\", label: \"Modules\" },\n { sleutel: \"mail\", label: \"E-mail\" },\n { sleutel: \"backup\", label: \"Back-up\" },\n ];\n\n return (\n /* Een formulier leest niet op 1300px breed; een invoerveld hoort niet breder te zijn dan de\n tekst die erin komt. Vandaar een leesbreedte in plaats van het hele werkblad vullen. */\n <div className=\"grid max-w-4xl gap-4\">\n <nav aria-label=\"Onderdelen van de instellingen\" className=\"flex flex-wrap gap-1\">\n {SECTIES.map((x) => (\n <button\n key={x.sleutel}\n type=\"button\"\n aria-current={sectie === x.sleutel ? \"page\" : undefined}\n onClick={() => setSectie(x.sleutel)}\n className=\"rounded-lg px-3 py-1.5 text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none aria-[current=page]:bg-slate-200 aria-[current=page]:text-slate-900\"\n >\n {x.label}\n </button>\n ))}\n </nav>\n\n {sectie === \"algemeen\" && (\n <>\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Algemeen</h3>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Sitenaam\"><Input value={s.name ?? \"\"} onChange={(e) => setS({ ...s, name: e.target.value })} /></Field>\n <Field label=\"Domein\"><Input value={s.domain ?? \"\"} onChange={(e) => setS({ ...s, domain: e.target.value })} /></Field>\n <Field label=\"Contact-e-mail\"><Input value={s.contactEmail ?? \"\"} onChange={(e) => setS({ ...s, contactEmail: e.target.value })} /></Field>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"huisstijl\" && (\n <>\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Huisstijl</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Thema-preset\" hint=\"Vult kleuren, lettertype en afronding in één keer in.\">\n <Select\n value=\"\"\n placeholder=\"Kies een preset…\"\n onValueChange={(id) => {\n const preset = builtinThemePresets.find((p) => p.id === id);\n if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });\n }}\n options={builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))}\n />\n </Field>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field\n label=\"Primaire kleur\"\n hint=\"Wordt ook gebruikt in je e-mails en op de factuur-PDF\"\n ><Input value={theme.primary} onChange={(e) => setS({ ...s, theme: { ...theme, primary: e.target.value } })} /></Field>\n <Field label=\"Accentkleur\"><Input value={theme.accent ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, accent: e.target.value } })} /></Field>\n <Field label=\"Achtergrondkleur\"><Input value={theme.background ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, background: e.target.value } })} placeholder=\"#ffffff\" /></Field>\n <Field label=\"Tekstkleur\"><Input value={theme.foreground ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } })} placeholder=\"#0f172a\" /></Field>\n </div>\n {/* Donkere modus. Het schema en de rendering ondersteunden dit al, maar er was geen knop:\n donker aanzetten kon alleen door site.config.ts te bewerken — dus niet door de klant\n zelf. De kleuren verschijnen pas als ze ergens toe doen; bij \"altijd licht\" zou een\n donkere achtergrondkleur alleen verwarren. */}\n <Field\n label=\"Donkere modus\"\n hint={\n donkerModus === \"auto\"\n ? \"Volgt de voorkeur van de bezoeker: wie zijn telefoon of computer op donker heeft staan, ziet de site donker.\"\n : donkerModus === \"dark\"\n ? \"De site is voor iedereen donker, ongeacht de voorkeur van de bezoeker.\"\n : \"De site is voor iedereen licht.\"\n }\n >\n <Select\n value={donkerModus}\n onValueChange={(v) =>\n setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), mode: v as \"light\" | \"dark\" | \"auto\" } } })\n }\n options={[\n { value: \"light\", label: \"Altijd licht\" },\n { value: \"dark\", label: \"Altijd donker\" },\n { value: \"auto\", label: \"Automatisch — volg de bezoeker\" },\n ]}\n />\n </Field>\n {donkerModus !== \"light\" && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <Field label=\"Donkere achtergrond\">\n <Input\n value={theme.dark?.background ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), background: e.target.value } } })}\n placeholder=\"#0f172a\"\n />\n </Field>\n <Field label=\"Donkere tekstkleur\">\n <Input\n value={theme.dark?.foreground ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), foreground: e.target.value } } })}\n placeholder=\"#e2e8f0\"\n />\n </Field>\n {/* Een merkkleur die op wit werkt is op zwart vaak te donker; daarom een eigen waarde. */}\n <Field label=\"Donkere merkkleur\" hint=\"Laat leeg om dezelfde te gebruiken.\">\n <Input\n value={theme.dark?.primary ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), primary: e.target.value } } })}\n placeholder={theme.primary}\n />\n </Field>\n </div>\n )}\n <Field label=\"Logo-URL (e-mail & factuur)\" hint=\"Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.\">\n <Input value={theme.logo ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, logo: e.target.value } })} placeholder=\"https://…/logo.png\" />\n </Field>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"seo\" && (\n <>\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">SEO (standaard)</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Standaard-omschrijving\">\n <Input value={s.seoDefaults?.description ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } })} />\n </Field>\n <Field label=\"OG-afbeelding (URL)\">\n <Input value={s.seoDefaults?.ogImage ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } })} />\n </Field>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"footer\" && (\n <>\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Footer</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Kolommen met links, social-links en copyright. Wijzigingen zijn direct live.</p>\n <div className=\"grid gap-4\">\n <div>\n <span className=\"text-sm font-medium\">Kolommen</span>\n <ArrayEditor\n items={footer.columns ?? []}\n onChange={(columns) => setS({ ...s, footer: { ...footer, columns } })}\n newItem={() => ({ heading: \"\", links: [] })}\n addLabel=\"+ Kolom\"\n emptyLabel=\"Nog geen kolommen.\"\n renderRow={(col, update) => (\n <div className=\"grid gap-2\">\n <Field label=\"Kop\"><Input value={col.heading ?? \"\"} onChange={(e) => update({ heading: e.target.value })} /></Field>\n <ArrayEditor\n items={col.links ?? []}\n onChange={(links) => update({ links })}\n newItem={() => ({ label: \"\", href: \"\" })}\n addLabel=\"+ Link\"\n renderRow={(link, updL) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label} placeholder=\"Label\" onChange={(e) => updL({ label: e.target.value })} />\n <Input value={link.href} placeholder=\"/pad of https://…\" onChange={(e) => updL({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n )}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Social-links</span>\n <ArrayEditor\n items={footer.social ?? []}\n onChange={(social) => setS({ ...s, footer: { ...footer, social } })}\n newItem={() => ({ platform: \"\", href: \"\" })}\n addLabel=\"+ Social-link\"\n renderRow={(link, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.platform} placeholder=\"bv. Instagram\" onChange={(e) => update({ platform: e.target.value })} />\n <Input value={link.href} placeholder=\"https://…\" onChange={(e) => update({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Copyright (leeg = automatisch)\">\n <Input value={footer.copyright ?? \"\"} onChange={(e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } })} />\n </Field>\n <label className=\"flex items-center gap-2 self-end text-sm\">\n <Switch checked={footer.showAdminLink !== false} onCheckedChange={(v) => setS({ ...s, footer: { ...footer, showAdminLink: v } })} />\n “Beheer”-link tonen\n </label>\n </div>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"modules\" && (\n <>\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Modules</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel.</p>\n <div className=\"grid gap-3\">\n {defs\n .filter((d) => !d.core)\n .map((d) => {\n const st = moduleState(d.id, d.defaultEnabled ?? true);\n return (\n <div key={d.id} className=\"rounded-lg border border-slate-200 p-3\">\n <label className=\"flex items-center justify-between gap-2\">\n <span>\n <strong className=\"text-sm\">{d.label}</strong>\n {d.description && <span className=\"block text-xs text-slate-500\">{d.description}</span>}\n </span>\n <Switch checked={st.enabled} onCheckedChange={(v) => setModule(d.id, { enabled: v, config: st.config })} />\n </label>\n {st.enabled && d.settingsSchema && (\n <div className=\"mt-3\">\n <ModuleSettingsForm\n schema={d.settingsSchema}\n value={st.config}\n onChange={(config) => setModule(d.id, { enabled: st.enabled, config })}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"mail\" && (\n <>\n {/* Site-brede mailprovider + opmaak (formulieren én shop). Zie docs/SHARED-SETTINGS.md. */}\n <MailSettings siteId={siteId} />\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">E-mailteksten</h3>\n <p className=\"text-sm text-slate-600\">\n De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder{\" \"}\n <strong>Formulieren</strong>, webwinkel-mails onder <strong>Webwinkel → E-mails</strong>. Provider en\n opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in.\n </p>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Formulier-afzender</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n De afzender-identiteit die formulier-notificaties gebruiken. Kies de XiCMS-afzender\n (werkt direct) of het eigen domein van de klant (vereist domeinverificatie). De provider\n waarmee daadwerkelijk verstuurd wordt, stel je hierboven bij <strong>E-mail</strong> in.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzender-modus\">\n <Select\n value={forms.mode}\n onValueChange={(v) => setS({ ...s, forms: { ...forms, mode: v as \"xicms\" | \"client\" } })}\n options={[\n { value: \"xicms\", label: \"XiCMS-domein (aanbevolen)\" },\n { value: \"client\", label: \"Eigen domein van de klant\" },\n ]}\n />\n </Field>\n <Field label=\"Van (from) e-mail\" hint=\"Moet een geverifieerd domein zijn.\">\n <Input value={forms.fromEmail ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } })} />\n </Field>\n <Field label=\"Van (naam)\"><Input value={forms.fromName ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } })} /></Field>\n <Field label=\"Notificaties naar\" hint=\"Meestal het mailadres van de klant.\">\n <Input value={forms.notifyTo ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } })} />\n </Field>\n </div>\n </Card>\n </>\n )}\n\n {sectie === \"backup\" && (\n <>\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Back-up &amp; herstel</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij)\n op in één bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden\n zelf blijven in de cloud staan — de back-up bevat de teksten, instellingen en verwijzingen.\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"secondary\" onClick={() => void downloadBackup()} disabled={exporting || restoring}>\n {exporting ? \"Bezig…\" : \"Back-up downloaden\"}\n </Button>\n <Button variant=\"secondary\" onClick={() => restoreInput.current?.click()} disabled={restoring || exporting}>\n {restoring ? \"Terugzetten…\" : \"Back-up terugzetten…\"}\n </Button>\n <input\n ref={restoreInput}\n type=\"file\"\n accept=\"application/json,.json\"\n hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void restoreFromFile(f);\n e.target.value = \"\";\n }}\n />\n </div>\n <p className=\"mt-2 text-xs text-slate-400\">\n Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een\n back-up van de huidige stand, zodat je nooit iets kwijtraakt.\n </p>\n </Card>\n\n <SnapshotsCard siteId={siteId} />\n </>\n )}\n\n {/* Sticky: één formulier, één opslaan-knop — die moet bereikbaar blijven vanuit elke sectie,\n ook onderaan een lange (Huisstijl, Footer). */}\n <div className=\"sticky bottom-0 -mx-4 flex justify-end border-t px-4 py-3\" style={{ borderColor: \"var(--color-border)\", background: \"var(--admin-canvas)\" }}>\n <Button onClick={save}>Instellingen opslaan</Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["ok"]}
@@ -478,10 +478,17 @@ function OrdersTab({ siteId }) {
478
478
  ] })
479
479
  ] }) })
480
480
  ] }),
481
- current.mail?.error && /* @__PURE__ */ jsxs("p", { className: "rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700", children: [
481
+ current.mail?.error ? /* @__PURE__ */ jsxs("p", { className: "rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700", children: [
482
482
  "Bevestigingsmail mislukt: ",
483
483
  current.mail.error
484
- ] }),
484
+ ] }) : current.mail?.confirmationSentAt ? (
485
+ /* Geslaagde verzending werd wél vastgelegd maar nergens getoond: je zag alleen
486
+ iets bij een FOUT, en moest dus maar aannemen dat de mail was aangekomen. */
487
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-slate-400", children: [
488
+ "Bevestigingsmail verstuurd op ",
489
+ fmtDate(current.mail.confirmationSentAt)
490
+ ] })
491
+ ) : null,
485
492
  shipOpen && current.status === "paid" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 rounded-lg border border-slate-200 p-3", children: [
486
493
  /* @__PURE__ */ jsx(
487
494
  Input,
@@ -1 +1 @@
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":[]}
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":"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","names":[]}
@@ -9,12 +9,6 @@ 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
12
  export declare function ProductForm({ siteId, product, categories, onDeleted, onDuplicated, onToast, confirm, }: {
19
13
  siteId: string;
20
14
  product: StoredProduct;