@flowselections/facturen-installatie-techniek 1.0.3 → 1.4.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/dist-lib/components/ArticlePhoto.d.ts +8 -0
- package/dist-lib/components/ArticlePhoto.d.ts.map +1 -0
- package/dist-lib/components/ArticlePhoto.js +7 -0
- package/dist-lib/components/FacturenForInstallatieTechniek.d.ts.map +1 -1
- package/dist-lib/components/FacturenForInstallatieTechniek.js +155 -52
- package/dist-lib/components/FactuurDetailPage.d.ts.map +1 -1
- package/dist-lib/components/FactuurDetailPage.js +240 -13
- package/dist-lib/components/InvoiceA4Preview.d.ts +19 -0
- package/dist-lib/components/InvoiceA4Preview.d.ts.map +1 -0
- package/dist-lib/components/InvoiceA4Preview.js +159 -0
- package/dist-lib/components/InvoiceDetailsFields.d.ts +7 -0
- package/dist-lib/components/InvoiceDetailsFields.d.ts.map +1 -0
- package/dist-lib/components/InvoiceDetailsFields.js +21 -0
- 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/WerkbonPreviewPanel.d.ts +4 -0
- package/dist-lib/components/WerkbonPreviewPanel.d.ts.map +1 -0
- package/dist-lib/components/WerkbonPreviewPanel.js +40 -0
- package/dist-lib/components/settings/InstellingenPage.d.ts +5 -0
- package/dist-lib/components/settings/InstellingenPage.d.ts.map +1 -0
- package/dist-lib/components/settings/InstellingenPage.js +119 -0
- package/dist-lib/components/settings/InvoiceBrandingSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/InvoiceBrandingSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/InvoiceBrandingSettingsCard.js +64 -0
- package/dist-lib/components/settings/InvoiceEmailSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/InvoiceEmailSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/InvoiceEmailSettingsCard.js +41 -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 +11 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +18 -2
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +11 -22550
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.js +3 -1
- package/dist-lib/integrations/supabase/client.d.ts +3 -22548
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.js +3 -11
- package/dist-lib/integrations/supabase/client.server.d.ts +2 -22547
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.js +1 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +6 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.js +99 -0
- package/dist-lib/integrations/supabase/types.d.ts +24886 -11380
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +2 -0
- package/dist-lib/lib/articles.functions.d.ts +24 -0
- package/dist-lib/lib/articles.functions.d.ts.map +1 -0
- package/dist-lib/lib/articles.functions.js +112 -0
- 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 +21 -0
- package/dist-lib/lib/config.d.ts.map +1 -0
- package/dist-lib/lib/config.js +22 -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 +200 -0
- package/dist-lib/lib/facturen.d.ts.map +1 -1
- package/dist-lib/lib/facturen.js +465 -16
- package/package.json +60 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArticlePhoto.d.ts","sourceRoot":"","sources":["../../src/components/ArticlePhoto.tsx"],"names":[],"mappings":"AAEA,KAAK,iBAAiB,GAAG;IACvB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,SAAuB,EAAE,EAAE,iBAAiB,+BAcpF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
export function ArticlePhoto({ src, alt, className = "h-14 w-14" }) {
|
|
4
|
+
const [failed, setFailed] = useState(false);
|
|
5
|
+
useEffect(() => setFailed(false), [src]);
|
|
6
|
+
return (_jsx("div", { className: `shrink-0 overflow-hidden rounded-sm bg-muted ${className}`, children: src && !failed ? (_jsx("img", { src: src, alt: alt, className: "h-full w-full object-cover", onError: () => setFailed(true) })) : (_jsx("div", { className: "h-full w-full", role: "img", "aria-label": `Geen foto beschikbaar voor ${alt}` })) }));
|
|
7
|
+
}
|
|
@@ -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,10 +1,14 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { WerkbonPreviewPanel } from "../components/WerkbonPreviewPanel";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { dependencyMessage } from "../lib/dependencies";
|
|
5
|
+
import { DEFAULT_VAT_RATE } from "../lib/config";
|
|
6
|
+
import { supabase } from "@flowselections/core";
|
|
7
|
+
import { Link, useNavigate } from "@tanstack/react-router";
|
|
4
8
|
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";
|
|
5
|
-
import { Plus
|
|
6
|
-
import { createInvoiceFromSource,
|
|
7
|
-
|
|
9
|
+
import { Plus } from "lucide-react";
|
|
10
|
+
import { createInvoiceFromSource, emptyInvoiceDetails, fetchInvoicesDetailed, fetchInvoiceKpis, fetchSourceLines, formatCents, listSources, SOURCE_LABELS, STATUS_LABELS, emptyInvoiceBranding, fetchInvoiceBranding, getInvoiceLogoUrl, editLineToBillable, emptyEditLine, peekNextInvoiceNumber, } from "../lib/facturen";
|
|
11
|
+
import { InvoiceA4Preview } from "./InvoiceA4Preview";
|
|
8
12
|
function statusVariant(status) {
|
|
9
13
|
if (status === "betaald")
|
|
10
14
|
return "default";
|
|
@@ -14,35 +18,112 @@ function statusVariant(status) {
|
|
|
14
18
|
return "outline";
|
|
15
19
|
return "secondary";
|
|
16
20
|
}
|
|
21
|
+
function InvoiceTable({ invoices, emptyText, onOpen, }) {
|
|
22
|
+
if (invoices.length === 0) {
|
|
23
|
+
return _jsx("p", { className: "text-sm text-muted-foreground", children: emptyText });
|
|
24
|
+
}
|
|
25
|
+
return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Nummer" }), _jsx(TableHead, { children: "Klant" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { children: "Datum" }), _jsx(TableHead, { className: "text-right", children: "Totaal" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: invoices.map((invoice) => (_jsxs(TableRow, { className: "cursor-pointer", onClick: () => onOpen(invoice.id), children: [_jsx(TableCell, { className: "font-medium", children: _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]
|
|
26
|
+
? SOURCE_LABELS[invoice.source_type] + (invoice.source_number ? ` ${invoice.source_number}` : "")
|
|
27
|
+
: "—" }) }), _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))) })] }));
|
|
28
|
+
}
|
|
17
29
|
export function FacturenForInstallatieTechniek() {
|
|
30
|
+
const navigate = useNavigate();
|
|
18
31
|
const [invoices, setInvoices] = useState([]);
|
|
19
32
|
const [loading, setLoading] = useState(true);
|
|
33
|
+
const [sessionExpired, setSessionExpired] = useState(false);
|
|
34
|
+
const [loadError, setLoadError] = useState(false);
|
|
35
|
+
const [hasMore, setHasMore] = useState(false);
|
|
36
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
37
|
+
const [kpiData, setKpiData] = useState(null);
|
|
20
38
|
const [statusFilter, setStatusFilter] = useState("alle");
|
|
21
39
|
const [sourceFilter, setSourceFilter] = useState("alle");
|
|
40
|
+
const [paidPeriod, setPaidPeriod] = useState("maand");
|
|
22
41
|
const [open, setOpen] = useState(false);
|
|
23
42
|
const [saving, setSaving] = useState(false);
|
|
43
|
+
const createRef = useRef(false);
|
|
24
44
|
// bron-tab
|
|
25
45
|
const [sourceType, setSourceType] = useState("work_order");
|
|
26
46
|
const [sources, setSources] = useState([]);
|
|
27
47
|
const [sourcesLoading, setSourcesLoading] = useState(false);
|
|
28
48
|
const [selectedSource, setSelectedSource] = useState("");
|
|
29
49
|
// handmatig-tab
|
|
30
|
-
const [
|
|
31
|
-
const [
|
|
32
|
-
const [
|
|
33
|
-
const [
|
|
50
|
+
const [pickedCustomerId, setPickedCustomerId] = useState(null);
|
|
51
|
+
const [tab, setTab] = useState("handmatig");
|
|
52
|
+
const [details, setDetails] = useState(emptyInvoiceDetails);
|
|
53
|
+
const [vatRate, setVatRate] = useState(String(DEFAULT_VAT_RATE));
|
|
54
|
+
const [lines, setLines] = useState([emptyEditLine()]);
|
|
55
|
+
const [nextNumber, setNextNumber] = useState(null);
|
|
56
|
+
const [linkedWorkOrder, setLinkedWorkOrder] = useState(null);
|
|
57
|
+
const [branding, setBranding] = useState(emptyInvoiceBranding);
|
|
58
|
+
const [logoUrl, setLogoUrl] = useState(null);
|
|
59
|
+
const resetForm = () => {
|
|
60
|
+
setPickedCustomerId(null);
|
|
61
|
+
setDetails(emptyInvoiceDetails(branding.payment_term_days));
|
|
62
|
+
setLines([emptyEditLine(vatRate)]);
|
|
63
|
+
setLinkedWorkOrder(null);
|
|
64
|
+
};
|
|
65
|
+
const openNew = () => {
|
|
66
|
+
resetForm();
|
|
67
|
+
setTab("handmatig");
|
|
68
|
+
setOpen(true);
|
|
69
|
+
};
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!open)
|
|
72
|
+
return;
|
|
73
|
+
fetchInvoiceBranding().then(async (row) => {
|
|
74
|
+
setBranding(row);
|
|
75
|
+
setLogoUrl(await getInvoiceLogoUrl(row.logo_path));
|
|
76
|
+
setDetails((current) => ({
|
|
77
|
+
...current,
|
|
78
|
+
payment_term_days: current.payment_term_days === "30" ? String(row.payment_term_days) : current.payment_term_days,
|
|
79
|
+
}));
|
|
80
|
+
}).catch(() => toast.error("Factuurhuisstijl kon niet worden geladen"));
|
|
81
|
+
peekNextInvoiceNumber().then(setNextNumber);
|
|
82
|
+
}, [open]);
|
|
34
83
|
const load = useCallback(async () => {
|
|
35
84
|
setLoading(true);
|
|
36
85
|
try {
|
|
37
|
-
|
|
86
|
+
const { data: userData } = await supabase.auth.getUser();
|
|
87
|
+
if (!userData?.user) {
|
|
88
|
+
setSessionExpired(true);
|
|
89
|
+
setLoadError(false);
|
|
90
|
+
setInvoices([]);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setSessionExpired(false);
|
|
94
|
+
const result = await fetchInvoicesDetailed({ status: statusFilter, source: sourceFilter });
|
|
95
|
+
setLoadError(result.failed);
|
|
96
|
+
setInvoices(result.failed ? [] : result.invoices);
|
|
97
|
+
setHasMore(result.hasMore);
|
|
98
|
+
fetchInvoiceKpis().then(setKpiData).catch(() => setKpiData(null));
|
|
38
99
|
}
|
|
39
100
|
catch {
|
|
40
|
-
|
|
101
|
+
setLoadError(true);
|
|
102
|
+
setInvoices([]);
|
|
41
103
|
}
|
|
42
104
|
finally {
|
|
43
105
|
setLoading(false);
|
|
44
106
|
}
|
|
45
|
-
}, []);
|
|
107
|
+
}, [statusFilter, sourceFilter]);
|
|
108
|
+
const loadMore = useCallback(async () => {
|
|
109
|
+
setLoadingMore(true);
|
|
110
|
+
try {
|
|
111
|
+
const offset = invoices.filter((i) => i.status !== "concept").length;
|
|
112
|
+
const result = await fetchInvoicesDetailed({ offset, status: statusFilter, source: sourceFilter });
|
|
113
|
+
if (result.failed) {
|
|
114
|
+
toast.error("Meer facturen laden is mislukt");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
setInvoices((prev) => {
|
|
118
|
+
const seen = new Set(prev.map((i) => i.id));
|
|
119
|
+
return [...prev, ...result.invoices.filter((i) => !seen.has(i.id))];
|
|
120
|
+
});
|
|
121
|
+
setHasMore(result.hasMore);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
setLoadingMore(false);
|
|
125
|
+
}
|
|
126
|
+
}, [invoices, statusFilter, sourceFilter]);
|
|
46
127
|
useEffect(() => {
|
|
47
128
|
void load();
|
|
48
129
|
}, [load]);
|
|
@@ -57,7 +138,7 @@ export function FacturenForInstallatieTechniek() {
|
|
|
57
138
|
if (active)
|
|
58
139
|
setSources(rows);
|
|
59
140
|
})
|
|
60
|
-
.catch(() => toast.error("
|
|
141
|
+
.catch((e) => toast.error(dependencyMessage(e, sourceType === "work_order" ? "werkbonnen" : "offertes", "Bronnen")))
|
|
61
142
|
.finally(() => {
|
|
62
143
|
if (active)
|
|
63
144
|
setSourcesLoading(false);
|
|
@@ -68,26 +149,43 @@ export function FacturenForInstallatieTechniek() {
|
|
|
68
149
|
}, [open, sourceType]);
|
|
69
150
|
const filtered = useMemo(() => invoices.filter((i) => (statusFilter === "alle" || i.status === statusFilter) &&
|
|
70
151
|
(sourceFilter === "alle" || i.source_type === sourceFilter)), [invoices, statusFilter, sourceFilter]);
|
|
152
|
+
const conceptInvoices = useMemo(() => filtered.filter((i) => i.status === "concept"), [filtered]);
|
|
153
|
+
const sendOrPaidInvoices = useMemo(() => filtered.filter((i) => i.status !== "concept"), [filtered]);
|
|
154
|
+
const openInvoice = useCallback((id) => navigate({
|
|
155
|
+
to: "/facturen-installatietechniek/detail/$id",
|
|
156
|
+
params: { id },
|
|
157
|
+
search: { send: false },
|
|
158
|
+
}), [navigate]);
|
|
71
159
|
const kpi = useMemo(() => {
|
|
72
160
|
const now = new Date();
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
161
|
+
const inPeriod = (d) => {
|
|
162
|
+
if (paidPeriod === "jaar")
|
|
163
|
+
return d.getFullYear() === now.getFullYear();
|
|
164
|
+
if (paidPeriod === "maand")
|
|
165
|
+
return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth();
|
|
166
|
+
// huidige kalenderweek (maandag t/m zondag)
|
|
167
|
+
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
168
|
+
start.setDate(start.getDate() - ((start.getDay() + 6) % 7));
|
|
169
|
+
const end = new Date(start);
|
|
170
|
+
end.setDate(end.getDate() + 7);
|
|
171
|
+
return d >= start && d < end;
|
|
172
|
+
};
|
|
173
|
+
const paidRows = Array.isArray(kpiData?.paidThisYear) ? kpiData.paidThisYear : [];
|
|
174
|
+
const paidMonthCents = paidRows
|
|
175
|
+
.filter((i) => inPeriod(new Date(i.invoice_date)))
|
|
176
|
+
.reduce((sum, i) => sum + (Number(i.total_cents) || 0), 0);
|
|
177
|
+
const openCount = kpiData?.openCount ?? 0;
|
|
178
|
+
return { paidMonthCents, openCount };
|
|
179
|
+
}, [kpiData, paidPeriod]);
|
|
85
180
|
const createFromSource = async () => {
|
|
86
181
|
const source = sources.find((s) => s.id === selectedSource);
|
|
87
182
|
if (!source) {
|
|
88
183
|
toast.error("Kies eerst een bron");
|
|
89
184
|
return;
|
|
90
185
|
}
|
|
186
|
+
if (createRef.current)
|
|
187
|
+
return;
|
|
188
|
+
createRef.current = true;
|
|
91
189
|
setSaving(true);
|
|
92
190
|
try {
|
|
93
191
|
const sourceLines = await fetchSourceLines(sourceType, source.id);
|
|
@@ -111,51 +209,56 @@ export function FacturenForInstallatieTechniek() {
|
|
|
111
209
|
toast.error("Factuur aanmaken mislukt");
|
|
112
210
|
}
|
|
113
211
|
finally {
|
|
212
|
+
createRef.current = false;
|
|
114
213
|
setSaving(false);
|
|
115
214
|
}
|
|
116
215
|
};
|
|
117
216
|
const createManual = async () => {
|
|
118
|
-
const payloadLines = lines
|
|
119
|
-
|
|
120
|
-
.map((l, index) => {
|
|
121
|
-
const quantity = parseFloat(l.quantity.replace(",", ".")) || 0;
|
|
122
|
-
const unitPrice = euroToCents(l.price);
|
|
123
|
-
return {
|
|
124
|
-
description: l.description.trim(),
|
|
125
|
-
kind: "other",
|
|
126
|
-
quantity,
|
|
127
|
-
unit: l.unit || "stuk",
|
|
128
|
-
unit_price_cents: unitPrice,
|
|
129
|
-
line_total_cents: Math.round(quantity * unitPrice),
|
|
130
|
-
sort_order: index + 1,
|
|
131
|
-
};
|
|
132
|
-
});
|
|
133
|
-
if (!customerName.trim() || payloadLines.length === 0) {
|
|
217
|
+
const payloadLines = lines.filter((l) => l.description.trim() !== "").map(editLineToBillable);
|
|
218
|
+
if (!details.customer_name.trim() || payloadLines.length === 0) {
|
|
134
219
|
toast.error("Vul een klantnaam en minimaal één regel in");
|
|
135
220
|
return;
|
|
136
221
|
}
|
|
222
|
+
if (createRef.current)
|
|
223
|
+
return;
|
|
224
|
+
createRef.current = true;
|
|
137
225
|
setSaving(true);
|
|
138
226
|
try {
|
|
139
227
|
const result = await createInvoiceFromSource({
|
|
140
|
-
sourceType: "manual",
|
|
141
|
-
sourceId: null,
|
|
142
|
-
|
|
143
|
-
vatRate: parseFloat(vatRate.replace(",", ".")) ||
|
|
144
|
-
invoiceDate,
|
|
228
|
+
sourceType: linkedWorkOrder ? "work_order" : "manual",
|
|
229
|
+
sourceId: linkedWorkOrder?.id ?? null,
|
|
230
|
+
customerId: linkedWorkOrder?.customer_id ?? pickedCustomerId ?? null,
|
|
231
|
+
vatRate: parseFloat(vatRate.replace(",", ".")) || DEFAULT_VAT_RATE,
|
|
145
232
|
lines: payloadLines,
|
|
233
|
+
details,
|
|
146
234
|
});
|
|
147
|
-
toast.success(`Factuur ${result.invoice_number} aangemaakt`);
|
|
148
|
-
|
|
149
|
-
setLines([emptyLine()]);
|
|
235
|
+
toast.success(result.reused ? `Bestaand concept ${result.invoice_number}` : `Factuur ${result.invoice_number} aangemaakt`);
|
|
236
|
+
resetForm();
|
|
150
237
|
setOpen(false);
|
|
151
|
-
await
|
|
238
|
+
await navigate({
|
|
239
|
+
to: "/facturen-installatietechniek/detail/$id",
|
|
240
|
+
params: { id: result.id },
|
|
241
|
+
search: { send: true },
|
|
242
|
+
});
|
|
152
243
|
}
|
|
153
244
|
catch {
|
|
154
245
|
toast.error("Factuur aanmaken mislukt");
|
|
155
246
|
}
|
|
156
247
|
finally {
|
|
248
|
+
createRef.current = false;
|
|
157
249
|
setSaving(false);
|
|
158
250
|
}
|
|
159
251
|
};
|
|
160
|
-
return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-
|
|
252
|
+
return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-2", children: _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(CardTitle, { className: "text-sm font-medium text-muted-foreground", children: paidPeriod === "week" ? "Betaald deze week" : paidPeriod === "jaar" ? "Betaald dit jaar" : "Betaald deze maand" }), _jsxs(Select, { value: paidPeriod, onValueChange: (v) => setPaidPeriod(v), children: [_jsx(SelectTrigger, { className: "h-7 w-24 text-xs", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "week", children: "Week" }), _jsx(SelectItem, { value: "maand", children: "Maand" }), _jsx(SelectItem, { value: "jaar", children: "Jaar" })] })] })] }) }), _jsx(CardContent, { className: "text-2xl font-semibold", children: formatCents(kpi.paidMonthCents) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-2", children: _jsx(CardTitle, { className: "text-sm font-medium text-muted-foreground", children: "Openstaand" }) }), _jsx(CardContent, { className: "text-2xl font-semibold", children: kpi.openCount })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsxs(Select, { value: statusFilter, onValueChange: setStatusFilter, children: [_jsx(SelectTrigger, { className: "w-44", children: _jsx(SelectValue, { placeholder: "Status" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle statussen" }), Object.entries(STATUS_LABELS).map(([value, label]) => (_jsx(SelectItem, { value: value, children: label }, value)))] })] }), _jsxs(Select, { value: sourceFilter, onValueChange: setSourceFilter, children: [_jsx(SelectTrigger, { className: "w-44", children: _jsx(SelectValue, { placeholder: "Bron" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "alle", children: "Alle bronnen" }), Object.entries(SOURCE_LABELS).map(([value, label]) => (_jsx(SelectItem, { value: value, children: label }, value)))] })] }), _jsx("div", { className: "ml-auto", children: _jsxs(Button, { onClick: openNew, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Nieuwe factuur"] }) })] }), loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden\u2026" })) : sessionExpired ? (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-start gap-3 py-6", children: [_jsx("p", { className: "text-sm", children: "Je sessie is verlopen, log opnieuw in om je facturen te zien." }), _jsx(Button, { asChild: true, children: _jsx(Link, { to: "/login", children: "Opnieuw inloggen" }) })] }) })) : loadError ? (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-start gap-3 py-6", children: [_jsx("p", { className: "text-sm", children: "Facturen konden niet worden geladen. Probeer het opnieuw." }), _jsx(Button, { onClick: () => void load(), children: "Opnieuw proberen" })] }) })) : (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Goedkeuren en verzenden" }) }), _jsx(CardContent, { children: _jsx(InvoiceTable, { invoices: conceptInvoices, emptyText: "Geen conceptfacturen.", onOpen: openInvoice }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Facturen" }) }), _jsxs(CardContent, { children: [_jsx(InvoiceTable, { invoices: sendOrPaidInvoices, emptyText: "Geen facturen gevonden.", onOpen: openInvoice }), hasMore && (_jsx("div", { className: "mt-4 flex justify-center", children: _jsx(Button, { variant: "outline", onClick: () => void loadMore(), disabled: loadingMore, children: loadingMore ? "Laden…" : "Meer laden" }) }))] })] })] })), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "flex max-h-[92vh] max-w-[calc(100vw-2rem)] flex-col sm:max-w-7xl", children: [_jsx(DialogHeader, { className: "shrink-0", children: _jsx(DialogTitle, { children: "Nieuwe factuur" }) }), _jsxs("div", { className: linkedWorkOrder ? "grid min-h-0 flex-1 gap-6 overflow-y-auto md:grid-cols-2 md:overflow-hidden" : "contents", children: [linkedWorkOrder && (_jsx("div", { className: "min-h-0 rounded-md border bg-muted/30 p-4 md:overflow-y-auto", children: _jsx(WerkbonPreviewPanel, { workOrderId: linkedWorkOrder.id }) })), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto pr-1", children: [linkedWorkOrder && _jsx("p", { className: "mb-2 text-xs uppercase tracking-wide text-muted-foreground", children: "Factuur" }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "bron", children: "Uit bron" }), _jsx(TabsTrigger, { value: "handmatig", children: "Handmatig" })] }), _jsxs(TabsContent, { value: "bron", className: "space-y-4 pt-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Brontype" }), _jsxs(Select, { value: sourceType, onValueChange: (v) => setSourceType(v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "work_order", children: "Werkbon" }), _jsx(SelectItem, { value: "quotation", children: "Offerte" })] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bron" }), sourcesLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Bronnen laden\u2026" })) : sources.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen factureerbare bronnen gevonden." })) : (_jsxs(Select, { value: selectedSource, onValueChange: setSelectedSource, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een bron" }) }), _jsx(SelectContent, { children: sources.map((s) => (_jsx(SelectItem, { value: s.id, children: (s.number ?? "zonder nummer") + (s.customer_name ? ` — ${s.customer_name}` : "") }, s.id))) })] }))] }), _jsx(Button, { onClick: createFromSource, disabled: saving || !selectedSource, className: "w-full", children: saving ? "Bezig…" : "Factuur aanmaken" })] }), _jsxs(TabsContent, { value: "handmatig", className: "space-y-4 pt-4", children: [linkedWorkOrder ? (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Gekoppeld aan werkbon ", _jsx("span", { className: "font-medium", children: linkedWorkOrder.number ?? "—" })] })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { htmlFor: "btw", className: "text-xs", children: "Standaard btw % nieuwe regels" }), _jsx(Input, { id: "btw", className: "h-8 w-20", value: vatRate, onChange: (e) => setVatRate(e.target.value) })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Klik op de teksten in de factuur om ze aan te passen." }), _jsx(InvoiceA4Preview, { branding: branding, details: details, lines: lines, invoiceNumber: nextNumber, numberIsProvisional: true, logoUrl: logoUrl, printable: false, editable: true, defaultVat: vatRate, onDetailsChange: setDetails, onLinesChange: setLines, onCustomerPick: linkedWorkOrder ? undefined : (c) => {
|
|
253
|
+
setPickedCustomerId(c.id);
|
|
254
|
+
setDetails((d) => ({
|
|
255
|
+
...d,
|
|
256
|
+
customer_name: c.name ?? "",
|
|
257
|
+
customer_address: c.billing_street ?? "",
|
|
258
|
+
customer_postal_code: c.billing_postcode ?? "",
|
|
259
|
+
customer_city: c.billing_city ?? "",
|
|
260
|
+
debtor_number: c.customer_number ?? "",
|
|
261
|
+
customer_vat_number: c.vat_number ?? "",
|
|
262
|
+
}));
|
|
263
|
+
} }), _jsx(Button, { onClick: createManual, disabled: saving, className: "w-full", children: saving ? "Bezig…" : "Factuur aanmaken" })] })] })] })] })] }) })] }));
|
|
161
264
|
}
|
|
@@ -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":"AAoDA,wBAAgB,iBAAiB,gCA6bhC"}
|