@flowselections/floriday-klanten-module 1.0.14 → 1.0.16
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/assets/floriday-logo.png.asset.json +11 -0
- package/dist-lib/components/CustomerAvatar.js +1 -1
- package/dist-lib/components/klanten/BackLink.d.ts +12 -0
- package/dist-lib/components/klanten/BackLink.d.ts.map +1 -0
- package/dist-lib/components/klanten/BackLink.js +12 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.d.ts +4 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.js +80 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.d.ts +8 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.d.ts.map +1 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.js +9 -0
- package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/GroupedKlantDetail.js +19 -5
- package/dist-lib/components/klanten/ImportKlanten.d.ts.map +1 -1
- package/dist-lib/components/klanten/ImportKlanten.js +46 -13
- package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetail.js +9 -3
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +6 -3
- package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -1
- package/dist-lib/components/klanten/NetwerkPagina.js +42 -10
- package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/NieuweKlant.js +23 -8
- package/dist-lib/components/klanten/PriceGroupBadges.d.ts +7 -0
- package/dist-lib/components/klanten/PriceGroupBadges.d.ts.map +1 -0
- package/dist-lib/components/klanten/PriceGroupBadges.js +13 -0
- package/dist-lib/components/klanten/SyncStatusChip.d.ts +4 -0
- package/dist-lib/components/klanten/SyncStatusChip.d.ts.map +1 -0
- package/dist-lib/components/klanten/SyncStatusChip.js +49 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts +8 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +75 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts +4 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +269 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts +2 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.js +133 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts +8 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.js +18 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +6 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +762 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +762 -3
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.js +4 -3
- package/dist-lib/integrations/supabase/client.server.d.ts +762 -3
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +789 -8
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/accounting/twinfield/client.server.d.ts +21 -0
- package/dist-lib/lib/accounting/twinfield/client.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/client.server.js +171 -0
- package/dist-lib/lib/accounting/twinfield/mapping.d.ts +46 -0
- package/dist-lib/lib/accounting/twinfield/mapping.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/mapping.js +109 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +36318 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.js +48 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.d.ts +12 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.js +124 -0
- package/dist-lib/lib/accounting.functions.d.ts +2286 -9
- package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-analytics.functions.d.ts +762 -3
- package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-schemas.d.ts +48 -46
- package/dist-lib/lib/customer-schemas.d.ts.map +1 -1
- package/dist-lib/lib/customer-schemas.js +46 -3
- package/dist-lib/lib/customers.functions.d.ts +18288 -129
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/customers.functions.js +146 -10
- package/dist-lib/lib/delivery-locations.functions.d.ts +3048 -12
- package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -1
- package/dist-lib/lib/external-customers.functions.d.ts +3048 -12
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-assortment.functions.d.ts +12127 -0
- package/dist-lib/lib/floriday-assortment.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-assortment.functions.js +213 -0
- package/dist-lib/lib/floriday-discounts.functions.d.ts +1800 -282
- package/dist-lib/lib/floriday-discounts.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-trade-items.functions.d.ts +12111 -0
- package/dist-lib/lib/floriday-trade-items.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-trade-items.functions.js +107 -0
- package/dist-lib/lib/local-discounts.functions.d.ts +5336 -23
- package/dist-lib/lib/local-discounts.functions.d.ts.map +1 -1
- package/dist-lib/lib/overdue-invoices.functions.d.ts +762 -3
- package/dist-lib/lib/overdue-invoices.functions.d.ts.map +1 -1
- package/dist-lib/lib/price-groups-local.functions.d.ts +84841 -0
- package/dist-lib/lib/price-groups-local.functions.d.ts.map +1 -0
- package/dist-lib/lib/price-groups-local.functions.js +148 -0
- package/dist-lib/lib/price-groups.functions.d.ts +12120 -0
- package/dist-lib/lib/price-groups.functions.d.ts.map +1 -0
- package/dist-lib/lib/price-groups.functions.js +115 -0
- package/dist-lib/lib/pricing.d.ts +24 -0
- package/dist-lib/lib/pricing.d.ts.map +1 -0
- package/dist-lib/lib/pricing.js +60 -0
- package/package.json +6 -3
|
@@ -1,23 +1,55 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Link } from "@tanstack/react-router";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
2
|
import { useQuery } from "@tanstack/react-query";
|
|
4
3
|
import { useServerFn } from "@tanstack/react-start";
|
|
5
4
|
import { useState } from "react";
|
|
6
5
|
import { Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Switch, Label, } from "@flowselections/core";
|
|
7
|
-
import {
|
|
6
|
+
import { Search, MapPin, Globe } from "lucide-react";
|
|
7
|
+
import { BackLink } from "../../components/klanten/BackLink";
|
|
8
8
|
import { listFloridayNetwork } from "../../lib/customers.functions";
|
|
9
|
+
import { CustomerAvatar } from "../CustomerAvatar";
|
|
10
|
+
import { PriceGroupBadges } from "./PriceGroupBadges";
|
|
11
|
+
import { FloridayDeeplinkButton } from "./FloridayDeeplinkButton";
|
|
12
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
13
|
+
const TYPE_OPTIONS = [
|
|
14
|
+
{ value: "CUSTOMER", label: "Klant" },
|
|
15
|
+
{ value: "AGENT", label: "Agent" },
|
|
16
|
+
{ value: "SUPPLIER", label: "Leverancier" },
|
|
17
|
+
{ value: "CARRIER", label: "Vervoerder" },
|
|
18
|
+
{ value: "OTHER", label: "Overig" },
|
|
19
|
+
];
|
|
9
20
|
export function NetwerkPagina() {
|
|
10
21
|
const [search, setSearch] = useState("");
|
|
11
|
-
const [hideUnknown, setHideUnknown] = useState(
|
|
22
|
+
const [hideUnknown, setHideUnknown] = useState(false);
|
|
23
|
+
const [types, setTypes] = useState(["CUSTOMER", "AGENT"]);
|
|
24
|
+
const [includeExpired, setIncludeExpired] = useState(false);
|
|
12
25
|
const list = useServerFn(listFloridayNetwork);
|
|
13
26
|
const { data, isLoading } = useQuery({
|
|
14
|
-
queryKey: ["floriday-network", search],
|
|
15
|
-
queryFn: () => list({ data: { search } }),
|
|
27
|
+
queryKey: ["floriday-network", search, types, includeExpired],
|
|
28
|
+
queryFn: () => list({ data: { search, types, includeExpired, limit: 1000 } }),
|
|
16
29
|
});
|
|
17
30
|
const all = data?.organizations ?? [];
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
: all;
|
|
31
|
+
const named = all.filter((o) => o.organization_name && o.organization_name.trim() !== "");
|
|
32
|
+
const orgs = hideUnknown ? named : all;
|
|
21
33
|
const hiddenCount = all.length - orgs.length;
|
|
22
|
-
|
|
34
|
+
const totalMatching = data?.total ?? all.length;
|
|
35
|
+
const truncated = !!data?.truncated;
|
|
36
|
+
const toggleType = (value) => {
|
|
37
|
+
setTypes((prev) => prev.includes(value) ? prev.filter((t) => t !== value) : [...prev, value]);
|
|
38
|
+
};
|
|
39
|
+
return (_jsx(TooltipPrimitive.Provider, { delayDuration: 200, children: _jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center justify-between gap-4", children: _jsxs("div", { children: [_jsx(BackLink, {}), _jsx("h1", { className: "text-2xl font-semibold", children: "Klanten toevoegen uit netwerk" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Doorzoek alle organisaties die via het netwerk beschikbaar zijn." })] }) }), _jsxs("div", { className: "relative 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 organisatienaam of GLN...", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsx("div", { className: "flex items-center gap-2 flex-wrap", children: TYPE_OPTIONS.map((opt) => {
|
|
40
|
+
const active = types.includes(opt.value);
|
|
41
|
+
return (_jsx("button", { type: "button", onClick: () => toggleType(opt.value), className: "inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs transition-colors " +
|
|
42
|
+
(active
|
|
43
|
+
? "bg-primary text-primary-foreground border-primary"
|
|
44
|
+
: "bg-card text-muted-foreground border-border hover:bg-muted"), children: opt.label }, opt.value));
|
|
45
|
+
}) }), _jsxs("div", { className: "flex items-center gap-6 flex-wrap text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "include-expired", checked: includeExpired, onCheckedChange: setIncludeExpired }), _jsx(Label, { htmlFor: "include-expired", className: "cursor-pointer", children: "Toon be\u00EBindigde relaties" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "hide-unknown", checked: hideUnknown, onCheckedChange: setHideUnknown }), _jsx(Label, { htmlFor: "hide-unknown", className: "cursor-pointer", children: "Verberg organisaties zonder naam" }), hideUnknown && hiddenCount > 0 ? (_jsxs("span", { className: "text-muted-foreground", children: ["(", hiddenCount, " verborgen)"] })) : null] })] }), _jsx("div", { className: "text-xs text-muted-foreground", children: isLoading ? ("Laden…") : (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium text-foreground", children: orgs.length.toLocaleString("nl-NL") }), " ", "weergegeven van", " ", _jsx("span", { className: "font-medium text-foreground", children: totalMatching.toLocaleString("nl-NL") }), " ", "organisaties \u00B7 bron: Floriday", truncated && (_jsx("span", { className: "ml-1 text-warning", children: "(verfijn de zoekopdracht om alles te zien)" }))] })) }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Organisatie" }), _jsx(TableHead, { children: "GLN" }), _jsx(TableHead, { children: "Adres" }), _jsx(TableHead, { children: "Land" }), _jsx(TableHead, { children: "Type" }), _jsx(TableHead, { children: "Prijscategorie" }), _jsx(TableHead, { children: "Bijgewerkt" })] }) }), _jsx(TableBody, { children: isLoading ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center py-8 text-muted-foreground", children: "Laden..." }) })) : orgs.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center py-8 text-muted-foreground", children: "Geen organisaties gevonden." }) })) : (orgs.map((o) => {
|
|
46
|
+
const displayName = o.organization_name && o.organization_name.trim() !== ""
|
|
47
|
+
? o.organization_name
|
|
48
|
+
: null;
|
|
49
|
+
const addressBits = [
|
|
50
|
+
o.address_line,
|
|
51
|
+
[o.postal_code, o.city].filter(Boolean).join(" "),
|
|
52
|
+
].filter((x) => x && String(x).trim() !== "");
|
|
53
|
+
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}`));
|
|
54
|
+
})) })] }) }) })] }) }));
|
|
23
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NieuweKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NieuweKlant.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NieuweKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NieuweKlant.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,WAAW,gCA2T1B"}
|
|
@@ -5,8 +5,9 @@ import { useForm } from "react-hook-form";
|
|
|
5
5
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
6
6
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
7
7
|
import { useServerFn } from "@tanstack/react-start";
|
|
8
|
-
import { Button, Card, CardContent,
|
|
9
|
-
import {
|
|
8
|
+
import { Button, Card, CardContent, Input, Label, Textarea, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@flowselections/core";
|
|
9
|
+
import { UserRound, MapPin, Landmark, ReceiptText, ChevronDown, Check, AlertCircle, Users, } from "lucide-react";
|
|
10
|
+
import { BackLink } from "../../components/klanten/BackLink";
|
|
10
11
|
import { createCustomer, listCustomerClasses } from "../../lib/customers.functions";
|
|
11
12
|
import { customerCreateSchema } from "../../lib/customer-schemas";
|
|
12
13
|
export function NieuweKlant() {
|
|
@@ -15,12 +16,18 @@ export function NieuweKlant() {
|
|
|
15
16
|
const classesFn = useServerFn(listCustomerClasses);
|
|
16
17
|
const [dupes, setDupes] = useState([]);
|
|
17
18
|
const [pendingData, setPendingData] = useState(null);
|
|
19
|
+
const [open, setOpen] = useState({
|
|
20
|
+
algemeen: true, adres: true, bank: true, facturering: true,
|
|
21
|
+
});
|
|
22
|
+
const toggle = (k) => setOpen((s) => ({ ...s, [k]: !s[k] }));
|
|
18
23
|
const { data: classData } = useQuery({
|
|
19
24
|
queryKey: ["customer-classes"],
|
|
20
25
|
queryFn: () => classesFn({ data: undefined }),
|
|
21
26
|
});
|
|
22
27
|
const form = useForm({
|
|
23
28
|
resolver: zodResolver(customerCreateSchema),
|
|
29
|
+
mode: "onBlur",
|
|
30
|
+
reValidateMode: "onChange",
|
|
24
31
|
defaultValues: {
|
|
25
32
|
company_name: "", vat_number: "", contact_person: "", email: "", phone: "",
|
|
26
33
|
street: "", house_number: "", postal_code: "", city: "", country: "Nederland",
|
|
@@ -50,17 +57,25 @@ export function NieuweKlant() {
|
|
|
50
57
|
});
|
|
51
58
|
const onSubmit = (values) => {
|
|
52
59
|
setPendingData(values);
|
|
53
|
-
mut.mutate(values);
|
|
60
|
+
mut.mutate({ ...values, push_to_twinfield: true });
|
|
54
61
|
};
|
|
55
62
|
const forceCreate = () => {
|
|
56
63
|
if (!pendingData)
|
|
57
64
|
return;
|
|
58
65
|
setDupes([]);
|
|
59
|
-
mut.mutate({ ...pendingData, force: true });
|
|
66
|
+
mut.mutate({ ...pendingData, force: true, push_to_twinfield: true });
|
|
60
67
|
};
|
|
61
|
-
const
|
|
62
|
-
|
|
68
|
+
const errors = form.formState.errors;
|
|
69
|
+
const errorCount = Object.keys(errors).length;
|
|
70
|
+
const touched = form.formState.touchedFields;
|
|
71
|
+
const values = form.watch();
|
|
72
|
+
const fieldErr = (k) => errors[k]?.message;
|
|
73
|
+
const isValid = (k) => !errors[k] && !!touched[k] && !!values[k];
|
|
74
|
+
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: "Nieuwe klant" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Vul de gegevens in om een nieuwe klant aan te maken." })] })] }), errorCount > 0 && form.formState.isSubmitted && (_jsxs("div", { className: "mb-4 flex items-start gap-3 rounded-xl border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm", 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: "Er zijn een aantal velden nog niet correct 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: isValid("customer_number"), children: _jsx(Input, { ...form.register("customer_number") }) }), _jsx(Field, { label: "Bedrijfsnaam", required: true, error: fieldErr("company_name"), success: isValid("company_name"), 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://", ...form.register("website") }) }), _jsx(Field, { label: "BTW-nummer", error: fieldErr("vat_number"), success: isValid("vat_number"), children: _jsx(Input, { ...form.register("vat_number") }) }), _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: _jsx(Input, { placeholder: "bv. Bezoek, Post", ...form.register("address_type") }) }), _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"), children: _jsx(Input, { ...form.register("address_line1") }) }), _jsx(Field, { label: "Adresregel 2", error: fieldErr("address_line2"), 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", error: fieldErr("postal_code"), success: isValid("postal_code"), children: _jsx(Input, { ...form.register("postal_code") }) }), _jsx(Field, { label: "Plaats", error: fieldErr("city"), success: isValid("city"), children: _jsx(Input, { ...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", children: _jsx(Input, { placeholder: "NL00 BANK 0000 0000 00", ...form.register("bank_iban") }) }), _jsx(Field, { label: "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", children: _jsx(Input, { ...form.register("bank_postal_code") }) }), _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", ...form.register("discount_percentage", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Aantal kortingsdagen", error: fieldErr("discount_days"), children: _jsx(Input, { type: "number", ...form.register("discount_days", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Aantal vervaldagen", error: fieldErr("payment_term_days"), children: _jsx(Input, { type: "number", ...form.register("payment_term_days", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Btw-weergave op factuur", error: fieldErr("vat_display"), children: _jsxs(Select, { value: form.watch("vat_display") || "", onValueChange: (v) => form.setValue("vat_display", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "bv. Inclusief, Exclusief" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "Inclusief", children: "Inclusief" }), _jsx(SelectItem, { value: "Exclusief", children: "Exclusief" })] })] }) }), _jsxs("div", { className: "md:col-span-2 space-y-1.5", children: [_jsx(Label, { children: "Notities" }), _jsx(Textarea, { rows: 4, ...form.register("notes") })] })] }) })] })] }), _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: () => navigate({ to: "/klanten" }), children: "Annuleren" }), _jsxs(Button, { type: "button", onClick: form.handleSubmit(onSubmit), disabled: mut.isPending, children: [_jsx(Check, { className: "h-4 w-4 mr-2" }), mut.isPending ? "Opslaan..." : "Klant aanmaken"] })] }) }), _jsx(AlertDialog, { open: dupes.length > 0, onOpenChange: (o) => !o && setDupes([]), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Mogelijke duplicaten gevonden" }), _jsx(AlertDialogDescription, { children: _jsx("ul", { className: "list-disc pl-5 space-y-1 mt-2", children: dupes.map((d) => _jsxs("li", { children: [_jsx("strong", { children: d.company_name }), " \u2014 ", d.reason] }, d.id + d.reason)) }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: forceCreate, children: "Toch aanmaken" })] })] }) })] }));
|
|
75
|
+
}
|
|
76
|
+
function Section({ k, title, icon, open, onToggle, children, }) {
|
|
77
|
+
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 }))] }));
|
|
63
78
|
}
|
|
64
|
-
function Field({ label, error, children }) {
|
|
65
|
-
return (_jsxs("div", { className: "space-y-1.5", children: [
|
|
79
|
+
function Field({ label, required, error, success, children, }) {
|
|
80
|
+
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] })] }));
|
|
66
81
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PriceGroup } from "../../lib/price-groups.functions";
|
|
2
|
+
export declare function PriceGroupBadges({ groups, max, emptyLabel, }: {
|
|
3
|
+
groups: PriceGroup[] | null | undefined;
|
|
4
|
+
max?: number;
|
|
5
|
+
emptyLabel?: string;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=PriceGroupBadges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PriceGroupBadges.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/PriceGroupBadges.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,GAAO,EACP,UAAgB,GACjB,EAAE;IACD,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,+BAoCA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from "@flowselections/core";
|
|
3
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
4
|
+
export function PriceGroupBadges({ groups, max = 1, emptyLabel = "—", }) {
|
|
5
|
+
const list = groups ?? [];
|
|
6
|
+
if (list.length === 0) {
|
|
7
|
+
return _jsx("span", { className: "text-muted-foreground", children: emptyLabel });
|
|
8
|
+
}
|
|
9
|
+
const visible = list.slice(0, max);
|
|
10
|
+
const extra = list.length - visible.length;
|
|
11
|
+
const tooltip = list.map((g) => g.name).join(", ");
|
|
12
|
+
return (_jsxs(TooltipPrimitive.Root, { children: [_jsx(TooltipPrimitive.Trigger, { asChild: true, children: _jsxs("span", { className: "inline-flex items-center gap-1 flex-wrap", children: [visible.map((g) => (_jsx(Badge, { variant: "secondary", className: "max-w-[160px] truncate", children: g.name }, g.id))), extra > 0 && (_jsxs(Badge, { variant: "outline", className: "text-xs", children: ["+", extra] }))] }) }), _jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { side: "top", sideOffset: 6, className: "z-50 max-w-xs rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md", children: [tooltip, _jsx(TooltipPrimitive.Arrow, { className: "fill-popover" })] }) })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncStatusChip.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SyncStatusChip.tsx"],"names":[],"mappings":"AAUA,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCAsEpE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { Badge } from "@flowselections/core";
|
|
6
|
+
import { CheckCircle2, Clock, AlertTriangle, Loader2 } from "lucide-react";
|
|
7
|
+
import { getTwinfieldSyncStatus, retryTwinfieldPush, } from "../../lib/accounting/twinfield/worker.functions";
|
|
8
|
+
export function SyncStatusChip({ customerId }) {
|
|
9
|
+
const qc = useQueryClient();
|
|
10
|
+
const [retrying, setRetrying] = useState(false);
|
|
11
|
+
const fetchStatus = useServerFn(getTwinfieldSyncStatus);
|
|
12
|
+
const doRetry = useServerFn(retryTwinfieldPush);
|
|
13
|
+
const { data } = useQuery({
|
|
14
|
+
queryKey: ["twinfield-sync-status", customerId],
|
|
15
|
+
queryFn: () => fetchStatus({ data: { customerId } }),
|
|
16
|
+
refetchInterval: 30000,
|
|
17
|
+
staleTime: 15000,
|
|
18
|
+
});
|
|
19
|
+
const retry = useMutation({
|
|
20
|
+
mutationFn: async () => {
|
|
21
|
+
setRetrying(true);
|
|
22
|
+
return doRetry({ data: { customerId } });
|
|
23
|
+
},
|
|
24
|
+
onSettled: () => {
|
|
25
|
+
setRetrying(false);
|
|
26
|
+
qc.invalidateQueries({ queryKey: ["twinfield-sync-status", customerId] });
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
if (!data)
|
|
30
|
+
return null;
|
|
31
|
+
const latest = data.latest;
|
|
32
|
+
const customer = data.customer;
|
|
33
|
+
const synced = customer?.twinfield_synced_at;
|
|
34
|
+
const status = latest?.status;
|
|
35
|
+
// Toon niets als er nooit een queue-rij is geweest én er ook geen sync bestaat
|
|
36
|
+
if (!synced && !latest)
|
|
37
|
+
return null;
|
|
38
|
+
if (status === "failed" || status === "dead") {
|
|
39
|
+
return (_jsx("button", { type: "button", onClick: () => !retrying && retry.mutate(), className: "inline-flex items-center gap-1.5 text-xs", title: latest?.last_error ?? "Onbekende fout", children: _jsxs(Badge, { variant: "destructive", className: "gap-1", children: [retrying ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(AlertTriangle, { className: "h-3 w-3" }), "Synchronisatie mislukt \u2014 opnieuw proberen"] }) }));
|
|
40
|
+
}
|
|
41
|
+
if (status === "pending" || status === "processing") {
|
|
42
|
+
return (_jsxs(Badge, { variant: "secondary", className: "gap-1", children: [_jsx(Clock, { className: "h-3 w-3" }), "Wacht op synchronisatie"] }));
|
|
43
|
+
}
|
|
44
|
+
if (synced) {
|
|
45
|
+
const when = new Date(synced).toLocaleString("nl-NL", { dateStyle: "short", timeStyle: "short" });
|
|
46
|
+
return (_jsxs(Badge, { variant: "outline", className: "gap-1 text-emerald-700 border-emerald-200 bg-emerald-50", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), "Gesynchroniseerd \u00B7 ", when] }));
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function CustomerPickerDialog({ priceGroupId, open, onOpenChange, value, onChange, }: {
|
|
2
|
+
priceGroupId?: string;
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (v: boolean) => void;
|
|
5
|
+
value?: string[];
|
|
6
|
+
onChange?: (keys: string[]) => void;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=CustomerPickerDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CustomerPickerDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/CustomerPickerDialog.tsx"],"names":[],"mappings":"AAaA,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,GAClD,EAAE;IACD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACrC,+BA+HA"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Button, Input, Checkbox, Badge, } from "@flowselections/core";
|
|
6
|
+
import { Search } from "lucide-react";
|
|
7
|
+
import { listExternalCustomers } from "../../../lib/external-customers.functions";
|
|
8
|
+
import { setPriceGroupMembers, getCustomerPriceGroup, } from "../../../lib/price-groups-local.functions";
|
|
9
|
+
export function CustomerPickerDialog({ priceGroupId, open, onOpenChange, value, onChange, }) {
|
|
10
|
+
const qc = useQueryClient();
|
|
11
|
+
const listCustomers = useServerFn(listExternalCustomers);
|
|
12
|
+
const getGroup = useServerFn(getCustomerPriceGroup);
|
|
13
|
+
const setMembers = useServerFn(setPriceGroupMembers);
|
|
14
|
+
const [search, setSearch] = useState("");
|
|
15
|
+
const [selected, setSelected] = useState(new Set());
|
|
16
|
+
const isControlled = onChange !== undefined;
|
|
17
|
+
const { data: customersData, isLoading } = useQuery({
|
|
18
|
+
queryKey: ["external-customers-picker"],
|
|
19
|
+
queryFn: () => listCustomers({ data: { search: "", status: "all", limit: 2000 } }),
|
|
20
|
+
enabled: open,
|
|
21
|
+
});
|
|
22
|
+
const { data: group } = useQuery({
|
|
23
|
+
queryKey: ["customer-price-group", priceGroupId],
|
|
24
|
+
queryFn: () => getGroup({ data: { id: priceGroupId } }),
|
|
25
|
+
enabled: open && !!priceGroupId && !isControlled,
|
|
26
|
+
});
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!open)
|
|
29
|
+
return;
|
|
30
|
+
if (isControlled) {
|
|
31
|
+
setSelected(new Set(value ?? []));
|
|
32
|
+
}
|
|
33
|
+
else if (group?.customerKeys) {
|
|
34
|
+
setSelected(new Set(group.customerKeys));
|
|
35
|
+
}
|
|
36
|
+
}, [group, open, isControlled, value]);
|
|
37
|
+
const customers = customersData?.customers ?? [];
|
|
38
|
+
const filtered = useMemo(() => {
|
|
39
|
+
const q = search.trim().toLowerCase();
|
|
40
|
+
if (!q)
|
|
41
|
+
return customers;
|
|
42
|
+
return customers.filter((c) => [c.display_name, c.commercial_name, c.city, c.email, c.vat_number]
|
|
43
|
+
.filter(Boolean).some((v) => String(v).toLowerCase().includes(q)));
|
|
44
|
+
}, [customers, search]);
|
|
45
|
+
const toggle = (key) => {
|
|
46
|
+
setSelected((prev) => {
|
|
47
|
+
const n = new Set(prev);
|
|
48
|
+
if (n.has(key))
|
|
49
|
+
n.delete(key);
|
|
50
|
+
else
|
|
51
|
+
n.add(key);
|
|
52
|
+
return n;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const saveMut = useMutation({
|
|
56
|
+
mutationFn: () => setMembers({ data: { id: priceGroupId, customerKeys: Array.from(selected) } }),
|
|
57
|
+
onSuccess: () => {
|
|
58
|
+
qc.invalidateQueries({ queryKey: ["customer-price-groups"] });
|
|
59
|
+
qc.invalidateQueries({ queryKey: ["customer-price-group", priceGroupId] });
|
|
60
|
+
onOpenChange(false);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const handleSave = () => {
|
|
64
|
+
if (isControlled) {
|
|
65
|
+
onChange(Array.from(selected));
|
|
66
|
+
onOpenChange(false);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
saveMut.mutate();
|
|
70
|
+
};
|
|
71
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-[720px]", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Klanten toewijzen" }) }), _jsxs("div", { className: "relative", 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 naam, plaats of BTW-nummer\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [_jsxs("span", { children: [filtered.length, " klanten"] }), _jsxs("span", { children: [_jsx("strong", { className: "text-foreground", children: selected.size }), " geselecteerd"] })] }), _jsx("div", { className: "max-h-[380px] overflow-y-auto border rounded-md divide-y", children: isLoading ? (_jsx("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "Laden\u2026" })) : filtered.length === 0 ? (_jsx("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "Geen klanten gevonden." })) : filtered.map((c) => {
|
|
72
|
+
const checked = selected.has(c.group_key);
|
|
73
|
+
return (_jsxs("label", { className: "flex items-center gap-3 p-2.5 hover:bg-muted/50 cursor-pointer", children: [_jsx(Checkbox, { checked: checked, onCheckedChange: () => toggle(c.group_key) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: c.display_name }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: [c.city, c.country].filter(Boolean).join(" · ") || "—" })] }), _jsx("div", { className: "flex gap-1", children: c.providers.slice(0, 3).map((p) => (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: p }, p))) })] }, c.group_key));
|
|
74
|
+
}) }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsx(Button, { onClick: handleSave, disabled: !isControlled && saveMut.isPending, children: !isControlled && saveMut.isPending ? "Opslaan…" : "Selectie opslaan" })] })] }) }));
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PriceGroupFormPage.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/PriceGroupFormPage.tsx"],"names":[],"mappings":"AAkLA,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,+BA0rB9E"}
|