@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.
- package/dist-lib/components/FacturenForInstallatieTechniek.d.ts.map +1 -1
- package/dist-lib/components/FacturenForInstallatieTechniek.js +12 -2
- package/dist-lib/components/FactuurDetailPage.d.ts.map +1 -1
- package/dist-lib/components/FactuurDetailPage.js +18 -2
- package/dist-lib/components/InvoiceA4Preview.d.ts.map +1 -1
- package/dist-lib/components/InvoiceA4Preview.js +64 -23
- package/dist-lib/components/ModuleErrorView.d.ts +6 -0
- package/dist-lib/components/ModuleErrorView.d.ts.map +1 -0
- package/dist-lib/components/ModuleErrorView.js +11 -0
- package/dist-lib/components/settings/ModuleHealthSettingsCard.d.ts +3 -0
- package/dist-lib/components/settings/ModuleHealthSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/ModuleHealthSettingsCard.js +47 -0
- package/dist-lib/index.d.ts +3 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +5 -1
- package/dist-lib/integrations/supabase/client.d.ts +2 -2
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.js +3 -14
- package/dist-lib/integrations/supabase/types.d.ts +224 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/articles.functions.d.ts +6 -1
- package/dist-lib/lib/articles.functions.d.ts.map +1 -1
- package/dist-lib/lib/articles.functions.js +43 -22
- package/dist-lib/lib/auth-attach.d.ts +6 -0
- package/dist-lib/lib/auth-attach.d.ts.map +1 -0
- package/dist-lib/lib/auth-attach.js +17 -0
- package/dist-lib/lib/config.d.ts +5 -0
- package/dist-lib/lib/config.d.ts.map +1 -1
- package/dist-lib/lib/config.js +5 -0
- package/dist-lib/lib/dependencies.d.ts +9 -0
- package/dist-lib/lib/dependencies.d.ts.map +1 -0
- package/dist-lib/lib/dependencies.js +43 -0
- package/dist-lib/lib/facturen.d.ts +4 -0
- package/dist-lib/lib/facturen.d.ts.map +1 -1
- package/dist-lib/lib/facturen.js +25 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FacturenForInstallatieTechniek.d.ts","sourceRoot":"","sources":["../../src/components/FacturenForInstallatieTechniek.tsx"],"names":[],"mappings":"
|
|
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("
|
|
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,
|
|
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":"
|
|
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,
|
|
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
|
|
19
|
+
if (!open)
|
|
18
20
|
return;
|
|
21
|
+
let active = true;
|
|
19
22
|
setLoading(true);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 (!
|
|
48
|
+
if (!idsKey)
|
|
35
49
|
return;
|
|
36
50
|
let active = true;
|
|
37
|
-
|
|
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
|
-
}, [
|
|
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,
|
|
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,
|
|
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()
|
|
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
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
+
}
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -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';
|
package/dist-lib/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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.
|
|
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 {
|
|
2
|
-
import type { Database } from
|
|
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":"
|
|
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
|
-
//
|
|
2
|
-
import {
|
|
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
|
-
|
|
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;
|