@flowselections/floriday-klanten-module 1.0.25 → 1.0.27

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 (96) hide show
  1. package/dist-lib/components/klanten/BewerkKlantRoute.d.ts +4 -1
  2. package/dist-lib/components/klanten/BewerkKlantRoute.d.ts.map +1 -1
  3. package/dist-lib/components/klanten/BewerkKlantRoute.js +7 -4
  4. package/dist-lib/components/klanten/ChannelBadges.d.ts +11 -0
  5. package/dist-lib/components/klanten/ChannelBadges.d.ts.map +1 -0
  6. package/dist-lib/components/klanten/ChannelBadges.js +21 -0
  7. package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -1
  8. package/dist-lib/components/klanten/DeliveryLocationsTab.js +9 -1
  9. package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -1
  10. package/dist-lib/components/klanten/GroupedKlantDetail.js +30 -5
  11. package/dist-lib/components/klanten/GroupedKlantDetailRoute.d.ts +4 -1
  12. package/dist-lib/components/klanten/GroupedKlantDetailRoute.d.ts.map +1 -1
  13. package/dist-lib/components/klanten/GroupedKlantDetailRoute.js +7 -4
  14. package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
  15. package/dist-lib/components/klanten/KlantDetail.js +26 -3
  16. package/dist-lib/components/klanten/KlantDetailLayout.d.ts +4 -2
  17. package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -1
  18. package/dist-lib/components/klanten/KlantDetailLayout.js +23 -21
  19. package/dist-lib/components/klanten/KlantDetailRoute.d.ts +4 -1
  20. package/dist-lib/components/klanten/KlantDetailRoute.d.ts.map +1 -1
  21. package/dist-lib/components/klanten/KlantDetailRoute.js +7 -4
  22. package/dist-lib/components/klanten/KlantFormulier.d.ts +1 -1
  23. package/dist-lib/components/klanten/KlantFormulier.d.ts.map +1 -1
  24. package/dist-lib/components/klanten/KlantFormulier.js +14 -4
  25. package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
  26. package/dist-lib/components/klanten/KlantenOverzicht.js +103 -16
  27. package/dist-lib/components/klanten/MultiSearchableSelect.d.ts +21 -0
  28. package/dist-lib/components/klanten/MultiSearchableSelect.d.ts.map +1 -0
  29. package/dist-lib/components/klanten/MultiSearchableSelect.js +86 -0
  30. package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -1
  31. package/dist-lib/components/klanten/NetwerkPagina.js +2 -1
  32. package/dist-lib/components/klanten/OvernemenTwinfieldKlant.d.ts.map +1 -1
  33. package/dist-lib/components/klanten/OvernemenTwinfieldKlant.js +1 -3
  34. package/dist-lib/components/klanten/OvernemenTwinfieldKlantRoute.d.ts +4 -1
  35. package/dist-lib/components/klanten/OvernemenTwinfieldKlantRoute.d.ts.map +1 -1
  36. package/dist-lib/components/klanten/OvernemenTwinfieldKlantRoute.js +7 -4
  37. package/dist-lib/components/klanten/SearchableSelect.d.ts +17 -0
  38. package/dist-lib/components/klanten/SearchableSelect.d.ts.map +1 -0
  39. package/dist-lib/components/klanten/SearchableSelect.js +57 -0
  40. package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts.map +1 -1
  41. package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +3 -2
  42. package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts.map +1 -1
  43. package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +2 -1
  44. package/dist-lib/integrations/supabase/auth-middleware.d.ts +33 -0
  45. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  46. package/dist-lib/integrations/supabase/client.d.ts +33 -0
  47. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  48. package/dist-lib/integrations/supabase/client.server.d.ts +33 -0
  49. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  50. package/dist-lib/integrations/supabase/types.d.ts +35 -0
  51. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  52. package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +99 -0
  53. package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/accounting.functions.d.ts +99 -0
  55. package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
  56. package/dist-lib/lib/countries.d.ts +31 -0
  57. package/dist-lib/lib/countries.d.ts.map +1 -0
  58. package/dist-lib/lib/countries.js +194 -0
  59. package/dist-lib/lib/customer-analytics.functions.d.ts +33 -0
  60. package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/customer-notes.functions.d.ts +39638 -0
  62. package/dist-lib/lib/customer-notes.functions.d.ts.map +1 -0
  63. package/dist-lib/lib/customer-notes.functions.js +59 -0
  64. package/dist-lib/lib/customers.functions.d.ts +297 -0
  65. package/dist-lib/lib/customers.functions.d.ts.map +1 -1
  66. package/dist-lib/lib/delivery-locations.functions.d.ts +132 -0
  67. package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -1
  68. package/dist-lib/lib/external-customers.functions.d.ts +132 -0
  69. package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
  70. package/dist-lib/lib/floriday-assortment.functions.d.ts +33 -0
  71. package/dist-lib/lib/floriday-assortment.functions.d.ts.map +1 -1
  72. package/dist-lib/lib/floriday-discounts.functions.d.ts +66 -0
  73. package/dist-lib/lib/floriday-discounts.functions.d.ts.map +1 -1
  74. package/dist-lib/lib/floriday-trade-items.functions.d.ts +33 -0
  75. package/dist-lib/lib/floriday-trade-items.functions.d.ts.map +1 -1
  76. package/dist-lib/lib/local-discounts.functions.d.ts +231 -0
  77. package/dist-lib/lib/local-discounts.functions.d.ts.map +1 -1
  78. package/dist-lib/lib/logistics-events.functions.d.ts +33 -0
  79. package/dist-lib/lib/logistics-events.functions.d.ts.map +1 -1
  80. package/dist-lib/lib/logistics-strategy.functions.d.ts +132 -0
  81. package/dist-lib/lib/logistics-strategy.functions.d.ts.map +1 -1
  82. package/dist-lib/lib/overdue-invoices.functions.d.ts +33 -0
  83. package/dist-lib/lib/overdue-invoices.functions.d.ts.map +1 -1
  84. package/dist-lib/lib/payment-method.d.ts +22 -0
  85. package/dist-lib/lib/payment-method.d.ts.map +1 -0
  86. package/dist-lib/lib/payment-method.js +74 -0
  87. package/dist-lib/lib/price-groups-local.functions.d.ts +231 -0
  88. package/dist-lib/lib/price-groups-local.functions.d.ts.map +1 -1
  89. package/dist-lib/lib/price-groups.functions.d.ts +33 -0
  90. package/dist-lib/lib/price-groups.functions.d.ts.map +1 -1
  91. package/dist-lib/lib/twinfield-adopt.functions.d.ts +66 -0
  92. package/dist-lib/lib/twinfield-adopt.functions.d.ts.map +1 -1
  93. package/dist-lib/lib/twinfield-ledger.functions.d.ts +33 -0
  94. package/dist-lib/lib/twinfield-ledger.functions.d.ts.map +1 -1
  95. package/package.json +1 -1
  96. package/public/flowselections-assets/Klanten/twinfield-logo.svg +218 -0
@@ -1,2 +1,5 @@
1
- export declare function BewerkKlantRoute(): import("react").JSX.Element;
1
+ /** Route-onafhankelijk: leest `$id` uit welke route de shell ook gebruikt. */
2
+ export declare function BewerkKlantRoute({ id: propId }?: {
3
+ id?: string;
4
+ }): import("react").JSX.Element;
2
5
  //# sourceMappingURL=BewerkKlantRoute.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BewerkKlantRoute.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/BewerkKlantRoute.tsx"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,gCAG/B"}
1
+ {"version":3,"file":"BewerkKlantRoute.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/BewerkKlantRoute.tsx"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,+BAKpE"}
@@ -1,8 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { getRouteApi } from "@tanstack/react-router";
2
+ import { useParams } from "@tanstack/react-router";
3
3
  import { BewerkKlant } from "./BewerkKlant";
4
- const route = getRouteApi("/_authenticated/klanten/bewerken/$id");
5
- export function BewerkKlantRoute() {
6
- const { id } = route.useParams();
4
+ /** Route-onafhankelijk: leest `$id` uit welke route de shell ook gebruikt. */
5
+ export function BewerkKlantRoute({ id: propId } = {}) {
6
+ const params = useParams({ strict: false });
7
+ const id = propId ?? params.id;
8
+ if (!id)
9
+ return _jsx("main", { className: "p-6 text-sm text-muted-foreground", children: "Geen klant geselecteerd." });
7
10
  return _jsx(BewerkKlant, { id: id });
8
11
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Toont per klant uit welk kanaal (bron) de gegevens komen: Floriday, Twinfield
3
+ * of een andere gekoppelde provider. Logo + naam, in gelijke grootte.
4
+ */
5
+ type Props = {
6
+ providers: string[] | null | undefined;
7
+ className?: string;
8
+ };
9
+ export declare function ChannelBadges({ providers, className }: Props): import("react").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=ChannelBadges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelBadges.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ChannelBadges.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,KAAK,KAAK,GAAG;IACX,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,KAAK,+BAqB5D"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Toont per klant uit welk kanaal (bron) de gegevens komen: Floriday, Twinfield
4
+ * of een andere gekoppelde provider. Logo + naam, in gelijke grootte.
5
+ */
6
+ const FLORIDAY_LOGO = "/flowselections-assets/Klanten/floriday-logo.png";
7
+ const TWINFIELD_LOGO = "/flowselections-assets/Klanten/twinfield-logo.svg";
8
+ const CHANNELS = {
9
+ floriday: { label: "Floriday", logo: FLORIDAY_LOGO },
10
+ twinfield: { label: "Twinfield", logo: TWINFIELD_LOGO },
11
+ exact: { label: "Exact" },
12
+ };
13
+ export function ChannelBadges({ providers, className }) {
14
+ const list = (providers ?? []).filter((p) => p && p !== "local");
15
+ if (list.length === 0)
16
+ return _jsx("span", { className: "text-muted-foreground", children: "\u2014" });
17
+ return (_jsx("div", { className: `flex items-center gap-1.5 flex-wrap ${className ?? ""}`, children: list.map((p) => {
18
+ const meta = CHANNELS[p] ?? { label: p.charAt(0).toUpperCase() + p.slice(1) };
19
+ return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-2 py-0.5 text-xs", children: [meta.logo ? (_jsx("img", { src: meta.logo, alt: "", "aria-hidden": "true", className: "h-4 w-4 object-contain" })) : null, _jsx("span", { children: meta.label })] }, p));
20
+ }) }));
21
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"DeliveryLocationsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/DeliveryLocationsTab.tsx"],"names":[],"mappings":"AAyDA,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,+BAkTA"}
1
+ {"version":3,"file":"DeliveryLocationsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/DeliveryLocationsTab.tsx"],"names":[],"mappings":"AA0DA,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,+BA4UA"}
@@ -7,6 +7,7 @@ import { MapPin, Plus, Pencil, Trash2, Truck, Sparkles, PackageCheck } from "luc
7
7
  import { listCarriers, listDeliveryLocations, upsertDeliveryLocation, deleteDeliveryLocation, pickCarrierForLocation, } from "../../lib/delivery-locations.functions";
8
8
  import { EU_COUNTRIES, findCountry } from "../../lib/eu-regions";
9
9
  import { decodeDeep } from "../../lib/decode-html";
10
+ import { countryNameToFlag } from "../../lib/countries";
10
11
  const EMPTY = {
11
12
  name: "", street: "", house_number: "", postal_code: "",
12
13
  city: "", province: "", country: "Nederland",
@@ -85,7 +86,14 @@ export function DeliveryLocationsTab({ customerId, groupKey, externalProvider, e
85
86
  qc.invalidateQueries({ queryKey: ["customer", ownerKey] });
86
87
  },
87
88
  });
88
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-sm text-muted-foreground", children: "Afleverlocaties staan volledig los van de boekhoudlocatie uit Twinfield en worden alleen gebruikt voor orderverwerking, planning, picking en levering. De vervoerder wordt automatisch gekoppeld op basis van het bedieningsgebied." }), _jsxs(Button, { size: "sm", onClick: startNew, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Locatie toevoegen"] })] }), locations.length === 0 ? (_jsx(Card, { children: _jsx(CardContent, { className: "py-10 text-center text-sm text-muted-foreground", children: "Nog geen afleverlocaties." }) })) : (_jsx("div", { className: "grid gap-3 md:grid-cols-2", children: locations.map((loc) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-5 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-semibold flex items-center gap-2", children: [_jsx(MapPin, { className: "h-4 w-4 text-muted-foreground" }), loc.name || [loc.street, loc.house_number].filter(Boolean).join(" ") || "Afleverlocatie"] }), _jsxs("div", { className: "text-sm text-muted-foreground mt-0.5", children: [[loc.street, loc.house_number].filter(Boolean).join(" "), (loc.street || loc.house_number) && (loc.postal_code || loc.city) ? ", " : "", [loc.postal_code, loc.city].filter(Boolean).join(" "), loc.province ? ` · ${loc.province}` : "", loc.country ? ` · ${loc.country}` : ""] })] }), _jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon", onClick: () => startEdit(loc), children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", children: _jsx(Trash2, { className: "h-4 w-4" }) }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Locatie verwijderen?" }), _jsx(AlertDialogDescription, { children: "Deze actie kan niet ongedaan worden gemaakt." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => delMut.mutate(loc.id), children: "Verwijderen" })] })] })] })] })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap text-sm", children: [loc.self_pickup ? (_jsxs(_Fragment, { children: [_jsx(PackageCheck, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: "Eigen vervoer" }), _jsx(Badge, { variant: "secondary", children: "Klant haalt zelf op" })] })) : (_jsxs(_Fragment, { children: [_jsx(Truck, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: carrierName(loc.carrier_id) }), loc.carrier_assignment_source === "auto" && loc.carrier_id && (_jsxs(Badge, { variant: "secondary", className: "gap-1", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "Automatisch"] })), loc.carrier_assignment_source === "manual" && (_jsx(Badge, { variant: "outline", children: "Handmatig" })), !loc.carrier_id && (_jsx(Badge, { variant: "outline", className: "text-amber-600 border-amber-300", children: "Geen match" }))] })), loc.is_primary && _jsx(Badge, { children: "Vaste locatie" })] })] }) }, loc.id))) })), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: form.id ? "Locatie bewerken" : "Nieuwe locatie" }), _jsx(DialogDescription, { children: "De vervoerder wordt automatisch gekozen op basis van plaats, provincie of land." })] }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsx(Label, { children: "Locatienaam (optioneel)" }), _jsx(Input, { value: form.name, onChange: (e) => setForm({ ...form, name: e.target.value }) })] }), _jsxs("div", { className: "md:col-span-2 grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "col-span-2", children: [_jsx(Label, { children: "Straat" }), _jsx(Input, { value: form.street, onChange: (e) => setForm({ ...form, street: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Huisnummer" }), _jsx(Input, { value: form.house_number, onChange: (e) => setForm({ ...form, house_number: e.target.value }) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Postcode" }), _jsx(Input, { value: form.postal_code, onChange: (e) => setForm({ ...form, postal_code: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Plaats" }), _jsx(Input, { value: form.city, onChange: (e) => setForm({ ...form, city: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Land" }), _jsxs(Select, { value: findCountry(form.country)?.name ?? "", onValueChange: (v) => setForm({ ...form, country: v, province: "" }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een land" }) }), _jsx(SelectContent, { children: EU_COUNTRIES.map((c) => (_jsx(SelectItem, { value: c.name, children: c.name }, c.code))) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Provincie" }), (() => {
89
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-sm text-muted-foreground", children: "Afleverlocaties staan volledig los van de boekhoudlocatie uit Twinfield en worden alleen gebruikt voor orderverwerking, planning, picking en levering. De vervoerder wordt automatisch gekoppeld op basis van het bedieningsgebied." }), _jsxs(Button, { size: "sm", onClick: startNew, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Locatie toevoegen"] })] }), locations.length === 0 ? (_jsx(Card, { children: _jsx(CardContent, { className: "py-10 text-center text-sm text-muted-foreground", children: "Nog geen afleverlocaties." }) })) : (_jsx("div", { className: "grid gap-3 md:grid-cols-2", children: locations.map((loc) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-5 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-semibold flex items-center gap-2", children: [_jsx(MapPin, { className: "h-4 w-4 text-muted-foreground" }), loc.name || [loc.street, loc.house_number].filter(Boolean).join(" ") || "Afleverlocatie"] }), _jsxs("div", { className: "text-sm text-muted-foreground mt-0.5", children: [[loc.street, loc.house_number].filter(Boolean).join(" "), (loc.street || loc.house_number) && (loc.postal_code || loc.city) ? ", " : "", [loc.postal_code, loc.city].filter(Boolean).join(" "), loc.province ? ` · ${loc.province}` : "", loc.country ? (_jsxs("span", { className: "inline-flex items-center gap-1 ml-1", children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(loc.country) ?? "🌐" }), _jsx("span", { children: loc.country })] })) : null] })] }), _jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon", onClick: () => startEdit(loc), children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", children: _jsx(Trash2, { className: "h-4 w-4" }) }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Locatie verwijderen?" }), _jsx(AlertDialogDescription, { children: "Deze actie kan niet ongedaan worden gemaakt." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => delMut.mutate(loc.id), children: "Verwijderen" })] })] })] })] })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap text-sm", children: [loc.self_pickup ? (_jsxs(_Fragment, { children: [_jsx(PackageCheck, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: "Eigen vervoer" }), _jsx(Badge, { variant: "secondary", children: "Klant haalt zelf op" })] })) : (_jsxs(_Fragment, { children: [_jsx(Truck, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: carrierName(loc.carrier_id) }), loc.carrier_assignment_source === "auto" && loc.carrier_id && (_jsxs(Badge, { variant: "secondary", className: "gap-1", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "Automatisch"] })), loc.carrier_assignment_source === "manual" && (_jsx(Badge, { variant: "outline", children: "Handmatig" })), !loc.carrier_id && (_jsx(Badge, { variant: "outline", className: "text-amber-600 border-amber-300", children: "Geen match" }))] })), loc.is_primary && _jsx(Badge, { children: "Vaste locatie" })] })] }) }, loc.id))) })), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: form.id ? "Locatie bewerken" : "Nieuwe locatie" }), _jsx(DialogDescription, { children: "De vervoerder wordt automatisch gekozen op basis van plaats, provincie of land." })] }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsx(Label, { children: "Locatienaam (optioneel)" }), _jsx(Input, { value: form.name, onChange: (e) => setForm({ ...form, name: e.target.value }) })] }), _jsxs("div", { className: "md:col-span-2 grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "col-span-2", children: [_jsx(Label, { children: "Straat" }), _jsx(Input, { value: form.street, onChange: (e) => setForm({ ...form, street: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Huisnummer" }), _jsx(Input, { value: form.house_number, onChange: (e) => setForm({ ...form, house_number: e.target.value }) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Postcode" }), _jsx(Input, { value: form.postal_code, onChange: (e) => setForm({ ...form, postal_code: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Plaats" }), _jsx(Input, { value: form.city, onChange: (e) => setForm({ ...form, city: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Land" }), _jsxs(Select, { value: findCountry(form.country)?.name ?? "", onValueChange: (v) => setForm({ ...form, country: v, province: "" }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een land", children: (() => {
90
+ const selectedName = findCountry(form.country)?.name ?? "";
91
+ const flag = countryNameToFlag(selectedName);
92
+ return selectedName ? (_jsxs("span", { className: "inline-flex items-center gap-2", children: [flag ? _jsx("span", { className: "text-base", "aria-hidden": "true", children: flag }) : _jsx("span", { className: "w-4" }), _jsx("span", { children: selectedName })] })) : null;
93
+ })() }) }), _jsx(SelectContent, { children: EU_COUNTRIES.map((c) => {
94
+ const flag = countryNameToFlag(c.code);
95
+ return (_jsx(SelectItem, { value: c.name, children: _jsxs("span", { className: "inline-flex items-center gap-2", children: [flag ? _jsx("span", { className: "text-base", "aria-hidden": "true", children: flag }) : _jsx("span", { className: "w-4" }), _jsx("span", { children: c.name })] }) }, c.code));
96
+ }) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Provincie" }), (() => {
89
97
  const provinces = findCountry(form.country)?.provinces ?? [];
90
98
  return provinces.length > 0 ? (_jsxs(Select, { value: form.province || "", onValueChange: (v) => setForm({ ...form, province: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een provincie" }) }), _jsx(SelectContent, { children: provinces.map((p) => (_jsx(SelectItem, { value: p, children: p }, p))) })] })) : (_jsx(Input, { value: form.province, onChange: (e) => setForm({ ...form, province: e.target.value }), placeholder: "Kies eerst een land", disabled: !form.country }));
91
99
  })()] }), _jsxs("div", { className: "md:col-span-2 space-y-2", children: [_jsx(Label, { children: "Vervoerder" }), _jsxs(Select, { value: form.self_pickup ? "__self__" : (form.carrier_id ?? "__none__"), onValueChange: (v) => {
@@ -1 +1 @@
1
- {"version":3,"file":"GroupedKlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/GroupedKlantDetail.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BAgRpE"}
1
+ {"version":3,"file":"GroupedKlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/GroupedKlantDetail.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BA4TpE"}
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useNavigate, Link } from "@tanstack/react-router";
3
- import { useQuery } from "@tanstack/react-query";
3
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
- import { useState } from "react";
6
- import { Card, CardContent, Badge, Button, Tabs, TabsList, TabsTrigger, TabsContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
5
+ import { useEffect, useState } from "react";
6
+ import { Card, CardContent, Badge, Button, toast, Tabs, TabsList, TabsTrigger, TabsContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
7
7
  import { Mail, MapPin, Phone, Pencil } from "lucide-react";
8
8
  import { BackLink } from "../../components/klanten/BackLink";
9
9
  import { getGroupedCustomerDetail, } from "../../lib/external-customers.functions";
10
10
  import { ProfileCard, KpiStrip, BadgeList, EmptyState, adaptGroupedToProfile, } from "../../components/klanten/KlantDetailLayout";
11
+ import { countryNameToFlag } from "../../lib/countries";
11
12
  import { DiscountsTab } from "../../components/klanten/DiscountsTab";
12
13
  import { PriceGroupBadges } from "../../components/klanten/PriceGroupBadges";
13
14
  import { listPriceGroupsForOrganizations } from "../../lib/price-groups.functions";
@@ -16,10 +17,15 @@ import { FloridayAssortmentTab } from "../../components/klanten/FloridayAssortme
16
17
  import { decodeDeep } from "../../lib/decode-html";
17
18
  import { LogisticsStrategyCard } from "../../components/klanten/LogisticsStrategyCard";
18
19
  import { DeliveryLocationsTab } from "../../components/klanten/DeliveryLocationsTab";
20
+ import { getCustomerNote, upsertCustomerNote } from "../../lib/customer-notes.functions";
21
+ import { supabase } from "../../integrations/supabase/client";
19
22
  export function GroupedKlantDetail({ groupKey }) {
20
23
  const navigate = useNavigate();
24
+ const qc = useQueryClient();
21
25
  const get = useServerFn(getGroupedCustomerDetail);
22
26
  const listPg = useServerFn(listPriceGroupsForOrganizations);
27
+ const getNote = useServerFn(getCustomerNote);
28
+ const saveNote = useServerFn(upsertCustomerNote);
23
29
  const [tab, setTab] = useState("overview");
24
30
  const { data, isLoading, error } = useQuery({
25
31
  queryKey: ["grouped-customer", groupKey],
@@ -35,6 +41,25 @@ export function GroupedKlantDetail({ groupKey }) {
35
41
  }).then(decodeDeep),
36
42
  enabled: !!floridayBuyerId,
37
43
  });
44
+ const { data: noteData, isLoading: noteLoading } = useQuery({
45
+ queryKey: ["customer-note", "group_key", groupKey],
46
+ queryFn: () => getNote({ data: { group_key: groupKey } }),
47
+ });
48
+ const noteMut = useMutation({
49
+ mutationFn: (notes) => saveNote({ data: { group_key: groupKey, notes } }),
50
+ onSuccess: () => {
51
+ qc.invalidateQueries({ queryKey: ["customer-note", "group_key", groupKey] });
52
+ toast.success("Notitie opgeslagen");
53
+ },
54
+ onError: () => toast.error("Notitie opslaan mislukt"),
55
+ });
56
+ useEffect(() => {
57
+ const channel = supabase
58
+ .channel(`group-notes-${groupKey}`)
59
+ .on("postgres_changes", { event: "*", schema: "public", table: "customer_notes" }, () => qc.invalidateQueries({ queryKey: ["customer-note", "group_key", groupKey] }))
60
+ .subscribe();
61
+ return () => { supabase.removeChannel(channel); };
62
+ }, [groupKey, qc]);
38
63
  if (isLoading)
39
64
  return _jsx("main", { className: "p-6", children: "Laden..." });
40
65
  if (error || !data) {
@@ -64,12 +89,12 @@ export function GroupedKlantDetail({ groupKey }) {
64
89
  provider: providerMap.get(s.provider)?.label ?? s.provider,
65
90
  }))
66
91
  .sort((a, b) => +new Date(b.created_at) - +new Date(a.created_at));
67
- return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [localCustomerId ? (_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs(Link, { to: "/klanten/bewerken/$id", params: { id: localCustomerId }, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }) })) : twinfieldCode ? (_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs(Link, { to: "/klanten/overnemen/$code", params: { code: twinfieldCode }, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }) })) : null, _jsx(FloridayDeeplinkButton, { organizationId: floridayBuyerId })] }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: profile }), _jsxs("div", { className: "space-y-4 min-w-0", children: [priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] })), _jsx(KpiStrip, { counts: {
92
+ return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [localCustomerId ? (_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs(Link, { to: "/klanten/bewerken/$id", params: { id: localCustomerId }, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }) })) : twinfieldCode ? (_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs(Link, { to: "/klanten/overnemen/$code", params: { code: twinfieldCode }, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }) })) : null, _jsx(FloridayDeeplinkButton, { organizationId: floridayBuyerId })] }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: profile, notes: noteData?.note ?? null, onSaveNotes: (n) => noteMut.mutate(n), isSaving: noteMut.isPending || noteLoading }), _jsxs("div", { className: "space-y-4 min-w-0", children: [priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] })), _jsx(KpiStrip, { counts: {
68
93
  contacts: contacts.length,
69
94
  locations: locations.length,
70
95
  assortment: 0,
71
96
  orders: 0,
72
- }, onSelect: setTab }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { className: "flex-wrap", children: [_jsx(TabsTrigger, { value: "overview", children: "Overzicht" }), _jsxs(TabsTrigger, { value: "contacts", children: ["Contacten (", contacts.length, ")"] }), _jsxs(TabsTrigger, { value: "locations", children: ["Locaties (", locations.length, ")"] }), _jsx(TabsTrigger, { value: "assortment", children: "Assortiment (0)" }), _jsx(TabsTrigger, { value: "orders", children: "Bestellingen (0)" }), _jsx(TabsTrigger, { value: "trade", children: "Handel" }), floridayBuyerId && (_jsx(TabsTrigger, { value: "discounts", children: "Kortingen & prijzen" })), _jsx(TabsTrigger, { value: "activity", children: "Activiteit" }), g.is_merged && sources.length > 1 && (_jsxs(TabsTrigger, { value: "sources", children: ["Bronnen (", sources.length, ")"] }))] }), _jsxs(TabsContent, { value: "overview", className: "space-y-4", children: [_jsx(LogisticsStrategyCard, { scope: "group", refKey: groupKey, description: "Geldt voor alle bronnen binnen deze klantgroep. Een strategie op een individuele klant heeft voorrang." }), (segments.length || tradeForms.length || markets.length || productGroups.length) ? (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 grid gap-4 md:grid-cols-2", children: [_jsx(BadgeList, { label: "Segmenten", items: segments }), _jsx(BadgeList, { label: "Handelsvorm", items: tradeForms }), _jsx(BadgeList, { label: "Afzetmarkten", items: markets }), _jsx(BadgeList, { label: "Productgroepen", items: productGroups })] }) })) : null, paymentMethods.length > 0 && (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsx(BadgeList, { label: "Betaalmethoden", items: paymentMethods }) }) })), !segments.length && !tradeForms.length && !markets.length && !productGroups.length && !paymentMethods.length && (_jsx(EmptyState, { message: "Geen extra profielinformatie beschikbaar voor deze klant." }))] }), _jsx(TabsContent, { value: "contacts", children: contacts.length === 0 ? (_jsx(EmptyState, { message: "Geen contactpersonen beschikbaar via de koppeling." })) : (_jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: contacts.map((p, i) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsx("div", { className: "font-semibold", children: p.full_name }), p.job_title && _jsx("div", { className: "text-sm text-muted-foreground", children: p.job_title }), p.phone && (_jsxs("a", { href: `tel:${p.phone}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.phone] })), p.email && (_jsxs("a", { href: `mailto:${p.email}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.email] })), _jsx(Badge, { variant: "secondary", children: p.source_label })] }) }, i))) })) }), _jsx(TabsContent, { value: "locations", children: _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "text-sm font-medium flex items-center gap-2", children: [_jsx(MapPin, { className: "h-4 w-4 text-muted-foreground" }), " Afleverlocaties (logistiek)"] }), _jsx(DeliveryLocationsTab, { groupKey: groupKey, customerId: localCustomerId })] }), locations.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "text-sm font-medium flex items-center gap-2", children: [_jsx(MapPin, { className: "h-4 w-4 text-muted-foreground" }), " Boekhoudlocaties uit de koppeling (read-only)"] }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Adres" }), _jsx(TableHead, { children: "GLN" }), _jsx(TableHead, { children: "Postcode" }), _jsx(TableHead, { children: "Bron" })] }) }), _jsx(TableBody, { children: locations.map((l, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: l.name || l.address_line || "—" }), l.name && l.address_line && (_jsxs("div", { className: "text-xs text-muted-foreground", children: [l.address_line, ", ", l.city ?? ""] }))] }), _jsx(TableCell, { className: "font-mono text-sm", children: l.gln || "—" }), _jsx(TableCell, { className: "text-sm", children: [l.postal_code, l.country].filter(Boolean).join(" · ") }), _jsx(TableCell, { children: _jsx(Badge, { variant: "secondary", children: l.source_label }) })] }, i))) })] }) }) })] }))] }) }), _jsx(TabsContent, { value: "assortment", children: floridayBuyerId ? (_jsx(FloridayAssortmentTab, { organizationId: floridayBuyerId })) : (_jsx(EmptyState, { message: "Assortiment is beschikbaar zodra de koppeling dit levert." })) }), _jsx(TabsContent, { value: "orders", children: _jsx(EmptyState, { message: "Bestellingen zijn beschikbaar zodra de koppeling dit levert." }) }), _jsx(TabsContent, { value: "trade", children: _jsx(EmptyState, { message: "Handelsinstellingen zijn niet beschikbaar via deze koppeling." }) }), floridayBuyerId && (_jsx(TabsContent, { value: "discounts", children: _jsx(DiscountsTab, { buyerOrganizationId: floridayBuyerId }) })), _jsx(TabsContent, { value: "activity", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: activity.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen activiteit voor deze klant." })) : (activity.map((a) => (_jsxs("div", { className: "flex items-center justify-between text-sm border-l-2 border-primary pl-3 py-1", children: [_jsxs("span", { children: ["Bijgewerkt via ", _jsx("strong", { children: a.provider })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: new Date(a.created_at).toLocaleString("nl-NL") })] }, a.id)))) }) }) }), g.is_merged && sources.length > 1 && (_jsx(TabsContent, { value: "sources", children: _jsx(CompareCard, { sources: sources, providerMap: providerMap }) }))] })] })] })] }));
97
+ }, onSelect: setTab }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { className: "flex-wrap", children: [_jsx(TabsTrigger, { value: "overview", children: "Overzicht" }), _jsxs(TabsTrigger, { value: "contacts", children: ["Contacten (", contacts.length, ")"] }), _jsxs(TabsTrigger, { value: "locations", children: ["Locaties (", locations.length, ")"] }), _jsx(TabsTrigger, { value: "assortment", children: "Assortiment (0)" }), _jsx(TabsTrigger, { value: "orders", children: "Bestellingen (0)" }), _jsx(TabsTrigger, { value: "trade", children: "Handel" }), floridayBuyerId && (_jsx(TabsTrigger, { value: "discounts", children: "Kortingen & prijzen" })), _jsx(TabsTrigger, { value: "activity", children: "Activiteit" }), g.is_merged && sources.length > 1 && (_jsxs(TabsTrigger, { value: "sources", children: ["Bronnen (", sources.length, ")"] }))] }), _jsxs(TabsContent, { value: "overview", className: "space-y-4", children: [_jsx(LogisticsStrategyCard, { scope: "group", refKey: groupKey, description: "Geldt voor alle bronnen binnen deze klantgroep. Een strategie op een individuele klant heeft voorrang." }), (segments.length || tradeForms.length || markets.length || productGroups.length) ? (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 grid gap-4 md:grid-cols-2", children: [_jsx(BadgeList, { label: "Segmenten", items: segments }), _jsx(BadgeList, { label: "Handelsvorm", items: tradeForms }), _jsx(BadgeList, { label: "Afzetmarkten", items: markets }), _jsx(BadgeList, { label: "Productgroepen", items: productGroups })] }) })) : null, paymentMethods.length > 0 && (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsx(BadgeList, { label: "Betaalmethoden", items: paymentMethods }) }) })), !segments.length && !tradeForms.length && !markets.length && !productGroups.length && !paymentMethods.length && (_jsx(EmptyState, { message: "Geen extra profielinformatie beschikbaar voor deze klant." }))] }), _jsx(TabsContent, { value: "contacts", children: contacts.length === 0 ? (_jsx(EmptyState, { message: "Geen contactpersonen beschikbaar via de koppeling." })) : (_jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: contacts.map((p, i) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsx("div", { className: "font-semibold", children: p.full_name }), p.job_title && _jsx("div", { className: "text-sm text-muted-foreground", children: p.job_title }), p.phone && (_jsxs("a", { href: `tel:${p.phone}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.phone] })), p.email && (_jsxs("a", { href: `mailto:${p.email}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.email] })), _jsx(Badge, { variant: "secondary", children: p.source_label })] }) }, i))) })) }), _jsx(TabsContent, { value: "locations", children: _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "text-sm font-medium flex items-center gap-2", children: [_jsx(MapPin, { className: "h-4 w-4 text-muted-foreground" }), " Afleverlocaties (logistiek)"] }), _jsx(DeliveryLocationsTab, { groupKey: groupKey, customerId: localCustomerId })] }), locations.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "text-sm font-medium flex items-center gap-2", children: [_jsx(MapPin, { className: "h-4 w-4 text-muted-foreground" }), " Boekhoudlocaties uit de koppeling (read-only)"] }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Adres" }), _jsx(TableHead, { children: "GLN" }), _jsx(TableHead, { children: "Postcode" }), _jsx(TableHead, { children: "Bron" })] }) }), _jsx(TableBody, { children: locations.map((l, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: l.name || l.address_line || "—" }), l.name && l.address_line && (_jsxs("div", { className: "text-xs text-muted-foreground", children: [l.address_line, ", ", l.city ?? ""] }))] }), _jsx(TableCell, { className: "font-mono text-sm", children: l.gln || "—" }), _jsxs(TableCell, { className: "text-sm", children: [l.postal_code ? _jsx("span", { className: "font-mono mr-1", children: l.postal_code }) : null, l.country ? (_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(l.country) ?? "🌐" }), _jsx("span", { children: l.country })] })) : null, !l.postal_code && !l.country ? "—" : null] }), _jsx(TableCell, { children: _jsx(Badge, { variant: "secondary", children: l.source_label }) })] }, i))) })] }) }) })] }))] }) }), _jsx(TabsContent, { value: "assortment", children: floridayBuyerId ? (_jsx(FloridayAssortmentTab, { organizationId: floridayBuyerId })) : (_jsx(EmptyState, { message: "Assortiment is beschikbaar zodra de koppeling dit levert." })) }), _jsx(TabsContent, { value: "orders", children: _jsx(EmptyState, { message: "Bestellingen zijn beschikbaar zodra de koppeling dit levert." }) }), _jsx(TabsContent, { value: "trade", children: _jsx(EmptyState, { message: "Handelsinstellingen zijn niet beschikbaar via deze koppeling." }) }), floridayBuyerId && (_jsx(TabsContent, { value: "discounts", children: _jsx(DiscountsTab, { buyerOrganizationId: floridayBuyerId }) })), _jsx(TabsContent, { value: "activity", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: activity.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen activiteit voor deze klant." })) : (activity.map((a) => (_jsxs("div", { className: "flex items-center justify-between text-sm border-l-2 border-primary pl-3 py-1", children: [_jsxs("span", { children: ["Bijgewerkt via ", _jsx("strong", { children: a.provider })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: new Date(a.created_at).toLocaleString("nl-NL") })] }, a.id)))) }) }) }), g.is_merged && sources.length > 1 && (_jsx(TabsContent, { value: "sources", children: _jsx(CompareCard, { sources: sources, providerMap: providerMap }) }))] })] })] })] }));
73
98
  }
74
99
  function collectContacts(sources, providerMap) {
75
100
  const out = [];
@@ -1,2 +1,5 @@
1
- export declare function GroupedKlantDetailRoute(): import("react").JSX.Element;
1
+ /** Route-onafhankelijk: leest `$groupKey` uit welke route de shell ook gebruikt. */
2
+ export declare function GroupedKlantDetailRoute({ groupKey: propKey }?: {
3
+ groupKey?: string;
4
+ }): import("react").JSX.Element;
2
5
  //# sourceMappingURL=GroupedKlantDetailRoute.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GroupedKlantDetailRoute.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/GroupedKlantDetailRoute.tsx"],"names":[],"mappings":"AAKA,wBAAgB,uBAAuB,gCAGtC"}
1
+ {"version":3,"file":"GroupedKlantDetailRoute.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/GroupedKlantDetailRoute.tsx"],"names":[],"mappings":"AAGA,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,+BAKxF"}
@@ -1,8 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { getRouteApi } from "@tanstack/react-router";
2
+ import { useParams } from "@tanstack/react-router";
3
3
  import { GroupedKlantDetail } from "./GroupedKlantDetail";
4
- const route = getRouteApi("/_authenticated/klanten/groep/$groupKey");
5
- export function GroupedKlantDetailRoute() {
6
- const { groupKey } = route.useParams();
4
+ /** Route-onafhankelijk: leest `$groupKey` uit welke route de shell ook gebruikt. */
5
+ export function GroupedKlantDetailRoute({ groupKey: propKey } = {}) {
6
+ const params = useParams({ strict: false });
7
+ const groupKey = propKey ?? params.groupKey;
8
+ if (!groupKey)
9
+ return _jsx("main", { className: "p-6 text-sm text-muted-foreground", children: "Geen klant geselecteerd." });
7
10
  return _jsx(GroupedKlantDetail, { groupKey: groupKey });
8
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BA0RjD"}
1
+ {"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"AAmCA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAsTjD"}
@@ -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, useMutation, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
- import { useState } from "react";
5
+ import { useEffect, useState } from "react";
6
6
  import { Card, CardContent, CardHeader, CardTitle, Badge, Button, toast, Tabs, TabsContent, TabsList, TabsTrigger, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@flowselections/core";
7
7
  import { Trash2, Mail, Phone, Power, Pencil, } from "lucide-react";
8
8
  import { getCustomer, deleteCustomer, updateCustomer } from "../../lib/customers.functions";
@@ -18,17 +18,40 @@ import { SyncStatusChip } from "../../components/klanten/SyncStatusChip";
18
18
  import { LogisticsStrategyCard } from "../../components/klanten/LogisticsStrategyCard";
19
19
  import { decodeDeep } from "../../lib/decode-html";
20
20
  import { getMissingTwinfieldFields } from "../../lib/twinfield-required-fields";
21
+ import { getCustomerNote, upsertCustomerNote } from "../../lib/customer-notes.functions";
22
+ import { supabase } from "../../integrations/supabase/client";
21
23
  export function KlantDetail({ id }) {
22
24
  const navigate = useNavigate();
23
25
  const qc = useQueryClient();
24
26
  const get = useServerFn(getCustomer);
25
27
  const del = useServerFn(deleteCustomer);
26
28
  const upd = useServerFn(updateCustomer);
29
+ const getNote = useServerFn(getCustomerNote);
30
+ const saveNote = useServerFn(upsertCustomerNote);
27
31
  const [tab, setTab] = useState("overview");
28
32
  const { data, isLoading } = useQuery({
29
33
  queryKey: ["customer", id],
30
34
  queryFn: () => get({ data: { id } }).then(decodeDeep),
31
35
  });
36
+ const { data: noteData, isLoading: noteLoading } = useQuery({
37
+ queryKey: ["customer-note", "customer_id", id],
38
+ queryFn: () => getNote({ data: { customer_id: id } }),
39
+ });
40
+ const noteMut = useMutation({
41
+ mutationFn: (notes) => saveNote({ data: { customer_id: id, notes } }),
42
+ onSuccess: () => {
43
+ qc.invalidateQueries({ queryKey: ["customer-note", "customer_id", id] });
44
+ toast.success("Notitie opgeslagen");
45
+ },
46
+ onError: (err) => toast.error("Notitie opslaan mislukt"),
47
+ });
48
+ useEffect(() => {
49
+ const channel = supabase
50
+ .channel(`customer-notes-${id}`)
51
+ .on("postgres_changes", { event: "*", schema: "public", table: "customer_notes" }, () => qc.invalidateQueries({ queryKey: ["customer-note", "customer_id", id] }))
52
+ .subscribe();
53
+ return () => { supabase.removeChannel(channel); };
54
+ }, [id, qc]);
32
55
  const delMut = useMutation({
33
56
  mutationFn: () => del({ data: { id } }),
34
57
  onSuccess: () => { toast.success("Verwijderd"); navigate({ to: "/klanten" }); },
@@ -47,10 +70,10 @@ export function KlantDetail({ id }) {
47
70
  const c = data.customer;
48
71
  const missingTwinfield = getMissingTwinfieldFields(c);
49
72
  const priceGroups = data.price_groups ?? c.price_groups ?? [];
50
- return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), missingTwinfield.length > 0 && (_jsxs("div", { className: "rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Onvolledig voor Twinfield" }), _jsxs("p", { className: "text-muted-foreground mt-0.5", children: ["Deze klant kan pas naar Twinfield worden gestuurd zodra de volgende gegevens zijn aangevuld: ", missingTwinfield.map((f) => f.label).join(", "), "."] })] })), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2", children: [_jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs(Link, { to: "/klanten/bewerken/$id", params: { id }, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }) }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => toggleActive.mutate(!c.is_active), children: [_jsx(Power, { className: "h-4 w-4 mr-2" }), c.is_active ? "Op inactief zetten" : "Activeren"] }), _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), " Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je ", _jsx("strong", { children: c.company_name }), " wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => delMut.mutate(), children: "Verwijderen" })] })] })] })] }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: adaptLocalToProfile(c, data.contacts) }), _jsxs("div", { className: "space-y-4 min-w-0", children: [priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] })), _jsx(KpiStrip, { counts: {
73
+ return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), missingTwinfield.length > 0 && (_jsxs("div", { className: "rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Onvolledig voor Twinfield" }), _jsxs("p", { className: "text-muted-foreground mt-0.5", children: ["Deze klant kan pas naar Twinfield worden gestuurd zodra de volgende gegevens zijn aangevuld: ", missingTwinfield.map((f) => f.label).join(", "), "."] })] })), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2", children: [_jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs(Link, { to: "/klanten/bewerken/$id", params: { id }, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }) }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => toggleActive.mutate(!c.is_active), children: [_jsx(Power, { className: "h-4 w-4 mr-2" }), c.is_active ? "Op inactief zetten" : "Activeren"] }), _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), " Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je ", _jsx("strong", { children: c.company_name }), " wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => delMut.mutate(), children: "Verwijderen" })] })] })] })] }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: adaptLocalToProfile(c, data.contacts), notes: noteData?.note ?? null, onSaveNotes: (n) => noteMut.mutate(n), isSaving: noteMut.isPending || noteLoading }), _jsxs("div", { className: "space-y-4 min-w-0", children: [priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] })), _jsx(KpiStrip, { counts: {
51
74
  contacts: data.contacts.length,
52
75
  locations: data.delivery_locations.length,
53
76
  assortment: data.assortment.length,
54
77
  orders: data.orders.length,
55
- }, onSelect: setTab }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { className: "flex flex-wrap h-auto gap-1 mb-4", children: [_jsx(TabsTrigger, { value: "overview", children: "Overzicht" }), _jsx(TabsTrigger, { value: "dashboard", children: "Analyses" }), _jsxs(TabsTrigger, { value: "contacts", children: ["Contacten (", data.contacts.length, ")"] }), _jsxs(TabsTrigger, { value: "locations", children: ["Locaties (", data.delivery_locations.length, ")"] }), _jsxs(TabsTrigger, { value: "assortment", children: ["Assortiment (", data.assortment.length, ")"] }), c.floriday_organization_id && (_jsx(TabsTrigger, { value: "floriday-assortment", children: "Floriday-aanbod" })), _jsxs(TabsTrigger, { value: "orders", children: ["Bestellingen (", data.orders.length, ")"] }), _jsx(TabsTrigger, { value: "trade", children: "Handel" }), _jsx(TabsTrigger, { value: "discounts", children: "Kortingen & prijzen" }), _jsx(TabsTrigger, { value: "activity", children: "Activiteit" })] }), _jsxs(TabsContent, { value: "overview", className: "space-y-4", children: [_jsx(LogisticsStrategyCard, { scope: "customer", refKey: id }), c.bio && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Bio" }) }), _jsx(CardContent, { className: "text-sm whitespace-pre-wrap", children: c.bio })] })), (c.segments?.length || c.trade_forms?.length || c.markets?.length || c.product_groups?.length) ? (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Profiel" }) }), _jsxs(CardContent, { className: "grid gap-4 md:grid-cols-2", children: [_jsx(BadgeList, { label: "Segmenten", items: c.segments }), _jsx(BadgeList, { label: "Handelsvorm", items: c.trade_forms }), _jsx(BadgeList, { label: "Afzetmarkten", items: c.markets }), _jsx(BadgeList, { label: "Productgroepen", items: c.product_groups })] })] })) : null, (c.payment_methods?.length ?? 0) > 0 && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Financi\u00EBle afhandeling" }) }), _jsx(CardContent, { children: _jsx(BadgeList, { label: "Betaalmethoden", items: c.payment_methods }) })] })), c.notes && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Notities" }) }), _jsx(CardContent, { className: "text-sm whitespace-pre-wrap", children: c.notes })] })), !c.bio && !c.notes && !(c.segments?.length || c.trade_forms?.length || c.markets?.length || c.product_groups?.length) && !(c.payment_methods?.length) && (_jsx(EmptyState, { message: "Geen extra profielinformatie beschikbaar voor deze klant." }))] }), _jsx(TabsContent, { value: "contacts", children: data.contacts.length === 0 ? (_jsx(EmptyState, { message: "Geen contactpersonen gesynchroniseerd." })) : (_jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: data.contacts.map((p) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsx("div", { className: "font-semibold", children: p.full_name }), p.job_title && _jsx("div", { className: "text-sm text-muted-foreground", children: p.job_title }), p.phone && (_jsxs("a", { href: `tel:${p.phone}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.phone] })), p.email && (_jsxs("a", { href: `mailto:${p.email}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.email] })), p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "secondary", children: "Inactief" })] }) }, p.id))) })) }), _jsx(TabsContent, { value: "trade", children: data.trade_settings.length === 0 ? (_jsx(EmptyState, { message: "Handelsinstellingen zijn (nog) niet beschikbaar via de Suppliers API voor deze klant." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: data.trade_settings.map((s) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Badge, { variant: s.is_active ? "default" : "secondary", children: s.is_active ? "Actief" : "Niet actief" }), _jsx("span", { className: "text-sm", children: s.setting_label })] }, s.id))) }) })) }), _jsx(TabsContent, { value: "locations", children: _jsx(DeliveryLocationsTab, { customerId: id }) }), _jsx(TabsContent, { value: "dashboard", children: _jsx(CustomerAnalyticsTab, { customerId: id }) }), _jsx(TabsContent, { value: "assortment", children: data.assortment.length === 0 ? (_jsx(EmptyState, { message: "Geen geselecteerd assortiment voor deze klant." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikelnummer" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Groep" }), _jsx(TableHead, { children: "Prijs" })] }) }), _jsx(TableBody, { children: data.assortment.map((a) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: a.article_number || "—" }), _jsx(TableCell, { children: a.product_name }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: a.product_group || "—" }), _jsx(TableCell, { children: a.price != null ? `€ ${Number(a.price).toFixed(2)}${a.unit ? ` / ${a.unit}` : ""}` : "—" })] }, a.id))) })] }) }) })) }), c.floriday_organization_id && (_jsx(TabsContent, { value: "floriday-assortment", children: _jsx(FloridayAssortmentTab, { organizationId: c.floriday_organization_id }) })), _jsx(TabsContent, { value: "orders", children: _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Datum" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Totaal" })] }) }), _jsx(TableBody, { children: data.orders.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center py-6 text-muted-foreground", children: "Geen bestellingen" }) })) : data.orders.map((o) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: o.order_number }), _jsx(TableCell, { children: o.order_date }), _jsx(TableCell, { children: _jsx(Badge, { children: o.status }) }), _jsx(TableCell, { children: o.total })] }, o.id))) })] }) }) }) }), _jsx(TabsContent, { value: "activity", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: data.audit.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen activiteit voor deze klant." })) : (data.audit.map((a) => _jsx(ActivityItem, { entry: a }, a.id))) }) }) }), _jsx(TabsContent, { value: "discounts", children: _jsx(LocalDiscountsTab, { customerId: id }) })] })] })] })] }));
78
+ }, onSelect: setTab }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { className: "flex flex-wrap h-auto gap-1 mb-4", children: [_jsx(TabsTrigger, { value: "overview", children: "Overzicht" }), _jsx(TabsTrigger, { value: "dashboard", children: "Analyses" }), _jsxs(TabsTrigger, { value: "contacts", children: ["Contacten (", data.contacts.length, ")"] }), _jsxs(TabsTrigger, { value: "locations", children: ["Locaties (", data.delivery_locations.length, ")"] }), _jsxs(TabsTrigger, { value: "assortment", children: ["Assortiment (", data.assortment.length, ")"] }), c.floriday_organization_id && (_jsx(TabsTrigger, { value: "floriday-assortment", children: "Floriday-aanbod" })), _jsxs(TabsTrigger, { value: "orders", children: ["Bestellingen (", data.orders.length, ")"] }), _jsx(TabsTrigger, { value: "trade", children: "Handel" }), _jsx(TabsTrigger, { value: "discounts", children: "Kortingen & prijzen" }), _jsx(TabsTrigger, { value: "activity", children: "Activiteit" })] }), _jsxs(TabsContent, { value: "overview", className: "space-y-4", children: [_jsx(LogisticsStrategyCard, { scope: "customer", refKey: id }), c.bio && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Bio" }) }), _jsx(CardContent, { className: "text-sm whitespace-pre-wrap", children: c.bio })] })), (c.segments?.length || c.trade_forms?.length || c.markets?.length || c.product_groups?.length) ? (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Profiel" }) }), _jsxs(CardContent, { className: "grid gap-4 md:grid-cols-2", children: [_jsx(BadgeList, { label: "Segmenten", items: c.segments }), _jsx(BadgeList, { label: "Handelsvorm", items: c.trade_forms }), _jsx(BadgeList, { label: "Afzetmarkten", items: c.markets }), _jsx(BadgeList, { label: "Productgroepen", items: c.product_groups })] })] })) : null, (c.payment_methods?.length ?? 0) > 0 && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Financi\u00EBle afhandeling" }) }), _jsx(CardContent, { children: _jsx(BadgeList, { label: "Betaalmethoden", items: c.payment_methods }) })] })), !c.bio && !(c.segments?.length || c.trade_forms?.length || c.markets?.length || c.product_groups?.length) && !(c.payment_methods?.length) && (_jsx(EmptyState, { message: "Geen extra profielinformatie beschikbaar voor deze klant." }))] }), _jsx(TabsContent, { value: "contacts", children: data.contacts.length === 0 ? (_jsx(EmptyState, { message: "Geen contactpersonen gesynchroniseerd." })) : (_jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: data.contacts.map((p) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsx("div", { className: "font-semibold", children: p.full_name }), p.job_title && _jsx("div", { className: "text-sm text-muted-foreground", children: p.job_title }), p.phone && (_jsxs("a", { href: `tel:${p.phone}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.phone] })), p.email && (_jsxs("a", { href: `mailto:${p.email}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.email] })), p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "secondary", children: "Inactief" })] }) }, p.id))) })) }), _jsx(TabsContent, { value: "trade", children: data.trade_settings.length === 0 ? (_jsx(EmptyState, { message: "Handelsinstellingen zijn (nog) niet beschikbaar via de Suppliers API voor deze klant." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: data.trade_settings.map((s) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Badge, { variant: s.is_active ? "default" : "secondary", children: s.is_active ? "Actief" : "Niet actief" }), _jsx("span", { className: "text-sm", children: s.setting_label })] }, s.id))) }) })) }), _jsx(TabsContent, { value: "locations", children: _jsx(DeliveryLocationsTab, { customerId: id }) }), _jsx(TabsContent, { value: "dashboard", children: _jsx(CustomerAnalyticsTab, { customerId: id }) }), _jsx(TabsContent, { value: "assortment", children: data.assortment.length === 0 ? (_jsx(EmptyState, { message: "Geen geselecteerd assortiment voor deze klant." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikelnummer" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Groep" }), _jsx(TableHead, { children: "Prijs" })] }) }), _jsx(TableBody, { children: data.assortment.map((a) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: a.article_number || "—" }), _jsx(TableCell, { children: a.product_name }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: a.product_group || "—" }), _jsx(TableCell, { children: a.price != null ? `€ ${Number(a.price).toFixed(2)}${a.unit ? ` / ${a.unit}` : ""}` : "—" })] }, a.id))) })] }) }) })) }), c.floriday_organization_id && (_jsx(TabsContent, { value: "floriday-assortment", children: _jsx(FloridayAssortmentTab, { organizationId: c.floriday_organization_id }) })), _jsx(TabsContent, { value: "orders", children: _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Datum" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Totaal" })] }) }), _jsx(TableBody, { children: data.orders.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center py-6 text-muted-foreground", children: "Geen bestellingen" }) })) : data.orders.map((o) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: o.order_number }), _jsx(TableCell, { children: o.order_date }), _jsx(TableCell, { children: _jsx(Badge, { children: o.status }) }), _jsx(TableCell, { children: o.total })] }, o.id))) })] }) }) }) }), _jsx(TabsContent, { value: "activity", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: data.audit.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen activiteit voor deze klant." })) : (data.audit.map((a) => _jsx(ActivityItem, { entry: a }, a.id))) }) }) }), _jsx(TabsContent, { value: "discounts", children: _jsx(LocalDiscountsTab, { customerId: id }) })] })] })] })] }));
56
79
  }
@@ -23,12 +23,14 @@ export type CustomerProfile = {
23
23
  phone_from_contact: boolean;
24
24
  has_contacts: boolean;
25
25
  };
26
- export declare function formatCountry(value: string | null | undefined): string | null;
27
26
  export declare function adaptLocalToProfile(c: any, contacts?: any[]): CustomerProfile;
28
27
  export declare function adaptGroupedToProfile(g: GroupedCustomer): CustomerProfile;
29
28
  export declare function adaptExternalToProfile(c: ExternalCustomer): CustomerProfile;
30
- export declare function ProfileCard({ profile: p }: {
29
+ export declare function ProfileCard({ profile: p, notes, onSaveNotes, isSaving, }: {
31
30
  profile: CustomerProfile;
31
+ notes?: string | null;
32
+ onSaveNotes?: (notes: string) => void | Promise<void>;
33
+ isSaving?: boolean;
32
34
  }): import("react").JSX.Element;
33
35
  export declare function SectionLabel({ children }: {
34
36
  children: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { Card, CardContent, Badge, Separator, } from "@flowselections/core";
4
- import { Mail, Phone, Globe, MapPin, Hash, Building2, ChevronDown, ChevronRight, Users, Truck, Package, ShoppingCart, } from "lucide-react";
2
+ import { useState, useEffect } from "react";
3
+ import { Card, CardContent, Badge, Separator, Textarea, Button, } from "@flowselections/core";
4
+ import { Mail, Phone, Globe, MapPin, Hash, Building2, ChevronDown, ChevronRight, Users, Truck, Package, ShoppingCart, StickyNote, Pencil, Check, X, } from "lucide-react";
5
5
  import { CustomerAvatar } from "../CustomerAvatar";
6
+ import { formatCountry, countryNameToFlag } from "../../lib/countries";
6
7
  function firstNonEmpty(values) {
7
8
  for (const v of values) {
8
9
  if (v != null && (typeof v !== "string" || v.trim() !== ""))
@@ -10,21 +11,6 @@ function firstNonEmpty(values) {
10
11
  }
11
12
  return null;
12
13
  }
13
- const COUNTRY_NAMES = {
14
- NL: "Nederland", BE: "België", DE: "Duitsland", FR: "Frankrijk",
15
- ES: "Spanje", IT: "Italië", GB: "Verenigd Koninkrijk", UK: "Verenigd Koninkrijk",
16
- PL: "Polen", DK: "Denemarken", SE: "Zweden", NO: "Noorwegen",
17
- AT: "Oostenrijk", CH: "Zwitserland", PT: "Portugal", IE: "Ierland",
18
- LU: "Luxemburg", CZ: "Tsjechië", FI: "Finland", US: "Verenigde Staten",
19
- };
20
- export function formatCountry(value) {
21
- if (!value)
22
- return null;
23
- const trimmed = value.trim();
24
- if (trimmed.length === 2)
25
- return COUNTRY_NAMES[trimmed.toUpperCase()] ?? trimmed;
26
- return trimmed;
27
- }
28
14
  export function adaptLocalToProfile(c, contacts = []) {
29
15
  const primary = contacts.find((x) => x.is_active !== false) ?? contacts[0] ?? null;
30
16
  const email = c.email ?? primary?.email ?? null;
@@ -120,13 +106,29 @@ export function adaptExternalToProfile(c) {
120
106
  has_contacts: !!c.contact_person,
121
107
  };
122
108
  }
123
- export function ProfileCard({ profile: p }) {
109
+ export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false, }) {
110
+ const [isEditing, setIsEditing] = useState(false);
111
+ const [draft, setDraft] = useState(notes ?? "");
124
112
  const address = [p.street, p.house_number].filter(Boolean).join(" ");
125
113
  const cityLine = [p.postal_code, p.city].filter(Boolean).join(" ");
126
- const fullAddress = [address, cityLine, p.country].filter(Boolean).join(", ");
114
+ const countryFlag = countryNameToFlag(p.country);
115
+ const countryDisplay = countryFlag ? `${countryFlag} ${p.country}` : p.country;
116
+ const fullAddress = [address, cityLine, countryDisplay].filter(Boolean).join(", ");
127
117
  const [showTech, setShowTech] = useState(false);
128
118
  const hasTech = !!(p.gln || p.rfh_id || p.organization_type);
129
- return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "Technische gegevens"] }), showTech && (_jsxs("div", { className: "space-y-3", children: [p.rfh_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-relatienr.", value: p.rfh_id, mono: true }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "GLN", value: p.gln, mono: true }), _jsx(InfoRow, { icon: _jsx(Building2, { className: "h-4 w-4" }), label: "Type", value: p.organization_type })] }))] }))] }) }));
119
+ const editable = !!onSaveNotes;
120
+ useEffect(() => {
121
+ if (!isEditing)
122
+ setDraft(notes ?? "");
123
+ }, [notes, isEditing]);
124
+ const handleSave = async () => {
125
+ await onSaveNotes?.(draft);
126
+ setIsEditing(false);
127
+ };
128
+ return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "Technische gegevens"] }), showTech && (_jsxs("div", { className: "space-y-3", children: [p.rfh_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-relatienr.", value: p.rfh_id, mono: true }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "GLN", value: p.gln, mono: true }), _jsx(InfoRow, { icon: _jsx(Building2, { className: "h-4 w-4" }), label: "Type", value: p.organization_type })] }))] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(SectionLabel, { children: "Notitie" }), editable && !isEditing && (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", "aria-label": "Notitie bewerken", children: [_jsx(Pencil, { className: "h-3 w-3" }), "Bewerken"] }))] }), isEditing ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Textarea, { value: draft, onChange: (e) => setDraft(e.target.value), rows: 4, placeholder: "Voeg een korte notitie over deze klant toe...", disabled: isSaving, className: "text-sm resize-none" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
129
+ setDraft(notes ?? "");
130
+ setIsEditing(false);
131
+ }, disabled: isSaving, children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Annuleren"] }), _jsx(Button, { type: "button", size: "sm", onClick: handleSave, disabled: isSaving || draft === (notes ?? ""), children: isSaving ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" }), "Opslaan..."] })) : (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Opslaan"] })) })] })] })) : notes ? (_jsx("p", { className: "text-sm whitespace-pre-wrap text-foreground", children: notes })) : editable ? (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex w-full items-center gap-2 rounded-md border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted", children: [_jsx(StickyNote, { className: "h-4 w-4" }), "Notitie toevoegen"] })) : (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(StickyNote, { className: "h-4 w-4" }), "Geen notitie"] }))] })] }) }));
130
132
  }
131
133
  export function SectionLabel({ children }) {
132
134
  return _jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground font-medium mb-2", children: children });
@@ -1,2 +1,5 @@
1
- export declare function KlantDetailRoute(): import("react").JSX.Element;
1
+ /** Route-onafhankelijk: leest `$id` uit welke route de shell ook gebruikt. */
2
+ export declare function KlantDetailRoute({ id: propId }?: {
3
+ id?: string;
4
+ }): import("react").JSX.Element;
2
5
  //# sourceMappingURL=KlantDetailRoute.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"KlantDetailRoute.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailRoute.tsx"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,gCAG/B"}
1
+ {"version":3,"file":"KlantDetailRoute.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailRoute.tsx"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,+BAKpE"}
@@ -1,8 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { getRouteApi } from "@tanstack/react-router";
2
+ import { useParams } from "@tanstack/react-router";
3
3
  import { KlantDetail } from "./KlantDetail";
4
- const route = getRouteApi("/_authenticated/klanten/$id");
5
- export function KlantDetailRoute() {
6
- const { id } = route.useParams();
4
+ /** Route-onafhankelijk: leest `$id` uit welke route de shell ook gebruikt. */
5
+ export function KlantDetailRoute({ id: propId } = {}) {
6
+ const params = useParams({ strict: false });
7
+ const id = propId ?? params.id;
8
+ if (!id)
9
+ return _jsx("main", { className: "p-6 text-sm text-muted-foreground", children: "Geen klant geselecteerd." });
7
10
  return _jsx(KlantDetail, { id: id });
8
11
  }
@@ -12,7 +12,7 @@ export declare const emptyKlantFormValues: KlantFormValues;
12
12
  */
13
13
  export declare function KlantFormulier({ title, description, submitLabel, submitting, defaultValues, onSubmit, onCancel, extra, numberReadOnly, numberHint, }: {
14
14
  title: string;
15
- description: string;
15
+ description?: string;
16
16
  submitLabel: string;
17
17
  submitting: boolean;
18
18
  defaultValues: KlantFormValues;
@@ -1 +1 @@
1
- {"version":3,"file":"KlantFormulier.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantFormulier.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAwB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAaxF,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,eAuBf,CAAC;AAErB;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,cAAc,EACd,UAAU,GACX,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,eAAe,CAAC;IAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,+BA0jBA"}
1
+ {"version":3,"file":"KlantFormulier.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantFormulier.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAwB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAcxF,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,eAuBf,CAAC;AAErB;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,cAAc,EACd,UAAU,GACX,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,eAAe,CAAC;IAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,+BAulBA"}