@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.
- 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 +24 -22
- 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 +56 -9
- 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
|
@@ -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 {
|
|
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:
|
|
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
|
-
|
|
131
|
-
|
|
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":"
|
|
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
|
}
|