@flowselections/floriday-klanten-module 1.0.24 → 1.0.26

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 +24 -22
  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 +56 -9
  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,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
2
+ import { useEffect, useRef, useState } from "react";
3
3
  import { useForm } from "react-hook-form";
4
4
  import { zodResolver } from "@hookform/resolvers/zod";
5
5
  import { useQuery } from "@tanstack/react-query";
@@ -10,7 +10,8 @@ import { BackLink } from "../../components/klanten/BackLink";
10
10
  import { listCustomerClasses } from "../../lib/customers.functions";
11
11
  import { listTwinfieldLedgerAccounts } from "../../lib/twinfield-ledger.functions";
12
12
  import { customerCreateSchema } from "../../lib/customer-schemas";
13
- import { TWINFIELD_REQUIRED_FIELDS, requiresVatNumber } from "../../lib/twinfield-required-fields";
13
+ import { requiresVatNumber } from "../../lib/twinfield-required-fields";
14
+ import { countryNameToFlag, COMMON_COUNTRY_NAMES } from "../../lib/countries";
14
15
  import { TWINFIELD_DEFAULTS, VAT_DISPLAY_OPTIONS, PAYMENT_SPEC_METHOD_OPTIONS, INVOICE_SEND_AS_OPTIONS, SEND_REMINDER_OPTIONS, DISCOUNT_SURCHARGE_OPTIONS, } from "../../lib/twinfield-defaults";
15
16
  /**
16
17
  * Standaardwaarden voor een nieuw formulier. Deze zijn gelijk aan wat Twinfield
@@ -49,7 +50,7 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
49
50
  const classesFn = useServerFn(listCustomerClasses);
50
51
  const ledgerFn = useServerFn(listTwinfieldLedgerAccounts);
51
52
  const [open, setOpen] = useState({
52
- algemeen: true, adres: true, bank: true, facturering: true, boekhouding: true,
53
+ algemeen: true, adres: true, bank: false, facturering: false, boekhouding: false,
53
54
  });
54
55
  const toggle = (k) => setOpen((s) => ({ ...s, [k]: !s[k] }));
55
56
  const { data: classData } = useQuery({
@@ -62,6 +63,7 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
62
63
  queryFn: () => ledgerFn({ data: { force: false } }),
63
64
  staleTime: 5 * 60 * 1000,
64
65
  });
66
+ const ledgerLoading = ledger === undefined;
65
67
  const form = useForm({
66
68
  resolver: zodResolver(customerCreateSchema),
67
69
  mode: "onBlur",
@@ -71,6 +73,7 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
71
73
  const errors = form.formState.errors;
72
74
  const errorCount = Object.keys(errors).length;
73
75
  const touched = form.formState.touchedFields;
76
+ const isDirty = form.formState.isDirty;
74
77
  const values = form.watch();
75
78
  // Standaard-grootboekrekening invullen zodra Twinfield die kent.
76
79
  useEffect(() => {
@@ -78,11 +81,32 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
78
81
  return;
79
82
  if (String(form.getValues("ledger_account") ?? "").trim())
80
83
  return;
81
- form.setValue("ledger_account", ledger.defaultCode, { shouldValidate: true });
84
+ form.setValue("ledger_account", ledger.defaultCode, { shouldValidate: true, shouldDirty: false });
82
85
  }, [ledger?.defaultCode, form]);
86
+ /**
87
+
88
+ * Adresregel 1 volgt straat + huisnummer zolang de gebruiker die regel niet
89
+ * zelf heeft aangepast. Zo staat er niet drie keer hetzelfde adres.
90
+ */
91
+ const autoLine1 = useRef(null);
92
+ const street = form.watch("street");
93
+ const houseNumber = form.watch("house_number");
94
+ useEffect(() => {
95
+ const next = [street, houseNumber].filter(Boolean).join(" ").trim();
96
+ const current = String(form.getValues("address_line1") ?? "").trim();
97
+ if (current && current !== autoLine1.current)
98
+ return; // handmatig ingevuld
99
+ if (current === next)
100
+ return;
101
+ autoLine1.current = next;
102
+ form.setValue("address_line1", next, { shouldDirty: true });
103
+ }, [street, houseNumber, form]);
83
104
  const fieldErr = (k) => errors[k]?.message;
84
105
  const isValid = (k) => !errors[k] && !!touched[k] && !!values[k];
85
106
  const vatRequired = requiresVatNumber(values.country);
107
+ /** Bevat een sectie een validatiefout? */
108
+ const sectionHasError = (keys) => keys.some((k) => !!errors[k]);
109
+ const filled = (v) => String(v ?? "").trim() !== "";
86
110
  /** Waarschuwingen die opslaan niet blokkeren, maar wel Twinfield raken. */
87
111
  const lengthWarning = (v, max, what) => String(v ?? "").trim().length > max
88
112
  ? `Twinfield kort ${what} af tot ${max} tekens.`
@@ -126,12 +150,35 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
126
150
  const t = v.trim();
127
151
  return t && !/^https?:\/\//i.test(t) ? `https://${t}` : t;
128
152
  };
129
- return (_jsxs("main", { className: "min-h-screen bg-muted/30 pb-28", children: [_jsxs("div", { className: "max-w-6xl mx-auto px-6 pt-6", children: [_jsx(BackLink, {}), _jsxs("header", { className: "flex items-start gap-4 mb-6", children: [_jsx("div", { className: "h-12 w-12 shrink-0 rounded-full bg-primary/10 text-primary grid place-items-center", children: _jsx(UserRound, { className: "h-6 w-6" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: title }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: description })] })] }), _jsxs("div", { className: "mb-4 rounded-xl border bg-card px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Vereist voor Twinfield" }), _jsx("p", { className: "text-muted-foreground mt-0.5", children: "Elke klant wordt automatisch naar Twinfield gestuurd. Deze gegevens zijn daarvoor verplicht:" }), _jsxs("ul", { className: "mt-2 flex flex-wrap gap-x-2 gap-y-1 text-muted-foreground", children: [TWINFIELD_REQUIRED_FIELDS.map((f) => (_jsx("li", { className: "rounded-md bg-muted px-2 py-0.5 text-xs", title: f.reason, children: f.label }, f.key))), vatRequired && (_jsx("li", { className: "rounded-md bg-muted px-2 py-0.5 text-xs", title: "Verplicht binnen de EU buiten Nederland", children: "Btw-nummer" }))] })] }), errorCount > 0 && (_jsxs("button", { type: "button", onClick: focusFirstError, className: "mb-4 w-full text-left flex items-start gap-3 rounded-xl border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm hover:bg-destructive/10 transition-colors", children: [_jsx(AlertCircle, { className: "h-5 w-5 shrink-0 text-destructive mt-0.5" }), _jsxs("div", { children: [_jsxs("p", { className: "font-medium text-destructive", children: ["Controleer ", errorCount, " ", errorCount === 1 ? "veld" : "velden"] }), _jsx("p", { className: "text-muted-foreground mt-0.5", children: "Klik hier om naar het eerste veld te gaan dat nog niet correct is ingevuld." })] })] })), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-4", children: [_jsx(Section, { k: "algemeen", title: "Algemeen", icon: _jsx(Users, { className: "h-5 w-5" }), open: open.algemeen, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Field, { label: "Nummer", error: fieldErr("customer_number"), success: !numberReadOnly && isValid("customer_number"), hint: numberHint, children: _jsx(Input, { readOnly: numberReadOnly, ...form.register("customer_number") }) }), _jsx(Field, { label: "Bedrijfsnaam", required: true, error: fieldErr("company_name"), success: isValid("company_name"), hint: lengthWarning(values.company_name, 40, "de bedrijfsnaam"), children: _jsx(Input, { "aria-invalid": !!errors.company_name, ...form.register("company_name") }) }), _jsx(Field, { label: "Taal", error: fieldErr("language"), children: _jsxs(Select, { value: form.watch("language") || "nl", onValueChange: (v) => form.setValue("language", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nl", children: "nl" }), _jsx(SelectItem, { value: "en", children: "en" }), _jsx(SelectItem, { value: "de", children: "de" }), _jsx(SelectItem, { value: "fr", children: "fr" })] })] }) }), _jsx(Field, { label: "Website", error: fieldErr("website"), success: isValid("website"), children: _jsx(Input, { placeholder: "https://", ...normalizeOnBlur("website", normUrl) }) }), _jsx(Field, { label: "BTW-nummer", required: vatRequired, error: fieldErr("vat_number"), success: isValid("vat_number"), children: _jsx(Input, { "aria-invalid": !!errors.vat_number, ...normalizeOnBlur("vat_number", normVat) }) }), _jsx(Field, { label: "KvK-nummer", error: fieldErr("coc_number"), success: isValid("coc_number"), children: _jsx(Input, { ...form.register("coc_number") }) }), _jsx(Field, { label: "Contactpersoon", error: fieldErr("contact_person"), success: isValid("contact_person"), children: _jsx(Input, { ...form.register("contact_person") }) }), _jsx(Field, { label: "T.a.v.", error: fieldErr("attention_to"), children: _jsx(Input, { ...form.register("attention_to") }) }), _jsx(Field, { label: "E-mail", required: true, error: fieldErr("email"), success: isValid("email"), children: _jsx(Input, { type: "email", "aria-invalid": !!errors.email, placeholder: "naam@bedrijf.nl", ...form.register("email") }) }), _jsx(Field, { label: "E-mail voor facturering", error: fieldErr("billing_email"), success: isValid("billing_email"), children: _jsx(Input, { type: "email", placeholder: "facturen@bedrijf.nl", ...form.register("billing_email") }) }), _jsx(Field, { label: "Telefoon", error: fieldErr("phone"), success: isValid("phone"), children: _jsx(Input, { ...form.register("phone") }) }), _jsx(Field, { label: "Faxnummer", error: fieldErr("fax"), children: _jsx(Input, { ...form.register("fax") }) }), _jsx(Field, { label: "Klantklasse", children: _jsxs(Select, { onValueChange: (v) => form.setValue("customer_class", v), defaultValue: "", children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies klasse..." }) }), _jsx(SelectContent, { children: (classData?.classes ?? []).map((c) => _jsx(SelectItem, { value: c, children: c }, c)) })] }) })] }) }), _jsx(Section, { k: "adres", title: "Adres", icon: _jsx(MapPin, { className: "h-5 w-5" }), open: open.adres, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Adres type", error: fieldErr("address_type"), children: _jsxs(Select, { value: form.watch("address_type") || TWINFIELD_DEFAULTS.address_type, onValueChange: (v) => form.setValue("address_type", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "invoice", children: "Factuur" }), _jsx(SelectItem, { value: "postal", children: "Post" })] })] }) }), _jsx(Field, { label: "ICP landcode", error: fieldErr("icp_country_code"), children: _jsx(Input, { placeholder: "bv. NL", ...form.register("icp_country_code") }) }), _jsx(Field, { label: "Adresregel 1", error: fieldErr("address_line1"), hint: lengthWarning(values.address_line1, 60, "adresregel 1"), children: _jsx(Input, { ...form.register("address_line1") }) }), _jsx(Field, { label: "Adresregel 2", error: fieldErr("address_line2"), hint: lengthWarning(values.address_line2, 60, "adresregel 2"), children: _jsx(Input, { ...form.register("address_line2") }) }), _jsx(Field, { label: "Straat", required: true, error: fieldErr("street"), success: isValid("street"), children: _jsx(Input, { "aria-invalid": !!errors.street, ...form.register("street") }) }), _jsx(Field, { label: "Huisnummer", required: true, error: fieldErr("house_number"), success: isValid("house_number"), children: _jsx(Input, { "aria-invalid": !!errors.house_number, ...form.register("house_number") }) }), _jsx(Field, { label: "Postcode", required: true, error: fieldErr("postal_code"), success: isValid("postal_code"), children: _jsx(Input, { "aria-invalid": !!errors.postal_code, ...normalizeOnBlur("postal_code", normPostal) }) }), _jsx(Field, { label: "Plaats", required: true, error: fieldErr("city"), success: isValid("city"), children: _jsx(Input, { "aria-invalid": !!errors.city, ...form.register("city") }) }), _jsx(Field, { label: "Land", required: true, error: fieldErr("country"), success: isValid("country"), children: _jsxs(Select, { value: form.watch("country") || "Nederland", onValueChange: (v) => form.setValue("country", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "Nederland", children: "Nederland" }), _jsx(SelectItem, { value: "Belgi\u00EB", children: "Belgi\u00EB" }), _jsx(SelectItem, { value: "Duitsland", children: "Duitsland" }), _jsx(SelectItem, { value: "Frankrijk", children: "Frankrijk" }), _jsx(SelectItem, { value: "Verenigd Koninkrijk", children: "Verenigd Koninkrijk" })] })] }) })] }) }), _jsx(Section, { k: "bank", title: "Bankgegevens", icon: _jsx(Landmark, { className: "h-5 w-5" }), open: open.bank, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Bank - Naam", children: _jsx(Input, { ...form.register("bank_name") }) }), _jsx(Field, { label: "Bank - Rekeninghouder", children: _jsx(Input, { ...form.register("bank_account_holder") }) }), _jsx(Field, { label: "Bank - Rekeningnummer", children: _jsx(Input, { ...form.register("bank_account_number") }) }), _jsx(Field, { label: "Bank - IBAN", error: fieldErr("bank_iban"), success: isValid("bank_iban"), children: _jsx(Input, { placeholder: "NL00 BANK 0000 0000 00", ...normalizeOnBlur("bank_iban", normIban) }) }), _jsx(Field, { label: "Bank - BIC", error: fieldErr("bank_bic"), success: isValid("bank_bic"), children: _jsx(Input, { ...form.register("bank_bic") }) }), _jsx(Field, { label: "Bank - Nationale bankcode", children: _jsx(Input, { ...form.register("bank_national_code") }) }), _jsx(Field, { label: "Bank - Adresregel 1", children: _jsx(Input, { ...form.register("bank_address_line1") }) }), _jsx(Field, { label: "Bank - Adresregel 2", children: _jsx(Input, { ...form.register("bank_address_line2") }) }), _jsx(Field, { label: "Bank - Huisnummer", children: _jsx(Input, { ...form.register("bank_house_number") }) }), _jsx(Field, { label: "Bank - Postcode", error: fieldErr("bank_postal_code"), children: _jsx(Input, { ...normalizeOnBlur("bank_postal_code", normPostal) }) }), _jsx(Field, { label: "Bank - Plaats", children: _jsx(Input, { ...form.register("bank_city") }) }), _jsx(Field, { label: "Bank - Staat", children: _jsx(Input, { ...form.register("bank_state") }) }), _jsx("div", { className: "md:col-span-2", children: _jsx(Field, { label: "Bank - Land", children: _jsx(Input, { ...form.register("bank_country") }) }) })] }) }), _jsx(Section, { k: "facturering", title: "Facturering & betaling", icon: _jsx(ReceiptText, { className: "h-5 w-5" }), open: open.facturering, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kortingspercentage (%)", error: fieldErr("discount_percentage"), children: _jsx(Input, { type: "number", step: "0.01", min: 0, max: 100, ...numberField("discount_percentage") }) }), _jsx(Field, { label: "Aantal kortingsdagen", error: fieldErr("discount_days"), children: _jsx(Input, { type: "number", min: 0, max: 365, ...numberField("discount_days") }) }), _jsx(Field, { label: "Aantal vervaldagen", error: fieldErr("payment_term_days"), children: _jsx(Input, { type: "number", min: 0, max: 365, ...numberField("payment_term_days") }) }), _jsx(Field, { label: "Btw-weergave op factuur", error: fieldErr("vat_display"), children: _jsxs(Select, { value: form.watch("vat_display") || TWINFIELD_DEFAULTS.vat_display, onValueChange: (v) => form.setValue("vat_display", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: VAT_DISPLAY_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Korting / opslag", error: fieldErr("discount_surcharge"), children: _jsxs(Select, { value: form.watch("discount_surcharge") || TWINFIELD_DEFAULTS.discount_surcharge, onValueChange: (v) => form.setValue("discount_surcharge", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DISCOUNT_SURCHARGE_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Verstuurmethode betaalspecificatie", error: fieldErr("payment_spec_method"), children: _jsxs(Select, { value: form.watch("payment_spec_method") || TWINFIELD_DEFAULTS.payment_spec_method, onValueChange: (v) => form.setValue("payment_spec_method", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: PAYMENT_SPEC_METHOD_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Elektronisch factureren", error: fieldErr("e_invoicing"), children: _jsxs(Select, { value: form.watch("e_invoicing") ? "ja" : "nee", onValueChange: (v) => form.setValue("e_invoicing", v === "ja"), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nee", children: "Nee" }), _jsx(SelectItem, { value: "ja", children: "Ja" })] })] }) }), _jsx(Field, { label: "Factuur versturen als", error: fieldErr("invoice_send_as"), children: _jsxs(Select, { value: form.watch("invoice_send_as") || TWINFIELD_DEFAULTS.invoice_send_as, onValueChange: (v) => form.setValue("invoice_send_as", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: INVOICE_SEND_AS_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsxs("div", { className: "md:col-span-2 space-y-1.5", children: [_jsx(Label, { children: "Notities" }), _jsx(Textarea, { rows: 4, ...form.register("notes") })] })] }) }), _jsx(Section, { k: "boekhouding", title: "Kredietbeheer & rekeningschema", icon: _jsx(BookOpen, { className: "h-5 w-5" }), open: open.boekhouding, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kredietlimiet (\u20AC)", error: fieldErr("credit_limit"), children: _jsx(Input, { type: "number", step: "0.01", min: 0, ...numberField("credit_limit") }) }), _jsx(Field, { label: "Aanmanen", error: fieldErr("send_reminder"), children: _jsxs(Select, { value: form.watch("send_reminder") || TWINFIELD_DEFAULTS.send_reminder, onValueChange: (v) => form.setValue("send_reminder", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SEND_REMINDER_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Geblokkeerd", error: fieldErr("blocked"), children: _jsxs(Select, { value: form.watch("blocked") ? "ja" : "nee", onValueChange: (v) => form.setValue("blocked", v === "ja"), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nee", children: "Nee" }), _jsx(SelectItem, { value: "ja", children: "Ja" })] })] }) }), _jsx(Field, { label: "Grootboekrekening", required: true, error: fieldErr("ledger_account"), success: isValid("ledger_account"), hint: ledger?.error
130
- ? `${ledger.error} Vul de rekening handmatig in.`
131
- : "Verzamelrekening debiteuren; kan per administratie verschillen.", children: (ledger?.accounts?.length ?? 0) > 0 ? (_jsxs(Select, { value: form.watch("ledger_account") || "", onValueChange: (v) => form.setValue("ledger_account", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies grootboekrekening..." }) }), _jsx(SelectContent, { children: (ledger?.accounts ?? []).map((a) => (_jsxs(SelectItem, { value: a.code, children: [a.code, a.name ? ` - ${a.name}` : ""] }, a.code))) })] })) : (_jsx(Input, { placeholder: "bv. 1300", "aria-invalid": !!errors.ledger_account, ...form.register("ledger_account") })) })] }) })] })] }), _jsx("div", { className: "fixed bottom-0 left-0 right-0 border-t bg-background/90 backdrop-blur z-20", children: _jsxs("div", { className: "max-w-6xl mx-auto px-6 py-3 flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, children: "Annuleren" }), _jsxs(Button, { type: "button", onClick: form.handleSubmit(onSubmit), disabled: submitting, children: [_jsx(Check, { className: "h-4 w-4 mr-2" }), submitting ? "Opslaan..." : submitLabel] })] }) }), extra] }));
153
+ return (_jsxs("main", { className: "min-h-screen bg-muted/30 pb-28", children: [_jsxs("div", { className: "max-w-6xl mx-auto px-6 pt-6", children: [_jsx(BackLink, {}), _jsxs("header", { className: "flex items-start gap-4 mb-6", children: [_jsx("div", { className: "h-12 w-12 shrink-0 rounded-full bg-primary/10 text-primary grid place-items-center", children: _jsx(UserRound, { className: "h-6 w-6" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: title }), description && (_jsx("p", { className: "text-sm text-muted-foreground mt-1", children: description }))] })] }), errorCount > 0 && (_jsxs("button", { type: "button", onClick: focusFirstError, className: "mb-4 w-full text-left flex items-start gap-3 rounded-xl border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm hover:bg-destructive/10 transition-colors", children: [_jsx(AlertCircle, { className: "h-5 w-5 shrink-0 text-destructive mt-0.5" }), _jsxs("div", { children: [_jsxs("p", { className: "font-medium text-destructive", children: ["Controleer ", errorCount, " ", errorCount === 1 ? "veld" : "velden"] }), _jsx("p", { className: "text-muted-foreground mt-0.5", children: "Klik hier om naar het eerste veld te gaan dat nog niet correct is ingevuld." })] })] })), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-4", children: [_jsx(Section, { k: "algemeen", title: "Algemeen", icon: _jsx(Users, { className: "h-5 w-5" }), open: open.algemeen, onToggle: toggle, hasError: sectionHasError([
154
+ "customer_number", "company_name", "language", "website", "vat_number",
155
+ "coc_number", "contact_person", "attention_to", "email", "billing_email",
156
+ "phone", "fax",
157
+ ]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Field, { label: "Nummer", error: fieldErr("customer_number"), success: !numberReadOnly && isValid("customer_number"), hint: numberHint, children: _jsx(Input, { readOnly: numberReadOnly, ...form.register("customer_number") }) }), _jsx(Field, { label: "Bedrijfsnaam", required: true, error: fieldErr("company_name"), success: isValid("company_name"), hint: lengthWarning(values.company_name, 40, "de bedrijfsnaam"), children: _jsx(Input, { "aria-invalid": !!errors.company_name, ...form.register("company_name") }) }), _jsx(Field, { label: "Taal", error: fieldErr("language"), children: _jsxs(Select, { value: form.watch("language") || "nl", onValueChange: (v) => form.setValue("language", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nl", children: "nl" }), _jsx(SelectItem, { value: "en", children: "en" }), _jsx(SelectItem, { value: "de", children: "de" }), _jsx(SelectItem, { value: "fr", children: "fr" })] })] }) }), _jsx(Field, { label: "Website", error: fieldErr("website"), success: isValid("website"), children: _jsx(Input, { placeholder: "https://", ...normalizeOnBlur("website", normUrl) }) }), _jsx(Field, { label: "BTW-nummer", required: vatRequired, error: fieldErr("vat_number"), success: isValid("vat_number"), children: _jsx(Input, { "aria-invalid": !!errors.vat_number, ...normalizeOnBlur("vat_number", normVat) }) }), _jsx(Field, { label: "KvK-nummer", error: fieldErr("coc_number"), success: isValid("coc_number"), children: _jsx(Input, { ...form.register("coc_number") }) }), _jsx(Field, { label: "Contactpersoon", error: fieldErr("contact_person"), success: isValid("contact_person"), children: _jsx(Input, { ...form.register("contact_person") }) }), _jsx(Field, { label: "T.a.v.", error: fieldErr("attention_to"), children: _jsx(Input, { ...form.register("attention_to") }) }), _jsx(Field, { label: "E-mail", required: true, error: fieldErr("email"), success: isValid("email"), children: _jsx(Input, { type: "email", "aria-invalid": !!errors.email, placeholder: "naam@bedrijf.nl", ...form.register("email") }) }), _jsx(Field, { label: "E-mail voor facturering", error: fieldErr("billing_email"), success: isValid("billing_email"), children: _jsx(Input, { type: "email", placeholder: "facturen@bedrijf.nl", ...form.register("billing_email") }) }), _jsx(Field, { label: "Telefoon", error: fieldErr("phone"), success: isValid("phone"), children: _jsx(Input, { ...form.register("phone") }) }), _jsx(Field, { label: "Faxnummer", error: fieldErr("fax"), children: _jsx(Input, { ...form.register("fax") }) }), _jsx(Field, { label: "Klantklasse", children: _jsxs(Select, { value: form.watch("customer_class") || "__none__", onValueChange: (v) => form.setValue("customer_class", v === "__none__" ? "" : v, { shouldDirty: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies klasse..." }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "Geen klasse" }), (classData?.classes ?? []).map((c) => _jsx(SelectItem, { value: c, children: c }, c))] })] }) }), _jsx(Field, { label: "Status", hint: "Een inactieve klant wordt in Twinfield verborgen.", children: _jsxs(Select, { value: form.watch("is_active") === false ? "inactief" : "actief", onValueChange: (v) => form.setValue("is_active", v === "actief", { shouldDirty: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "actief", children: "Actief" }), _jsx(SelectItem, { value: "inactief", children: "Inactief" })] })] }) })] }) }), _jsx(Section, { k: "adres", title: "Adres", icon: _jsx(MapPin, { className: "h-5 w-5" }), open: open.adres, onToggle: toggle, hasError: sectionHasError([
158
+ "address_type", "icp_country_code", "address_line1", "address_line2",
159
+ "street", "house_number", "postal_code", "city", "country",
160
+ ]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Straat", required: true, error: fieldErr("street"), success: isValid("street"), children: _jsx(Input, { "aria-invalid": !!errors.street, ...form.register("street") }) }), _jsx(Field, { label: "Huisnummer", required: true, error: fieldErr("house_number"), success: isValid("house_number"), children: _jsx(Input, { "aria-invalid": !!errors.house_number, ...form.register("house_number") }) }), _jsx(Field, { label: "Postcode", required: true, error: fieldErr("postal_code"), success: isValid("postal_code"), children: _jsx(Input, { "aria-invalid": !!errors.postal_code, ...normalizeOnBlur("postal_code", normPostal) }) }), _jsx(Field, { label: "Plaats", required: true, error: fieldErr("city"), success: isValid("city"), children: _jsx(Input, { "aria-invalid": !!errors.city, ...form.register("city") }) }), _jsx(Field, { label: "Land", required: true, error: fieldErr("country"), success: isValid("country"), children: _jsxs(Select, { value: form.watch("country") || "Nederland", onValueChange: (v) => form.setValue("country", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { children: (() => {
161
+ const selectedCountry = form.watch("country") || "Nederland";
162
+ const flag = countryNameToFlag(selectedCountry);
163
+ return (_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: selectedCountry })] }));
164
+ })() }) }), _jsx(SelectContent, { children: COMMON_COUNTRY_NAMES.map((name) => {
165
+ const flag = countryNameToFlag(name);
166
+ return (_jsx(SelectItem, { value: 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: name })] }) }, name));
167
+ }) })] }) }), _jsx(Field, { label: "Adres type", error: fieldErr("address_type"), children: _jsxs(Select, { value: form.watch("address_type") || TWINFIELD_DEFAULTS.address_type, onValueChange: (v) => form.setValue("address_type", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "invoice", children: "Factuur" }), _jsx(SelectItem, { value: "postal", children: "Post" })] })] }) }), _jsx(Field, { label: "ICP landcode", error: fieldErr("icp_country_code"), children: _jsx(Input, { placeholder: "bv. NL", ...form.register("icp_country_code") }) }), _jsx(Field, { label: "Adresregel 1", error: fieldErr("address_line1"), hint: lengthWarning(values.address_line1, 60, "adresregel 1") ??
168
+ "Wordt automatisch gevuld uit straat en huisnummer; alleen aanpassen bij een afwijkend adres.", children: _jsx(Input, { ...form.register("address_line1") }) }), _jsx(Field, { label: "Adresregel 2", error: fieldErr("address_line2"), hint: lengthWarning(values.address_line2, 60, "adresregel 2") ??
169
+ "Optioneel, bijvoorbeeld een postbus of gebouwnaam.", children: _jsx(Input, { ...form.register("address_line2") }) })] }) }), _jsx(Section, { k: "bank", title: "Bankgegevens", icon: _jsx(Landmark, { className: "h-5 w-5" }), open: open.bank, onToggle: toggle, summary: filled(values.bank_iban) ? `IBAN ${values.bank_iban}` : "Nog leeg", hasError: sectionHasError(["bank_iban", "bank_bic", "bank_postal_code"]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Bank - Naam", children: _jsx(Input, { ...form.register("bank_name") }) }), _jsx(Field, { label: "Bank - Rekeninghouder", children: _jsx(Input, { ...form.register("bank_account_holder") }) }), _jsx(Field, { label: "Bank - Rekeningnummer", children: _jsx(Input, { ...form.register("bank_account_number") }) }), _jsx(Field, { label: "Bank - IBAN", error: fieldErr("bank_iban"), success: isValid("bank_iban"), children: _jsx(Input, { placeholder: "NL00 BANK 0000 0000 00", ...normalizeOnBlur("bank_iban", normIban) }) }), _jsx(Field, { label: "Bank - BIC", error: fieldErr("bank_bic"), success: isValid("bank_bic"), children: _jsx(Input, { ...form.register("bank_bic") }) }), _jsx(Field, { label: "Bank - Nationale bankcode", children: _jsx(Input, { ...form.register("bank_national_code") }) }), _jsx(Field, { label: "Bank - Adresregel 1", children: _jsx(Input, { ...form.register("bank_address_line1") }) }), _jsx(Field, { label: "Bank - Adresregel 2", children: _jsx(Input, { ...form.register("bank_address_line2") }) }), _jsx(Field, { label: "Bank - Huisnummer", children: _jsx(Input, { ...form.register("bank_house_number") }) }), _jsx(Field, { label: "Bank - Postcode", error: fieldErr("bank_postal_code"), children: _jsx(Input, { ...normalizeOnBlur("bank_postal_code", normPostal) }) }), _jsx(Field, { label: "Bank - Plaats", children: _jsx(Input, { ...form.register("bank_city") }) }), _jsx(Field, { label: "Bank - Staat", children: _jsx(Input, { ...form.register("bank_state") }) }), _jsx("div", { className: "md:col-span-2", children: _jsx(Field, { label: "Bank - Land", children: _jsx(Input, { ...form.register("bank_country") }) }) })] }) }), _jsx(Section, { k: "facturering", title: "Facturering & betaling", icon: _jsx(ReceiptText, { className: "h-5 w-5" }), open: open.facturering, onToggle: toggle, summary: `${values.payment_term_days ?? TWINFIELD_DEFAULTS.payment_term_days} vervaldagen · ${values.vat_display || TWINFIELD_DEFAULTS.vat_display}`, hasError: sectionHasError([
170
+ "discount_percentage", "discount_days", "payment_term_days", "vat_display",
171
+ "discount_surcharge", "payment_spec_method", "invoice_send_as", "notes",
172
+ ]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kortingspercentage (%)", error: fieldErr("discount_percentage"), hint: "Alleen voor dit systeem; Twinfield accepteert dit niet op een debiteur.", children: _jsx(Input, { type: "number", step: "0.01", min: 0, max: 100, ...numberField("discount_percentage") }) }), _jsx(Field, { label: "Aantal kortingsdagen", error: fieldErr("discount_days"), hint: "Alleen voor dit systeem; gaat niet mee naar Twinfield.", children: _jsx(Input, { type: "number", min: 0, max: 365, ...numberField("discount_days") }) }), _jsx(Field, { label: "Aantal vervaldagen", error: fieldErr("payment_term_days"), children: _jsx(Input, { type: "number", min: 0, max: 365, ...numberField("payment_term_days") }) }), _jsx(Field, { label: "Btw-weergave op factuur", error: fieldErr("vat_display"), children: _jsxs(Select, { value: form.watch("vat_display") || TWINFIELD_DEFAULTS.vat_display, onValueChange: (v) => form.setValue("vat_display", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: VAT_DISPLAY_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Korting / opslag", error: fieldErr("discount_surcharge"), children: _jsxs(Select, { value: form.watch("discount_surcharge") || TWINFIELD_DEFAULTS.discount_surcharge, onValueChange: (v) => form.setValue("discount_surcharge", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DISCOUNT_SURCHARGE_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Verstuurmethode betaalspecificatie", error: fieldErr("payment_spec_method"), children: _jsxs(Select, { value: form.watch("payment_spec_method") || TWINFIELD_DEFAULTS.payment_spec_method, onValueChange: (v) => form.setValue("payment_spec_method", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: PAYMENT_SPEC_METHOD_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Elektronisch factureren", error: fieldErr("e_invoicing"), children: _jsxs(Select, { value: form.watch("e_invoicing") ? "ja" : "nee", onValueChange: (v) => form.setValue("e_invoicing", v === "ja"), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nee", children: "Nee" }), _jsx(SelectItem, { value: "ja", children: "Ja" })] })] }) }), _jsx(Field, { label: "Factuur versturen als", error: fieldErr("invoice_send_as"), children: _jsxs(Select, { value: form.watch("invoice_send_as") || TWINFIELD_DEFAULTS.invoice_send_as, onValueChange: (v) => form.setValue("invoice_send_as", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: INVOICE_SEND_AS_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsxs("div", { className: "md:col-span-2 space-y-1.5", children: [_jsx(Label, { children: "Notities" }), _jsx(Textarea, { rows: 4, ...form.register("notes") }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Interne notitie; wordt niet naar Twinfield gestuurd." })] })] }) }), _jsx(Section, { k: "boekhouding", title: "Kredietbeheer & rekeningschema", icon: _jsx(BookOpen, { className: "h-5 w-5" }), open: open.boekhouding, onToggle: toggle, summary: filled(values.ledger_account)
173
+ ? `Grootboek ${values.ledger_account}`
174
+ : "Grootboekrekening nog niet gekozen", hasError: sectionHasError(["credit_limit", "send_reminder", "ledger_account"]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kredietlimiet (\u20AC)", error: fieldErr("credit_limit"), children: _jsx(Input, { type: "number", step: "0.01", min: 0, ...numberField("credit_limit") }) }), _jsx(Field, { label: "Aanmanen", error: fieldErr("send_reminder"), children: _jsxs(Select, { value: form.watch("send_reminder") || TWINFIELD_DEFAULTS.send_reminder, onValueChange: (v) => form.setValue("send_reminder", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SEND_REMINDER_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Geblokkeerd", error: fieldErr("blocked"), children: _jsxs(Select, { value: form.watch("blocked") ? "ja" : "nee", onValueChange: (v) => form.setValue("blocked", v === "ja"), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nee", children: "Nee" }), _jsx(SelectItem, { value: "ja", children: "Ja" })] })] }) }), _jsx(Field, { label: "Grootboekrekening", required: true, error: fieldErr("ledger_account"), success: isValid("ledger_account"), hint: ledgerLoading
175
+ ? "Rekeningschema wordt geladen uit Twinfield..."
176
+ : ledger?.error
177
+ ? `${ledger.error} Vul de rekening handmatig in.`
178
+ : "Verzamelrekening debiteuren; kan per administratie verschillen.", children: ledgerLoading ? (_jsx(Input, { disabled: true, placeholder: "Laden..." })) : (ledger?.accounts?.length ?? 0) > 0 ? (_jsxs(Select, { value: form.watch("ledger_account") || "", onValueChange: (v) => form.setValue("ledger_account", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies grootboekrekening..." }) }), _jsx(SelectContent, { children: (ledger?.accounts ?? []).map((a) => (_jsxs(SelectItem, { value: a.code, children: [a.code, a.name ? ` - ${a.name}` : ""] }, a.code))) })] })) : (_jsx(Input, { placeholder: "bv. 1300", "aria-invalid": !!errors.ledger_account, ...form.register("ledger_account") })) })] }) })] })] }), _jsx("div", { className: "fixed bottom-0 left-0 right-0 border-t bg-background/90 backdrop-blur z-20", children: _jsxs("div", { className: "max-w-6xl mx-auto px-6 py-3 flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, children: "Annuleren" }), _jsxs(Button, { type: "button", onClick: form.handleSubmit(onSubmit), disabled: submitting || !isDirty, children: [_jsx(Check, { className: "h-4 w-4 mr-2" }), submitting ? "Opslaan..." : submitLabel] })] }) }), extra] }));
132
179
  }
133
- function Section({ k, title, icon, open, onToggle, children, }) {
134
- return (_jsxs(Card, { className: "rounded-2xl border shadow-sm overflow-hidden", children: [_jsxs("button", { type: "button", onClick: () => onToggle(k), className: "w-full flex items-center justify-between px-6 py-4 hover:bg-muted/40 transition-colors", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-primary", children: icon }), _jsx("span", { className: "text-base font-semibold", children: title })] }), _jsx(ChevronDown, { className: `h-5 w-5 text-muted-foreground transition-transform ${open ? "" : "-rotate-90"}` })] }), open && (_jsx(CardContent, { className: "px-6 pb-6 pt-0", children: children }))] }));
180
+ function Section({ k, title, icon, open, onToggle, children, summary, hasError, }) {
181
+ return (_jsxs(Card, { className: "rounded-2xl border shadow-sm overflow-hidden", children: [_jsxs("button", { type: "button", onClick: () => onToggle(k), className: "w-full flex items-center justify-between px-6 py-4 hover:bg-muted/40 transition-colors", children: [_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsx("span", { className: "text-primary", children: icon }), _jsx("span", { className: "text-base font-semibold", children: title }), hasError && (_jsx("span", { className: "h-2 w-2 rounded-full bg-destructive", "aria-label": "Deze sectie bevat een fout" })), !open && summary && (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: summary }))] }), _jsx(ChevronDown, { className: `h-5 w-5 text-muted-foreground transition-transform ${open ? "" : "-rotate-90"}` })] }), open && (_jsx(CardContent, { className: "px-6 pb-6 pt-0", children: children }))] }));
135
182
  }
136
183
  function Field({ label, required, error, success, hint, children, }) {
137
184
  return (_jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { className: "text-sm font-medium", children: [label, required && _jsx("span", { className: "text-destructive ml-0.5", children: "*" })] }), _jsxs("div", { className: `relative ${error ? "[&_input]:border-destructive [&_input]:focus-visible:ring-destructive/30" : ""}`, children: [children, success && !error && (_jsx(Check, { className: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-emerald-600" }))] }), error && _jsxs("p", { className: "text-xs text-destructive flex items-center gap-1", children: [_jsx(AlertCircle, { className: "h-3 w-3" }), error] }), !error && hint && _jsx("p", { className: "text-xs text-muted-foreground", children: hint })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"AA+CA,wBAAgB,gBAAgB,gCAmgB/B"}
1
+ {"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"AAmDA,wBAAgB,gBAAgB,gCAmoB/B"}
@@ -13,13 +13,18 @@ import { supabase } from "../../integrations/supabase/client";
13
13
  import { decodeDeep } from "../../lib/decode-html";
14
14
  import { CustomerAvatar } from "../CustomerAvatar";
15
15
  import { PriceGroupBadges } from "./PriceGroupBadges";
16
- import { formatCountry } from "./KlantDetailLayout";
16
+ import { ChannelBadges } from "./ChannelBadges";
17
+ import { derivePaymentMethod, paymentToneClasses } from "../../lib/payment-method";
18
+ import { formatCountry, getCountryFlagAndName, countryNameToFlag } from "../../lib/countries";
19
+ import { MultiSearchableSelect } from "./MultiSearchableSelect";
17
20
  export function KlantenOverzicht() {
18
21
  const navigate = useNavigate();
19
22
  const queryClient = useQueryClient();
20
23
  const [search, setSearch] = useState("");
21
- const [customerClass, setCustomerClass] = useState("__all__");
22
- const [country, setCountry] = useState("__all__");
24
+ const [selectedClasses, setSelectedClasses] = useState([]);
25
+ const [selectedCountries, setSelectedCountries] = useState([]);
26
+ const [selectedChannels, setSelectedChannels] = useState([]);
27
+ const [selectedPayments, setSelectedPayments] = useState([]);
23
28
  const [view, setView] = useState("all");
24
29
  const [sort, setSort] = useState({ col: "company_name", dir: "asc" });
25
30
  const [page, setPage] = useState(1);
@@ -113,6 +118,7 @@ export function KlantenOverzicht() {
113
118
  is_active: !!c.is_active,
114
119
  overdue: null,
115
120
  price_groups: c.price_groups ?? [],
121
+ payment_method: derivePaymentMethod(null, c),
116
122
  twinfield_code: c.twinfield_code ?? null,
117
123
  }));
118
124
  const ext = (externalData?.customers ?? [])
@@ -157,6 +163,7 @@ export function KlantenOverzicht() {
157
163
  is_active: c.is_active,
158
164
  overdue,
159
165
  price_groups: localRow?.price_groups ?? [],
166
+ payment_method: derivePaymentMethod((c.sources ?? []), localRow),
160
167
  twinfield_code: twinfieldSource?.external_id ?? localRow?.twinfield_code ?? null,
161
168
  };
162
169
  });
@@ -173,10 +180,43 @@ export function KlantenOverzicht() {
173
180
  }, [allRows]);
174
181
  const countries = useMemo(() => {
175
182
  const set = new Set();
176
- allRows.forEach((c) => { if (c.country)
177
- set.add(c.country); });
183
+ allRows.forEach((c) => {
184
+ const name = formatCountry(c.country);
185
+ if (name)
186
+ set.add(name);
187
+ });
188
+ return Array.from(set).sort();
189
+ }, [allRows]);
190
+ const channelOptions = useMemo(() => {
191
+ const set = new Set();
192
+ allRows.forEach((c) => (c.providers ?? []).forEach((p) => {
193
+ if (p && p !== "local")
194
+ set.add(p);
195
+ }));
178
196
  return Array.from(set).sort();
179
197
  }, [allRows]);
198
+ const paymentOptions = useMemo(() => {
199
+ const set = new Set();
200
+ allRows.forEach((c) => { if (c.payment_method?.label)
201
+ set.add(c.payment_method.label); });
202
+ return Array.from(set).sort();
203
+ }, [allRows]);
204
+ const classOptions = useMemo(() => (classData?.classes ?? []).map((c) => ({ value: c, label: c })), [classData]);
205
+ const countryOptions = useMemo(() => countries.map((c) => {
206
+ const { flag } = getCountryFlagAndName(c);
207
+ return {
208
+ value: c,
209
+ label: (_jsxs("span", { className: "inline-flex items-center gap-2", children: [flag ? _jsx("span", { className: "text-base", children: flag }) : _jsx(MapPin, { className: "h-3.5 w-3.5 text-muted-foreground" }), _jsx("span", { children: c })] })),
210
+ };
211
+ }), [countries]);
212
+ const channelSelectOptions = useMemo(() => [
213
+ ...channelOptions.map((p) => ({
214
+ value: p,
215
+ label: p.charAt(0).toUpperCase() + p.slice(1),
216
+ })),
217
+ { value: "__none__", label: "Geen kanaal" },
218
+ ], [channelOptions]);
219
+ const paymentSelectOptions = useMemo(() => paymentOptions.map((p) => ({ value: p, label: p })), [paymentOptions]);
180
220
  const customers = useMemo(() => {
181
221
  let rows = allRows;
182
222
  if (view === "active")
@@ -185,10 +225,23 @@ export function KlantenOverzicht() {
185
225
  rows = rows.filter((c) => !c.is_active);
186
226
  if (view === "overdue")
187
227
  rows = rows.filter((c) => (c.overdue?.count ?? 0) > 0);
188
- if (customerClass !== "__all__")
189
- rows = rows.filter((c) => c.customer_class === customerClass);
190
- if (country !== "__all__")
191
- rows = rows.filter((c) => c.country === country);
228
+ if (selectedClasses.length > 0)
229
+ rows = rows.filter((c) => c.customer_class && selectedClasses.includes(c.customer_class));
230
+ if (selectedCountries.length > 0)
231
+ rows = rows.filter((c) => {
232
+ const name = formatCountry(c.country);
233
+ return name && selectedCountries.includes(name);
234
+ });
235
+ if (selectedChannels.length > 0) {
236
+ rows = rows.filter((c) => {
237
+ const externalProviders = (c.providers ?? []).filter((p) => p && p !== "local");
238
+ if (selectedChannels.includes("__none__") && externalProviders.length === 0)
239
+ return true;
240
+ return selectedChannels.some((ch) => externalProviders.includes(ch));
241
+ });
242
+ }
243
+ if (selectedPayments.length > 0)
244
+ rows = rows.filter((c) => c.payment_method?.label && selectedPayments.includes(c.payment_method.label));
192
245
  if (search) {
193
246
  const s = search.toLowerCase();
194
247
  rows = rows.filter((c) => [c.company_name, c.commercial_name, c.contact_person, c.email, c.city]
@@ -201,7 +254,7 @@ export function KlantenOverzicht() {
201
254
  const cmp = av.localeCompare(bv);
202
255
  return sort.dir === "asc" ? cmp : -cmp;
203
256
  });
204
- }, [allRows, view, customerClass, country, search, sort]);
257
+ }, [allRows, view, selectedClasses, selectedCountries, selectedChannels, selectedPayments, search, sort]);
205
258
  const total = customers.length;
206
259
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
207
260
  const currentPage = Math.min(page, totalPages);
@@ -212,15 +265,49 @@ export function KlantenOverzicht() {
212
265
  const activeFilterChips = [];
213
266
  if (search)
214
267
  activeFilterChips.push({ key: "s", label: `Zoek: "${search}"`, clear: () => { setSearch(""); resetPage(); } });
215
- if (customerClass !== "__all__")
216
- activeFilterChips.push({ key: "c", label: `Klasse: ${customerClass}`, clear: () => { setCustomerClass("__all__"); resetPage(); } });
217
- if (country !== "__all__")
218
- activeFilterChips.push({ key: "co", label: `Land: ${country}`, clear: () => { setCountry("__all__"); resetPage(); } });
268
+ selectedClasses.forEach((cls) => {
269
+ activeFilterChips.push({
270
+ key: `c-${cls}`,
271
+ label: `Klasse: ${cls}`,
272
+ clear: () => { setSelectedClasses((prev) => prev.filter((v) => v !== cls)); resetPage(); },
273
+ });
274
+ });
275
+ selectedCountries.forEach((ctry) => {
276
+ const countryFlag = countryNameToFlag(ctry) ?? "🌐";
277
+ activeFilterChips.push({
278
+ key: `co-${ctry}`,
279
+ label: _jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryFlag }), "Land: ", ctry] }),
280
+ clear: () => { setSelectedCountries((prev) => prev.filter((v) => v !== ctry)); resetPage(); },
281
+ });
282
+ });
283
+ selectedChannels.forEach((ch) => {
284
+ const label = ch === "__none__" ? "Geen" : ch.charAt(0).toUpperCase() + ch.slice(1);
285
+ activeFilterChips.push({
286
+ key: `ch-${ch}`,
287
+ label: `Kanaal: ${label}`,
288
+ clear: () => { setSelectedChannels((prev) => prev.filter((v) => v !== ch)); resetPage(); },
289
+ });
290
+ });
291
+ selectedPayments.forEach((pm) => {
292
+ activeFilterChips.push({
293
+ key: `pm-${pm}`,
294
+ label: `Betaalvorm: ${pm}`,
295
+ clear: () => { setSelectedPayments((prev) => prev.filter((v) => v !== pm)); resetPage(); },
296
+ });
297
+ });
219
298
  const toggleSort = (col) => setSort((s) => (s.col === col ? { col, dir: s.dir === "asc" ? "desc" : "asc" } : { col, dir: "asc" }));
220
299
  const lastUpdated = externalData?.last_fetched_at ?? null;
221
300
  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: () => navigate({ to: "/klanten/import" }), children: [_jsx(Upload, { className: "h-4 w-4 mr-2" }), " Importeren"] }), _jsxs(Button, { onClick: () => navigate({ 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
222
301
  ? `Klanten met openstaande facturen waarvan de vervaldatum is verstreken.\nTotaal openstaand: ${formatEuro(overdueData.totals.amount)} · ${overdueData.totals.invoices} facturen`
223
- : "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" })] }), _jsxs(Select, { value: customerClass, onValueChange: (v) => { setCustomerClass(v); resetPage(); }, children: [_jsx(SelectTrigger, { className: "w-40", children: _jsx(SelectValue, { placeholder: "Klasse" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle klassen" }), (classData?.classes ?? []).map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] }), _jsxs(Select, { value: country, onValueChange: (v) => { setCountry(v); resetPage(); }, children: [_jsx(SelectTrigger, { className: "w-36", children: _jsx(SelectValue, { placeholder: "Land" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle landen" }), countries.map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] })] }), 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: () => { setSearch(""); setCustomerClass("__all__"); setCountry("__all__"); resetPage(); }, 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: _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: 6 }).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: 6, children: _jsx(EmptyState, { onAdd: () => navigate({ to: "/klanten/nieuw" }) }) }) })) : pageRows.map((c) => {
302
+ : "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: () => {
303
+ setSearch("");
304
+ setSelectedClasses([]);
305
+ setSelectedCountries([]);
306
+ setSelectedChannels([]);
307
+ setSelectedPayments([]);
308
+ setView("all");
309
+ setPage(1);
310
+ }, 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: () => navigate({ to: "/klanten/nieuw" }) }) }) })) : pageRows.map((c) => {
224
311
  const secondary = c.commercial_name && c.commercial_name !== c.company_name
225
312
  ? c.commercial_name
226
313
  : c.city ?? c.email ?? null;
@@ -229,7 +316,7 @@ export function KlantenOverzicht() {
229
316
  : navigate({
230
317
  to: "/klanten/groep/$groupKey",
231
318
  params: { groupKey: c.group_key },
232
- }), children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(CustomerAvatar, { logoUrl: c.logo_url, name: c.company_name, size: "sm" }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-2", children: [_jsx("span", { className: "truncate", children: c.company_name }), c.overdue && c.overdue.count > 0 && (_jsxs("span", { title: `${c.overdue.count} openstaande factu${c.overdue.count === 1 ? "ur" : "ren"} · oudste vervallen op ${c.overdue.oldest_due}`, className: "inline-flex items-center gap-1 rounded-full bg-destructive/10 text-destructive px-2 py-0.5 text-[11px] font-medium shrink-0", children: [_jsx(AlertCircle, { className: "h-3 w-3" }), " ", formatEuro(c.overdue.total), " achterstallig"] }))] }), secondary && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: secondary })] })] }) }), _jsx(TableCell, { className: "py-3", children: c.contact_person || c.email ? (_jsxs("div", { className: "min-w-0", children: [c.contact_person && _jsx("div", { className: "text-sm truncate", children: c.contact_person }), c.email && (_jsxs("a", { href: `mailto:${c.email}`, onClick: (e) => e.stopPropagation(), className: "text-xs text-muted-foreground inline-flex items-center gap-1 hover:underline truncate", children: [_jsx(Mail, { className: "h-3 w-3" }), " ", c.email] }))] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: c.customer_class ? _jsx(Badge, { variant: "secondary", children: c.customer_class }) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(PriceGroupBadges, { groups: c.price_groups }) }), _jsx(TableCell, { className: "py-3 text-sm", children: c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(MapPin, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", formatCountry(c.country)] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(StatusPill, { active: c.is_active }), c.id ? (_jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "h-8 w-8 p-0", title: "Klant bewerken", children: _jsxs(Link, { to: "/klanten/bewerken/$id", params: { id: c.id }, onClick: (e) => e.stopPropagation(), children: [_jsx(Pencil, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Bewerken" })] }) })) : c.twinfield_code ? (_jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "h-8 w-8 p-0", title: "Twinfield-klant overnemen en bewerken", children: _jsxs(Link, { to: "/klanten/overnemen/$code", params: { code: c.twinfield_code }, onClick: (e) => e.stopPropagation(), children: [_jsx(Pencil, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Bewerken" })] }) })) : null] }) })] }, c.key));
319
+ }), children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(CustomerAvatar, { logoUrl: c.logo_url, name: c.company_name, size: "sm" }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-2", children: [_jsx("span", { className: "truncate", children: c.company_name }), c.overdue && c.overdue.count > 0 && (_jsxs("span", { title: `${c.overdue.count} openstaande factu${c.overdue.count === 1 ? "ur" : "ren"} · oudste vervallen op ${c.overdue.oldest_due}`, className: "inline-flex items-center gap-1 rounded-full bg-destructive/10 text-destructive px-2 py-0.5 text-[11px] font-medium shrink-0", children: [_jsx(AlertCircle, { className: "h-3 w-3" }), " ", formatEuro(c.overdue.total), " achterstallig"] }))] }), secondary && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: secondary })] })] }) }), _jsx(TableCell, { className: "py-3", children: c.contact_person || c.email ? (_jsxs("div", { className: "min-w-0", children: [c.contact_person && _jsx("div", { className: "text-sm truncate", children: c.contact_person }), c.email && (_jsxs("a", { href: `mailto:${c.email}`, onClick: (e) => e.stopPropagation(), className: "text-xs text-muted-foreground inline-flex items-center gap-1 hover:underline truncate", children: [_jsx(Mail, { className: "h-3 w-3" }), " ", c.email] }))] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: c.customer_class ? _jsx(Badge, { variant: "secondary", children: c.customer_class }) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(PriceGroupBadges, { groups: c.price_groups }) }), _jsx(TableCell, { className: "py-3", children: _jsx(ChannelBadges, { providers: c.providers }) }), _jsx(TableCell, { className: "py-3", children: _jsx("span", { className: `inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${paymentToneClasses(c.payment_method.tone)}`, children: c.payment_method.label }) }), _jsx(TableCell, { className: "py-3 text-sm", children: c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(c.country) ?? "🌐" }), _jsx("span", { children: formatCountry(c.country) })] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(StatusPill, { active: c.is_active }), c.id ? (_jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "h-8 w-8 p-0", title: "Klant bewerken", children: _jsxs(Link, { to: "/klanten/bewerken/$id", params: { id: c.id }, onClick: (e) => e.stopPropagation(), children: [_jsx(Pencil, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Bewerken" })] }) })) : c.twinfield_code ? (_jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "h-8 w-8 p-0", title: "Twinfield-klant overnemen en bewerken", children: _jsxs(Link, { to: "/klanten/overnemen/$code", params: { code: c.twinfield_code }, onClick: (e) => e.stopPropagation(), children: [_jsx(Pencil, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Bewerken" })] }) })) : null] }) })] }, c.key));
233
320
  }) })] }) }) }), _jsxs("div", { className: "flex items-center justify-between gap-3 px-4 py-3 border-t text-sm flex-wrap", children: [_jsx("span", { className: "text-muted-foreground", children: isLoading ? "Laden..." : total === 0 ? "0 klanten" : `${startIdx + 1}–${endIdx} van ${total}` }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => { setPageSize(Number(v)); setPage(1); }, children: [_jsx(SelectTrigger, { className: "w-20 h-8", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "25", children: "25" }), _jsx(SelectItem, { value: "50", children: "50" }), _jsx(SelectItem, { value: "100", children: "100" })] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: currentPage <= 1, children: [_jsx(ChevronLeft, { className: "h-4 w-4 mr-1" }), " Vorige"] }), _jsxs("span", { className: "text-muted-foreground", children: [currentPage, " / ", totalPages] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.min(totalPages, p + 1)), disabled: currentPage >= totalPages, children: ["Volgende ", _jsx(ChevronRight, { className: "h-4 w-4 ml-1" })] })] })] })] })] }) }));
234
321
  }
235
322
  function StatCard({ icon, label, value, active, onClick, tooltip, tint = "muted", disabled = false, }) {
@@ -0,0 +1,21 @@
1
+ export type SearchableOption = {
2
+ value: string;
3
+ label: React.ReactNode;
4
+ /** Extra zoektermen (bijv. Engelse naam van het land). Default wordt label gebruikt. */
5
+ search?: string;
6
+ };
7
+ export type MultiSearchableSelectProps = {
8
+ values: string[];
9
+ onValuesChange: (values: string[]) => void;
10
+ options: SearchableOption[];
11
+ placeholder?: string;
12
+ triggerClassName?: string;
13
+ /** Geeft aan of het invoerveld gefocust moet worden zodra de popover opent. */
14
+ autoFocus?: boolean;
15
+ /** Tekst voor de "selecteer alles" optie. */
16
+ selectAllLabel?: string;
17
+ /** Tekst voor de "wis alles" optie. */
18
+ clearAllLabel?: string;
19
+ };
20
+ export declare function MultiSearchableSelect({ values, onValuesChange, options, placeholder, triggerClassName, autoFocus, selectAllLabel, clearAllLabel, }: MultiSearchableSelectProps): import("react").JSX.Element;
21
+ //# sourceMappingURL=MultiSearchableSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiSearchableSelect.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/MultiSearchableSelect.tsx"],"names":[],"mappings":"AAaA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,cAAc,EACd,OAAO,EACP,WAA4B,EAC5B,gBAAgB,EAChB,SAAgB,EAChB,cAAmC,EACnC,aAA8B,GAC/B,EAAE,0BAA0B,+BAsL5B"}
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo, useRef, useState } from "react";
3
+ import { Button, Checkbox, Input, Popover, PopoverContent, PopoverTrigger, Separator, } from "@flowselections/core";
4
+ import { ChevronDown, X } from "lucide-react";
5
+ import { cn } from "@flowselections/core";
6
+ export function MultiSearchableSelect({ values, onValuesChange, options, placeholder = "Selecteer...", triggerClassName, autoFocus = true, selectAllLabel = "Alles selecteren", clearAllLabel = "Alles wissen", }) {
7
+ const [open, setOpen] = useState(false);
8
+ const [query, setQuery] = useState("");
9
+ const inputRef = useRef(null);
10
+ const selectableOptions = useMemo(() => options.filter((o) => o.value !== "__all__"), [options]);
11
+ const selectedMap = useMemo(() => {
12
+ const map = new Set(values);
13
+ return map;
14
+ }, [values]);
15
+ const filtered = useMemo(() => {
16
+ const q = query.trim().toLowerCase();
17
+ if (!q)
18
+ return selectableOptions;
19
+ return selectableOptions.filter((o) => {
20
+ const haystack = [normalizeForSearch(o.label), o.search]
21
+ .filter(Boolean)
22
+ .join(" ")
23
+ .toLowerCase();
24
+ return haystack.includes(q);
25
+ });
26
+ }, [selectableOptions, query]);
27
+ const isAllSelected = selectableOptions.length > 0 &&
28
+ selectableOptions.every((o) => selectedMap.has(o.value));
29
+ const handleToggle = (value) => {
30
+ const next = new Set(selectedMap);
31
+ if (next.has(value))
32
+ next.delete(value);
33
+ else
34
+ next.add(value);
35
+ onValuesChange(Array.from(next));
36
+ };
37
+ const handleSelectAll = () => {
38
+ onValuesChange(selectableOptions.map((o) => o.value));
39
+ };
40
+ const handleClearAll = () => {
41
+ onValuesChange([]);
42
+ };
43
+ const handleTriggerClear = (e) => {
44
+ e.stopPropagation();
45
+ handleClearAll();
46
+ setQuery("");
47
+ };
48
+ const showClear = values.length > 0;
49
+ const selectedLabels = useMemo(() => {
50
+ return values
51
+ .map((v) => options.find((o) => o.value === v))
52
+ .filter(Boolean);
53
+ }, [values, options]);
54
+ const triggerLabel = useMemo(() => {
55
+ if (values.length === 0)
56
+ return placeholder;
57
+ if (values.length === 1)
58
+ return selectedLabels[0]?.label ?? placeholder;
59
+ return `${values.length} geselecteerd`;
60
+ }, [values, selectedLabels, placeholder]);
61
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, className: cn("justify-between font-normal bg-background hover:bg-accent", triggerClassName), children: [_jsx("span", { className: "truncate", children: triggerLabel }), _jsxs("span", { className: "ml-2 flex shrink-0 items-center gap-1", children: [showClear && (_jsx("span", { role: "button", tabIndex: 0, onClick: handleTriggerClear, onKeyDown: (e) => {
62
+ if (e.key === "Enter" || e.key === " ") {
63
+ e.preventDefault();
64
+ handleTriggerClear(e);
65
+ }
66
+ }, className: "inline-flex rounded-sm hover:bg-muted", "aria-label": "Keuze wissen", children: _jsx(X, { className: "h-4 w-4 text-muted-foreground" }) })), _jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" })] })] }) }), _jsxs(PopoverContent, { className: "p-0 w-[var(--radix-popover-trigger-width)]", align: "start", sideOffset: 4, onOpenAutoFocus: (e) => {
67
+ if (!autoFocus)
68
+ e.preventDefault();
69
+ }, children: [_jsx("div", { className: "p-2 border-b", children: _jsxs("div", { className: "relative", children: [_jsx(Input, { ref: inputRef, placeholder: "Zoeken...", value: query, onChange: (e) => setQuery(e.target.value), className: "pr-8", autoFocus: autoFocus }), query && (_jsx("button", { type: "button", onClick: () => setQuery(""), className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground", "aria-label": "Zoekopdracht wissen", children: _jsx(X, { className: "h-4 w-4" }) }))] }) }), _jsx("div", { className: "max-h-[40vh] overflow-y-auto overscroll-contain py-1", children: filtered.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "Geen resultaten" })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-1 px-2 py-1", children: [_jsx("button", { type: "button", onClick: handleSelectAll, className: "text-xs text-muted-foreground hover:text-foreground underline px-1 py-1", children: selectAllLabel }), _jsx(Separator, { orientation: "vertical", className: "h-3" }), _jsx("button", { type: "button", onClick: handleClearAll, className: "text-xs text-muted-foreground hover:text-foreground underline px-1 py-1", children: clearAllLabel })] }), filtered.map((o) => {
70
+ const isSelected = selectedMap.has(o.value);
71
+ return (_jsxs("button", { type: "button", onClick: () => handleToggle(o.value), className: cn("w-full text-left px-3 py-2 text-sm flex items-center gap-2 hover:bg-accent hover:text-accent-foreground", isSelected && "bg-accent/50"), children: [_jsx(Checkbox, { checked: isSelected, onCheckedChange: () => handleToggle(o.value), "aria-label": normalizeForSearch(o.label), className: "pointer-events-none" }), _jsx("span", { className: "truncate", children: o.label })] }, o.value));
72
+ })] })) })] })] }));
73
+ }
74
+ function normalizeForSearch(node) {
75
+ if (typeof node === "string")
76
+ return node;
77
+ if (typeof node === "number")
78
+ return String(node);
79
+ if (Array.isArray(node))
80
+ return node.map(normalizeForSearch).join(" ");
81
+ if (node && typeof node === "object" && "props" in node) {
82
+ const children = node.props?.children;
83
+ return normalizeForSearch(children);
84
+ }
85
+ return "";
86
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"NetwerkPagina.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NetwerkPagina.tsx"],"names":[],"mappings":"AAmCA,wBAAgB,aAAa,gCA4O5B"}
1
+ {"version":3,"file":"NetwerkPagina.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NetwerkPagina.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,aAAa,gCAmP5B"}
@@ -5,6 +5,7 @@ import { useState } from "react";
5
5
  import { Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Switch, Label, } from "@flowselections/core";
6
6
  import { Search, MapPin, Globe } from "lucide-react";
7
7
  import { BackLink } from "../../components/klanten/BackLink";
8
+ import { countryNameToFlag } from "../../lib/countries";
8
9
  import { listFloridayNetwork } from "../../lib/customers.functions";
9
10
  import { CustomerAvatar } from "../CustomerAvatar";
10
11
  import { PriceGroupBadges } from "./PriceGroupBadges";
@@ -51,6 +52,6 @@ export function NetwerkPagina() {
51
52
  o.address_line,
52
53
  [o.postal_code, o.city].filter(Boolean).join(" "),
53
54
  ].filter((x) => x && String(x).trim() !== "");
54
- return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsx(CustomerAvatar, { logoUrl: o.logo_url, name: displayName ?? "?", size: "sm" }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-2", children: [displayName ? (_jsx("span", { className: "truncate", children: displayName })) : (_jsx("span", { className: "italic text-muted-foreground", children: "Onbekende organisatie" })), o.is_expired && (_jsx(Badge, { variant: "outline", className: "text-[10px] uppercase tracking-wide", children: "be\u00EBindigd" }))] }), o.commercial_name && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: o.commercial_name })), o.website && (_jsxs("a", { href: o.website.startsWith("http") ? o.website : `https://${o.website}`, target: "_blank", rel: "noreferrer", className: "text-xs text-muted-foreground inline-flex items-center gap-1 hover:underline", children: [_jsx(Globe, { className: "h-3 w-3" }), " ", o.website] })), _jsx("div", { className: "mt-1", children: _jsx(FloridayDeeplinkButton, { organizationId: o.organization_id }) })] })] }) }), _jsx(TableCell, { children: o.gln ? _jsx(Badge, { variant: "outline", children: o.gln }) : "—" }), _jsx(TableCell, { className: "text-sm", children: addressBits.length ? (_jsx("div", { className: "min-w-0", children: _jsxs("div", { className: "inline-flex items-start gap-1 text-muted-foreground", children: [_jsx(MapPin, { className: "h-3 w-3 mt-0.5 shrink-0" }), _jsx("span", { className: "truncate", children: addressBits.join(", ") })] }) })) : ("—") }), _jsx(TableCell, { children: o.country ?? "—" }), _jsx(TableCell, { className: "text-xs text-muted-foreground uppercase tracking-wide", children: o.organization_type ?? "—" }), _jsx(TableCell, { children: _jsx(PriceGroupBadges, { groups: o.price_groups }) }), _jsx(TableCell, { className: "text-muted-foreground text-sm", children: new Date(o.fetched_at).toLocaleDateString("nl-NL") })] }, `${o.connection_id}-${o.organization_id}`));
55
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsx(CustomerAvatar, { logoUrl: o.logo_url, name: displayName ?? "?", size: "sm" }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-2", children: [displayName ? (_jsx("span", { className: "truncate", children: displayName })) : (_jsx("span", { className: "italic text-muted-foreground", children: "Onbekende organisatie" })), o.is_expired && (_jsx(Badge, { variant: "outline", className: "text-[10px] uppercase tracking-wide", children: "be\u00EBindigd" }))] }), o.commercial_name && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: o.commercial_name })), o.website && (_jsxs("a", { href: o.website.startsWith("http") ? o.website : `https://${o.website}`, target: "_blank", rel: "noreferrer", className: "text-xs text-muted-foreground inline-flex items-center gap-1 hover:underline", children: [_jsx(Globe, { className: "h-3 w-3" }), " ", o.website] })), _jsx("div", { className: "mt-1", children: _jsx(FloridayDeeplinkButton, { organizationId: o.organization_id }) })] })] }) }), _jsx(TableCell, { children: o.gln ? _jsx(Badge, { variant: "outline", children: o.gln }) : "—" }), _jsx(TableCell, { className: "text-sm", children: addressBits.length ? (_jsx("div", { className: "min-w-0", children: _jsxs("div", { className: "inline-flex items-start gap-1 text-muted-foreground", children: [_jsx(MapPin, { className: "h-3 w-3 mt-0.5 shrink-0" }), _jsx("span", { className: "truncate", children: addressBits.join(", ") })] }) })) : ("—") }), _jsx(TableCell, { children: o.country ? (_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(o.country) ?? "🌐" }), _jsx("span", { children: o.country })] })) : "—" }), _jsx(TableCell, { className: "text-xs text-muted-foreground uppercase tracking-wide", children: o.organization_type ?? "—" }), _jsx(TableCell, { children: _jsx(PriceGroupBadges, { groups: o.price_groups }) }), _jsx(TableCell, { className: "text-muted-foreground text-sm", children: new Date(o.fetched_at).toLocaleDateString("nl-NL") })] }, `${o.connection_id}-${o.organization_id}`));
55
56
  })) })] }) }) })] }) }));
56
57
  }
@@ -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,+BAuGjE"}
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"}
@@ -95,7 +95,5 @@ export function OvernemenTwinfieldKlant({ code }) {
95
95
  customer_number: code,
96
96
  force: true,
97
97
  };
98
- return (_jsx(KlantFormulier, { title: `${p.company_name || `Debiteur ${code}`} bewerken`, description: p.addressMissing
99
- ? `Deze klant komt uit Twinfield (debiteur ${code}). Twinfield levert voor deze debiteur geen straat/huisnummer aan de koppeling — vul die hieronder handmatig aan. Bij opslaan wordt dezelfde debiteur in Twinfield bijgewerkt.`
100
- : `Deze klant komt uit Twinfield (debiteur ${code}). Vul ontbrekende gegevens aan; bij opslaan wordt dezelfde debiteur in Twinfield bijgewerkt.`, submitLabel: "Overnemen en opslaan", submitting: mut.isPending, defaultValues: defaults, numberReadOnly: true, numberHint: `Twinfield-debiteurnummer ${code} — niet aanpasbaar`, onSubmit: (values) => mut.mutate(values), onCancel: () => navigate({ to: "/klanten" }) }));
98
+ return (_jsx(KlantFormulier, { title: `${p.company_name || `Debiteur ${code}`} bewerken`, submitLabel: "Wijzigingen opslaan", submitting: mut.isPending, defaultValues: defaults, numberReadOnly: true, numberHint: `Twinfield-debiteurnummer ${code} — niet aanpasbaar`, onSubmit: (values) => mut.mutate(values), onCancel: () => navigate({ to: "/klanten" }) }));
101
99
  }