@flowselections/facturen-installatie-techniek 1.4.5 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist-lib/components/FacturenForInstallatieTechniek.d.ts.map +1 -1
- package/dist-lib/components/FacturenForInstallatieTechniek.js +56 -25
- package/dist-lib/components/FactuurDetailPage.d.ts.map +1 -1
- package/dist-lib/components/FactuurDetailPage.js +26 -8
- package/dist-lib/components/InvoiceA4Preview.d.ts.map +1 -1
- package/dist-lib/components/InvoiceA4Preview.js +10 -6
- package/dist-lib/components/settings/InvoiceModuleSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/InvoiceModuleSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/InvoiceModuleSettingsCard.js +53 -0
- package/dist-lib/components/settings/ModuleHealthSettingsCard.d.ts.map +1 -1
- package/dist-lib/components/settings/ModuleHealthSettingsCard.js +2 -0
- package/dist-lib/index.d.ts +2 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +5 -2
- package/dist-lib/integrations/supabase/types.d.ts +572 -1
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/config.d.ts +0 -4
- package/dist-lib/lib/config.d.ts.map +1 -1
- package/dist-lib/lib/config.js +2 -4
- package/dist-lib/lib/dependencies.d.ts +2 -0
- package/dist-lib/lib/dependencies.d.ts.map +1 -1
- package/dist-lib/lib/dependencies.js +19 -0
- package/dist-lib/lib/facturen.d.ts +45 -8
- package/dist-lib/lib/facturen.d.ts.map +1 -1
- package/dist-lib/lib/facturen.js +132 -48
- package/dist-lib/lib/rekenregels.d.ts +49 -0
- package/dist-lib/lib/rekenregels.d.ts.map +1 -0
- package/dist-lib/lib/rekenregels.js +135 -0
- package/dist-lib/lib/settings.d.ts +32 -0
- package/dist-lib/lib/settings.d.ts.map +1 -0
- package/dist-lib/lib/settings.js +117 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @flowselections/facturen-installatie-techniek
|
|
2
|
+
|
|
3
|
+
Facturen-module voor FlowSelections-shells: facturen maken uit werkbonnen/offertes of handmatig, bewerken, samenvoegen, als PDF versturen.
|
|
4
|
+
|
|
5
|
+
## Instellingen (per database)
|
|
6
|
+
- **Btw-tarieven** — tabel `invoice_vat_rates` (tarief, omschrijving, standaard, actief). Standaard 21% (standaard) en 9%. "Btw verlegd" (0%) is altijd een aparte keuze. Beheer: Instellingen → Btw en tijdzone.
|
|
7
|
+
- **Tijdzone** — tabel `invoice_module_settings.timezone`, standaard `Europe/Amsterdam`. Bepaalt "vandaag" voor de factuurdatum en de week/maand/jaar-periodes.
|
|
8
|
+
- Ontbreken deze tabellen of rijen, dan gebruikt de module de standaardwaarden hierboven en logt een waarschuwing (`[facturen] instellingen ontbreken…`); de controlekaart toont dit ook.
|
|
9
|
+
|
|
10
|
+
## Rekenregels
|
|
11
|
+
`src/lib/rekenregels.ts` bevat de gedeelde FlowSelections-rekenregels (bedragen naar centen, kalenderdatums, pakket-, reis-, afrondings- en mandaatregels). Deze module gebruikt `parseBedragNaarCenten`, `vandaagLokaal`, `plusDagen` en `lokaleDatum`; basispakketten rekenen nog met de bestaande factuurlogica.
|
|
12
|
+
|
|
13
|
+
## Gedrag
|
|
14
|
+
- Bedragen en aantallen: "12,50", "12.50", "1.250,00" en "1,250.00" worden herkend; ongeldige invoer geeft "Ongeldig bedrag" en blokkeert opslaan.
|
|
15
|
+
- Per bron (werkbon/offerte) bestaat maximaal één niet-gecrediteerde factuur (unieke index + controle in `invoice_create_from_source`).
|
|
16
|
+
- Samenvoegen alleen bij dezelfde klant (klant-id) en hetzelfde btw-regime; ook in `invoice_merge` gecontroleerd.
|
|
17
|
+
- Opslaan controleert of een ander intussen wijzigde (`invoice_update_full_vat`, optionele `_expected_updated_at`).
|
|
18
|
+
- Lange facturen worden meerdere A4-pagina's met kopregel en paginanummer. De PDF wordt alleen gedownload via de knop "PDF downloaden".
|
|
19
|
+
- Verzenden naar een ander adres wijzigt het klantrecord niet.
|
|
20
|
+
|
|
21
|
+
## Vereist in de omgeving
|
|
22
|
+
- Shell-sessie en Supabase-client via `@flowselections/core`.
|
|
23
|
+
- Opslagbuckets `layout-logos` en `artikel-fotos`.
|
|
24
|
+
- Server: `SUPABASE_URL`, `SUPABASE_PUBLISHABLE_KEY` (door de shell geleverd).
|
|
25
|
+
|
|
26
|
+
## Tests
|
|
27
|
+
`bunx vitest@3 run`
|
|
@@ -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":"AAqLA,wBAAgB,8BAA8B,gCAwjB7C"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { WerkbonPreviewPanel } from "./WerkbonPreviewPanel.js";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { dependencyMessage } from "../lib/dependencies";
|
|
5
|
-
import {
|
|
4
|
+
import { dependencyMessage, explainError } from "../lib/dependencies";
|
|
5
|
+
import { plusDagen, vandaagLokaal } from "../lib/rekenregels";
|
|
6
|
+
import { useInvoiceSettings } from "../lib/settings";
|
|
6
7
|
import { supabase } from "@flowselections/core";
|
|
7
8
|
import { Link, useNavigate } from "@tanstack/react-router";
|
|
8
9
|
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Dialog, DialogContent, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
|
|
9
10
|
import { Plus } from "lucide-react";
|
|
10
|
-
import { createInvoiceFromSource, emptyInvoiceDetails, fetchInvoicesDetailed, fetchInvoiceKpis, fetchSourceLines, formatCents, listSources, SOURCE_LABELS, STATUS_LABELS, emptyInvoiceBranding, fetchInvoiceBranding, getInvoiceLogoUrl, editLineToBillable, emptyEditLine, peekNextInvoiceNumber, canMerge, canSelectForMerge, mergeInvoices, } from "../lib/facturen";
|
|
11
|
+
import { createInvoiceFromSource, emptyInvoiceDetails, firstLineInputError, mergeBlockReason, fetchInvoicesDetailed, fetchInvoiceKpis, fetchSourceLines, formatCents, listSources, SOURCE_LABELS, STATUS_LABELS, emptyInvoiceBranding, fetchInvoiceBranding, getInvoiceLogoUrl, editLineToBillable, emptyEditLine, peekNextInvoiceNumber, canMerge, canSelectForMerge, mergeInvoices, } from "../lib/facturen";
|
|
11
12
|
import { InvoiceA4Preview } from "./InvoiceA4Preview";
|
|
12
13
|
function statusVariant(status) {
|
|
13
14
|
if (status === "betaald")
|
|
@@ -34,7 +35,7 @@ function InvoiceTable({ invoices, emptyText, onOpen, selection, }) {
|
|
|
34
35
|
onOpen(invoice.id);
|
|
35
36
|
}, children: [selection && (_jsx(TableCell, { onClick: (e) => e.stopPropagation(), children: _jsx("input", { type: "checkbox", "aria-label": `Selecteer ${invoice.invoice_number ?? "factuur"}`, className: "h-4 w-4 accent-primary", checked: checked, disabled: !selectable, onChange: () => selection.onToggle(invoice) }) })), _jsx(TableCell, { className: "font-medium", children: selection ? (invoice.invoice_number ?? "—") : (_jsx(Link, { to: "/facturen-installatietechniek/detail/$id", params: { id: invoice.id }, search: { send: false }, className: "underline-offset-2 hover:underline", children: invoice.invoice_number ?? "—" })) }), _jsx(TableCell, { children: invoice.customer_name ?? "—" }), _jsx(TableCell, { children: _jsx(Badge, { variant: "outline", children: SOURCE_LABELS[invoice.source_type]
|
|
36
37
|
? SOURCE_LABELS[invoice.source_type] + (invoice.source_number ? ` ${invoice.source_number}` : "")
|
|
37
|
-
: "—" }) }), _jsx(TableCell, { children: new Date(invoice.invoice_date).toLocaleDateString("nl-NL") }), _jsx(TableCell, { className: "text-right", children: formatCents(invoice.total_cents) }), _jsx(TableCell, { children: _jsx(Badge, { variant: statusVariant(invoice.status), children: STATUS_LABELS[invoice.status] ?? invoice.status }) })] }, invoice.id));
|
|
38
|
+
: "—" }) }), _jsx(TableCell, { children: invoice.invoice_date ? new Date(`${invoice.invoice_date}T12:00:00`).toLocaleDateString("nl-NL") : "—" }), _jsx(TableCell, { className: "text-right", children: formatCents(invoice.total_cents) }), _jsx(TableCell, { children: _jsx(Badge, { variant: statusVariant(invoice.status), children: STATUS_LABELS[invoice.status] ?? invoice.status }) })] }, invoice.id));
|
|
38
39
|
}) })] }));
|
|
39
40
|
}
|
|
40
41
|
export function FacturenForInstallatieTechniek() {
|
|
@@ -60,9 +61,18 @@ export function FacturenForInstallatieTechniek() {
|
|
|
60
61
|
const mergeRef = useRef(false);
|
|
61
62
|
const mergeIds = useMemo(() => new Set(mergeSelected.map((i) => i.id)), [mergeSelected]);
|
|
62
63
|
const stopMerge = () => { setMergeMode(false); setMergeSelected([]); setMergeConfirm(false); };
|
|
63
|
-
const toggleMerge = (invoice) =>
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
const toggleMerge = (invoice) => {
|
|
65
|
+
if (!mergeSelected.some((i) => i.id === invoice.id)) {
|
|
66
|
+
const reason = mergeBlockReason(invoice, mergeSelected);
|
|
67
|
+
if (reason) {
|
|
68
|
+
toast.error(`Niet samen te voegen: ${reason}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
setMergeSelected((cur) => cur.some((i) => i.id === invoice.id)
|
|
73
|
+
? cur.filter((i) => i.id !== invoice.id)
|
|
74
|
+
: canSelectForMerge(invoice, cur) ? [...cur, invoice] : cur);
|
|
75
|
+
};
|
|
66
76
|
const doMerge = async () => {
|
|
67
77
|
if (!canMerge(mergeSelected) || mergeRef.current)
|
|
68
78
|
return;
|
|
@@ -75,7 +85,8 @@ export function FacturenForInstallatieTechniek() {
|
|
|
75
85
|
await navigate({ to: "/facturen-installatietechniek/detail/$id", params: { id }, search: { send: true } });
|
|
76
86
|
}
|
|
77
87
|
catch (e) {
|
|
78
|
-
|
|
88
|
+
console.error("Samenvoegen mislukt", e);
|
|
89
|
+
toast.error(`Samenvoegen mislukt: ${explainError(e)}`);
|
|
79
90
|
}
|
|
80
91
|
finally {
|
|
81
92
|
mergeRef.current = false;
|
|
@@ -91,7 +102,9 @@ export function FacturenForInstallatieTechniek() {
|
|
|
91
102
|
const [pickedCustomerId, setPickedCustomerId] = useState(null);
|
|
92
103
|
const [tab, setTab] = useState("handmatig");
|
|
93
104
|
const [details, setDetails] = useState(emptyInvoiceDetails);
|
|
94
|
-
const
|
|
105
|
+
const settings = useInvoiceSettings();
|
|
106
|
+
const [vatRate, setVatRate] = useState(String(settings.defaultVatRate));
|
|
107
|
+
useEffect(() => { setVatRate(String(settings.defaultVatRate)); }, [settings.defaultVatRate]);
|
|
95
108
|
const [lines, setLines] = useState([emptyEditLine()]);
|
|
96
109
|
const [nextNumber, setNextNumber] = useState(null);
|
|
97
110
|
const [linkedWorkOrder, setLinkedWorkOrder] = useState(null);
|
|
@@ -198,26 +211,27 @@ export function FacturenForInstallatieTechniek() {
|
|
|
198
211
|
search: { send: false },
|
|
199
212
|
}), [navigate]);
|
|
200
213
|
const kpi = useMemo(() => {
|
|
201
|
-
|
|
214
|
+
// Kalenderdatums (yyyy-mm-dd) in de tijdzone uit de instellingen.
|
|
215
|
+
const today = vandaagLokaal(settings.timezone);
|
|
216
|
+
const weekday = (new Date(`${today}T00:00:00Z`).getUTCDay() + 6) % 7; // ma = 0
|
|
217
|
+
const weekStart = plusDagen(today, -weekday);
|
|
218
|
+
const weekEnd = plusDagen(weekStart, 7);
|
|
202
219
|
const inPeriod = (d) => {
|
|
220
|
+
if (!d)
|
|
221
|
+
return false;
|
|
203
222
|
if (paidPeriod === "jaar")
|
|
204
|
-
return d.
|
|
223
|
+
return d.slice(0, 4) === today.slice(0, 4);
|
|
205
224
|
if (paidPeriod === "maand")
|
|
206
|
-
return d.
|
|
207
|
-
|
|
208
|
-
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
209
|
-
start.setDate(start.getDate() - ((start.getDay() + 6) % 7));
|
|
210
|
-
const end = new Date(start);
|
|
211
|
-
end.setDate(end.getDate() + 7);
|
|
212
|
-
return d >= start && d < end;
|
|
225
|
+
return d.slice(0, 7) === today.slice(0, 7);
|
|
226
|
+
return d >= weekStart && d < weekEnd;
|
|
213
227
|
};
|
|
214
228
|
const paidRows = Array.isArray(kpiData?.paidThisYear) ? kpiData.paidThisYear : [];
|
|
215
229
|
const paidMonthCents = paidRows
|
|
216
|
-
.filter((i) => inPeriod(
|
|
230
|
+
.filter((i) => inPeriod(String(i.invoice_date ?? "").slice(0, 10)))
|
|
217
231
|
.reduce((sum, i) => sum + (Number(i.total_cents) || 0), 0);
|
|
218
232
|
const openCount = kpiData?.openCount ?? 0;
|
|
219
233
|
return { paidMonthCents, openCount };
|
|
220
|
-
}, [kpiData, paidPeriod]);
|
|
234
|
+
}, [kpiData, paidPeriod, settings.timezone]);
|
|
221
235
|
const createFromSource = async () => {
|
|
222
236
|
const source = sources.find((s) => s.id === selectedSource);
|
|
223
237
|
if (!source) {
|
|
@@ -229,6 +243,11 @@ export function FacturenForInstallatieTechniek() {
|
|
|
229
243
|
createRef.current = true;
|
|
230
244
|
setSaving(true);
|
|
231
245
|
try {
|
|
246
|
+
const { data: sess } = await supabase.auth.getSession();
|
|
247
|
+
if (!sess?.session) {
|
|
248
|
+
toast.error("Factuur aanmaken mislukt: Je bent niet (meer) ingelogd. Log opnieuw in.");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
232
251
|
const sourceLines = await fetchSourceLines(sourceType, source.id);
|
|
233
252
|
if (sourceLines.length === 0) {
|
|
234
253
|
toast.error("Deze bron heeft geen factureerbare regels");
|
|
@@ -246,8 +265,9 @@ export function FacturenForInstallatieTechniek() {
|
|
|
246
265
|
setOpen(false);
|
|
247
266
|
await load();
|
|
248
267
|
}
|
|
249
|
-
catch {
|
|
250
|
-
|
|
268
|
+
catch (e) {
|
|
269
|
+
console.error("Factuur aanmaken mislukt", e);
|
|
270
|
+
toast.error(`Factuur aanmaken mislukt: ${explainError(e)}`);
|
|
251
271
|
}
|
|
252
272
|
finally {
|
|
253
273
|
createRef.current = false;
|
|
@@ -255,6 +275,11 @@ export function FacturenForInstallatieTechniek() {
|
|
|
255
275
|
}
|
|
256
276
|
};
|
|
257
277
|
const createManual = async () => {
|
|
278
|
+
const invalid = firstLineInputError(lines);
|
|
279
|
+
if (invalid) {
|
|
280
|
+
toast.error(invalid);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
258
283
|
const payloadLines = lines.filter((l) => l.description.trim() !== "").map(editLineToBillable);
|
|
259
284
|
if (!details.customer_name.trim() || payloadLines.length === 0) {
|
|
260
285
|
toast.error("Vul een klantnaam en minimaal één regel in");
|
|
@@ -265,11 +290,16 @@ export function FacturenForInstallatieTechniek() {
|
|
|
265
290
|
createRef.current = true;
|
|
266
291
|
setSaving(true);
|
|
267
292
|
try {
|
|
293
|
+
const { data: sess } = await supabase.auth.getSession();
|
|
294
|
+
if (!sess?.session) {
|
|
295
|
+
toast.error("Factuur aanmaken mislukt: Je bent niet (meer) ingelogd. Log opnieuw in.");
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
268
298
|
const result = await createInvoiceFromSource({
|
|
269
299
|
sourceType: linkedWorkOrder ? "work_order" : "manual",
|
|
270
300
|
sourceId: linkedWorkOrder?.id ?? null,
|
|
271
301
|
customerId: linkedWorkOrder?.customer_id ?? pickedCustomerId ?? null,
|
|
272
|
-
vatRate:
|
|
302
|
+
vatRate: Number(vatRate.replace(",", ".")) || settings.defaultVatRate,
|
|
273
303
|
lines: payloadLines,
|
|
274
304
|
details,
|
|
275
305
|
});
|
|
@@ -282,8 +312,9 @@ export function FacturenForInstallatieTechniek() {
|
|
|
282
312
|
search: { send: true },
|
|
283
313
|
});
|
|
284
314
|
}
|
|
285
|
-
catch {
|
|
286
|
-
|
|
315
|
+
catch (e) {
|
|
316
|
+
console.error("Factuur aanmaken mislukt", e);
|
|
317
|
+
toast.error(`Factuur aanmaken mislukt: ${explainError(e)}`);
|
|
287
318
|
}
|
|
288
319
|
finally {
|
|
289
320
|
createRef.current = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FactuurDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/FactuurDetailPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FactuurDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/FactuurDetailPage.tsx"],"names":[],"mappings":"AAgDA,wBAAgB,iBAAiB,gCAudhC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { useInvoiceSettings } from "../lib/settings";
|
|
4
4
|
import { Link, useNavigate, useParams, useSearch } from "@tanstack/react-router";
|
|
5
5
|
import { Badge, Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, Label, Textarea, toast, } from "@flowselections/core";
|
|
6
6
|
import { ArrowLeft, ChevronLeft, ChevronRight, Download, FileCheck2, Mail, Pencil, Save, X } from "lucide-react";
|
|
7
|
-
import { detailsFromRecord, downloadPdfBase64, elementToPdfBase64, emptyInvoiceBranding, emptyInvoiceDetails, fetchCustomerEmail, fetchInvoice, fetchInvoiceEmailPurpose, fetchSenderForPurpose, fetchInvoiceItems, fetchInvoices, getInvoiceLogoUrl, itemToEditLine, registerInvoiceSend, sendInvoiceEmail, setInvoiceNote, SOURCE_LABELS, STATUS_LABELS, updateInvoiceFull, } from "../lib/facturen";
|
|
7
|
+
import { detailsFromRecord, downloadPdfBase64, elementToPdfBase64, emptyInvoiceBranding, emptyInvoiceDetails, fetchCustomerEmail, fetchInvoice, fetchInvoiceEmailPurpose, fetchSenderForPurpose, fetchInvoiceItems, fetchInvoices, getInvoiceLogoUrl, itemToEditLine, registerInvoiceSend, sendInvoiceEmail, setInvoiceNote, SOURCE_LABELS, STATUS_LABELS, updateInvoiceFull, firstLineInputError, InvoiceConflictError, } from "../lib/facturen";
|
|
8
8
|
import { InvoiceA4Preview } from "./InvoiceA4Preview";
|
|
9
9
|
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
10
10
|
const isValidEmail = (value) => EMAIL_PATTERN.test(value.trim());
|
|
@@ -39,6 +39,9 @@ export function FactuurDetailPage() {
|
|
|
39
39
|
const [reverseCharge, setReverseCharge] = useState(false);
|
|
40
40
|
const [noteSaving, setNoteSaving] = useState(false);
|
|
41
41
|
const [noteSavedAt, setNoteSavedAt] = useState(null);
|
|
42
|
+
const settings = useInvoiceSettings();
|
|
43
|
+
const settingsRef = useRef(settings);
|
|
44
|
+
settingsRef.current = settings;
|
|
42
45
|
const loadSeqRef = useRef(0);
|
|
43
46
|
const load = useCallback(async () => {
|
|
44
47
|
const seq = ++loadSeqRef.current;
|
|
@@ -56,7 +59,7 @@ export function FactuurDetailPage() {
|
|
|
56
59
|
setNoteSavedAt(null);
|
|
57
60
|
setDetails(detailsFromRecord(inv));
|
|
58
61
|
const safeRows = Array.isArray(rows) ? rows : [];
|
|
59
|
-
setLines(safeRows.map((r) => itemToEditLine(r, Number(inv?.vat_rate ??
|
|
62
|
+
setLines(safeRows.map((r) => itemToEditLine(r, Number(inv?.vat_rate ?? settingsRef.current.defaultVatRate))));
|
|
60
63
|
setLogoUrl(logo);
|
|
61
64
|
}
|
|
62
65
|
catch {
|
|
@@ -111,18 +114,29 @@ export function FactuurDetailPage() {
|
|
|
111
114
|
search: { send: false },
|
|
112
115
|
});
|
|
113
116
|
};
|
|
117
|
+
const saveRef = useRef(false);
|
|
118
|
+
const lineError = firstLineInputError(lines);
|
|
114
119
|
const save = async () => {
|
|
120
|
+
if (saveRef.current)
|
|
121
|
+
return;
|
|
122
|
+
if (lineError) {
|
|
123
|
+
toast.error(`Kan niet opslaan: ${lineError}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
saveRef.current = true;
|
|
115
127
|
setSaving(true);
|
|
116
128
|
try {
|
|
117
|
-
await updateInvoiceFull(id, details, lines, invoice?.note ?? null, reverseCharge);
|
|
129
|
+
await updateInvoiceFull(id, details, lines, invoice?.note ?? null, reverseCharge, invoice?.updated_at ?? null);
|
|
118
130
|
toast.success("Factuur opgeslagen");
|
|
119
131
|
setEditing(false);
|
|
120
132
|
await load();
|
|
121
133
|
}
|
|
122
134
|
catch (e) {
|
|
123
|
-
|
|
135
|
+
console.error("[facturen] opslaan mislukt", e);
|
|
136
|
+
toast.error(e instanceof InvoiceConflictError ? e.message : e instanceof Error ? e.message : "Opslaan mislukt");
|
|
124
137
|
}
|
|
125
138
|
finally {
|
|
139
|
+
saveRef.current = false;
|
|
126
140
|
setSaving(false);
|
|
127
141
|
}
|
|
128
142
|
};
|
|
@@ -174,9 +188,8 @@ export function FactuurDetailPage() {
|
|
|
174
188
|
return;
|
|
175
189
|
setPreparingPdf(true);
|
|
176
190
|
try {
|
|
177
|
-
const pdf = await elementToPdfBase64(sheetRef.current);
|
|
191
|
+
const pdf = await elementToPdfBase64(sheetRef.current, { header: `Factuur ${invoice?.invoice_number ?? ""}`.trim() });
|
|
178
192
|
setPreparedPdf(pdf);
|
|
179
|
-
downloadPdfBase64(pdf, pdfFilename);
|
|
180
193
|
}
|
|
181
194
|
catch (error) {
|
|
182
195
|
toast.error(error instanceof Error ? error.message : "De factuur-PDF kon niet worden aangemaakt");
|
|
@@ -281,5 +294,10 @@ export function FactuurDetailPage() {
|
|
|
281
294
|
}
|
|
282
295
|
const isConcept = invoice.status === "concept";
|
|
283
296
|
const wasSent = Boolean(invoice.sent_at);
|
|
284
|
-
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(Badge, { variant: "secondary", className: "px-3 py-1.5 text-sm", title: "De status wordt automatisch bepaald", children: STATUS_LABELS[invoice.status] ?? invoice.status })] })] }), _jsxs("div", { className: "flex flex-col gap-6 xl:flex-row xl:items-start", children: [_jsxs("aside", { className: "w-full shrink-0 space-y-2 rounded-lg border border-border bg-card p-4 xl:sticky xl:top-36 xl:w-72", children: [_jsx(Label, { htmlFor: "inv-note", className: "text-sm font-semibold", children: "Notitie" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Alleen intern. Komt niet op de factuur of in de e-mail." }), _jsx(Textarea, { id: "inv-note", rows: 6, value: noteText, onChange: (e) => setNoteText(e.target.value), placeholder: "Typ hier een notitie\u2026" }), _jsxs(Button, { size: "sm", className: "w-full", onClick: () => void saveNote(), disabled: noteSaving || noteText === (invoice.note ?? ""), children: [_jsx(Save, { className: "mr-2 h-4 w-4" }), noteSaving ? "Opslaan…" : "Notitie opslaan"] }), noteSavedAt ? (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Laatst opgeslagen ", new Date(noteSavedAt).toLocaleString("nl-NL")] })) : null] }), _jsx("div", { className: "min-w-0 flex-1", 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) ||
|
|
297
|
+
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 || Boolean(lineError), title: lineError ?? undefined, 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(Badge, { variant: "secondary", className: "px-3 py-1.5 text-sm", title: "De status wordt automatisch bepaald", children: STATUS_LABELS[invoice.status] ?? invoice.status })] })] }), _jsxs("div", { className: "flex flex-col gap-6 xl:flex-row xl:items-start", children: [_jsxs("aside", { className: "w-full shrink-0 space-y-2 rounded-lg border border-border bg-card p-4 xl:sticky xl:top-36 xl:w-72", children: [_jsx(Label, { htmlFor: "inv-note", className: "text-sm font-semibold", children: "Notitie" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Alleen intern. Komt niet op de factuur of in de e-mail." }), _jsx(Textarea, { id: "inv-note", rows: 6, value: noteText, onChange: (e) => setNoteText(e.target.value), placeholder: "Typ hier een notitie\u2026" }), _jsxs(Button, { size: "sm", className: "w-full", onClick: () => void saveNote(), disabled: noteSaving || noteText === (invoice.note ?? ""), children: [_jsx(Save, { className: "mr-2 h-4 w-4" }), noteSaving ? "Opslaan…" : "Notitie opslaan"] }), noteSavedAt ? (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Laatst opgeslagen ", new Date(noteSavedAt).toLocaleString("nl-NL")] })) : null] }), _jsx("div", { className: "min-w-0 flex-1", 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) || settings.defaultVatRate), onDetailsChange: setDetails, onLinesChange: setLines, reverseCharge: reverseCharge, onReverseChargeChange: setReverseCharge }) })] }), _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…" : preparedPdf ? `${pdfFilename} is klaar als bijlage.` : "PDF kon niet worden voorbereid." })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: !preparedPdf || preparingPdf, onClick: () => { try {
|
|
298
|
+
downloadPdfBase64(preparedPdf, pdfFilename);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
toast.error(e instanceof Error ? e.message : "Downloaden mislukt");
|
|
302
|
+
} }, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), "PDF downloaden"] }), !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" })] })] }) })] }));
|
|
285
303
|
}
|
|
@@ -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":"AAOA,OAAO,EAWL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,cAAc,EAGpB,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;IAC7C,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9C,CAAC;AAoDF,eAAO,MAAM,gBAAgB,+FAuN3B,CAAC"}
|
|
@@ -1,14 +1,14 @@
|
|
|
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 {
|
|
3
|
+
import { SEARCH_DEBOUNCE_MS } from "../lib/config";
|
|
4
|
+
import { useInvoiceSettings } from "../lib/settings";
|
|
4
5
|
import { dependencyMessage } from "../lib/dependencies";
|
|
5
6
|
import { useServerFn } from "@tanstack/react-start";
|
|
6
7
|
import { Button, Input, Popover, PopoverContent, PopoverTrigger } from "@flowselections/core";
|
|
7
8
|
import { ChevronDown, Minus, Package, Plus, Printer, Search, Trash2 } from "lucide-react";
|
|
8
|
-
import { applyVatChoice, editLineToBillable, emptyEditLine, searchCustomers, fetchPackages, formatCents, basisLinePatch, isBasisPackage, splitQuarterDescription, } from "../lib/facturen";
|
|
9
|
+
import { applyVatChoice, editLineToBillable, lineInputErrors, emptyEditLine, searchCustomers, fetchPackages, formatCents, basisLinePatch, isBasisPackage, splitQuarterDescription, } from "../lib/facturen";
|
|
9
10
|
import { listInvoiceArticles } from "../lib/articles.functions";
|
|
10
11
|
import { ArticlePhoto } from "./ArticlePhoto";
|
|
11
|
-
const VAT_OPTIONS = CONFIG_VAT_OPTIONS;
|
|
12
12
|
function CustomerPicker({ details, onDetailsChange, onPick }) {
|
|
13
13
|
const [open, setOpen] = useState(false);
|
|
14
14
|
const [q, setQ] = useState("");
|
|
@@ -38,8 +38,11 @@ function CustomerPicker({ details, onDetailsChange, onPick }) {
|
|
|
38
38
|
const filtered = Array.isArray(items) ? items : [];
|
|
39
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))) })] })] })] }));
|
|
40
40
|
}
|
|
41
|
-
export const InvoiceA4Preview = forwardRef(function InvoiceA4Preview({ branding, details, lines, invoiceNumber, numberIsProvisional, logoUrl, printable = true, editable = false, defaultVat
|
|
41
|
+
export const InvoiceA4Preview = forwardRef(function InvoiceA4Preview({ branding, details, lines, invoiceNumber, numberIsProvisional, logoUrl, printable = true, editable = false, defaultVat: defaultVatProp, onDetailsChange, onLinesChange, onCustomerPick, reverseCharge = false, onReverseChargeChange }, ref) {
|
|
42
42
|
const safeLines = Array.isArray(lines) ? lines : [];
|
|
43
|
+
const settings = useInvoiceSettings();
|
|
44
|
+
const defaultVat = defaultVatProp ?? String(settings.defaultVatRate);
|
|
45
|
+
const VAT_OPTIONS = settings.vatRates.filter((r) => r.active).map((r) => String(r.rate));
|
|
43
46
|
const listArticles = useServerFn(listInvoiceArticles);
|
|
44
47
|
const [articles, setArticles] = useState([]);
|
|
45
48
|
const articleIds = safeLines.map((line) => line.articleId).filter((id) => Boolean(id));
|
|
@@ -94,8 +97,9 @@ export const InvoiceA4Preview = forwardRef(function InvoiceA4Preview({ branding,
|
|
|
94
97
|
const fmtDate = (v) => (v ? new Date(`${v}T12:00:00`).toLocaleDateString("nl-NL") : "—");
|
|
95
98
|
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) => {
|
|
96
99
|
const c = computed[i];
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
const errs = editable ? lineInputErrors(line) : {};
|
|
101
|
+
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) || isBasisLine(line), 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" }), isBasisLine(line) ? _jsx(BasisBadge, {}) : null, 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: isBasisLine(line) ? (_jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx("button", { type: "button", "aria-label": "Kwartier minder", onClick: () => stepQuarter(i, -1), disabled: (line.extraQuarters ?? splitQuarterDescription(line.description).quarters) <= 0, className: "rounded border border-border p-0.5 disabled:opacity-40", children: _jsx(Minus, { className: "h-3 w-3" }) }), _jsxs("span", { className: "whitespace-nowrap text-xs tabular-nums", children: ["+", line.extraQuarters ?? splitQuarterDescription(line.description).quarters, " kw"] }), _jsx("button", { type: "button", "aria-label": "Kwartier meer", onClick: () => stepQuarter(i, 1), className: "rounded border border-border p-0.5", children: _jsx(Plus, { className: "h-3 w-3" }) })] })) : _jsxs(_Fragment, { children: [_jsx("input", { value: line.quantity, "aria-invalid": Boolean(errs.quantity), onChange: (e) => setL(i, { quantity: e.target.value }), className: `w-full rounded-sm border border-dashed bg-transparent px-1 py-0.5 text-right outline-none focus:border-primary ${errs.quantity ? "border-destructive" : "border-border"}` }), errs.quantity ? _jsx("span", { className: "block text-right text-[10px] text-destructive", "data-html2canvas-ignore": true, children: errs.quantity }) : null] }) }), _jsxs("td", { className: "py-1 pl-1", children: [_jsx("input", { value: line.price, placeholder: "0,00", "aria-invalid": Boolean(errs.price), readOnly: isBasisLine(line), onChange: (e) => setL(i, { price: e.target.value }), className: `w-full rounded-sm border border-dashed bg-transparent px-1 py-0.5 text-right outline-none focus:border-primary read-only:border-transparent read-only:bg-muted/40 ${errs.price ? "border-destructive" : "border-border"}` }), errs.price ? _jsx("span", { className: "block text-right text-[10px] text-destructive", "data-html2canvas-ignore": true, children: errs.price }) : null] }), _jsx("td", { className: "py-1 pl-1", children: _jsx("select", { value: line.vat, disabled: Boolean(line.articleId) || reverseCharge, 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 || "—" }), isBasisLine(line) ? _jsx(BasisBadge, {}) : null, 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));
|
|
102
|
+
}) : _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: reverseCharge ? "0" : 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) })] }), editable && onReverseChargeChange ? (_jsxs("div", { className: "flex items-center justify-between gap-2", "data-html2canvas-ignore": true, children: [_jsx("span", { children: "Btw factuur" }), _jsx("div", { className: "flex overflow-hidden rounded-md border border-border", children: [...VAT_OPTIONS, "verlegd"].map((c) => {
|
|
99
103
|
const active = c === "verlegd" ? reverseCharge : !reverseCharge && computed.length > 0 && computed.every((l) => String(l.vat_rate) === c);
|
|
100
104
|
return (_jsx("button", { type: "button", onClick: () => { onReverseChargeChange(c === "verlegd"); onLinesChange?.(applyVatChoice(safeLines, c)); }, className: `px-2 py-0.5 text-xs ${active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted"}`, children: c === "verlegd" ? "Verlegd" : `${c}%` }, c));
|
|
101
105
|
}) })] })) : null, reverseCharge ? (_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { children: "Btw verlegd" }), _jsx("span", { children: formatCents(0) })] })) : (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 + (reverseCharge ? 0 : vatTotal)) })] }), reverseCharge ? _jsxs("p", { className: "pt-1 text-xs", children: ["Btw verlegd naar afnemer", details.customer_vat_number ? ` (btw-nr. ${details.customer_vat_number})` : ""] }) : null] }), _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}}` })] }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InvoiceModuleSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/InvoiceModuleSettingsCard.tsx"],"names":[],"mappings":"AAKA,wBAAgB,yBAAyB,gCAiFxC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Switch, toast } from "@flowselections/core";
|
|
4
|
+
import { Loader2, Percent, Plus } from "lucide-react";
|
|
5
|
+
import { fetchAllVatRates, saveTimezone, saveVatRate, useInvoiceSettings } from "../../lib/settings";
|
|
6
|
+
export function InvoiceModuleSettingsCard() {
|
|
7
|
+
const settings = useInvoiceSettings();
|
|
8
|
+
const [tz, setTz] = useState(settings.timezone);
|
|
9
|
+
const [rates, setRates] = useState([]);
|
|
10
|
+
const [loadError, setLoadError] = useState(null);
|
|
11
|
+
const [busy, setBusy] = useState(null);
|
|
12
|
+
const [newRate, setNewRate] = useState("");
|
|
13
|
+
const [newDesc, setNewDesc] = useState("");
|
|
14
|
+
const reload = async () => {
|
|
15
|
+
try {
|
|
16
|
+
setRates(await fetchAllVatRates());
|
|
17
|
+
setLoadError(null);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
const m = e instanceof Error ? e.message : "Laden mislukt";
|
|
21
|
+
console.error("[facturen] btw-tarieven", e);
|
|
22
|
+
setLoadError(m);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
useEffect(() => { void reload(); }, []);
|
|
26
|
+
useEffect(() => { setTz(settings.timezone); }, [settings.timezone]);
|
|
27
|
+
const run = async (key, fn, ok) => {
|
|
28
|
+
if (busy)
|
|
29
|
+
return;
|
|
30
|
+
setBusy(key);
|
|
31
|
+
try {
|
|
32
|
+
await fn();
|
|
33
|
+
await reload();
|
|
34
|
+
toast.success(ok);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
console.error("[facturen] instellingen", e);
|
|
38
|
+
toast.error(e instanceof Error ? e.message : "Opslaan mislukt");
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
setBusy(null);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const addRate = () => {
|
|
45
|
+
const n = Number(newRate.replace(",", "."));
|
|
46
|
+
if (!Number.isFinite(n) || n <= 0 || n >= 100) {
|
|
47
|
+
toast.error("Vul een btw-percentage tussen 0 en 100 in");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
void run("new", async () => { await saveVatRate({ rate: n, description: newDesc, is_default: false, active: true }); setNewRate(""); setNewDesc(""); }, "Btw-tarief toegevoegd");
|
|
51
|
+
};
|
|
52
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Percent, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Btw en tijdzone" })] }), _jsx(CardDescription, { children: "Btw-tarieven voor factuurregels en de tijdzone voor factuurdatums en periodes. \"Btw verlegd\" is altijd beschikbaar." })] }), _jsxs(CardContent, { className: "space-y-5", children: [settings.usingFallback && settings.warnings.length ? (_jsx("p", { className: "rounded-md border border-destructive/40 p-2 text-xs text-destructive", children: settings.warnings.join(" ") })) : null, _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "invoice-tz", children: "Tijdzone" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { id: "invoice-tz", value: tz, onChange: (e) => setTz(e.target.value), placeholder: "Europe/Amsterdam" }), _jsx(Button, { onClick: () => void run("tz", () => saveTimezone(tz.trim()), "Tijdzone opgeslagen"), disabled: Boolean(busy) || !tz.trim() || tz.trim() === settings.timezone, children: busy === "tz" ? _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : "Opslaan" })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Btw-tarieven" }), loadError ? _jsxs("p", { className: "text-xs text-destructive", children: ["Btw-tarieven niet beschikbaar: ", loadError] }) : null, _jsx("div", { className: "space-y-2", children: rates.map((r) => (_jsxs("div", { className: "flex flex-wrap items-center gap-3 rounded-md border border-border p-2 text-sm", children: [_jsxs("span", { className: "w-12 font-medium tabular-nums", children: [r.rate, "%"] }), _jsx("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: r.description || "—" }), _jsxs("label", { className: "flex items-center gap-1 text-xs", children: [_jsx("input", { type: "radio", name: "invoice-vat-default", checked: r.is_default, disabled: Boolean(busy) || !r.active, onChange: () => void run(`d-${r.id}`, () => saveVatRate({ ...r, is_default: true }), `${r.rate}% is nu standaard`) }), "Standaard"] }), _jsxs("label", { className: "flex items-center gap-1 text-xs", children: [_jsx(Switch, { checked: r.active, disabled: Boolean(busy) || r.is_default, onCheckedChange: (v) => void run(`a-${r.id}`, () => saveVatRate({ ...r, active: v }), v ? "Tarief geactiveerd" : "Tarief uitgezet") }), "Actief"] })] }, r.id ?? r.rate))) }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { value: newRate, onChange: (e) => setNewRate(e.target.value), placeholder: "%", className: "w-20", inputMode: "decimal" }), _jsx(Input, { value: newDesc, onChange: (e) => setNewDesc(e.target.value), placeholder: "Omschrijving", maxLength: 60 }), _jsx(Button, { variant: "outline", onClick: addRate, disabled: Boolean(busy) || !newRate.trim(), children: busy === "new" ? _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : _jsx(Plus, { className: "h-4 w-4" }) })] })] })] })] }));
|
|
53
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModuleHealthSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ModuleHealthSettingsCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ModuleHealthSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ModuleHealthSettingsCard.tsx"],"names":[],"mappings":"AAuBA,mFAAmF;AACnF,wBAAgB,wBAAwB,gCAoDvC"}
|
|
@@ -4,6 +4,7 @@ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, supa
|
|
|
4
4
|
import { CheckCircle2, AlertTriangle, RefreshCw } from "lucide-react";
|
|
5
5
|
import { STORAGE_BUCKETS } from "../../lib/config";
|
|
6
6
|
import { isMissingObjectError } from "../../lib/dependencies";
|
|
7
|
+
import { loadInvoiceSettings } from "../../lib/settings";
|
|
7
8
|
import { fetchInvoiceBranding, fetchInvoiceEmailPurpose, fetchSenderForPurpose } from "../../lib/facturen";
|
|
8
9
|
const db = supabase;
|
|
9
10
|
async function tableCheck(table, label, module) {
|
|
@@ -33,6 +34,7 @@ export function ModuleHealthSettingsCard() {
|
|
|
33
34
|
return { label: "Afzender factuurmail", ok: Boolean(s?.id), hint: `Geen mailbox gekoppeld aan '${p}'.` };
|
|
34
35
|
})
|
|
35
36
|
.catch((e) => ({ label: "Afzender factuurmail", ok: false, hint: isMissingObjectError(e) ? "Module E-mail - Authenticatie ontbreekt." : "Niet bereikbaar." })),
|
|
37
|
+
loadInvoiceSettings(true).then((st) => ({ label: "Btw-tarieven en tijdzone", ok: !st.usingFallback, hint: `${st.warnings.join(" ")} Standaardwaarden worden gebruikt.` })),
|
|
36
38
|
bucketCheck(STORAGE_BUCKETS.logos, "Opslag logo"),
|
|
37
39
|
bucketCheck(STORAGE_BUCKETS.articlePhotos, "Opslag artikelfoto's"),
|
|
38
40
|
tableCheck("artikelen_instellingen", "Marge-instelling", "Artikelen"),
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { FlowModule } from "@flowselections/core";
|
|
2
2
|
import { InvoiceEmailSettingsCard } from "./components/settings/InvoiceEmailSettingsCard";
|
|
3
3
|
import { ModuleHealthSettingsCard } from "./components/settings/ModuleHealthSettingsCard";
|
|
4
|
+
import { InvoiceModuleSettingsCard } from "./components/settings/InvoiceModuleSettingsCard";
|
|
5
|
+
export { InvoiceModuleSettingsCard };
|
|
4
6
|
export { ModuleHealthSettingsCard };
|
|
5
7
|
export { ModuleErrorView } from "./components/ModuleErrorView";
|
|
6
8
|
export { InvoiceEmailSettingsCard };
|
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,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,
|
|
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,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC5F,OAAO,EAAE,yBAAyB,EAAE,CAAC;AACrC,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,UAiB/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
|
@@ -5,6 +5,8 @@ import { FileText } from "lucide-react";
|
|
|
5
5
|
import { InvoiceBrandingSettingsCard } from "./components/settings/InvoiceBrandingSettingsCard";
|
|
6
6
|
import { InvoiceEmailSettingsCard } from "./components/settings/InvoiceEmailSettingsCard";
|
|
7
7
|
import { ModuleHealthSettingsCard } from "./components/settings/ModuleHealthSettingsCard";
|
|
8
|
+
import { InvoiceModuleSettingsCard } from "./components/settings/InvoiceModuleSettingsCard";
|
|
9
|
+
export { InvoiceModuleSettingsCard };
|
|
8
10
|
export { ModuleHealthSettingsCard };
|
|
9
11
|
export { ModuleErrorView } from "./components/ModuleErrorView";
|
|
10
12
|
export { InvoiceEmailSettingsCard };
|
|
@@ -15,7 +17,7 @@ export { TemplateModuleLogo } from './components/TemplateModuleLogo';
|
|
|
15
17
|
export const facturenInstallatietechniekModule = {
|
|
16
18
|
id: "facturen-installatietechniek",
|
|
17
19
|
name: "Facturen Installatietechniek",
|
|
18
|
-
version: "1.
|
|
20
|
+
version: "1.5.0", // houd gelijk aan package.json (zie src/lib/module-version.test.ts)
|
|
19
21
|
nav: {
|
|
20
22
|
label: "Facturen",
|
|
21
23
|
href: "/facturen-installatietechniek",
|
|
@@ -24,7 +26,8 @@ export const facturenInstallatietechniekModule = {
|
|
|
24
26
|
settingsCards: [
|
|
25
27
|
{ component: InvoiceBrandingSettingsCard, order: 20 },
|
|
26
28
|
{ component: InvoiceEmailSettingsCard, order: 21 },
|
|
27
|
-
{ component:
|
|
29
|
+
{ component: InvoiceModuleSettingsCard, order: 22 },
|
|
30
|
+
{ component: ModuleHealthSettingsCard, order: 23 },
|
|
28
31
|
],
|
|
29
32
|
};
|
|
30
33
|
export { InvoiceDetailsFields } from "./components/InvoiceDetailsFields";
|