@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.
Files changed (30) hide show
  1. package/dist-lib/components/klanten/BewerkKlant.d.ts.map +1 -1
  2. package/dist-lib/components/klanten/BewerkKlant.js +2 -1
  3. package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -1
  4. package/dist-lib/components/klanten/DeliveryLocationsTab.js +2 -1
  5. package/dist-lib/components/klanten/ImportKlanten.d.ts.map +1 -1
  6. package/dist-lib/components/klanten/ImportKlanten.js +2 -1
  7. package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
  8. package/dist-lib/components/klanten/KlantenOverzicht.js +38 -6
  9. package/dist-lib/components/klanten/LocalDiscountFormDialog.d.ts.map +1 -1
  10. package/dist-lib/components/klanten/LocalDiscountFormDialog.js +2 -1
  11. package/dist-lib/components/klanten/LocalDiscountsTab.d.ts.map +1 -1
  12. package/dist-lib/components/klanten/LocalDiscountsTab.js +2 -1
  13. package/dist-lib/components/klanten/LogisticsStrategyCard.d.ts.map +1 -1
  14. package/dist-lib/components/klanten/LogisticsStrategyCard.js +2 -1
  15. package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -1
  16. package/dist-lib/components/klanten/NieuweKlant.js +2 -1
  17. package/dist-lib/components/klanten/OvernemenTwinfieldKlant.d.ts.map +1 -1
  18. package/dist-lib/components/klanten/OvernemenTwinfieldKlant.js +2 -1
  19. package/dist-lib/components/klanten/RouteError.d.ts +6 -0
  20. package/dist-lib/components/klanten/RouteError.d.ts.map +1 -0
  21. package/dist-lib/components/klanten/RouteError.js +13 -0
  22. package/dist-lib/lib/customers.functions.d.ts.map +1 -1
  23. package/dist-lib/lib/customers.functions.js +39 -35
  24. package/dist-lib/lib/errors.d.ts +7 -0
  25. package/dist-lib/lib/errors.d.ts.map +1 -0
  26. package/dist-lib/lib/errors.js +36 -0
  27. package/dist-lib/lib/external-customers.functions.d.ts +1 -1
  28. package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/external-customers.functions.js +28 -12
  30. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"BewerkKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/BewerkKlant.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAmDjD"}
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 instanceof Error ? e.message : "Opslaan mislukt"),
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":"AAmEA,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
+ {"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?.message ?? "Opslaan mislukt"),
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":"AA8MA,wBAAgB,aAAa,gCAsc5B"}
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 instanceof Error ? e.message : "Import mislukt"),
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,gCA+oB/B"}
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({ key: "s", label: `Zoek: "${search}"`, clear: () => { setSearch(""); resetPage(); } });
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: search, onChange: (e) => { setSearch(e.target.value); resetPage(); }, 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: () => {
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" }) }) }) })) : pageRows.map((c) => {
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 gevonden" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "Pas je filters aan of voeg handmatig een klant toe." })] }), _jsx("div", { className: "flex gap-2 mt-2", children: _jsxs(Button, { size: "sm", onClick: onAdd, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Nieuwe klant"] }) })] }));
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":"AAQA,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
+ {"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.message),
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":"AAkDA,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,+BA2JvE"}
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.message),
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":"AAOA,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
+ {"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 instanceof Error ? e.message : "Opslaan mislukt"),
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":"AAcA,wBAAgB,WAAW,gCAgE1B"}
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 instanceof Error ? e.message : "Fout"),
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":"AAuBA;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,+BAkGjE"}
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 instanceof Error ? e.message : "Overnemen mislukt"),
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
+ }