@flowselections/floriday-klanten-module 1.0.28 → 1.0.29
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/klanten/BewerkKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/BewerkKlant.js +2 -1
- package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -1
- package/dist-lib/components/klanten/DeliveryLocationsTab.js +2 -1
- package/dist-lib/components/klanten/ImportKlanten.d.ts.map +1 -1
- package/dist-lib/components/klanten/ImportKlanten.js +2 -1
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +38 -6
- package/dist-lib/components/klanten/LocalDiscountFormDialog.d.ts.map +1 -1
- package/dist-lib/components/klanten/LocalDiscountFormDialog.js +2 -1
- package/dist-lib/components/klanten/LocalDiscountsTab.d.ts.map +1 -1
- package/dist-lib/components/klanten/LocalDiscountsTab.js +2 -1
- package/dist-lib/components/klanten/LogisticsStrategyCard.d.ts.map +1 -1
- package/dist-lib/components/klanten/LogisticsStrategyCard.js +2 -1
- package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/NieuweKlant.js +2 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlant.js +2 -1
- package/dist-lib/components/klanten/RouteError.d.ts +6 -0
- package/dist-lib/components/klanten/RouteError.d.ts.map +1 -0
- package/dist-lib/components/klanten/RouteError.js +13 -0
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/customers.functions.js +39 -35
- package/dist-lib/lib/errors.d.ts +7 -0
- package/dist-lib/lib/errors.d.ts.map +1 -0
- package/dist-lib/lib/errors.js +36 -0
- package/dist-lib/lib/external-customers.functions.d.ts +1 -1
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/external-customers.functions.js +28 -12
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BewerkKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/BewerkKlant.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BewerkKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/BewerkKlant.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAmDjD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { friendlyError } from "../../lib/errors";
|
|
2
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
3
4
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
5
|
import { useServerFn } from "@tanstack/react-start";
|
|
@@ -47,7 +48,7 @@ export function BewerkKlant({ id }) {
|
|
|
47
48
|
qc.invalidateQueries({ queryKey: ["twinfield-sync-status", id] });
|
|
48
49
|
navigate({ to: "/klanten/$id", params: { id } });
|
|
49
50
|
},
|
|
50
|
-
onError: (e) => toast.error(e
|
|
51
|
+
onError: (e) => toast.error(friendlyError(e, "Opslaan mislukt")),
|
|
51
52
|
});
|
|
52
53
|
if (isLoading)
|
|
53
54
|
return _jsx("main", { className: "p-6", children: "Laden..." });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeliveryLocationsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/DeliveryLocationsTab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DeliveryLocationsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/DeliveryLocationsTab.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,oBAAoB,CAAC,EACnC,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,+BAoaA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { friendlyError } from "../../lib/errors";
|
|
2
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
4
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
5
|
import { useServerFn } from "@tanstack/react-start";
|
|
@@ -118,7 +119,7 @@ export function DeliveryLocationsTab({ customerId, groupKey, externalProvider, e
|
|
|
118
119
|
qc.invalidateQueries({ queryKey: ["customer", ownerKey] });
|
|
119
120
|
setOpen(false);
|
|
120
121
|
},
|
|
121
|
-
onError: (e) => toast.error(e
|
|
122
|
+
onError: (e) => toast.error(friendlyError(e, "Opslaan mislukt")),
|
|
122
123
|
});
|
|
123
124
|
const delMut = useMutation({
|
|
124
125
|
mutationFn: (id) => deleteFn({ data: { id } }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImportKlanten.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ImportKlanten.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ImportKlanten.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ImportKlanten.tsx"],"names":[],"mappings":"AA+MA,wBAAgB,aAAa,gCAsc5B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { friendlyError } from "../../lib/errors";
|
|
2
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
3
4
|
import { useEffect, useMemo, useState } from "react";
|
|
4
5
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
@@ -235,7 +236,7 @@ export function ImportKlanten() {
|
|
|
235
236
|
queryClient.invalidateQueries({ queryKey: ["customers-external"] });
|
|
236
237
|
}
|
|
237
238
|
},
|
|
238
|
-
onError: (e) => toast.error(e
|
|
239
|
+
onError: (e) => toast.error(friendlyError(e, "Import mislukt")),
|
|
239
240
|
});
|
|
240
241
|
const validCount = useMemo(() => rows.filter((r) => r.data && r.errors.length === 0).length, [rows]);
|
|
241
242
|
const invalidCount = rows.length - validCount;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"AAoDA,wBAAgB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"AAoDA,wBAAgB,gBAAgB,gCAkrB/B"}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Link, useNavigate } from "@tanstack/react-router";
|
|
3
3
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
4
|
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
-
import { useEffect, useMemo, useRef } from "react";
|
|
5
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { usePersistedState, markReturnToList, consumeReturnToList } from "../../lib/persisted-state";
|
|
7
7
|
import { Button, Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsList, TabsTrigger, Skeleton, } from "@flowselections/core";
|
|
8
8
|
import { Plus, Search, Users, UserCheck, UserX, AlertCircle, X, ArrowUpDown, Mail, MapPin, Upload, ChevronLeft, ChevronRight, Pencil, } from "lucide-react";
|
|
@@ -30,6 +30,19 @@ export function KlantenOverzicht() {
|
|
|
30
30
|
const [sort, setSort] = usePersistedState("klanten:sort", { col: "company_name", dir: "asc" });
|
|
31
31
|
const [page, setPage] = usePersistedState("klanten:page", 1);
|
|
32
32
|
const [pageSize, setPageSize] = usePersistedState("klanten:pageSize", 25, "local");
|
|
33
|
+
// Zoekveld: de invoer reageert direct, het filteren pas na een korte pauze,
|
|
34
|
+
// zodat typen soepel blijft ook bij een volledig klantenbestand.
|
|
35
|
+
const [searchInput, setSearchInput] = useState(search);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (searchInput === search)
|
|
38
|
+
return;
|
|
39
|
+
const t = setTimeout(() => {
|
|
40
|
+
setSearch(searchInput);
|
|
41
|
+
setPage(1);
|
|
42
|
+
}, 250);
|
|
43
|
+
return () => clearTimeout(t);
|
|
44
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
45
|
+
}, [searchInput]);
|
|
33
46
|
// Paginanummer alleen bewaren binnen de klantenflow; vers binnenkomen start op pagina 1.
|
|
34
47
|
useEffect(() => {
|
|
35
48
|
if (!consumeReturnToList())
|
|
@@ -47,16 +60,19 @@ export function KlantenOverzicht() {
|
|
|
47
60
|
const { data: classData } = useQuery({
|
|
48
61
|
queryKey: ["customer-classes"],
|
|
49
62
|
queryFn: () => classesFn({ data: undefined }).then(decodeDeep),
|
|
63
|
+
staleTime: 5 * 60000,
|
|
50
64
|
});
|
|
51
65
|
const { data: localData, isLoading: loadingLocal } = useQuery({
|
|
52
66
|
queryKey: ["customers-local"],
|
|
53
67
|
queryFn: () => list({
|
|
54
68
|
data: { search: "", customer_class: "", country: "", is_active: "all", source: "local" },
|
|
55
69
|
}).then(decodeDeep),
|
|
70
|
+
staleTime: 60000,
|
|
56
71
|
});
|
|
57
72
|
const { data: externalData, isLoading: loadingExternal } = useQuery({
|
|
58
73
|
queryKey: ["customers-external"],
|
|
59
74
|
queryFn: () => listExternal({ data: { status: "all" } }).then(decodeDeep),
|
|
75
|
+
staleTime: 60000,
|
|
60
76
|
});
|
|
61
77
|
const { data: overdueData } = useQuery({
|
|
62
78
|
queryKey: ["overdue-balances"],
|
|
@@ -275,7 +291,11 @@ export function KlantenOverzicht() {
|
|
|
275
291
|
const resetPage = () => setPage(1);
|
|
276
292
|
const activeFilterChips = [];
|
|
277
293
|
if (search)
|
|
278
|
-
activeFilterChips.push({
|
|
294
|
+
activeFilterChips.push({
|
|
295
|
+
key: "s",
|
|
296
|
+
label: `Zoek: "${search}"`,
|
|
297
|
+
clear: () => { setSearchInput(""); setSearch(""); resetPage(); },
|
|
298
|
+
});
|
|
279
299
|
selectedClasses.forEach((cls) => {
|
|
280
300
|
activeFilterChips.push({
|
|
281
301
|
key: `c-${cls}`,
|
|
@@ -310,7 +330,8 @@ export function KlantenOverzicht() {
|
|
|
310
330
|
const lastUpdated = externalData?.last_fetched_at ?? null;
|
|
311
331
|
return (_jsx(TooltipPrimitive.Provider, { delayDuration: 200, children: _jsxs("main", { className: "p-6 space-y-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Klanten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer en doorzoek al je klanten." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => goTo({ to: "/klanten/import" }), children: [_jsx(Upload, { className: "h-4 w-4 mr-2" }), " Importeren"] }), _jsxs(Button, { onClick: () => goTo({ to: "/klanten/nieuw" }), children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Nieuwe klant"] })] })] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3", children: [_jsx(StatCard, { icon: _jsx(Users, { className: "h-5 w-5" }), tint: "muted", label: "Alle klanten", value: totals.all, tooltip: "Totaal aantal klanten (lokaal + gekoppelde bronnen).", active: view === "all", onClick: () => { setView("all"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(UserCheck, { className: "h-5 w-5" }), tint: "success", label: "Actieve klanten", value: totals.active, tooltip: "Klanten met minimaal \u00E9\u00E9n bestelling in de afgelopen 12 maanden.", active: view === "active", onClick: () => { setView("active"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(UserX, { className: "h-5 w-5" }), tint: "warning", label: "Inactieve klanten", value: totals.inactive, tooltip: "Klanten zonder omzet in de afgelopen 12 maanden.", active: view === "inactive", onClick: () => { setView("inactive"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(AlertCircle, { className: "h-5 w-5" }), tint: "destructive", label: "Achterstallige betalingen", value: totals.overdue, tooltip: (overdueData?.totals
|
|
312
332
|
? `Klanten met openstaande facturen waarvan de vervaldatum is verstreken.\nTotaal openstaand: ${formatEuro(overdueData.totals.amount)} · ${overdueData.totals.invoices} facturen`
|
|
313
|
-
: "Klanten met openstaande facturen waarvan de vervaldatum is verstreken."), active: view === "overdue", onClick: () => { setView("overdue"); resetPage(); } })] }), _jsxs(Card, { children: [_jsx("div", { className: "border-b px-4 pt-3", children: _jsx(Tabs, { value: view, onValueChange: (v) => { setView(v); resetPage(); }, children: _jsxs(TabsList, { className: "flex-wrap", children: [_jsxs(TabsTrigger, { value: "all", children: ["Alle ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.all ?? 0 })] }), _jsxs(TabsTrigger, { value: "active", children: ["Actief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.active ?? 0 })] }), _jsxs(TabsTrigger, { value: "inactive", children: ["Inactief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.inactive ?? 0 })] }), _jsxs(TabsTrigger, { value: "overdue", children: ["Achterstallig ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.overdue ?? 0 })] })] }) }) }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap p-4 border-b", children: [_jsxs("div", { className: "relative flex-1 min-w-[240px] max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op bedrijf, contactpersoon of e-mail...", value:
|
|
333
|
+
: "Klanten met openstaande facturen waarvan de vervaldatum is verstreken."), active: view === "overdue", onClick: () => { setView("overdue"); resetPage(); } })] }), _jsxs(Card, { children: [_jsx("div", { className: "border-b px-4 pt-3", children: _jsx(Tabs, { value: view, onValueChange: (v) => { setView(v); resetPage(); }, children: _jsxs(TabsList, { className: "flex-wrap", children: [_jsxs(TabsTrigger, { value: "all", children: ["Alle ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.all ?? 0 })] }), _jsxs(TabsTrigger, { value: "active", children: ["Actief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.active ?? 0 })] }), _jsxs(TabsTrigger, { value: "inactive", children: ["Inactief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.inactive ?? 0 })] }), _jsxs(TabsTrigger, { value: "overdue", children: ["Achterstallig ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.overdue ?? 0 })] })] }) }) }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap p-4 border-b", children: [_jsxs("div", { className: "relative flex-1 min-w-[240px] max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { "aria-label": "Zoek klanten", placeholder: "Zoek op bedrijf, contactpersoon of e-mail...", value: searchInput, onChange: (e) => setSearchInput(e.target.value), className: "pl-9" })] }), _jsx(MultiSearchableSelect, { values: selectedClasses, onValuesChange: (v) => { setSelectedClasses(v); resetPage(); }, options: classOptions, placeholder: "Alle klassen", triggerClassName: "w-40" }), _jsx(MultiSearchableSelect, { values: selectedCountries, onValuesChange: (v) => { setSelectedCountries(v); resetPage(); }, options: countryOptions, placeholder: "Alle landen", triggerClassName: "w-36" }), _jsx(MultiSearchableSelect, { values: selectedChannels, onValuesChange: (v) => { setSelectedChannels(v); resetPage(); }, options: channelSelectOptions, placeholder: "Alle kanalen", triggerClassName: "w-40" }), _jsx(MultiSearchableSelect, { values: selectedPayments, onValuesChange: (v) => { setSelectedPayments(v); resetPage(); }, options: paymentSelectOptions, placeholder: "Alle betaalvormen", triggerClassName: "w-40" })] }), activeFilterChips.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap px-4 py-2 border-b bg-muted/30", children: [activeFilterChips.map((chip) => (_jsxs("button", { type: "button", onClick: chip.clear, className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1 text-xs hover:bg-muted", children: [chip.label, " ", _jsx(X, { className: "h-3 w-3" })] }, chip.key))), _jsx("button", { type: "button", onClick: () => {
|
|
334
|
+
setSearchInput("");
|
|
314
335
|
setSearch("");
|
|
315
336
|
setSelectedClasses([]);
|
|
316
337
|
setSelectedCountries([]);
|
|
@@ -318,7 +339,16 @@ export function KlantenOverzicht() {
|
|
|
318
339
|
setSelectedPayments([]);
|
|
319
340
|
setView("all");
|
|
320
341
|
setPage(1);
|
|
321
|
-
}, className: "text-xs text-muted-foreground hover:text-foreground underline", children: "Alles wissen" })] })), _jsx(CardContent, { className: "p-0", children: _jsx("div", { className: "max-h-[60vh] overflow-y-auto overflow-x-auto overscroll-contain [-webkit-overflow-scrolling:touch]", children: _jsxs(Table, { children: [_jsx(TableHeader, { className: "sticky top-0 z-10 bg-background", children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: _jsx(SortHeader, { label: "Bedrijf", col: "company_name", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Contact" }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Klasse", col: "customer_class", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Prijscategorie" }), _jsx(TableHead, { children: "Kanaal" }), _jsx(TableHead, { children: "Betaalvorm" }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Land", col: "country", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: isLoading ? (Array.from({ length: 6 }).map((_, i) => (_jsx(TableRow, { children: Array.from({ length: 8 }).map((__, j) => (_jsx(TableCell, { className: "py-4", children: _jsx(Skeleton, { className: "h-4 w-full max-w-[160px]" }) }, j))) }, i)))) : pageRows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 8, children: _jsx(EmptyState, { onAdd: () => goTo({ to: "/klanten/nieuw" })
|
|
342
|
+
}, className: "text-xs text-muted-foreground hover:text-foreground underline", children: "Alles wissen" })] })), _jsx(CardContent, { className: "p-0", children: _jsx("div", { className: "max-h-[60vh] overflow-y-auto overflow-x-auto overscroll-contain [-webkit-overflow-scrolling:touch]", children: _jsxs(Table, { children: [_jsx(TableHeader, { className: "sticky top-0 z-10 bg-background", children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: _jsx(SortHeader, { label: "Bedrijf", col: "company_name", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Contact" }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Klasse", col: "customer_class", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Prijscategorie" }), _jsx(TableHead, { children: "Kanaal" }), _jsx(TableHead, { children: "Betaalvorm" }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Land", col: "country", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: isLoading ? (Array.from({ length: 6 }).map((_, i) => (_jsx(TableRow, { children: Array.from({ length: 8 }).map((__, j) => (_jsx(TableCell, { className: "py-4", children: _jsx(Skeleton, { className: "h-4 w-full max-w-[160px]" }) }, j))) }, i)))) : pageRows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 8, children: _jsx(EmptyState, { onAdd: () => goTo({ to: "/klanten/nieuw" }), filtered: activeFilterChips.length > 0, onClearFilters: () => {
|
|
343
|
+
setSearchInput("");
|
|
344
|
+
setSearch("");
|
|
345
|
+
setSelectedClasses([]);
|
|
346
|
+
setSelectedCountries([]);
|
|
347
|
+
setSelectedChannels([]);
|
|
348
|
+
setSelectedPayments([]);
|
|
349
|
+
setView("all");
|
|
350
|
+
setPage(1);
|
|
351
|
+
} }) }) })) : pageRows.map((c) => {
|
|
322
352
|
const secondary = c.commercial_name && c.commercial_name !== c.company_name
|
|
323
353
|
? c.commercial_name
|
|
324
354
|
: c.city ?? c.email ?? null;
|
|
@@ -352,8 +382,10 @@ function StatusPill({ active }) {
|
|
|
352
382
|
? "bg-primary/10 text-primary"
|
|
353
383
|
: "bg-muted text-muted-foreground"}`, children: [_jsx("span", { className: `h-1.5 w-1.5 rounded-full ${active ? "bg-primary" : "bg-muted-foreground"}` }), active ? "Actief" : "Inactief"] }));
|
|
354
384
|
}
|
|
355
|
-
function EmptyState({ onAdd }) {
|
|
356
|
-
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center gap-3", children: [_jsx("div", { className: "h-12 w-12 rounded-full bg-muted flex items-center justify-center", children: _jsx(Users, { className: "h-6 w-6 text-muted-foreground" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Geen klanten
|
|
385
|
+
function EmptyState({ onAdd, filtered = false, onClearFilters, }) {
|
|
386
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center gap-3", children: [_jsx("div", { className: "h-12 w-12 rounded-full bg-muted flex items-center justify-center", children: _jsx(Users, { className: "h-6 w-6 text-muted-foreground" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: filtered ? "Geen klanten binnen deze filters" : "Nog geen klanten" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: filtered
|
|
387
|
+
? "Verruim of wis je filters om meer klanten te zien."
|
|
388
|
+
: "Voeg je eerste klant toe of importeer een bestand." })] }), _jsxs("div", { className: "flex gap-2 mt-2", children: [filtered && onClearFilters && (_jsxs(Button, { size: "sm", variant: "outline", onClick: onClearFilters, children: [_jsx(X, { className: "h-4 w-4 mr-2" }), " Filters wissen"] })), _jsxs(Button, { size: "sm", onClick: onAdd, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Nieuwe klant"] })] })] }));
|
|
357
389
|
}
|
|
358
390
|
function relativeTime(iso) {
|
|
359
391
|
if (!iso)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalDiscountFormDialog.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/LocalDiscountFormDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalDiscountFormDialog.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/LocalDiscountFormDialog.tsx"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,gBAAgB,EAEtB,MAAM,iCAAiC,CAAC;AAGzC,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACnC,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,KAAK,+BAiMzF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { friendlyError } from "../../lib/errors";
|
|
2
3
|
import { useEffect, useState } from "react";
|
|
3
4
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
5
|
import { useServerFn } from "@tanstack/react-start";
|
|
@@ -101,7 +102,7 @@ export function LocalDiscountFormDialog({ open, onOpenChange, customerId, editin
|
|
|
101
102
|
qc.invalidateQueries({ queryKey: ["local-discounts", customerId] });
|
|
102
103
|
onOpenChange(false);
|
|
103
104
|
},
|
|
104
|
-
onError: (err) => toast.error(err
|
|
105
|
+
onError: (err) => toast.error(friendlyError(err, "Opslaan mislukt")),
|
|
105
106
|
});
|
|
106
107
|
const canSave = (source === "agreement" ? !!netPrice : !!discountValue) && !save.isPending;
|
|
107
108
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: editing ? "Korting bewerken" : "Nieuwe korting" }) }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Bron" }), _jsxs(Select, { value: source, onValueChange: (v) => setSource(v), disabled: !!editing, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "agreement", children: "Contract (vaste nettoprijs)" }), _jsx(SelectItem, { value: "sales_strategy", children: "Kortingsregel (%/bedrag)" }), _jsx(SelectItem, { value: "price_group", children: "Staffelkorting (o.b.v. hoeveelheid)" })] })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Product" }), _jsxs(Select, { value: productId, onValueChange: setProductId, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een product" }) }), _jsx(SelectContent, { children: products.map((p) => (_jsxs(SelectItem, { value: p.id, children: [p.product, p.sale_price != null ? ` — € ${Number(p.sale_price).toFixed(2)}` : ""] }, p.id))) })] })] }), source === "agreement" ? (_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Basisprijs (optioneel)" }), _jsx(Input, { type: "number", step: "0.01", value: basePrice, onChange: (e) => setBasePrice(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Nettoprijs *" }), _jsx(Input, { type: "number", step: "0.01", value: netPrice, onChange: (e) => setNetPrice(e.target.value) })] })] })) : (_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Type" }), _jsxs(Select, { value: discountType, onValueChange: (v) => setDiscountType(v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "percentage", children: "Percentage (%)" }), _jsx(SelectItem, { value: "amount", children: "Vast bedrag" })] })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Waarde *" }), _jsx(Input, { type: "number", step: "0.01", value: discountValue, onChange: (e) => setDiscountValue(e.target.value) })] })] })), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Min. hoeveelheid" }), _jsx(Input, { type: "number", step: "1", value: minQuantity, onChange: (e) => setMinQuantity(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Geldig van" }), _jsx(Input, { type: "date", value: validFrom, onChange: (e) => setValidFrom(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Geldig tot" }), _jsx(Input, { type: "date", value: validUntil, onChange: (e) => setValidUntil(e.target.value) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Notities" }), _jsx(Input, { value: notes, onChange: (e) => setNotes(e.target.value) })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsx(Button, { disabled: !canSave, onClick: () => save.mutate(), children: save.isPending ? "Opslaan…" : "Opslaan" })] })] }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalDiscountsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/LocalDiscountsTab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalDiscountsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/LocalDiscountsTab.tsx"],"names":[],"mappings":"AAmDA,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,+BA2JvE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { friendlyError } from "../../lib/errors";
|
|
2
3
|
import { useMemo, useState } from "react";
|
|
3
4
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
5
|
import { useServerFn } from "@tanstack/react-start";
|
|
@@ -71,7 +72,7 @@ export function LocalDiscountsTab({ customerId }) {
|
|
|
71
72
|
toast.success("Korting verwijderd");
|
|
72
73
|
qc.invalidateQueries({ queryKey: ["local-discounts", customerId] });
|
|
73
74
|
},
|
|
74
|
-
onError: (err) => toast.error(err
|
|
75
|
+
onError: (err) => toast.error(friendlyError(err, "Actie mislukt")),
|
|
75
76
|
});
|
|
76
77
|
const filtered = useMemo(() => {
|
|
77
78
|
const rows = data?.rows ?? [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogisticsStrategyCard.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/LogisticsStrategyCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LogisticsStrategyCard.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/LogisticsStrategyCard.tsx"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,oCAAoC,CAAC;AAI5C,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,MAAM,EACN,WAAW,GACZ,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+BAuEA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { friendlyError } from "../../lib/errors";
|
|
2
3
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
4
|
import { useServerFn } from "@tanstack/react-start";
|
|
4
5
|
import { Card, CardContent, CardHeader, CardTitle, Badge, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
|
|
@@ -25,7 +26,7 @@ export function LogisticsStrategyCard({ scope, refKey, description, }) {
|
|
|
25
26
|
toast.success("Logistieke strategie bijgewerkt");
|
|
26
27
|
qc.invalidateQueries({ queryKey: ["logistics-strategy-binding", scope, refKey] });
|
|
27
28
|
},
|
|
28
|
-
onError: (e) => toast.error(e
|
|
29
|
+
onError: (e) => toast.error(friendlyError(e, "Opslaan mislukt")),
|
|
29
30
|
});
|
|
30
31
|
const strategies = strategiesData?.strategies ?? [];
|
|
31
32
|
const current = bindingData?.binding?.strategy_id ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NieuweKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NieuweKlant.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NieuweKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NieuweKlant.tsx"],"names":[],"mappings":"AAeA,wBAAgB,WAAW,gCAgE1B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { friendlyError } from "../../lib/errors";
|
|
2
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
3
4
|
import { useState } from "react";
|
|
4
5
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
@@ -23,7 +24,7 @@ export function NieuweKlant() {
|
|
|
23
24
|
qc.invalidateQueries({ queryKey: ["customers"] });
|
|
24
25
|
navigate({ to: "/klanten/$id", params: { id: r.customer.id } });
|
|
25
26
|
},
|
|
26
|
-
onError: (e) => toast.error(e
|
|
27
|
+
onError: (e) => toast.error(friendlyError(e, "Klant aanmaken mislukt")),
|
|
27
28
|
});
|
|
28
29
|
const onSubmit = (values) => {
|
|
29
30
|
setPendingData(values);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OvernemenTwinfieldKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/OvernemenTwinfieldKlant.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OvernemenTwinfieldKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/OvernemenTwinfieldKlant.tsx"],"names":[],"mappings":"AAwBA;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,+BAkGjE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { friendlyError } from "../../lib/errors";
|
|
2
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
3
4
|
import { useEffect } from "react";
|
|
4
5
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
@@ -49,7 +50,7 @@ export function OvernemenTwinfieldKlant({ code }) {
|
|
|
49
50
|
else
|
|
50
51
|
navigate({ to: "/klanten" });
|
|
51
52
|
},
|
|
52
|
-
onError: (e) => toast.error(e
|
|
53
|
+
onError: (e) => toast.error(friendlyError(e, "Overnemen mislukt")),
|
|
53
54
|
});
|
|
54
55
|
if (isLoading || existingId)
|
|
55
56
|
return _jsx("main", { className: "p-6", children: "Laden..." });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Nette foutweergave voor route-loaders en render-fouten binnen Klanten. */
|
|
2
|
+
export declare function RouteError({ error }: {
|
|
3
|
+
error: unknown;
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
|
+
export declare function RouteNotFound(): import("react").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=RouteError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RouteError.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/RouteError.tsx"],"names":[],"mappings":"AAKA,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,+BAoBvD;AAED,wBAAgB,aAAa,gCAU5B"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRouter } from "@tanstack/react-router";
|
|
3
|
+
import { Button, Card, CardContent } from "@flowselections/core";
|
|
4
|
+
import { AlertCircle, RefreshCw } from "lucide-react";
|
|
5
|
+
import { friendlyError } from "../../lib/errors";
|
|
6
|
+
/** Nette foutweergave voor route-loaders en render-fouten binnen Klanten. */
|
|
7
|
+
export function RouteError({ error }) {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
return (_jsx("div", { className: "p-6", children: _jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-center justify-center gap-3 py-12 text-center", children: [_jsx("div", { className: "h-12 w-12 rounded-full bg-destructive/10 flex items-center justify-center", children: _jsx(AlertCircle, { className: "h-6 w-6 text-destructive" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Deze pagina kon niet worden geladen" }), _jsx("div", { className: "mt-0.5 text-sm text-muted-foreground", children: friendlyError(error) })] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => router.invalidate(), children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), " Opnieuw proberen"] })] }) }) }));
|
|
10
|
+
}
|
|
11
|
+
export function RouteNotFound() {
|
|
12
|
+
return (_jsx("div", { className: "p-6", children: _jsx(Card, { children: _jsx(CardContent, { className: "py-12 text-center text-sm text-muted-foreground", children: "Deze pagina bestaat niet (meer)." }) }) }));
|
|
13
|
+
}
|