@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.
- package/dist-lib/components/klanten/BewerkKlantRoute.d.ts +4 -1
- package/dist-lib/components/klanten/BewerkKlantRoute.d.ts.map +1 -1
- package/dist-lib/components/klanten/BewerkKlantRoute.js +7 -4
- package/dist-lib/components/klanten/ChannelBadges.d.ts +11 -0
- package/dist-lib/components/klanten/ChannelBadges.d.ts.map +1 -0
- package/dist-lib/components/klanten/ChannelBadges.js +21 -0
- package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -1
- package/dist-lib/components/klanten/DeliveryLocationsTab.js +9 -1
- package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/GroupedKlantDetail.js +30 -5
- package/dist-lib/components/klanten/GroupedKlantDetailRoute.d.ts +4 -1
- package/dist-lib/components/klanten/GroupedKlantDetailRoute.d.ts.map +1 -1
- package/dist-lib/components/klanten/GroupedKlantDetailRoute.js +7 -4
- package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetail.js +26 -3
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts +4 -2
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetailLayout.js +23 -21
- package/dist-lib/components/klanten/KlantDetailRoute.d.ts +4 -1
- package/dist-lib/components/klanten/KlantDetailRoute.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetailRoute.js +7 -4
- package/dist-lib/components/klanten/KlantFormulier.d.ts +1 -1
- package/dist-lib/components/klanten/KlantFormulier.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantFormulier.js +14 -4
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +103 -16
- package/dist-lib/components/klanten/MultiSearchableSelect.d.ts +21 -0
- package/dist-lib/components/klanten/MultiSearchableSelect.d.ts.map +1 -0
- package/dist-lib/components/klanten/MultiSearchableSelect.js +86 -0
- package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -1
- package/dist-lib/components/klanten/NetwerkPagina.js +2 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlant.js +1 -3
- package/dist-lib/components/klanten/OvernemenTwinfieldKlantRoute.d.ts +4 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlantRoute.d.ts.map +1 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlantRoute.js +7 -4
- package/dist-lib/components/klanten/SearchableSelect.d.ts +17 -0
- package/dist-lib/components/klanten/SearchableSelect.d.ts.map +1 -0
- package/dist-lib/components/klanten/SearchableSelect.js +57 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts.map +1 -1
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +3 -2
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts.map +1 -1
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +2 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +33 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +33 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +33 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +35 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +99 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts.map +1 -1
- package/dist-lib/lib/accounting.functions.d.ts +99 -0
- package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
- package/dist-lib/lib/countries.d.ts +31 -0
- package/dist-lib/lib/countries.d.ts.map +1 -0
- package/dist-lib/lib/countries.js +194 -0
- package/dist-lib/lib/customer-analytics.functions.d.ts +33 -0
- package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-notes.functions.d.ts +39638 -0
- package/dist-lib/lib/customer-notes.functions.d.ts.map +1 -0
- package/dist-lib/lib/customer-notes.functions.js +59 -0
- package/dist-lib/lib/customers.functions.d.ts +297 -0
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/delivery-locations.functions.d.ts +132 -0
- package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -1
- package/dist-lib/lib/external-customers.functions.d.ts +132 -0
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-assortment.functions.d.ts +33 -0
- package/dist-lib/lib/floriday-assortment.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-discounts.functions.d.ts +66 -0
- package/dist-lib/lib/floriday-discounts.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-trade-items.functions.d.ts +33 -0
- package/dist-lib/lib/floriday-trade-items.functions.d.ts.map +1 -1
- package/dist-lib/lib/local-discounts.functions.d.ts +231 -0
- package/dist-lib/lib/local-discounts.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-events.functions.d.ts +33 -0
- package/dist-lib/lib/logistics-events.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-strategy.functions.d.ts +132 -0
- package/dist-lib/lib/logistics-strategy.functions.d.ts.map +1 -1
- package/dist-lib/lib/overdue-invoices.functions.d.ts +33 -0
- package/dist-lib/lib/overdue-invoices.functions.d.ts.map +1 -1
- package/dist-lib/lib/payment-method.d.ts +22 -0
- package/dist-lib/lib/payment-method.d.ts.map +1 -0
- package/dist-lib/lib/payment-method.js +74 -0
- package/dist-lib/lib/price-groups-local.functions.d.ts +231 -0
- package/dist-lib/lib/price-groups-local.functions.d.ts.map +1 -1
- package/dist-lib/lib/price-groups.functions.d.ts +33 -0
- package/dist-lib/lib/price-groups.functions.d.ts.map +1 -1
- package/dist-lib/lib/twinfield-adopt.functions.d.ts +66 -0
- package/dist-lib/lib/twinfield-adopt.functions.d.ts.map +1 -1
- package/dist-lib/lib/twinfield-ledger.functions.d.ts +33 -0
- package/dist-lib/lib/twinfield-ledger.functions.d.ts.map +1 -1
- package/package.json +1 -1
- package/public/flowselections-assets/Klanten/twinfield-logo.svg +218 -0
|
@@ -11,6 +11,7 @@ import { listCustomerClasses } from "../../lib/customers.functions";
|
|
|
11
11
|
import { listTwinfieldLedgerAccounts } from "../../lib/twinfield-ledger.functions";
|
|
12
12
|
import { customerCreateSchema } from "../../lib/customer-schemas";
|
|
13
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
|
|
@@ -72,6 +73,7 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
|
|
|
72
73
|
const errors = form.formState.errors;
|
|
73
74
|
const errorCount = Object.keys(errors).length;
|
|
74
75
|
const touched = form.formState.touchedFields;
|
|
76
|
+
const isDirty = form.formState.isDirty;
|
|
75
77
|
const values = form.watch();
|
|
76
78
|
// Standaard-grootboekrekening invullen zodra Twinfield die kent.
|
|
77
79
|
useEffect(() => {
|
|
@@ -79,9 +81,10 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
|
|
|
79
81
|
return;
|
|
80
82
|
if (String(form.getValues("ledger_account") ?? "").trim())
|
|
81
83
|
return;
|
|
82
|
-
form.setValue("ledger_account", ledger.defaultCode, { shouldValidate: true });
|
|
84
|
+
form.setValue("ledger_account", ledger.defaultCode, { shouldValidate: true, shouldDirty: false });
|
|
83
85
|
}, [ledger?.defaultCode, form]);
|
|
84
86
|
/**
|
|
87
|
+
|
|
85
88
|
* Adresregel 1 volgt straat + huisnummer zolang de gebruiker die regel niet
|
|
86
89
|
* zelf heeft aangepast. Zo staat er niet drie keer hetzelfde adres.
|
|
87
90
|
*/
|
|
@@ -147,14 +150,21 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
|
|
|
147
150
|
const t = v.trim();
|
|
148
151
|
return t && !/^https?:\/\//i.test(t) ? `https://${t}` : t;
|
|
149
152
|
};
|
|
150
|
-
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 })] })] }), 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([
|
|
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([
|
|
151
154
|
"customer_number", "company_name", "language", "website", "vat_number",
|
|
152
155
|
"coc_number", "contact_person", "attention_to", "email", "billing_email",
|
|
153
156
|
"phone", "fax",
|
|
154
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([
|
|
155
158
|
"address_type", "icp_country_code", "address_line1", "address_line2",
|
|
156
159
|
"street", "house_number", "postal_code", "city", "country",
|
|
157
|
-
]), 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, {
|
|
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") ??
|
|
158
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") ??
|
|
159
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([
|
|
160
170
|
"discount_percentage", "discount_days", "payment_term_days", "vat_display",
|
|
@@ -165,7 +175,7 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
|
|
|
165
175
|
? "Rekeningschema wordt geladen uit Twinfield..."
|
|
166
176
|
: ledger?.error
|
|
167
177
|
? `${ledger.error} Vul de rekening handmatig in.`
|
|
168
|
-
: "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, children: [_jsx(Check, { className: "h-4 w-4 mr-2" }), submitting ? "Opslaan..." : submitLabel] })] }) }), extra] }));
|
|
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] }));
|
|
169
179
|
}
|
|
170
180
|
function Section({ k, title, icon, open, onToggle, children, summary, hasError, }) {
|
|
171
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 }))] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"
|
|
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 {
|
|
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 [
|
|
22
|
-
const [
|
|
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) => {
|
|
177
|
-
|
|
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 (
|
|
189
|
-
rows = rows.filter((c) => c.customer_class
|
|
190
|
-
if (
|
|
191
|
-
rows = rows.filter((c) =>
|
|
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,
|
|
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
|
-
|
|
216
|
-
activeFilterChips.push({
|
|
217
|
-
|
|
218
|
-
|
|
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" })] }),
|
|
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(
|
|
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":"
|
|
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,+
|
|
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`,
|
|
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
|
}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** Route-onafhankelijk: leest `$code` uit welke route de shell ook gebruikt. */
|
|
2
|
+
export declare function OvernemenTwinfieldKlantRoute({ code: propCode }?: {
|
|
3
|
+
code?: string;
|
|
4
|
+
}): import("react").JSX.Element;
|
|
2
5
|
//# sourceMappingURL=OvernemenTwinfieldKlantRoute.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OvernemenTwinfieldKlantRoute.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/OvernemenTwinfieldKlantRoute.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OvernemenTwinfieldKlantRoute.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/OvernemenTwinfieldKlantRoute.tsx"],"names":[],"mappings":"AAGA,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,+BAKtF"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useParams } from "@tanstack/react-router";
|
|
3
3
|
import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant";
|
|
4
|
-
|
|
5
|
-
export function OvernemenTwinfieldKlantRoute() {
|
|
6
|
-
const
|
|
4
|
+
/** Route-onafhankelijk: leest `$code` uit welke route de shell ook gebruikt. */
|
|
5
|
+
export function OvernemenTwinfieldKlantRoute({ code: propCode } = {}) {
|
|
6
|
+
const params = useParams({ strict: false });
|
|
7
|
+
const code = propCode ?? params.code;
|
|
8
|
+
if (!code)
|
|
9
|
+
return _jsx("main", { className: "p-6 text-sm text-muted-foreground", children: "Geen klant geselecteerd." });
|
|
7
10
|
return _jsx(OvernemenTwinfieldKlant, { code: code });
|
|
8
11
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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 SearchableSelectProps = {
|
|
8
|
+
value: string;
|
|
9
|
+
onValueChange: (value: 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
|
+
};
|
|
16
|
+
export declare function SearchableSelect({ value, onValueChange, options, placeholder, triggerClassName, autoFocus, }: SearchableSelectProps): import("react").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=SearchableSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchableSelect.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SearchableSelect.tsx"],"names":[],"mappings":"AAWA,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,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,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;CACrB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,aAAa,EACb,OAAO,EACP,WAA4B,EAC5B,gBAAgB,EAChB,SAAgB,GACjB,EAAE,qBAAqB,+BA6HvB"}
|