@flowselections/facturen-installatie-techniek 1.3.4 → 1.4.2

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.
Files changed (36) hide show
  1. package/dist-lib/components/FacturenForInstallatieTechniek.d.ts.map +1 -1
  2. package/dist-lib/components/FacturenForInstallatieTechniek.js +12 -2
  3. package/dist-lib/components/FactuurDetailPage.d.ts.map +1 -1
  4. package/dist-lib/components/FactuurDetailPage.js +18 -2
  5. package/dist-lib/components/InvoiceA4Preview.d.ts.map +1 -1
  6. package/dist-lib/components/InvoiceA4Preview.js +64 -23
  7. package/dist-lib/components/ModuleErrorView.d.ts +6 -0
  8. package/dist-lib/components/ModuleErrorView.d.ts.map +1 -0
  9. package/dist-lib/components/ModuleErrorView.js +11 -0
  10. package/dist-lib/components/settings/ModuleHealthSettingsCard.d.ts +3 -0
  11. package/dist-lib/components/settings/ModuleHealthSettingsCard.d.ts.map +1 -0
  12. package/dist-lib/components/settings/ModuleHealthSettingsCard.js +47 -0
  13. package/dist-lib/index.d.ts +3 -0
  14. package/dist-lib/index.d.ts.map +1 -1
  15. package/dist-lib/index.js +5 -1
  16. package/dist-lib/integrations/supabase/client.d.ts +2 -2
  17. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/client.js +3 -14
  19. package/dist-lib/integrations/supabase/types.d.ts +224 -0
  20. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  21. package/dist-lib/lib/articles.functions.d.ts +6 -1
  22. package/dist-lib/lib/articles.functions.d.ts.map +1 -1
  23. package/dist-lib/lib/articles.functions.js +43 -22
  24. package/dist-lib/lib/auth-attach.d.ts +6 -0
  25. package/dist-lib/lib/auth-attach.d.ts.map +1 -0
  26. package/dist-lib/lib/auth-attach.js +17 -0
  27. package/dist-lib/lib/config.d.ts +5 -0
  28. package/dist-lib/lib/config.d.ts.map +1 -1
  29. package/dist-lib/lib/config.js +5 -0
  30. package/dist-lib/lib/dependencies.d.ts +9 -0
  31. package/dist-lib/lib/dependencies.d.ts.map +1 -0
  32. package/dist-lib/lib/dependencies.js +43 -0
  33. package/dist-lib/lib/facturen.d.ts +4 -0
  34. package/dist-lib/lib/facturen.d.ts.map +1 -1
  35. package/dist-lib/lib/facturen.js +25 -1
  36. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"FacturenForInstallatieTechniek.d.ts","sourceRoot":"","sources":["../../src/components/FacturenForInstallatieTechniek.tsx"],"names":[],"mappings":"AAgJA,wBAAgB,8BAA8B,gCAyd7C"}
1
+ {"version":3,"file":"FacturenForInstallatieTechniek.d.ts","sourceRoot":"","sources":["../../src/components/FacturenForInstallatieTechniek.tsx"],"names":[],"mappings":"AAiJA,wBAAgB,8BAA8B,gCAge7C"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { WerkbonPreviewPanel } from "../components/WerkbonPreviewPanel";
3
- import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { dependencyMessage } from "../lib/dependencies";
4
5
  import { DEFAULT_VAT_RATE } from "../lib/config";
5
6
  import { supabase } from "@flowselections/core";
6
7
  import { Link, useNavigate } from "@tanstack/react-router";
@@ -39,6 +40,7 @@ export function FacturenForInstallatieTechniek() {
39
40
  const [paidPeriod, setPaidPeriod] = useState("maand");
40
41
  const [open, setOpen] = useState(false);
41
42
  const [saving, setSaving] = useState(false);
43
+ const createRef = useRef(false);
42
44
  // bron-tab
43
45
  const [sourceType, setSourceType] = useState("work_order");
44
46
  const [sources, setSources] = useState([]);
@@ -136,7 +138,7 @@ export function FacturenForInstallatieTechniek() {
136
138
  if (active)
137
139
  setSources(rows);
138
140
  })
139
- .catch(() => toast.error("Bronnen konden niet worden geladen"))
141
+ .catch((e) => toast.error(dependencyMessage(e, sourceType === "work_order" ? "werkbonnen" : "offertes", "Bronnen")))
140
142
  .finally(() => {
141
143
  if (active)
142
144
  setSourcesLoading(false);
@@ -181,6 +183,9 @@ export function FacturenForInstallatieTechniek() {
181
183
  toast.error("Kies eerst een bron");
182
184
  return;
183
185
  }
186
+ if (createRef.current)
187
+ return;
188
+ createRef.current = true;
184
189
  setSaving(true);
185
190
  try {
186
191
  const sourceLines = await fetchSourceLines(sourceType, source.id);
@@ -204,6 +209,7 @@ export function FacturenForInstallatieTechniek() {
204
209
  toast.error("Factuur aanmaken mislukt");
205
210
  }
206
211
  finally {
212
+ createRef.current = false;
207
213
  setSaving(false);
208
214
  }
209
215
  };
@@ -213,6 +219,9 @@ export function FacturenForInstallatieTechniek() {
213
219
  toast.error("Vul een klantnaam en minimaal één regel in");
214
220
  return;
215
221
  }
222
+ if (createRef.current)
223
+ return;
224
+ createRef.current = true;
216
225
  setSaving(true);
217
226
  try {
218
227
  const result = await createInvoiceFromSource({
@@ -236,6 +245,7 @@ export function FacturenForInstallatieTechniek() {
236
245
  toast.error("Factuur aanmaken mislukt");
237
246
  }
238
247
  finally {
248
+ createRef.current = false;
239
249
  setSaving(false);
240
250
  }
241
251
  };
@@ -1 +1 @@
1
- {"version":3,"file":"FactuurDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/FactuurDetailPage.tsx"],"names":[],"mappings":"AAoDA,wBAAgB,iBAAiB,gCA+ahC"}
1
+ {"version":3,"file":"FactuurDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/FactuurDetailPage.tsx"],"names":[],"mappings":"AAoDA,wBAAgB,iBAAiB,gCA6bhC"}
@@ -33,6 +33,9 @@ export function FactuurDetailPage() {
33
33
  const siblingRetryRef = useRef(false);
34
34
  const sheetRef = useRef(null);
35
35
  const autoOpenedRef = useRef(false);
36
+ const sendingRef = useRef(false);
37
+ const statusRef = useRef(false);
38
+ const [senderChecked, setSenderChecked] = useState(false);
36
39
  const loadSeqRef = useRef(0);
37
40
  const load = useCallback(async () => {
38
41
  const seq = ++loadSeqRef.current;
@@ -103,6 +106,9 @@ export function FactuurDetailPage() {
103
106
  });
104
107
  };
105
108
  const changeStatus = async (status) => {
109
+ if (statusRef.current)
110
+ return;
111
+ statusRef.current = true;
106
112
  setSaving(true);
107
113
  try {
108
114
  await updateInvoiceStatus(id, status);
@@ -113,6 +119,7 @@ export function FactuurDetailPage() {
113
119
  toast.error("Status bijwerken mislukt");
114
120
  }
115
121
  finally {
122
+ statusRef.current = false;
116
123
  setSaving(false);
117
124
  }
118
125
  };
@@ -150,10 +157,11 @@ export function FactuurDetailPage() {
150
157
  message: `Beste ${details.customer_attention || details.customer_name || "klant"},\n\nIn de bijlage vindt u factuur ${num}.\n\nMet vriendelijke groet,\n${company}`,
151
158
  });
152
159
  setSender(null);
160
+ setSenderChecked(false);
153
161
  setAttachPdf(true);
154
162
  setPreparedPdf("");
155
163
  setMailToTouched(false);
156
- fetchInvoiceEmailPurpose().then(fetchSenderForPurpose).then(setSender).catch(() => setSender(null));
164
+ fetchInvoiceEmailPurpose().then(fetchSenderForPurpose).then(setSender).catch(() => setSender(null)).finally(() => setSenderChecked(true));
157
165
  setSendOpen(true);
158
166
  if (!sheetRef.current)
159
167
  return;
@@ -192,6 +200,10 @@ export function FactuurDetailPage() {
192
200
  }
193
201
  if (!invoice)
194
202
  return;
203
+ if (senderChecked && !sender) {
204
+ toast.error("Er is geen afzender ingesteld. Stel die in bij Instellingen → Factuur e-mail.");
205
+ return;
206
+ }
195
207
  if (!attachPdf || !preparedPdf) {
196
208
  toast.error("Vink de PDF-bijlage aan voordat u de factuur verzendt");
197
209
  return;
@@ -204,8 +216,11 @@ export function FactuurDetailPage() {
204
216
  };
205
217
  const doSend = async () => {
206
218
  setConfirmOpen(false);
219
+ if (sendingRef.current)
220
+ return;
207
221
  if (!invoice || !isValidEmail(mail.to) || !attachPdf || !preparedPdf)
208
222
  return;
223
+ sendingRef.current = true;
209
224
  const targetInvoice = nextInvoice ?? prevInvoice;
210
225
  setSaving(true);
211
226
  try {
@@ -242,6 +257,7 @@ export function FactuurDetailPage() {
242
257
  toast.error(e instanceof Error ? e.message : "Verzenden mislukt");
243
258
  }
244
259
  finally {
260
+ sendingRef.current = false;
245
261
  setSaving(false);
246
262
  }
247
263
  };
@@ -258,5 +274,5 @@ export function FactuurDetailPage() {
258
274
  }
259
275
  const isConcept = invoice.status === "concept";
260
276
  const wasSent = Boolean(invoice.sent_at);
261
- return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "sticky top-16 z-20 -mx-6 flex flex-wrap items-center gap-3 border-b border-border/60 bg-background px-6 py-3", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsxs(Link, { to: "/facturen-installatietechniek", className: "gap-1", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Overzicht"] }) }), _jsx("h1", { className: "text-xl font-semibold", children: invoice.invoice_number ?? "Concept" }), _jsxs(Badge, { variant: "outline", children: [SOURCE_LABELS[invoice.source_type] ?? invoice.source_type, invoice.source_number ? ` ${invoice.source_number}` : ""] }), invoice.sent_at ? (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["Verzonden ", new Date(invoice.sent_at).toLocaleString("nl-NL"), " aan ", invoice.sent_to] })) : null, _jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-1 rounded-full border border-border bg-card p-1", children: [_jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 rounded-full", "aria-label": "Vorige factuur op de lijst", title: "Vorige factuur op de lijst", disabled: editing || loading || siblingsLoading || !prevInvoice, onClick: () => goToSibling(prevInvoice?.id), children: _jsx(ChevronLeft, { className: "h-5 w-5" }) }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 rounded-full", "aria-label": "Volgende factuur op de lijst", title: "Volgende factuur op de lijst", disabled: editing || loading || siblingsLoading || !nextInvoice, onClick: () => goToSibling(nextInvoice?.id), children: _jsx(ChevronRight, { className: "h-5 w-5" }) })] }), editing ? (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: () => { setEditing(false); void load(); }, disabled: saving, children: [_jsx(X, { className: "mr-2 h-4 w-4" }), "Annuleren"] }), _jsxs(Button, { onClick: save, disabled: saving, children: [_jsx(Save, { className: "mr-2 h-4 w-4" }), saving ? "Opslaan…" : "Opslaan"] })] })) : (_jsxs(_Fragment, { children: [!wasSent ? (_jsxs(Button, { variant: "outline", onClick: () => (isConcept ? setEditing(true) : toast.error("Alleen conceptfacturen kunnen worden bewerkt. Zet de status terug op Concept.")), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Bewerken"] })) : null, _jsxs(Button, { onClick: () => void openSend(), children: [_jsx(Mail, { className: "mr-2 h-4 w-4" }), wasSent ? "Opnieuw verzenden" : "Verzenden"] })] })), _jsx("div", { className: "w-44", children: _jsxs(Select, { value: invoice.status, onValueChange: (v) => changeStatus(v), disabled: saving || editing, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.entries(STATUS_LABELS).map(([value, label]) => (_jsx(SelectItem, { value: value, children: label }, value))) })] }) })] })] }), _jsx("div", { children: _jsx("div", { className: "min-w-0", children: _jsx(InvoiceA4Preview, { ref: sheetRef, branding: invoice.branding_snapshot ?? emptyInvoiceBranding(), details: details, lines: lines, invoiceNumber: invoice.invoice_number, logoUrl: logoUrl, printable: !editing, editable: editing, defaultVat: String(Number(invoice.vat_rate) || DEFAULT_VAT_RATE), onDetailsChange: setDetails, onLinesChange: setLines }) }) }), _jsx(Dialog, { open: sendOpen, onOpenChange: changeSendOpen, children: _jsxs(DialogContent, { className: "flex max-h-[92vh] max-w-[calc(100vw-2rem)] flex-col sm:max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Factuur verzenden" }) }), _jsxs("div", { className: "min-h-0 flex-1 space-y-3 overflow-y-auto pr-1", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "mail-to", children: "E-mailadres klant" }), _jsx(Input, { id: "mail-to", type: "email", value: mail.to, onChange: (e) => setMail({ ...mail, to: e.target.value }), onBlur: () => setMailToTouched(true), placeholder: "naam@bedrijf.nl", "aria-invalid": mailToTouched && !isValidEmail(mail.to) }), mailToTouched && !isValidEmail(mail.to) ? (_jsx("p", { className: "text-xs text-destructive", children: "Vul een geldig e-mailadres in, bijvoorbeeld naam@bedrijf.nl" })) : null] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "mail-subject", children: "Onderwerp" }), _jsx(Input, { id: "mail-subject", value: mail.subject, onChange: (e) => setMail({ ...mail, subject: e.target.value }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "mail-msg", children: "Bericht" }), _jsx(Textarea, { id: "mail-msg", rows: 6, value: mail.message, onChange: (e) => setMail({ ...mail, message: e.target.value }) })] }), _jsxs("label", { className: "flex cursor-pointer items-start gap-3 rounded-md border border-border p-3", children: [_jsx("input", { type: "checkbox", checked: attachPdf, onChange: (event) => setAttachPdf(event.target.checked), className: "mt-1 h-4 w-4 accent-primary" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium", children: [preparedPdf ? _jsx(FileCheck2, { className: "h-4 w-4 text-primary" }) : _jsx(Download, { className: "h-4 w-4" }), "Factuur als PDF meesturen"] }), _jsx("span", { className: "mt-1 block truncate text-xs text-muted-foreground", children: preparingPdf ? "PDF wordt aangemaakt en gedownload…" : preparedPdf ? `${pdfFilename} is gedownload en klaar als bijlage.` : "PDF kon niet worden voorbereid." })] })] }), !attachPdf ? _jsx("p", { className: "text-xs text-destructive", children: "Vink de PDF aan om deze e-mail te kunnen versturen." }) : null, _jsxs("p", { className: "text-xs text-muted-foreground", children: [sender ? _jsxs(_Fragment, { children: ["Verzonden vanaf ", _jsx("span", { className: "font-medium text-foreground", children: sender.account_email }), ". "] }) : _jsx("span", { className: "text-destructive", children: "Geen afzender ingesteld (Instellingen \u2192 Factuur e-mail). " }), "De factuur wordt als PDF-bijlage aangeboden aan de e-mailmodule."] }), _jsxs(Button, { className: "w-full", onClick: send, disabled: saving || preparingPdf || !attachPdf || !preparedPdf || !isValidEmail(mail.to), children: [_jsx(Mail, { className: "mr-2 h-4 w-4" }), saving ? "Bezig…" : preparingPdf ? "PDF voorbereiden…" : "Verzenden met PDF"] })] })] }) }), _jsx(Dialog, { open: confirmOpen, onOpenChange: setConfirmOpen, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Ander e-mailadres dan bekend" }) }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [knownEmail ? (_jsxs(_Fragment, { children: ["Dit adres wijkt af van het bekende adres van de klant (", _jsx("span", { className: "font-medium text-foreground", children: knownEmail }), ")."] })) : (_jsx(_Fragment, { children: "Er is geen e-mailadres van deze klant bekend." })), " ", "Weet je zeker dat je naar ", _jsx("span", { className: "font-medium text-foreground", children: mail.to.trim() }), " wilt verzenden?"] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => setConfirmOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: () => void doSend(), disabled: saving, children: "Toch verzenden" })] })] }) })] }));
277
+ return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "sticky top-16 z-20 -mx-6 flex flex-wrap items-center gap-3 border-b border-border/60 bg-background px-6 py-3", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsxs(Link, { to: "/facturen-installatietechniek", className: "gap-1", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Overzicht"] }) }), _jsx("h1", { className: "text-xl font-semibold", children: invoice.invoice_number ?? "Concept" }), _jsxs(Badge, { variant: "outline", children: [SOURCE_LABELS[invoice.source_type] ?? invoice.source_type, invoice.source_number ? ` ${invoice.source_number}` : ""] }), invoice.sent_at ? (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["Verzonden ", new Date(invoice.sent_at).toLocaleString("nl-NL"), " aan ", invoice.sent_to] })) : null, _jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-1 rounded-full border border-border bg-card p-1", children: [_jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 rounded-full", "aria-label": "Vorige factuur op de lijst", title: "Vorige factuur op de lijst", disabled: editing || loading || siblingsLoading || !prevInvoice, onClick: () => goToSibling(prevInvoice?.id), children: _jsx(ChevronLeft, { className: "h-5 w-5" }) }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 rounded-full", "aria-label": "Volgende factuur op de lijst", title: "Volgende factuur op de lijst", disabled: editing || loading || siblingsLoading || !nextInvoice, onClick: () => goToSibling(nextInvoice?.id), children: _jsx(ChevronRight, { className: "h-5 w-5" }) })] }), editing ? (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: () => { setEditing(false); void load(); }, disabled: saving, children: [_jsx(X, { className: "mr-2 h-4 w-4" }), "Annuleren"] }), _jsxs(Button, { onClick: save, disabled: saving, children: [_jsx(Save, { className: "mr-2 h-4 w-4" }), saving ? "Opslaan…" : "Opslaan"] })] })) : (_jsxs(_Fragment, { children: [!wasSent ? (_jsxs(Button, { variant: "outline", onClick: () => (isConcept ? setEditing(true) : toast.error("Alleen conceptfacturen kunnen worden bewerkt. Zet de status terug op Concept.")), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Bewerken"] })) : null, _jsxs(Button, { onClick: () => void openSend(), children: [_jsx(Mail, { className: "mr-2 h-4 w-4" }), wasSent ? "Opnieuw verzenden" : "Verzenden"] })] })), _jsx("div", { className: "w-44", children: _jsxs(Select, { value: invoice.status, onValueChange: (v) => changeStatus(v), disabled: saving || editing, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.entries(STATUS_LABELS).map(([value, label]) => (_jsx(SelectItem, { value: value, children: label }, value))) })] }) })] })] }), _jsx("div", { children: _jsx("div", { className: "min-w-0", children: _jsx(InvoiceA4Preview, { ref: sheetRef, branding: invoice.branding_snapshot ?? emptyInvoiceBranding(), details: details, lines: lines, invoiceNumber: invoice.invoice_number, logoUrl: logoUrl, printable: !editing, editable: editing, defaultVat: String(Number(invoice.vat_rate) || DEFAULT_VAT_RATE), onDetailsChange: setDetails, onLinesChange: setLines }) }) }), _jsx(Dialog, { open: sendOpen, onOpenChange: changeSendOpen, children: _jsxs(DialogContent, { className: "flex max-h-[92vh] max-w-[calc(100vw-2rem)] flex-col sm:max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Factuur verzenden" }) }), _jsxs("div", { className: "min-h-0 flex-1 space-y-3 overflow-y-auto pr-1", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "mail-to", children: "E-mailadres klant" }), _jsx(Input, { id: "mail-to", type: "email", value: mail.to, onChange: (e) => setMail({ ...mail, to: e.target.value }), onBlur: () => setMailToTouched(true), placeholder: "naam@bedrijf.nl", "aria-invalid": mailToTouched && !isValidEmail(mail.to) }), mailToTouched && !isValidEmail(mail.to) ? (_jsx("p", { className: "text-xs text-destructive", children: "Vul een geldig e-mailadres in, bijvoorbeeld naam@bedrijf.nl" })) : null] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "mail-subject", children: "Onderwerp" }), _jsx(Input, { id: "mail-subject", value: mail.subject, onChange: (e) => setMail({ ...mail, subject: e.target.value }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "mail-msg", children: "Bericht" }), _jsx(Textarea, { id: "mail-msg", rows: 6, value: mail.message, onChange: (e) => setMail({ ...mail, message: e.target.value }) })] }), _jsxs("label", { className: "flex cursor-pointer items-start gap-3 rounded-md border border-border p-3", children: [_jsx("input", { type: "checkbox", checked: attachPdf, onChange: (event) => setAttachPdf(event.target.checked), className: "mt-1 h-4 w-4 accent-primary" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium", children: [preparedPdf ? _jsx(FileCheck2, { className: "h-4 w-4 text-primary" }) : _jsx(Download, { className: "h-4 w-4" }), "Factuur als PDF meesturen"] }), _jsx("span", { className: "mt-1 block truncate text-xs text-muted-foreground", children: preparingPdf ? "PDF wordt aangemaakt en gedownload…" : preparedPdf ? `${pdfFilename} is gedownload en klaar als bijlage.` : "PDF kon niet worden voorbereid." })] })] }), !attachPdf ? _jsx("p", { className: "text-xs text-destructive", children: "Vink de PDF aan om deze e-mail te kunnen versturen." }) : null, _jsxs("p", { className: "text-xs text-muted-foreground", children: [!senderChecked ? _jsx(_Fragment, { children: "Afzender controleren\u2026 " }) : sender ? _jsxs(_Fragment, { children: ["Verzonden vanaf ", _jsx("span", { className: "font-medium text-foreground", children: sender.account_email }), ". "] }) : _jsx("span", { className: "text-destructive", children: "Geen afzender ingesteld (Instellingen \u2192 Factuur e-mail). " }), "De factuur wordt als PDF-bijlage aangeboden aan de e-mailmodule."] }), _jsxs(Button, { className: "w-full", onClick: send, disabled: saving || preparingPdf || (senderChecked && !sender) || !attachPdf || !preparedPdf || !isValidEmail(mail.to), children: [_jsx(Mail, { className: "mr-2 h-4 w-4" }), saving ? "Bezig…" : preparingPdf ? "PDF voorbereiden…" : "Verzenden met PDF"] })] })] }) }), _jsx(Dialog, { open: confirmOpen, onOpenChange: setConfirmOpen, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Ander e-mailadres dan bekend" }) }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [knownEmail ? (_jsxs(_Fragment, { children: ["Dit adres wijkt af van het bekende adres van de klant (", _jsx("span", { className: "font-medium text-foreground", children: knownEmail }), ")."] })) : (_jsx(_Fragment, { children: "Er is geen e-mailadres van deze klant bekend." })), " ", "Weet je zeker dat je naar ", _jsx("span", { className: "font-medium text-foreground", children: mail.to.trim() }), " wilt verzenden?"] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => setConfirmOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: () => void doSend(), disabled: saving, children: "Toch verzenden" })] })] }) })] }));
262
278
  }
@@ -1 +1 @@
1
- {"version":3,"file":"InvoiceA4Preview.d.ts","sourceRoot":"","sources":["../../src/components/InvoiceA4Preview.tsx"],"names":[],"mappings":"AAKA,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAIzB,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACxC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CAC9C,CAAC;AA+CF,eAAO,MAAM,gBAAgB,+FA8K3B,CAAC"}
1
+ {"version":3,"file":"InvoiceA4Preview.d.ts","sourceRoot":"","sources":["../../src/components/InvoiceA4Preview.tsx"],"names":[],"mappings":"AAMA,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAIzB,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACxC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CAC9C,CAAC;AAqDF,eAAO,MAAM,gBAAgB,+FA4K3B,CAAC"}
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useMemo, useState } from "react";
3
- import { DEFAULT_VAT_RATE, VAT_OPTIONS as CONFIG_VAT_OPTIONS } from "../lib/config";
3
+ import { DEFAULT_VAT_RATE, SEARCH_DEBOUNCE_MS, VAT_OPTIONS as CONFIG_VAT_OPTIONS } from "../lib/config";
4
+ import { dependencyMessage } from "../lib/dependencies";
4
5
  import { useServerFn } from "@tanstack/react-start";
5
6
  import { Button, Input, Popover, PopoverContent, PopoverTrigger } from "@flowselections/core";
6
7
  import { ChevronDown, Package, Plus, Printer, Search, Trash2 } from "lucide-react";
7
- import { editLineToBillable, emptyEditLine, fetchCustomers, fetchPackages, formatCents, } from "../lib/facturen";
8
+ import { editLineToBillable, emptyEditLine, searchCustomers, fetchPackages, formatCents, } from "../lib/facturen";
8
9
  import { listInvoiceArticles } from "../lib/articles.functions";
9
10
  import { ArticlePhoto } from "./ArticlePhoto";
10
11
  const VAT_OPTIONS = CONFIG_VAT_OPTIONS;
@@ -13,37 +14,47 @@ function CustomerPicker({ details, onDetailsChange, onPick }) {
13
14
  const [q, setQ] = useState("");
14
15
  const [items, setItems] = useState([]);
15
16
  const [loading, setLoading] = useState(false);
17
+ const [error, setError] = useState("");
16
18
  useEffect(() => {
17
- if (!open || items.length)
19
+ if (!open)
18
20
  return;
21
+ let active = true;
19
22
  setLoading(true);
20
- fetchCustomers().then((r) => setItems(Array.isArray(r) ? r : [])).catch(() => setItems([])).finally(() => setLoading(false));
21
- }, [open, items.length]);
22
- const term = q.trim().toLowerCase();
23
- const safe = Array.isArray(items) ? items : [];
24
- const filtered = term ? safe.filter((c) => `${c.name ?? ""} ${c.customer_number ?? ""} ${c.billing_city ?? ""}`.toLowerCase().includes(term)) : safe;
25
- return (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { value: details.customer_name, placeholder: "Klantnaam", onChange: (e) => onDetailsChange?.({ ...details, customer_name: e.target.value }), className: "w-full rounded-sm border border-dashed border-border bg-transparent px-1 py-0.5 font-semibold outline-none focus:border-primary" }), _jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-6 shrink-0 px-1.5 print:hidden", "aria-label": "Kies klant", children: _jsx(ChevronDown, { className: "h-3.5 w-3.5" }) }) }), _jsxs(PopoverContent, { align: "start", className: "w-80 p-2", children: [_jsx(Input, { autoFocus: true, placeholder: "Zoek klant\u2026", value: q, onChange: (e) => setQ(e.target.value) }), _jsx("div", { className: "mt-2 max-h-64 overflow-y-auto", children: loading ? _jsx("p", { className: "p-2 text-xs text-muted-foreground", children: "Laden\u2026" }) : filtered.length === 0 ? _jsx("p", { className: "p-2 text-xs text-muted-foreground", children: "Geen klanten gevonden." }) : filtered.map((c) => (_jsxs("button", { type: "button", onClick: () => { onPick(c); setOpen(false); setQ(""); }, className: "block w-full rounded-sm px-2 py-1.5 text-left text-sm hover:bg-accent", children: [_jsx("div", { className: "font-medium", children: c.name ?? "—" }), _jsx("div", { className: "text-xs text-muted-foreground", children: [c.customer_number, c.billing_city].filter(Boolean).join(" · ") })] }, c.id))) })] })] })] }));
23
+ const t = setTimeout(() => {
24
+ searchCustomers(q)
25
+ .then((r) => { if (active) {
26
+ setItems(Array.isArray(r) ? r : []);
27
+ setError("");
28
+ } })
29
+ .catch((e) => { if (active) {
30
+ setItems([]);
31
+ setError(dependencyMessage(e, "klanten", "Klanten"));
32
+ } })
33
+ .finally(() => { if (active)
34
+ setLoading(false); });
35
+ }, SEARCH_DEBOUNCE_MS);
36
+ return () => { active = false; clearTimeout(t); };
37
+ }, [open, q]);
38
+ const filtered = Array.isArray(items) ? items : [];
39
+ return (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { value: details.customer_name, placeholder: "Klantnaam", onChange: (e) => onDetailsChange?.({ ...details, customer_name: e.target.value }), className: "w-full rounded-sm border border-dashed border-border bg-transparent px-1 py-0.5 font-semibold outline-none focus:border-primary" }), _jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-6 shrink-0 px-1.5 print:hidden", "aria-label": "Kies klant", children: _jsx(ChevronDown, { className: "h-3.5 w-3.5" }) }) }), _jsxs(PopoverContent, { align: "start", className: "w-80 p-2", children: [_jsx(Input, { autoFocus: true, placeholder: "Zoek klant\u2026", value: q, onChange: (e) => setQ(e.target.value) }), _jsx("div", { className: "mt-2 max-h-64 overflow-y-auto", children: error ? _jsx("p", { className: "p-2 text-xs text-destructive", children: error }) : loading ? _jsx("p", { className: "p-2 text-xs text-muted-foreground", children: "Laden\u2026" }) : filtered.length === 0 ? _jsx("p", { className: "p-2 text-xs text-muted-foreground", children: "Geen klanten gevonden." }) : filtered.map((c) => (_jsxs("button", { type: "button", onClick: () => { onPick(c); setOpen(false); setQ(""); }, className: "block w-full rounded-sm px-2 py-1.5 text-left text-sm hover:bg-accent", children: [_jsx("div", { className: "font-medium", children: c.name ?? "—" }), _jsx("div", { className: "text-xs text-muted-foreground", children: [c.customer_number, c.billing_city].filter(Boolean).join(" · ") })] }, c.id))) })] })] })] }));
26
40
  }
27
41
  export const InvoiceA4Preview = forwardRef(function InvoiceA4Preview({ branding, details, lines, invoiceNumber, numberIsProvisional, logoUrl, printable = true, editable = false, defaultVat = String(DEFAULT_VAT_RATE), onDetailsChange, onLinesChange, onCustomerPick }, ref) {
28
42
  const safeLines = Array.isArray(lines) ? lines : [];
29
43
  const listArticles = useServerFn(listInvoiceArticles);
30
44
  const [articles, setArticles] = useState([]);
31
- const [articlesLoading, setArticlesLoading] = useState(false);
32
45
  const articleIds = safeLines.map((line) => line.articleId).filter((id) => Boolean(id));
46
+ const idsKey = [...new Set(articleIds)].sort().join(",");
33
47
  useEffect(() => {
34
- if (!editable && articleIds.length === 0)
48
+ if (!idsKey)
35
49
  return;
36
50
  let active = true;
37
- setArticlesLoading(true);
38
- listArticles()
51
+ listArticles({ data: { ids: idsKey.split(",") } })
39
52
  .then((result) => { if (active)
40
53
  setArticles(Array.isArray(result) ? result : []); })
41
54
  .catch(() => { if (active)
42
- setArticles([]); })
43
- .finally(() => { if (active)
44
- setArticlesLoading(false); });
55
+ setArticles([]); });
45
56
  return () => { active = false; };
46
- }, [editable, listArticles, articleIds.join(",")]);
57
+ }, [listArticles, idsKey]);
47
58
  const articleById = useMemo(() => new Map(articles.map((article) => [article.id, article])), [articles]);
48
59
  const computed = safeLines.map(editLineToBillable);
49
60
  const subtotal = computed.reduce((s, l) => s + (l.line_total_cents ?? 0), 0);
@@ -62,26 +73,56 @@ export const InvoiceA4Preview = forwardRef(function InvoiceA4Preview({ branding,
62
73
  return (_jsxs("div", { className: "space-y-2", children: [printable ? (_jsx("div", { className: "flex justify-end print:hidden", children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => window.print(), children: [_jsx(Printer, { className: "mr-2 h-4 w-4" }), "Afdrukken"] }) })) : null, _jsxs("article", { ref: ref, className: "invoice-print-target mx-auto flex min-h-[1123px] w-full max-w-[794px] flex-col bg-card text-card-foreground shadow-sm ring-1 ring-border", style: { "--invoice-primary": branding.primary_color, "--invoice-secondary": branding.secondary_color }, children: [_jsxs("header", { className: "flex h-36 items-stretch bg-[var(--invoice-primary)] px-8 py-4 text-primary-foreground", children: [_jsxs("div", { className: "flex flex-1 items-center gap-3", children: [_jsx("div", { className: "flex h-full items-center", children: logoUrl ? _jsx("img", { src: logoUrl, alt: `${branding.company_name} logo`, crossOrigin: "anonymous", className: "max-h-full max-w-56 object-contain" }) : _jsx("span", { className: "text-xs opacity-75", children: "BEDRIJFSLOGO" }) }), branding.show_company_name !== false ? _jsx("div", { className: "text-2xl font-semibold", children: branding.company_name || "Bedrijfsnaam" }) : null] }), _jsx("div", { className: "flex flex-col items-start justify-center text-left text-[11px] leading-4", children: contact.map((v) => _jsx("div", { children: v }, v)) })] }), _jsxs("div", { className: "flex flex-1 flex-col px-10 py-8 text-xs", children: [_jsxs("div", { className: "mb-8 flex items-start justify-between gap-8", children: [_jsxs("div", { className: "w-64 space-y-0.5 leading-5", children: [editable && onCustomerPick ? (_jsx(CustomerPicker, { details: details, onDetailsChange: onDetailsChange, onPick: onCustomerPick })) : (F({ k: "customer_name", placeholder: "Klantnaam", className: "font-semibold" })), editable || details.customer_attention ? _jsx("div", { children: F({ k: "customer_attention", placeholder: "T.a.v." }) }) : null, _jsx("div", { children: F({ k: "customer_address", placeholder: "Adres" }) }), _jsxs("div", { className: "flex gap-1", children: [F({ k: "customer_postal_code", placeholder: "Postcode", className: "w-24" }), F({ k: "customer_city", placeholder: "Plaats" })] }), editable || details.customer_vat_number ? _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "shrink-0", children: "Btw-nr:" }), F({ k: "customer_vat_number", placeholder: "—" })] }) : null] }), _jsxs("div", { className: "min-w-72", children: [_jsx("h2", { className: "mb-3 text-2xl font-semibold", children: "FACTUUR" }), _jsxs("dl", { className: "grid grid-cols-[auto_1fr] items-center gap-x-5 gap-y-1", children: [_jsx("dt", { children: "Factuurnummer" }), _jsxs("dd", { className: "font-medium", children: [number, numberIsProvisional && invoiceNumber ? _jsx("span", { className: "ml-1 text-[10px] font-normal text-muted-foreground print:hidden", children: "(voorlopig)" }) : null] }), _jsx("dt", { children: "Factuurdatum" }), _jsx("dd", { children: editable ? F({ k: "invoice_date", placeholder: "", type: "date" }) : fmtDate(details.invoice_date) }), _jsx("dt", { children: "Betaaltermijn" }), _jsx("dd", { children: editable ? _jsxs("span", { className: "flex items-center gap-1", children: [F({ k: "payment_term_days", placeholder: "30", className: "w-14" }), "dagen"] }) : `${details.payment_term_days || branding.payment_term_days} dagen` }), _jsx("dt", { children: "Vervaldatum" }), _jsx("dd", { children: formatDueDate(details.invoice_date, details.payment_term_days) }), _jsx("dt", { children: "Debiteurnummer" }), _jsx("dd", { children: F({ k: "debtor_number", placeholder: "—" }) }), _jsx("dt", { children: "Referentie" }), _jsx("dd", { children: F({ k: "reference", placeholder: "—" }) }), _jsx("dt", { children: "Werkbonnummer" }), _jsx("dd", { children: F({ k: "source_number", placeholder: "—" }) }), _jsx("dt", { children: "Werkadres" }), _jsx("dd", { children: F({ k: "work_address", placeholder: "—" }) }), _jsx("dt", { children: "Uitvoerdatum" }), _jsx("dd", { children: editable ? F({ k: "execution_date", placeholder: "", type: "date" }) : fmtDate(details.execution_date) })] })] })] }), _jsxs("table", { className: "w-full table-fixed border-collapse text-left", children: [_jsx("thead", { className: "border-y border-foreground/30", children: _jsxs("tr", { children: [_jsx("th", { className: "w-[44%] py-2", children: "Omschrijving" }), _jsx("th", { className: "w-[12%] py-2 text-right", children: "Aantal" }), _jsx("th", { className: "w-[15%] py-2 text-right", children: "Prijs" }), _jsx("th", { className: "w-[11%] py-2 text-right", children: "Btw" }), _jsx("th", { className: "w-[15%] py-2 text-right", children: "Totaal" }), editable ? _jsx("th", { className: "w-[3%]" }) : null] }) }), _jsx("tbody", { children: safeLines.length ? safeLines.map((line, i) => {
63
74
  const c = computed[i];
64
75
  return (_jsx("tr", { className: "border-b border-border/60 align-top", children: editable ? (_jsxs(_Fragment, { children: [_jsx("td", { className: "py-1 pr-2", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("textarea", { rows: 1, value: line.description, placeholder: "Omschrijving", readOnly: Boolean(line.articleId), onChange: (e) => setL(i, { description: e.target.value }), className: "min-w-0 flex-1 resize-y rounded-sm border border-dashed border-border bg-transparent px-1 py-0.5 outline-none focus:border-primary read-only:border-transparent read-only:bg-muted/40" }), line.articleId ? _jsx(ArticlePhoto, { src: articleById.get(line.articleId)?.photo_url ?? line.photoUrl, alt: line.description, className: "h-9 w-9" }) : null] }) }), _jsx("td", { className: "py-1", children: _jsx("input", { value: line.quantity, onChange: (e) => setL(i, { quantity: e.target.value }), className: "w-full rounded-sm border border-dashed border-border bg-transparent px-1 py-0.5 text-right outline-none focus:border-primary" }) }), _jsx("td", { className: "py-1 pl-1", children: _jsx("input", { value: line.price, placeholder: "0,00", readOnly: Boolean(line.articleId), onChange: (e) => setL(i, { price: e.target.value }), className: "w-full rounded-sm border border-dashed border-border bg-transparent px-1 py-0.5 text-right outline-none focus:border-primary read-only:border-transparent read-only:bg-muted/40" }) }), _jsx("td", { className: "py-1 pl-1", children: _jsx("select", { value: line.vat, disabled: Boolean(line.articleId), onChange: (e) => setL(i, { vat: e.target.value }), className: "w-full rounded-sm border border-dashed border-border bg-transparent px-0.5 py-0.5 text-right outline-none disabled:border-transparent disabled:bg-muted/40 disabled:opacity-100", children: [...new Set([...VAT_OPTIONS, line.vat])].map((v) => _jsxs("option", { value: v, children: [v, "%"] }, v)) }) }), _jsx("td", { className: "py-1.5 text-right", children: formatCents(c.line_total_cents) }), _jsx("td", { className: "py-1 text-right print:hidden", children: _jsx("button", { type: "button", "aria-label": "Regel verwijderen", onClick: () => onLinesChange?.(safeLines.filter((_, idx) => idx !== i)), className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })] })) : (_jsxs(_Fragment, { children: [_jsx("td", { className: "whitespace-pre-wrap break-words py-2 pr-3", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "min-w-0 flex-1", children: line.description || "—" }), line.articleId ? _jsx(ArticlePhoto, { src: articleById.get(line.articleId)?.photo_url ?? line.photoUrl, alt: line.description, className: "h-9 w-9" }) : null] }) }), _jsxs("td", { className: "py-2 text-right", children: [c.quantity, " ", line.unit] }), _jsx("td", { className: "py-2 text-right", children: formatCents(c.unit_price_cents) }), _jsxs("td", { className: "py-2 text-right", children: [c.vat_rate, "%"] }), _jsx("td", { className: "py-2 text-right", children: formatCents(c.line_total_cents) })] })) }, i));
65
- }) : _jsx("tr", { children: _jsx("td", { colSpan: editable ? 6 : 5, className: "py-8 text-center text-muted-foreground", children: "Nog geen factuurregels" }) }) })] }), editable ? _jsx(AddLineBar, { defaultVat: defaultVat, articles: articles, articlesLoading: articlesLoading, onAdd: (l) => onLinesChange?.([...safeLines, l]) }) : null, _jsxs("div", { className: "mt-6 ml-auto w-64 space-y-1.5", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { children: "Totaal excl. btw" }), _jsx("span", { children: formatCents(subtotal) })] }), (vatGroups.length ? vatGroups : [{ rate: Number(defaultVat) || 0, vat: 0 }]).map((g) => (_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { children: ["Btw ", g.rate, "%"] }), _jsx("span", { children: formatCents(g.vat) })] }, g.rate))), _jsxs("div", { className: "flex justify-between border-t border-foreground pt-2 text-sm font-semibold", children: [_jsx("span", { children: "Door u te betalen" }), _jsx("span", { children: formatCents(subtotal + vatTotal) })] })] }), _jsxs("p", { className: "mt-auto pb-5 pt-8 text-center font-medium", children: ["Graag betaling binnen ", details.payment_term_days || branding.payment_term_days, " dagen o.v.v. ", number] })] }), _jsxs("footer", { className: "grid min-h-16 grid-cols-3 items-center gap-4 bg-[var(--invoice-secondary)] px-8 py-4 text-[10px] text-primary-foreground", children: [_jsxs("span", { children: ["KvK ", branding.chamber_of_commerce_number || "—"] }), _jsxs("span", { className: "text-center", children: ["Btw-id ", branding.vat_identification_number || "—"] }), _jsxs("span", { className: "text-right", children: ["IBAN ", branding.iban || "—"] })] })] }), _jsx("style", { children: `@page{size:A4;margin:0}@media print{body *{visibility:hidden!important}.invoice-print-target,.invoice-print-target *{visibility:visible!important}.invoice-print-target{position:absolute!important;inset:0!important;width:210mm!important;min-height:297mm!important;height:auto!important;max-width:none!important;box-shadow:none!important;print-color-adjust:exact!important;-webkit-print-color-adjust:exact!important}.invoice-print-target tr{break-inside:avoid}}` })] }));
76
+ }) : _jsx("tr", { children: _jsx("td", { colSpan: editable ? 6 : 5, className: "py-8 text-center text-muted-foreground", children: "Nog geen factuurregels" }) }) })] }), editable ? _jsx(AddLineBar, { defaultVat: defaultVat, onAdd: (l) => onLinesChange?.([...safeLines, l]) }) : null, _jsxs("div", { className: "mt-6 ml-auto w-64 space-y-1.5", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { children: "Totaal excl. btw" }), _jsx("span", { children: formatCents(subtotal) })] }), (vatGroups.length ? vatGroups : [{ rate: Number(defaultVat) || 0, vat: 0 }]).map((g) => (_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { children: ["Btw ", g.rate, "%"] }), _jsx("span", { children: formatCents(g.vat) })] }, g.rate))), _jsxs("div", { className: "flex justify-between border-t border-foreground pt-2 text-sm font-semibold", children: [_jsx("span", { children: "Door u te betalen" }), _jsx("span", { children: formatCents(subtotal + vatTotal) })] })] }), _jsxs("p", { className: "mt-auto pb-5 pt-8 text-center font-medium", children: ["Graag betaling binnen ", details.payment_term_days || branding.payment_term_days, " dagen o.v.v. ", number] })] }), _jsxs("footer", { className: "grid min-h-16 grid-cols-3 items-center gap-4 bg-[var(--invoice-secondary)] px-8 py-4 text-[10px] text-primary-foreground", children: [_jsxs("span", { children: ["KvK ", branding.chamber_of_commerce_number || "—"] }), _jsxs("span", { className: "text-center", children: ["Btw-id ", branding.vat_identification_number || "—"] }), _jsxs("span", { className: "text-right", children: ["IBAN ", branding.iban || "—"] })] })] }), _jsx("style", { children: `@page{size:A4;margin:0}@media print{body *{visibility:hidden!important}.invoice-print-target,.invoice-print-target *{visibility:visible!important}.invoice-print-target{position:absolute!important;inset:0!important;width:210mm!important;min-height:297mm!important;height:auto!important;max-width:none!important;box-shadow:none!important;print-color-adjust:exact!important;-webkit-print-color-adjust:exact!important}.invoice-print-target tr{break-inside:avoid}}` })] }));
66
77
  });
67
- function AddLineBar({ defaultVat, articles, articlesLoading, onAdd }) {
78
+ function AddLineBar({ defaultVat, onAdd }) {
79
+ const searchArticles = useServerFn(listInvoiceArticles);
80
+ const [articles, setArticles] = useState([]);
81
+ const [articlesLoading, setArticlesLoading] = useState(false);
82
+ const [articlesError, setArticlesError] = useState("");
83
+ const [packagesError, setPackagesError] = useState("");
68
84
  const [packages, setPackages] = useState([]);
69
85
  const [packageOpen, setPackageOpen] = useState(false);
70
86
  const [packageQuery, setPackageQuery] = useState("");
71
87
  const [articleOpen, setArticleOpen] = useState(false);
72
88
  const [articleQuery, setArticleQuery] = useState("");
73
89
  useEffect(() => {
74
- fetchPackages().then((r) => setPackages(Array.isArray(r) ? r : [])).catch(() => setPackages([]));
90
+ fetchPackages()
91
+ .then((r) => { setPackages(Array.isArray(r) ? r : []); setPackagesError(""); })
92
+ .catch((e) => { setPackages([]); setPackagesError(dependencyMessage(e, "werkbonnen", "Pakketten")); });
75
93
  }, []);
94
+ useEffect(() => {
95
+ if (!articleOpen)
96
+ return;
97
+ let active = true;
98
+ setArticlesLoading(true);
99
+ const t = setTimeout(() => {
100
+ searchArticles({ data: { query: articleQuery } })
101
+ .then((r) => { if (active) {
102
+ setArticles(Array.isArray(r) ? r : []);
103
+ setArticlesError("");
104
+ } })
105
+ .catch((e) => {
106
+ if (!active)
107
+ return;
108
+ setArticles([]);
109
+ const msg = e instanceof Error ? e.message : "";
110
+ setArticlesError(msg.startsWith("MISSING_DEPENDENCY:") ? `Materialen niet beschikbaar: ${msg.slice(19).trim()}` : "Artikelen konden niet worden geladen. Probeer het opnieuw.");
111
+ })
112
+ .finally(() => { if (active)
113
+ setArticlesLoading(false); });
114
+ }, SEARCH_DEBOUNCE_MS);
115
+ return () => { active = false; clearTimeout(t); };
116
+ }, [articleOpen, articleQuery, searchArticles]);
76
117
  const safePackages = Array.isArray(packages) ? packages : [];
77
118
  const safeArticles = Array.isArray(articles) ? articles : [];
78
119
  const filteredPackages = safePackages.filter((p) => !packageQuery.trim() || [p.name, p.code, p.description].some((v) => (v ?? "").toLowerCase().includes(packageQuery.trim().toLowerCase())));
79
- const filteredArticles = safeArticles.filter((article) => !articleQuery.trim() || [article.name, article.code, article.gtin, article.description].some((value) => (value ?? "").toLowerCase().includes(articleQuery.trim().toLowerCase())));
120
+ const filteredArticles = safeArticles;
80
121
  return (_jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2 print:hidden", children: [_jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: () => onAdd(emptyEditLine(defaultVat)), children: [_jsx(Plus, { className: "mr-1 h-4 w-4" }), "Vrije regel"] }), _jsxs(Popover, { open: articleOpen, onOpenChange: setArticleOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", size: "sm", variant: "outline", children: [_jsx(Search, { className: "mr-1 h-4 w-4" }), "Materiaal zoeken"] }) }), _jsxs(PopoverContent, { className: "w-[min(42rem,calc(100vw-2rem))] p-2", align: "start", children: [_jsx(Input, { autoFocus: true, placeholder: "Zoek op titel, artikelcode, EAN of omschrijving\u2026", value: articleQuery, onChange: (e) => setArticleQuery(e.target.value) }), _jsx("div", { className: "mt-2 flex max-h-[18rem] flex-wrap content-start touch-pan-y gap-1 overflow-y-auto overscroll-contain", onWheel: (event) => {
81
122
  event.preventDefault();
82
123
  event.stopPropagation();
83
124
  event.currentTarget.scrollTop += event.deltaY;
84
- }, children: articlesLoading ? _jsx("p", { className: "w-full p-3 text-sm text-muted-foreground", children: "Artikelen laden\u2026" }) : filteredArticles.length === 0 ? _jsx("p", { className: "w-full p-3 text-sm text-muted-foreground", children: "Geen artikelen gevonden." }) : filteredArticles.map((article) => (_jsxs(Button, { type: "button", variant: "ghost", className: "flex h-auto w-fit flex-col items-start gap-2 overflow-hidden p-2 text-left", onClick: () => {
125
+ }, children: articlesError ? _jsx("p", { className: "w-full p-3 text-sm text-destructive", children: articlesError }) : articlesLoading ? _jsx("p", { className: "w-full p-3 text-sm text-muted-foreground", children: "Artikelen laden\u2026" }) : filteredArticles.length === 0 ? _jsx("p", { className: "w-full p-3 text-sm text-muted-foreground", children: "Geen artikelen gevonden." }) : filteredArticles.map((article) => (_jsxs(Button, { type: "button", variant: "ghost", className: "flex h-auto w-fit flex-col items-start gap-2 overflow-hidden p-2 text-left", onClick: () => {
85
126
  onAdd({
86
127
  description: article.description?.trim() ? `${article.name} — ${article.description.trim()}` : article.name,
87
128
  quantity: "1",
@@ -94,7 +135,7 @@ function AddLineBar({ defaultVat, articles, articlesLoading, onAdd }) {
94
135
  });
95
136
  setArticleOpen(false);
96
137
  setArticleQuery("");
97
- }, children: [_jsx(ArticlePhoto, { src: article.photo_url, alt: article.name, className: "h-24 w-24" }), _jsxs("span", { className: "w-24", children: [_jsx("span", { className: "line-clamp-2 block whitespace-normal font-medium leading-snug", children: article.name }), _jsxs("span", { className: "mt-1 block truncate text-xs text-muted-foreground", children: ["EAN ", article.gtin || "—"] }), _jsx("span", { className: "mt-1 block font-semibold", children: formatCents(article.sales_price_cents) })] })] }, article.id))) })] })] }), _jsxs(Popover, { open: packageOpen, onOpenChange: setPackageOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", size: "sm", variant: "outline", children: [_jsx(Package, { className: "mr-1 h-4 w-4" }), "Pakket"] }) }), _jsxs(PopoverContent, { className: "w-96 p-2", align: "start", children: [_jsx(Input, { autoFocus: true, placeholder: "Zoek pakket\u2026", value: packageQuery, onChange: (e) => setPackageQuery(e.target.value) }), _jsx("div", { className: "mt-2 max-h-72 overflow-y-auto", children: filteredPackages.length === 0 ? _jsx("p", { className: "p-2 text-sm text-muted-foreground", children: "Geen pakketten gevonden." }) : filteredPackages.map((p) => (_jsxs(Button, { type: "button", variant: "ghost", className: "flex h-auto w-full items-start justify-between gap-2 px-2 py-1.5 text-left text-sm", onClick: () => {
138
+ }, children: [_jsx(ArticlePhoto, { src: article.photo_url, alt: article.name, className: "h-24 w-24" }), _jsxs("span", { className: "w-24", children: [_jsx("span", { className: "line-clamp-2 block whitespace-normal font-medium leading-snug", children: article.name }), _jsxs("span", { className: "mt-1 block truncate text-xs text-muted-foreground", children: ["EAN ", article.gtin || "—"] }), _jsx("span", { className: "mt-1 block font-semibold", children: formatCents(article.sales_price_cents) })] })] }, article.id))) })] })] }), _jsxs(Popover, { open: packageOpen, onOpenChange: setPackageOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", size: "sm", variant: "outline", children: [_jsx(Package, { className: "mr-1 h-4 w-4" }), "Pakket"] }) }), _jsxs(PopoverContent, { className: "w-96 p-2", align: "start", children: [_jsx(Input, { autoFocus: true, placeholder: "Zoek pakket\u2026", value: packageQuery, onChange: (e) => setPackageQuery(e.target.value) }), _jsx("div", { className: "mt-2 max-h-72 overflow-y-auto", children: packagesError ? _jsx("p", { className: "p-2 text-sm text-destructive", children: packagesError }) : filteredPackages.length === 0 ? _jsx("p", { className: "p-2 text-sm text-muted-foreground", children: "Geen pakketten gevonden." }) : filteredPackages.map((p) => (_jsxs(Button, { type: "button", variant: "ghost", className: "flex h-auto w-full items-start justify-between gap-2 px-2 py-1.5 text-left text-sm", onClick: () => {
98
139
  const cents = Math.round(Number(p.unit_price_cents) || 0);
99
140
  onAdd({
100
141
  description: p.description?.trim() ? `${p.name} — ${p.description.trim()}` : p.name,
@@ -0,0 +1,6 @@
1
+ /** Nette foutweergave voor de pagina's van de Facturen-module (geen wit scherm). */
2
+ export declare function ModuleErrorView({ reset }: {
3
+ error?: unknown;
4
+ reset?: () => void;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=ModuleErrorView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModuleErrorView.d.ts","sourceRoot":"","sources":["../../src/components/ModuleErrorView.tsx"],"names":[],"mappings":"AAGA,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,+BAgBjF"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "@flowselections/core";
3
+ import { useRouter } from "@tanstack/react-router";
4
+ /** Nette foutweergave voor de pagina's van de Facturen-module (geen wit scherm). */
5
+ export function ModuleErrorView({ reset }) {
6
+ const router = useRouter();
7
+ return (_jsxs("main", { className: "space-y-4 p-6", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Er ging iets mis bij het laden van deze pagina." }), _jsx(Button, { variant: "outline", onClick: () => {
8
+ reset?.();
9
+ void router.invalidate();
10
+ }, children: "Opnieuw proberen" })] }));
11
+ }
@@ -0,0 +1,3 @@
1
+ /** Toont wat er in deze omgeving nog niet is ingesteld voor de Facturen-module. */
2
+ export declare function ModuleHealthSettingsCard(): import("react").JSX.Element;
3
+ //# sourceMappingURL=ModuleHealthSettingsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModuleHealthSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ModuleHealthSettingsCard.tsx"],"names":[],"mappings":"AAsBA,mFAAmF;AACnF,wBAAgB,wBAAwB,gCAmDvC"}
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from "react";
3
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, supabase } from "@flowselections/core";
4
+ import { CheckCircle2, AlertTriangle, RefreshCw } from "lucide-react";
5
+ import { STORAGE_BUCKETS } from "../../lib/config";
6
+ import { isMissingObjectError } from "../../lib/dependencies";
7
+ import { fetchInvoiceBranding, fetchInvoiceEmailPurpose, fetchSenderForPurpose } from "../../lib/facturen";
8
+ const db = supabase;
9
+ async function tableCheck(table, label, module) {
10
+ const { error } = await db.from(table).select("*", { head: true, count: "exact" }).limit(1);
11
+ if (!error)
12
+ return { label, ok: true, hint: "" };
13
+ return { label, ok: false, hint: isMissingObjectError(error) ? `Module ${module} ontbreekt.` : "Niet bereikbaar." };
14
+ }
15
+ async function bucketCheck(bucket, label) {
16
+ const { error } = await supabase.storage.from(bucket).list("", { limit: 1 });
17
+ return { label, ok: !error, hint: error ? `Opslagmap '${bucket}' ontbreekt of is niet toegankelijk.` : "" };
18
+ }
19
+ /** Toont wat er in deze omgeving nog niet is ingesteld voor de Facturen-module. */
20
+ export function ModuleHealthSettingsCard() {
21
+ const [checks, setChecks] = useState(null);
22
+ const run = useCallback(async () => {
23
+ setChecks(null);
24
+ const results = await Promise.all([
25
+ fetchInvoiceBranding()
26
+ .then((b) => ({ label: "Bedrijfsgegevens", ok: Boolean(b.company_name.trim() && b.iban.trim()), hint: "Vul bedrijfsnaam en IBAN in bij Factuurhuisstijl." }))
27
+ .catch(() => ({ label: "Bedrijfsgegevens", ok: false, hint: "Niet bereikbaar." })),
28
+ fetchInvoiceEmailPurpose()
29
+ .then(async (p) => {
30
+ if (!p)
31
+ return { label: "Afzender factuurmail", ok: false, hint: "Kies een e-maildoeleinde bij Factuur e-mail." };
32
+ const s = await fetchSenderForPurpose(p);
33
+ return { label: "Afzender factuurmail", ok: Boolean(s?.id), hint: `Geen mailbox gekoppeld aan '${p}'.` };
34
+ })
35
+ .catch((e) => ({ label: "Afzender factuurmail", ok: false, hint: isMissingObjectError(e) ? "Module E-mail - Authenticatie ontbreekt." : "Niet bereikbaar." })),
36
+ bucketCheck(STORAGE_BUCKETS.logos, "Opslag logo"),
37
+ bucketCheck(STORAGE_BUCKETS.articlePhotos, "Opslag artikelfoto's"),
38
+ tableCheck("artikelen_instellingen", "Marge-instelling", "Artikelen"),
39
+ tableCheck("itk_customers", "Klanten", "Klanten"),
40
+ tableCheck("werkbonnen", "Werkbonnen", "Werkbonnen"),
41
+ tableCheck("itk_offertes", "Offertes", "Offertes"),
42
+ ]);
43
+ setChecks(results.map((r) => (r.ok ? { ...r, hint: "" } : r)));
44
+ }, []);
45
+ useEffect(() => { void run(); }, [run]);
46
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Controle Facturen-module" }), _jsx(CardDescription, { children: "Wat er in deze omgeving nog ingesteld moet worden" })] }), _jsxs(CardContent, { className: "space-y-2", children: [checks === null ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Controleren\u2026" })) : (checks.map((c) => (_jsxs("div", { className: "flex items-start gap-2 text-sm", children: [c.ok ? _jsx(CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0 text-primary" }) : _jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-destructive" }), _jsxs("span", { children: [_jsx("span", { className: "font-medium", children: c.label }), c.hint ? _jsx("span", { className: "block text-xs text-muted-foreground", children: c.hint }) : null] })] }, c.label)))), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void run(), disabled: checks === null, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Opnieuw controleren"] })] })] }));
47
+ }
@@ -1,5 +1,8 @@
1
1
  import type { FlowModule } from "@flowselections/core";
2
2
  import { InvoiceEmailSettingsCard } from "./components/settings/InvoiceEmailSettingsCard";
3
+ import { ModuleHealthSettingsCard } from "./components/settings/ModuleHealthSettingsCard";
4
+ export { ModuleHealthSettingsCard };
5
+ export { ModuleErrorView } from "./components/ModuleErrorView";
3
6
  export { InvoiceEmailSettingsCard };
4
7
  export * from './_core-safelist';
5
8
  export { FacturenForInstallatieTechniek } from './components/FacturenForInstallatieTechniek';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,CAAC;AAEpC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,8BAA8B,EAAE,MAAM,6CAA6C,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAErE,eAAO,MAAM,iCAAiC,EAAE,UAe/C,CAAC;AACF,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,mDAAmD,CAAC;AAChG,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,wBAAwB,EAAE,CAAC;AAEpC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,8BAA8B,EAAE,MAAM,6CAA6C,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAErE,eAAO,MAAM,iCAAiC,EAAE,UAgB/C,CAAC;AACF,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,mDAAmD,CAAC;AAChG,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC"}
package/dist-lib/index.js CHANGED
@@ -4,6 +4,9 @@
4
4
  import { FileText } from "lucide-react";
5
5
  import { InvoiceBrandingSettingsCard } from "./components/settings/InvoiceBrandingSettingsCard";
6
6
  import { InvoiceEmailSettingsCard } from "./components/settings/InvoiceEmailSettingsCard";
7
+ import { ModuleHealthSettingsCard } from "./components/settings/ModuleHealthSettingsCard";
8
+ export { ModuleHealthSettingsCard };
9
+ export { ModuleErrorView } from "./components/ModuleErrorView";
7
10
  export { InvoiceEmailSettingsCard };
8
11
  export * from './_core-safelist';
9
12
  export { FacturenForInstallatieTechniek } from './components/FacturenForInstallatieTechniek';
@@ -12,7 +15,7 @@ export { TemplateModuleLogo } from './components/TemplateModuleLogo';
12
15
  export const facturenInstallatietechniekModule = {
13
16
  id: "facturen-installatietechniek",
14
17
  name: "Facturen Installatietechniek",
15
- version: "1.3.3", // houd gelijk aan package.json (zie src/lib/module-version.test.ts)
18
+ version: "1.4.0", // houd gelijk aan package.json (zie src/lib/module-version.test.ts)
16
19
  nav: {
17
20
  label: "Facturen",
18
21
  href: "/facturen-installatietechniek",
@@ -21,6 +24,7 @@ export const facturenInstallatietechniekModule = {
21
24
  settingsCards: [
22
25
  { component: InvoiceBrandingSettingsCard, order: 20 },
23
26
  { component: InvoiceEmailSettingsCard, order: 21 },
27
+ { component: ModuleHealthSettingsCard, order: 22 },
24
28
  ],
25
29
  };
26
30
  export { InvoiceDetailsFields } from "./components/InvoiceDetailsFields";
@@ -1,4 +1,4 @@
1
- import { type SupabaseClient } from '@supabase/supabase-js';
2
- import type { Database } from './types';
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import type { Database } from "./types.js";
3
3
  export declare const supabase: SupabaseClient<Database>;
4
4
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAWxC,eAAO,MAAM,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAM5C,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,eAAO,MAAM,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAuD,CAAC"}
@@ -1,16 +1,5 @@
1
- // This file is automatically generated. Do not edit it directly.
2
- import { createClient } from '@supabase/supabase-js';
3
- import { brokeredPreviewStorage } from './previewAuthStorage';
4
- const SUPABASE_URL = "https://wieomvgjyxoerzndxnla.supabase.co";
5
- const SUPABASE_PUBLISHABLE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndpZW9tdmdqeXhvZXJ6bmR4bmxhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODMwNzQzMDgsImV4cCI6MjA5ODY1MDMwOH0.uP3RBhvEMqCW61E-firCtlBGsgRyWY9BkLriF-yInho";
1
+ // Geen project-URL of sleutel in module-code: de client komt van @flowselections/core (env-gebaseerd, dezelfde sessie als de shell).
2
+ import { supabase as coreSupabase } from "@flowselections/core";
6
3
  // Import the supabase client like this:
7
4
  // import { supabase } from "../../integrations/supabase/client";
8
- // Explicit annotation: the inferred type of createClient<Database> exceeds
9
- // the maximum the compiler can serialize for declaration emit (TS7056).
10
- export const supabase = createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
11
- auth: {
12
- storage: brokeredPreviewStorage(),
13
- persistSession: true,
14
- autoRefreshToken: true,
15
- }
16
- });
5
+ export const supabase = coreSupabase;