@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"asset_id": "3e27c03f-e308-40e7-ac35-07bf1161c1e2",
|
|
4
|
+
"project_id": "dfdc14cf-30fe-4e0b-8abc-a3ae973943d8",
|
|
5
|
+
"url": "/__l5e/assets-v1/3e27c03f-e308-40e7-ac35-07bf1161c1e2/floriday-logo.png",
|
|
6
|
+
"r2_key": "a/v1/dfdc14cf-30fe-4e0b-8abc-a3ae973943d8/3e27c03f-e308-40e7-ac35-07bf1161c1e2/floriday-logo.png",
|
|
7
|
+
"original_filename": "floriday-logo.png",
|
|
8
|
+
"size": 6921,
|
|
9
|
+
"content_type": "image/png",
|
|
10
|
+
"created_at": "2026-07-07T13:17:11Z"
|
|
11
|
+
}
|
|
@@ -12,7 +12,7 @@ function initials(name) {
|
|
|
12
12
|
}
|
|
13
13
|
export function CustomerAvatar({ logoUrl, name, size = "md", className, }) {
|
|
14
14
|
const sizeClass = size === "sm" ? "h-8 w-8 text-xs" : size === "lg" ? "h-16 w-16 text-lg" : "h-10 w-10 text-sm";
|
|
15
|
-
return (_jsx("div", { className: cn("relative shrink-0 overflow-hidden rounded-
|
|
15
|
+
return (_jsx("div", { className: cn("relative shrink-0 overflow-hidden rounded-md bg-white flex items-center justify-center font-medium text-muted-foreground border border-border", sizeClass, className), children: logoUrl ? (_jsx("img", { src: logoUrl, alt: name ?? "", className: "h-full w-full object-contain p-1", onError: (e) => {
|
|
16
16
|
e.currentTarget.style.display = "none";
|
|
17
17
|
} })) : (_jsx("span", { children: initials(name) })) }));
|
|
18
18
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
to?: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Consistent back link used across the Klanten module.
|
|
8
|
+
* Matches the styling of the "Nieuwe klant" page.
|
|
9
|
+
*/
|
|
10
|
+
export declare function BackLink({ to, label, className }: Props): import("react").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=BackLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackLink.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/BackLink.tsx"],"names":[],"mappings":"AAGA,KAAK,KAAK,GAAG;IACX,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAe,EAAE,KAA4B,EAAE,SAAc,EAAE,EAAE,KAAK,+BAchG"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowLeft } from "lucide-react";
|
|
3
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
4
|
+
/**
|
|
5
|
+
* Consistent back link used across the Klanten module.
|
|
6
|
+
* Matches the styling of the "Nieuwe klant" page.
|
|
7
|
+
*/
|
|
8
|
+
export function BackLink({ to = "/klanten", label = "Terug naar klanten", className = "" }) {
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
return (_jsxs("button", { type: "button", onClick: () => navigate({ to }), className: "inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors mb-6 " +
|
|
11
|
+
className, children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), " ", label] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FloridayAssortmentTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/FloridayAssortmentTab.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,qBAAqB,CAAC,EAAE,cAAc,EAAE,EAAE;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,+BAqKnF"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { Card, CardContent, Badge, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
|
|
6
|
+
import { Search, ImageOff } from "lucide-react";
|
|
7
|
+
import { listFloridayCustomerAssortment, } from "../../lib/floriday-assortment.functions";
|
|
8
|
+
function formatPrice(price, currency, unit) {
|
|
9
|
+
if (price == null)
|
|
10
|
+
return "—";
|
|
11
|
+
const cur = currency ?? "EUR";
|
|
12
|
+
let formatted;
|
|
13
|
+
try {
|
|
14
|
+
formatted = new Intl.NumberFormat("nl-NL", { style: "currency", currency: cur }).format(price);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
formatted = `${cur} ${price.toFixed(2)}`;
|
|
18
|
+
}
|
|
19
|
+
return unit ? `${formatted} / ${unit}` : formatted;
|
|
20
|
+
}
|
|
21
|
+
function formatDateRange(from, until) {
|
|
22
|
+
const fmt = (v) => (v ? new Date(v).toLocaleDateString("nl-NL") : null);
|
|
23
|
+
const f = fmt(from);
|
|
24
|
+
const u = fmt(until);
|
|
25
|
+
if (!f && !u)
|
|
26
|
+
return null;
|
|
27
|
+
return `${f ?? "…"} → ${u ?? "…"}`;
|
|
28
|
+
}
|
|
29
|
+
export function FloridayAssortmentTab({ organizationId }) {
|
|
30
|
+
const list = useServerFn(listFloridayCustomerAssortment);
|
|
31
|
+
const [search, setSearch] = useState("");
|
|
32
|
+
const [source, setSource] = useState("all");
|
|
33
|
+
const { data, isLoading, error } = useQuery({
|
|
34
|
+
queryKey: ["floriday-assortment", organizationId],
|
|
35
|
+
queryFn: () => list({ data: { organizationId } }),
|
|
36
|
+
enabled: !!organizationId,
|
|
37
|
+
staleTime: 60000,
|
|
38
|
+
});
|
|
39
|
+
const filtered = useMemo(() => {
|
|
40
|
+
const rows = data?.rows ?? [];
|
|
41
|
+
const q = search.trim().toLowerCase();
|
|
42
|
+
return rows.filter((r) => {
|
|
43
|
+
if (source !== "all" && r.source !== source)
|
|
44
|
+
return false;
|
|
45
|
+
if (!q)
|
|
46
|
+
return true;
|
|
47
|
+
return ((r.trade_item_name ?? "").toLowerCase().includes(q) ||
|
|
48
|
+
(r.vbn_code ?? "").toLowerCase().includes(q) ||
|
|
49
|
+
r.trade_item_id.toLowerCase().includes(q) ||
|
|
50
|
+
r.source_label.toLowerCase().includes(q));
|
|
51
|
+
});
|
|
52
|
+
}, [data, search, source]);
|
|
53
|
+
if (isLoading) {
|
|
54
|
+
return _jsx(Card, { children: _jsx(CardContent, { className: "py-8 text-sm text-muted-foreground", children: "Assortiment laden\u2026" }) });
|
|
55
|
+
}
|
|
56
|
+
if (error) {
|
|
57
|
+
return (_jsx(Card, { children: _jsx(CardContent, { className: "py-6 text-sm text-destructive", children: error.message }) }));
|
|
58
|
+
}
|
|
59
|
+
const s = data?.summary;
|
|
60
|
+
const rows = data?.rows ?? [];
|
|
61
|
+
if (rows.length === 0) {
|
|
62
|
+
return (_jsx(Card, { children: _jsxs(CardContent, { className: "py-10 text-center space-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Geen Floriday-assortiment beschikbaar" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Deze klant zit nog niet in een Floriday-prijsgroep of contract." })] }) }));
|
|
63
|
+
}
|
|
64
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [_jsxs("span", { children: [_jsx("strong", { className: "text-foreground", children: s?.price_groups ?? 0 }), " prijsgroepen"] }), _jsxs("span", { children: [_jsx("strong", { className: "text-foreground", children: s?.contracts ?? 0 }), " contracten"] }), _jsxs("span", { children: [_jsx("strong", { className: "text-foreground", children: s?.unique_items ?? 0 }), " artikelen"] }), s?.last_synced_at && (_jsxs("span", { children: ["laatste sync: ", new Date(s.last_synced_at).toLocaleString("nl-NL")] }))] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", 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 naam, VBN-code of bron\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsx("div", { className: "flex items-center gap-1", children: ["all", "price_group", "contract"].map((v) => {
|
|
65
|
+
const label = v === "all" ? "Alle" : v === "price_group" ? "Prijsgroep" : "Contract";
|
|
66
|
+
const active = source === v;
|
|
67
|
+
return (_jsx("button", { type: "button", onClick: () => setSource(v), className: "inline-flex items-center rounded-full border px-3 py-1 text-xs transition-colors " +
|
|
68
|
+
(active
|
|
69
|
+
? "bg-primary text-primary-foreground border-primary"
|
|
70
|
+
: "bg-card text-muted-foreground border-border hover:bg-muted"), children: label }, v));
|
|
71
|
+
}) })] }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[64px]" }), _jsx(TableHead, { children: "Artikel" }), _jsx(TableHead, { children: "Kenmerken" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { className: "text-right", children: "Prijs" })] }) }), _jsx(TableBody, { children: filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "text-center py-6 text-muted-foreground text-sm", children: "Geen resultaten voor deze filter." }) })) : filtered.map((r) => {
|
|
72
|
+
const chars = [
|
|
73
|
+
r.color,
|
|
74
|
+
r.length_cm != null ? `${r.length_cm} cm` : null,
|
|
75
|
+
r.packaging,
|
|
76
|
+
].filter(Boolean);
|
|
77
|
+
const range = formatDateRange(r.valid_from, r.valid_until);
|
|
78
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: r.image_url ? (_jsx("img", { src: r.image_url, alt: "", loading: "lazy", className: "h-10 w-10 rounded object-cover bg-muted" })) : (_jsx("div", { className: "h-10 w-10 rounded bg-muted flex items-center justify-center text-muted-foreground", children: _jsx(ImageOff, { className: "h-4 w-4" }) })) }), _jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: r.trade_item_name ?? r.trade_item_id }), r.vbn_code && (_jsxs("div", { className: "text-xs text-muted-foreground font-mono", children: ["VBN ", r.vbn_code] }))] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: chars.length ? chars.join(" · ") : "—" }), _jsxs(TableCell, { children: [_jsx(Badge, { variant: r.source === "contract" ? "default" : "secondary", title: range ?? undefined, children: r.source_label }), range && (_jsx("div", { className: "text-[10px] text-muted-foreground mt-1", children: range }))] }), _jsx(TableCell, { className: "text-right whitespace-nowrap tabular-nums", children: formatPrice(r.price, r.currency, r.unit) })] }, r.key));
|
|
79
|
+
}) })] }) }) })] }));
|
|
80
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
organizationId?: string | null;
|
|
3
|
+
size?: "sm" | "default";
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function FloridayDeeplinkButton({ organizationId, size, className }: Props): import("react").JSX.Element | null;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=FloridayDeeplinkButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FloridayDeeplinkButton.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/FloridayDeeplinkButton.tsx"],"names":[],"mappings":"AAGA,KAAK,KAAK,GAAG;IACX,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,EAAE,cAAc,EAAE,IAAW,EAAE,SAAS,EAAE,EAAE,KAAK,sCAWvF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "@flowselections/core";
|
|
3
|
+
import floridayLogo from "../../assets/floriday-logo.png.asset.json";
|
|
4
|
+
export function FloridayDeeplinkButton({ organizationId, size = "sm", className }) {
|
|
5
|
+
if (!organizationId)
|
|
6
|
+
return null;
|
|
7
|
+
const url = `https://app.floriday.io/match-connect/network/customers/${organizationId}/details`;
|
|
8
|
+
return (_jsx(Button, { variant: "outline", size: size, asChild: true, className: className, children: _jsxs("a", { href: url, target: "_blank", rel: "noopener noreferrer", children: [_jsx("img", { src: floridayLogo.url, alt: "", className: "h-4 w-4 mr-2" }), "Bekijken in Floriday"] }) }));
|
|
9
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GroupedKlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/GroupedKlantDetail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GroupedKlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/GroupedKlantDetail.tsx"],"names":[],"mappings":"AA0BA,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BA0OpE"}
|
|
@@ -3,14 +3,20 @@ import { useNavigate } from "@tanstack/react-router";
|
|
|
3
3
|
import { useQuery } from "@tanstack/react-query";
|
|
4
4
|
import { useServerFn } from "@tanstack/react-start";
|
|
5
5
|
import { useState } from "react";
|
|
6
|
-
import { Card, CardContent, Badge,
|
|
7
|
-
import {
|
|
6
|
+
import { Card, CardContent, Badge, Tabs, TabsList, TabsTrigger, TabsContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
|
|
7
|
+
import { Mail, Phone } from "lucide-react";
|
|
8
|
+
import { BackLink } from "../../components/klanten/BackLink";
|
|
8
9
|
import { getGroupedCustomerDetail, } from "../../lib/external-customers.functions";
|
|
9
10
|
import { ProfileCard, KpiStrip, BadgeList, EmptyState, adaptGroupedToProfile, } from "../../components/klanten/KlantDetailLayout";
|
|
10
11
|
import { DiscountsTab } from "../../components/klanten/DiscountsTab";
|
|
12
|
+
import { PriceGroupBadges } from "../../components/klanten/PriceGroupBadges";
|
|
13
|
+
import { listPriceGroupsForOrganizations } from "../../lib/price-groups.functions";
|
|
14
|
+
import { FloridayDeeplinkButton } from "../../components/klanten/FloridayDeeplinkButton";
|
|
15
|
+
import { FloridayAssortmentTab } from "../../components/klanten/FloridayAssortmentTab";
|
|
11
16
|
export function GroupedKlantDetail({ groupKey }) {
|
|
12
17
|
const navigate = useNavigate();
|
|
13
18
|
const get = useServerFn(getGroupedCustomerDetail);
|
|
19
|
+
const listPg = useServerFn(listPriceGroupsForOrganizations);
|
|
14
20
|
const [tab, setTab] = useState("overview");
|
|
15
21
|
const { data, isLoading, error } = useQuery({
|
|
16
22
|
queryKey: ["grouped-customer", groupKey],
|
|
@@ -19,7 +25,7 @@ export function GroupedKlantDetail({ groupKey }) {
|
|
|
19
25
|
if (isLoading)
|
|
20
26
|
return _jsx("main", { className: "p-6", children: "Laden..." });
|
|
21
27
|
if (error || !data) {
|
|
22
|
-
return (_jsxs("main", { className: "p-6 space-y-3", children: [
|
|
28
|
+
return (_jsxs("main", { className: "p-6 space-y-3", children: [_jsx(BackLink, {}), _jsx("p", { className: "text-sm text-destructive", children: error instanceof Error ? error.message : "Klant niet gevonden." })] }));
|
|
23
29
|
}
|
|
24
30
|
const g = data.group;
|
|
25
31
|
const providerMap = new Map(data.providers.map((p) => [p.provider, p]));
|
|
@@ -27,6 +33,14 @@ export function GroupedKlantDetail({ groupKey }) {
|
|
|
27
33
|
const profile = adaptGroupedToProfile(g);
|
|
28
34
|
const floridaySource = sources.find((s) => s.provider === "floriday");
|
|
29
35
|
const floridayBuyerId = floridaySource?.external_id ?? null;
|
|
36
|
+
const { data: pgData } = useQuery({
|
|
37
|
+
queryKey: ["price-groups", floridayBuyerId],
|
|
38
|
+
queryFn: () => listPg({ data: { organizationIds: floridayBuyerId ? [floridayBuyerId] : [] } }),
|
|
39
|
+
enabled: !!floridayBuyerId,
|
|
40
|
+
});
|
|
41
|
+
const priceGroups = floridayBuyerId
|
|
42
|
+
? pgData?.byOrganization?.[floridayBuyerId] ?? []
|
|
43
|
+
: [];
|
|
30
44
|
const contacts = collectContacts(sources, providerMap);
|
|
31
45
|
const locations = collectLocations(sources, providerMap);
|
|
32
46
|
const segments = collectArray(sources, ["segments"]);
|
|
@@ -43,13 +57,13 @@ export function GroupedKlantDetail({ groupKey }) {
|
|
|
43
57
|
provider: providerMap.get(s.provider)?.label ?? s.provider,
|
|
44
58
|
}))
|
|
45
59
|
.sort((a, b) => +new Date(b.created_at) - +new Date(a.created_at));
|
|
46
|
-
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center justify-
|
|
60
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), _jsx("div", { className: "flex items-center justify-end", children: _jsx(FloridayDeeplinkButton, { organizationId: floridayBuyerId }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: profile }), _jsxs("div", { className: "space-y-4 min-w-0", children: [priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] })), _jsx(KpiStrip, { counts: {
|
|
47
61
|
contacts: contacts.length,
|
|
48
62
|
locations: locations.length,
|
|
49
63
|
assortment: 0,
|
|
50
64
|
orders: 0,
|
|
51
65
|
lastSync: g.fetched_at,
|
|
52
|
-
}, onSelect: setTab }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { className: "flex-wrap", children: [_jsx(TabsTrigger, { value: "overview", children: "Overzicht" }), _jsxs(TabsTrigger, { value: "contacts", children: ["Contacten (", contacts.length, ")"] }), _jsxs(TabsTrigger, { value: "locations", children: ["Locaties (", locations.length, ")"] }), _jsx(TabsTrigger, { value: "assortment", children: "Assortiment (0)" }), _jsx(TabsTrigger, { value: "orders", children: "Bestellingen (0)" }), _jsx(TabsTrigger, { value: "trade", children: "Handel" }), floridayBuyerId && (_jsx(TabsTrigger, { value: "discounts", children: "Kortingen & prijzen" })), _jsx(TabsTrigger, { value: "activity", children: "Activiteit" }), g.is_merged && sources.length > 1 && (_jsxs(TabsTrigger, { value: "sources", children: ["Bronnen (", sources.length, ")"] }))] }), _jsxs(TabsContent, { value: "overview", className: "space-y-4", children: [(segments.length || tradeForms.length || markets.length || productGroups.length) ? (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 grid gap-4 md:grid-cols-2", children: [_jsx(BadgeList, { label: "Segmenten", items: segments }), _jsx(BadgeList, { label: "Handelsvorm", items: tradeForms }), _jsx(BadgeList, { label: "Afzetmarkten", items: markets }), _jsx(BadgeList, { label: "Productgroepen", items: productGroups })] }) })) : null, paymentMethods.length > 0 && (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsx(BadgeList, { label: "Betaalmethoden", items: paymentMethods }) }) })), !segments.length && !tradeForms.length && !markets.length && !productGroups.length && !paymentMethods.length && (_jsx(EmptyState, { message: "Geen extra profielinformatie beschikbaar voor deze klant." }))] }), _jsx(TabsContent, { value: "contacts", children: contacts.length === 0 ? (_jsx(EmptyState, { message: "Geen contactpersonen beschikbaar via de koppeling." })) : (_jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: contacts.map((p, i) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsx("div", { className: "font-semibold", children: p.full_name }), p.job_title && _jsx("div", { className: "text-sm text-muted-foreground", children: p.job_title }), p.phone && (_jsxs("a", { href: `tel:${p.phone}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.phone] })), p.email && (_jsxs("a", { href: `mailto:${p.email}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.email] })), _jsx(Badge, { variant: "secondary", children: p.source_label })] }) }, i))) })) }), _jsx(TabsContent, { value: "locations", children: locations.length === 0 ? (_jsx(EmptyState, { message: "Geen afleverlocaties beschikbaar via de koppeling." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Adres" }), _jsx(TableHead, { children: "GLN" }), _jsx(TableHead, { children: "Postcode" }), _jsx(TableHead, { children: "Bron" })] }) }), _jsx(TableBody, { children: locations.map((l, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: l.name || l.address_line || "—" }), l.name && l.address_line && (_jsxs("div", { className: "text-xs text-muted-foreground", children: [l.address_line, ", ", l.city ?? ""] }))] }), _jsx(TableCell, { className: "font-mono text-sm", children: l.gln || "—" }), _jsx(TableCell, { className: "text-sm", children: [l.postal_code, l.country].filter(Boolean).join(" · ") }), _jsx(TableCell, { children: _jsx(Badge, { variant: "secondary", children: l.source_label }) })] }, i))) })] }) }) })) }), _jsx(TabsContent, { value: "assortment", children: _jsx(EmptyState, { message: "Assortiment is beschikbaar zodra de koppeling dit levert." }) }), _jsx(TabsContent, { value: "orders", children: _jsx(EmptyState, { message: "Bestellingen zijn beschikbaar zodra de koppeling dit levert." }) }), _jsx(TabsContent, { value: "trade", children: _jsx(EmptyState, { message: "Handelsinstellingen zijn niet beschikbaar via deze koppeling." }) }), floridayBuyerId && (_jsx(TabsContent, { value: "discounts", children: _jsx(DiscountsTab, { buyerOrganizationId: floridayBuyerId }) })), _jsx(TabsContent, { value: "activity", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: activity.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen activiteit voor deze klant." })) : (activity.map((a) => (_jsxs("div", { className: "flex items-center justify-between text-sm border-l-2 border-primary pl-3 py-1", children: [_jsxs("span", { children: ["Bijgewerkt via ", _jsx("strong", { children: a.provider })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: new Date(a.created_at).toLocaleString("nl-NL") })] }, a.id)))) }) }) }), g.is_merged && sources.length > 1 && (_jsx(TabsContent, { value: "sources", children: _jsx(CompareCard, { sources: sources, providerMap: providerMap }) }))] })] })] })] }));
|
|
66
|
+
}, onSelect: setTab }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { className: "flex-wrap", children: [_jsx(TabsTrigger, { value: "overview", children: "Overzicht" }), _jsxs(TabsTrigger, { value: "contacts", children: ["Contacten (", contacts.length, ")"] }), _jsxs(TabsTrigger, { value: "locations", children: ["Locaties (", locations.length, ")"] }), _jsx(TabsTrigger, { value: "assortment", children: "Assortiment (0)" }), _jsx(TabsTrigger, { value: "orders", children: "Bestellingen (0)" }), _jsx(TabsTrigger, { value: "trade", children: "Handel" }), floridayBuyerId && (_jsx(TabsTrigger, { value: "discounts", children: "Kortingen & prijzen" })), _jsx(TabsTrigger, { value: "activity", children: "Activiteit" }), g.is_merged && sources.length > 1 && (_jsxs(TabsTrigger, { value: "sources", children: ["Bronnen (", sources.length, ")"] }))] }), _jsxs(TabsContent, { value: "overview", className: "space-y-4", children: [(segments.length || tradeForms.length || markets.length || productGroups.length) ? (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 grid gap-4 md:grid-cols-2", children: [_jsx(BadgeList, { label: "Segmenten", items: segments }), _jsx(BadgeList, { label: "Handelsvorm", items: tradeForms }), _jsx(BadgeList, { label: "Afzetmarkten", items: markets }), _jsx(BadgeList, { label: "Productgroepen", items: productGroups })] }) })) : null, paymentMethods.length > 0 && (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsx(BadgeList, { label: "Betaalmethoden", items: paymentMethods }) }) })), !segments.length && !tradeForms.length && !markets.length && !productGroups.length && !paymentMethods.length && (_jsx(EmptyState, { message: "Geen extra profielinformatie beschikbaar voor deze klant." }))] }), _jsx(TabsContent, { value: "contacts", children: contacts.length === 0 ? (_jsx(EmptyState, { message: "Geen contactpersonen beschikbaar via de koppeling." })) : (_jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: contacts.map((p, i) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsx("div", { className: "font-semibold", children: p.full_name }), p.job_title && _jsx("div", { className: "text-sm text-muted-foreground", children: p.job_title }), p.phone && (_jsxs("a", { href: `tel:${p.phone}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.phone] })), p.email && (_jsxs("a", { href: `mailto:${p.email}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.email] })), _jsx(Badge, { variant: "secondary", children: p.source_label })] }) }, i))) })) }), _jsx(TabsContent, { value: "locations", children: locations.length === 0 ? (_jsx(EmptyState, { message: "Geen afleverlocaties beschikbaar via de koppeling." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Adres" }), _jsx(TableHead, { children: "GLN" }), _jsx(TableHead, { children: "Postcode" }), _jsx(TableHead, { children: "Bron" })] }) }), _jsx(TableBody, { children: locations.map((l, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: l.name || l.address_line || "—" }), l.name && l.address_line && (_jsxs("div", { className: "text-xs text-muted-foreground", children: [l.address_line, ", ", l.city ?? ""] }))] }), _jsx(TableCell, { className: "font-mono text-sm", children: l.gln || "—" }), _jsx(TableCell, { className: "text-sm", children: [l.postal_code, l.country].filter(Boolean).join(" · ") }), _jsx(TableCell, { children: _jsx(Badge, { variant: "secondary", children: l.source_label }) })] }, i))) })] }) }) })) }), _jsx(TabsContent, { value: "assortment", children: floridayBuyerId ? (_jsx(FloridayAssortmentTab, { organizationId: floridayBuyerId })) : (_jsx(EmptyState, { message: "Assortiment is beschikbaar zodra de koppeling dit levert." })) }), _jsx(TabsContent, { value: "orders", children: _jsx(EmptyState, { message: "Bestellingen zijn beschikbaar zodra de koppeling dit levert." }) }), _jsx(TabsContent, { value: "trade", children: _jsx(EmptyState, { message: "Handelsinstellingen zijn niet beschikbaar via deze koppeling." }) }), floridayBuyerId && (_jsx(TabsContent, { value: "discounts", children: _jsx(DiscountsTab, { buyerOrganizationId: floridayBuyerId }) })), _jsx(TabsContent, { value: "activity", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: activity.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen activiteit voor deze klant." })) : (activity.map((a) => (_jsxs("div", { className: "flex items-center justify-between text-sm border-l-2 border-primary pl-3 py-1", children: [_jsxs("span", { children: ["Bijgewerkt via ", _jsx("strong", { children: a.provider })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: new Date(a.created_at).toLocaleString("nl-NL") })] }, a.id)))) }) }) }), g.is_merged && sources.length > 1 && (_jsx(TabsContent, { value: "sources", children: _jsx(CompareCard, { sources: sources, providerMap: providerMap }) }))] })] })] })] }));
|
|
53
67
|
}
|
|
54
68
|
function collectContacts(sources, providerMap) {
|
|
55
69
|
const out = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImportKlanten.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ImportKlanten.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ImportKlanten.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ImportKlanten.tsx"],"names":[],"mappings":"AA6MA,wBAAgB,aAAa,gCAic5B"}
|
|
@@ -4,18 +4,29 @@ import { useEffect, useMemo, useState } from "react";
|
|
|
4
4
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
5
|
import { useServerFn } from "@tanstack/react-start";
|
|
6
6
|
import { Button, Card, CardContent, CardHeader, CardTitle, Input, Label, toast, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Badge, Checkbox, } from "@flowselections/core";
|
|
7
|
-
import {
|
|
7
|
+
import { Upload, Download, FileText, CheckCircle2, AlertTriangle, XCircle } from "lucide-react";
|
|
8
|
+
import { BackLink } from "../../components/klanten/BackLink";
|
|
8
9
|
import * as XLSX from "xlsx";
|
|
9
10
|
import { bulkImportCustomers } from "../../lib/customers.functions";
|
|
10
11
|
import { customerImportRowSchema } from "../../lib/customer-schemas";
|
|
11
|
-
|
|
12
|
+
// Alleen `company_name` is écht verplicht om te kunnen importeren. Overige
|
|
13
|
+
// velden mogen leeg zijn — ontbrekende gegevens worden na de import getoond
|
|
14
|
+
// als waarschuwing zodat je ze later kunt aanvullen.
|
|
15
|
+
const REQUIRED = ["company_name"];
|
|
16
|
+
const RECOMMENDED_FIELDS = [
|
|
17
|
+
{ key: "email", label: "E-mailadres" },
|
|
18
|
+
{ key: "street", label: "Straat" },
|
|
19
|
+
{ key: "house_number", label: "Huisnummer" },
|
|
20
|
+
{ key: "country", label: "Land" },
|
|
21
|
+
];
|
|
22
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
12
23
|
// Alle interne doelvelden die de importer ondersteunt (voor handmatige mapping)
|
|
13
24
|
const TARGET_FIELDS = [
|
|
14
25
|
{ key: "company_name", label: "Bedrijfsnaam", required: true },
|
|
15
|
-
{ key: "email", label: "E-mailadres"
|
|
16
|
-
{ key: "street", label: "Straat"
|
|
17
|
-
{ key: "house_number", label: "Huisnummer"
|
|
18
|
-
{ key: "country", label: "Land"
|
|
26
|
+
{ key: "email", label: "E-mailadres" },
|
|
27
|
+
{ key: "street", label: "Straat" },
|
|
28
|
+
{ key: "house_number", label: "Huisnummer" },
|
|
29
|
+
{ key: "country", label: "Land" },
|
|
19
30
|
{ key: "customer_number", label: "Klantnummer" },
|
|
20
31
|
{ key: "language", label: "Taal" },
|
|
21
32
|
{ key: "website", label: "Website" },
|
|
@@ -273,13 +284,18 @@ export function ImportKlanten() {
|
|
|
273
284
|
if (val !== undefined && val !== "")
|
|
274
285
|
r[target] = val;
|
|
275
286
|
}
|
|
276
|
-
// Fallback: als
|
|
277
|
-
// gevuld, gebruik dan die als straat (verplicht veld).
|
|
287
|
+
// Fallback: als straat ontbreekt maar adresregel 1 is gevuld, gebruik die.
|
|
278
288
|
const street = r.street || r.address_line1 || "";
|
|
279
|
-
const houseNumber = r.house_number || "
|
|
289
|
+
const houseNumber = r.house_number || "";
|
|
290
|
+
// E-mail sanitair: alleen doorlaten als het qua vorm lijkt op een
|
|
291
|
+
// e-mailadres. Ongeldige waarden worden weggelaten en als
|
|
292
|
+
// waarschuwing gerapporteerd.
|
|
293
|
+
const rawEmail = (r.email || "").trim();
|
|
294
|
+
const emailValid = rawEmail !== "" && EMAIL_RE.test(rawEmail);
|
|
295
|
+
const email = emailValid ? rawEmail : "";
|
|
280
296
|
const candidate = {
|
|
281
297
|
company_name: r.company_name ?? "",
|
|
282
|
-
email
|
|
298
|
+
email,
|
|
283
299
|
street,
|
|
284
300
|
house_number: houseNumber,
|
|
285
301
|
country: r.country ?? "",
|
|
@@ -320,13 +336,24 @@ export function ImportKlanten() {
|
|
|
320
336
|
payment_term_days: r.payment_term_days ?? "",
|
|
321
337
|
vat_display: r.vat_display ?? "",
|
|
322
338
|
};
|
|
339
|
+
const warnings = [];
|
|
340
|
+
for (const f of RECOMMENDED_FIELDS) {
|
|
341
|
+
const v = candidate[f.key];
|
|
342
|
+
if (v === undefined || v === null || v === "") {
|
|
343
|
+
warnings.push(`${f.label} ontbreekt`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (rawEmail !== "" && !emailValid) {
|
|
347
|
+
warnings.push("E-mailadres ongeldig — leeg gelaten");
|
|
348
|
+
}
|
|
323
349
|
const res = customerImportRowSchema.safeParse(candidate);
|
|
324
350
|
if (res.success)
|
|
325
|
-
return { raw: r, data: res.data, errors: [] };
|
|
351
|
+
return { raw: r, data: res.data, errors: [], warnings };
|
|
326
352
|
return {
|
|
327
353
|
raw: r,
|
|
328
354
|
data: null,
|
|
329
355
|
errors: res.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`),
|
|
356
|
+
warnings,
|
|
330
357
|
};
|
|
331
358
|
});
|
|
332
359
|
};
|
|
@@ -384,7 +411,7 @@ export function ImportKlanten() {
|
|
|
384
411
|
}
|
|
385
412
|
mut.mutate(valid);
|
|
386
413
|
};
|
|
387
|
-
return (_jsxs("main", { className: "p-6 max-w-5xl mx-auto space-y-4", children: [
|
|
414
|
+
return (_jsxs("main", { className: "p-6 max-w-5xl mx-auto space-y-4", children: [_jsx(BackLink, {}), _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 importeren" }), _jsxs("p", { className: "text-sm text-muted-foreground mt-1", children: ["Upload een CSV of Excel om meerdere klanten tegelijk toe te voegen. Alleen ", _jsx("strong", { children: "bedrijfsnaam" }), " is verplicht \u2014 ontbrekende gegevens zoals e-mail, adres of land worden na de import als waarschuwing getoond zodat je ze later kunt aanvullen."] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: downloadTemplateXlsx, children: [_jsx(Download, { className: "h-4 w-4 mr-2" }), " Template (.xlsx)"] }), _jsxs(Button, { variant: "outline", onClick: downloadTemplateCsv, children: [_jsx(Download, { className: "h-4 w-4 mr-2" }), " Template (.csv)"] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "1. Bestand kiezen" }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx(Label, { htmlFor: "csvfile", className: "sr-only", children: "Excel- of CSV-bestand" }), _jsx(Input, { id: "csvfile", type: "file", accept: ".csv,.xlsx,.xls,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel", onChange: (e) => { const f = e.target.files?.[0]; if (f)
|
|
388
415
|
handleFile(f); }, className: "max-w-sm" }), fileName && (_jsxs("span", { className: "inline-flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(FileText, { className: "h-4 w-4" }), " ", fileName] }))] }), _jsxs("label", { className: "inline-flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: skipDuplicates, onCheckedChange: (v) => setSkipDuplicates(v === true) }), "Duplicaten overslaan (op e-mail, BTW of vergelijkbare bedrijfsnaam)"] })] })] }), sourceHeaders.length > 0 && (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "text-base", children: ["2. Velden koppelen (", sourceHeaders.length, " kolommen)"] }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Kies per bronkolom naar welk klantveld deze moet worden ge\u00EFmporteerd." })] }), _jsx(CardContent, { className: "p-0", children: _jsx("div", { className: "max-h-[420px] overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Bronkolom (uit bestand)" }), _jsx(TableHead, { children: "Voorbeeldwaarde" }), _jsx(TableHead, { className: "w-[280px]", children: "Koppelen aan klantveld" })] }) }), _jsx(TableBody, { children: sourceHeaders.map((h) => {
|
|
389
416
|
const sample = sourceRows.find((r) => r[h])?.[h] ?? "";
|
|
390
417
|
return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-sm font-medium", children: h || _jsx("span", { className: "text-muted-foreground italic", children: "(zonder naam)" }) }), _jsx(TableCell, { className: "text-xs text-muted-foreground truncate max-w-[280px]", children: sample }), _jsx(TableCell, { children: _jsxs("select", { className: "w-full h-9 rounded-md border border-input bg-background px-2 text-sm", value: mapping[h] ?? "", onChange: (e) => setMapping((m) => ({ ...m, [h]: e.target.value })), children: [_jsx("option", { value: "", children: "\u2014 Negeren \u2014" }), TARGET_FIELDS.map((f) => (_jsxs("option", { value: f.key, children: [f.label, f.required ? " *" : ""] }, f.key)))] }) })] }, h));
|
|
@@ -395,7 +422,13 @@ export function ImportKlanten() {
|
|
|
395
422
|
setRows([]);
|
|
396
423
|
setFileName("");
|
|
397
424
|
setResult(null);
|
|
398
|
-
}, children: "Wissen" })] })] })), rows.length > 0 && (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "text-base", children: ["3. Voorbeeld (", rows.length, " rijen)"] }), _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsxs(Badge, { variant: "secondary", className: "bg-success/10 text-success", children: [_jsx(CheckCircle2, { className: "h-3 w-3 mr-1" }), " ", validCount, " geldig"] }), invalidCount > 0 && (_jsxs(Badge, { variant: "secondary", className: "bg-destructive/10 text-destructive", children: [_jsx(XCircle, { className: "h-3 w-3 mr-1" }), " ", invalidCount, " ongeldig"] }))] })] }), _jsx(CardContent, { className: "p-0", children: _jsx("div", { className: "max-h-[420px] overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-10", children: "#" }), _jsx(TableHead, { children: "Bedrijf" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Plaats" }), _jsx(TableHead, { children: "Land" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: rows.map((r, i) => (_jsxs(TableRow, { className: r.errors.length > 0
|
|
425
|
+
}, children: "Wissen" })] })] })), rows.length > 0 && (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "text-base", children: ["3. Voorbeeld (", rows.length, " rijen)"] }), _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsxs(Badge, { variant: "secondary", className: "bg-success/10 text-success", children: [_jsx(CheckCircle2, { className: "h-3 w-3 mr-1" }), " ", validCount, " geldig"] }), rows.filter((r) => r.errors.length === 0 && r.warnings.length > 0).length > 0 && (_jsxs(Badge, { variant: "secondary", className: "bg-warning/10 text-warning", children: [_jsx(AlertTriangle, { className: "h-3 w-3 mr-1" }), rows.filter((r) => r.errors.length === 0 && r.warnings.length > 0).length, " met waarschuwingen"] })), invalidCount > 0 && (_jsxs(Badge, { variant: "secondary", className: "bg-destructive/10 text-destructive", children: [_jsx(XCircle, { className: "h-3 w-3 mr-1" }), " ", invalidCount, " ongeldig"] }))] })] }), _jsx(CardContent, { className: "p-0", children: _jsx("div", { className: "max-h-[420px] overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-10", children: "#" }), _jsx(TableHead, { children: "Bedrijf" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Plaats" }), _jsx(TableHead, { children: "Land" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: rows.map((r, i) => (_jsxs(TableRow, { className: r.errors.length > 0
|
|
426
|
+
? "bg-destructive/5"
|
|
427
|
+
: r.warnings.length > 0
|
|
428
|
+
? "bg-warning/5"
|
|
429
|
+
: "", children: [_jsx(TableCell, { className: "text-xs text-muted-foreground", children: i + 1 }), _jsx(TableCell, { className: "text-sm", children: r.raw.company_name || _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "text-sm", children: r.raw.email || _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "text-sm", children: r.raw.city || _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "text-sm", children: r.raw.country || _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { children: r.errors.length > 0 ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs text-destructive", title: r.errors.join("\n"), children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5" }), " ", r.errors[0]] })) : r.warnings.length > 0 ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs text-warning", title: r.warnings.join("\n"), children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5" }), r.warnings.length === 1
|
|
430
|
+
? r.warnings[0]
|
|
431
|
+
: `${r.warnings.length} waarschuwingen`] })) : (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs text-success", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), " OK"] })) })] }, i))) })] }) }) }), _jsxs("div", { className: "flex justify-end gap-2 p-4 border-t", children: [_jsx(Button, { variant: "outline", onClick: () => {
|
|
399
432
|
setRows([]);
|
|
400
433
|
setFileName("");
|
|
401
434
|
setResult(null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAwQjD"}
|
|
@@ -4,12 +4,17 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
4
4
|
import { useServerFn } from "@tanstack/react-start";
|
|
5
5
|
import { useState } from "react";
|
|
6
6
|
import { Card, CardContent, CardHeader, CardTitle, Badge, Button, toast, Tabs, TabsContent, TabsList, TabsTrigger, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@flowselections/core";
|
|
7
|
-
import {
|
|
7
|
+
import { Trash2, Mail, Phone, Power, } from "lucide-react";
|
|
8
8
|
import { getCustomer, deleteCustomer, updateCustomer } from "../../lib/customers.functions";
|
|
9
9
|
import { ProfileCard, KpiStrip, BadgeList, EmptyState, ActivityItem, adaptLocalToProfile, } from "../../components/klanten/KlantDetailLayout";
|
|
10
10
|
import { DeliveryLocationsTab } from "../../components/klanten/DeliveryLocationsTab";
|
|
11
|
+
import { BackLink } from "../../components/klanten/BackLink";
|
|
11
12
|
import { CustomerAnalyticsTab } from "../../components/klanten/CustomerAnalyticsTab";
|
|
12
13
|
import { LocalDiscountsTab } from "../../components/klanten/LocalDiscountsTab";
|
|
14
|
+
import { PriceGroupBadges } from "../../components/klanten/PriceGroupBadges";
|
|
15
|
+
import { FloridayDeeplinkButton } from "../../components/klanten/FloridayDeeplinkButton";
|
|
16
|
+
import { FloridayAssortmentTab } from "../../components/klanten/FloridayAssortmentTab";
|
|
17
|
+
import { SyncStatusChip } from "../../components/klanten/SyncStatusChip";
|
|
13
18
|
export function KlantDetail({ id }) {
|
|
14
19
|
const navigate = useNavigate();
|
|
15
20
|
const qc = useQueryClient();
|
|
@@ -34,11 +39,12 @@ export function KlantDetail({ id }) {
|
|
|
34
39
|
if (!data)
|
|
35
40
|
return _jsx("main", { className: "p-6", children: "Klant niet gevonden" });
|
|
36
41
|
const c = data.customer;
|
|
37
|
-
|
|
42
|
+
const priceGroups = data.price_groups ?? c.price_groups ?? [];
|
|
43
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2", children: [_jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => toggleActive.mutate(!c.is_active), children: [_jsx(Power, { className: "h-4 w-4 mr-2" }), c.is_active ? "Op inactief zetten" : "Activeren"] }), _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), " Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je ", _jsx("strong", { children: c.company_name }), " wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => delMut.mutate(), children: "Verwijderen" })] })] })] })] }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: adaptLocalToProfile(c, data.contacts) }), _jsxs("div", { className: "space-y-4 min-w-0", children: [priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] })), _jsx(KpiStrip, { counts: {
|
|
38
44
|
contacts: data.contacts.length,
|
|
39
45
|
locations: data.delivery_locations.length,
|
|
40
46
|
assortment: data.assortment.length,
|
|
41
47
|
orders: data.orders.length,
|
|
42
48
|
lastSync: c.last_synced_at,
|
|
43
|
-
}, onSelect: setTab }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { className: "flex flex-wrap h-auto gap-1 mb-4", children: [_jsx(TabsTrigger, { value: "overview", children: "Overzicht" }), _jsx(TabsTrigger, { value: "dashboard", children: "Analyses" }), _jsxs(TabsTrigger, { value: "contacts", children: ["Contacten (", data.contacts.length, ")"] }), _jsxs(TabsTrigger, { value: "locations", children: ["Locaties (", data.delivery_locations.length, ")"] }), _jsxs(TabsTrigger, { value: "assortment", children: ["Assortiment (", data.assortment.length, ")"] }), _jsxs(TabsTrigger, { value: "orders", children: ["Bestellingen (", data.orders.length, ")"] }), _jsx(TabsTrigger, { value: "trade", children: "Handel" }), _jsx(TabsTrigger, { value: "discounts", children: "Kortingen & prijzen" }), _jsx(TabsTrigger, { value: "activity", children: "Activiteit" })] }), _jsxs(TabsContent, { value: "overview", className: "space-y-4", children: [c.bio && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Bio" }) }), _jsx(CardContent, { className: "text-sm whitespace-pre-wrap", children: c.bio })] })), (c.segments?.length || c.trade_forms?.length || c.markets?.length || c.product_groups?.length) ? (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Profiel" }) }), _jsxs(CardContent, { className: "grid gap-4 md:grid-cols-2", children: [_jsx(BadgeList, { label: "Segmenten", items: c.segments }), _jsx(BadgeList, { label: "Handelsvorm", items: c.trade_forms }), _jsx(BadgeList, { label: "Afzetmarkten", items: c.markets }), _jsx(BadgeList, { label: "Productgroepen", items: c.product_groups })] })] })) : null, (c.payment_methods?.length ?? 0) > 0 && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Financi\u00EBle afhandeling" }) }), _jsx(CardContent, { children: _jsx(BadgeList, { label: "Betaalmethoden", items: c.payment_methods }) })] })), c.notes && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Notities" }) }), _jsx(CardContent, { className: "text-sm whitespace-pre-wrap", children: c.notes })] })), !c.bio && !c.notes && !(c.segments?.length || c.trade_forms?.length || c.markets?.length || c.product_groups?.length) && !(c.payment_methods?.length) && (_jsx(EmptyState, { message: "Geen extra profielinformatie beschikbaar voor deze klant." }))] }), _jsx(TabsContent, { value: "contacts", children: data.contacts.length === 0 ? (_jsx(EmptyState, { message: "Geen contactpersonen gesynchroniseerd." })) : (_jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: data.contacts.map((p) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsx("div", { className: "font-semibold", children: p.full_name }), p.job_title && _jsx("div", { className: "text-sm text-muted-foreground", children: p.job_title }), p.phone && (_jsxs("a", { href: `tel:${p.phone}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.phone] })), p.email && (_jsxs("a", { href: `mailto:${p.email}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.email] })), p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "secondary", children: "Inactief" })] }) }, p.id))) })) }), _jsx(TabsContent, { value: "trade", children: data.trade_settings.length === 0 ? (_jsx(EmptyState, { message: "Handelsinstellingen zijn (nog) niet beschikbaar via de Suppliers API voor deze klant." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: data.trade_settings.map((s) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Badge, { variant: s.is_active ? "default" : "secondary", children: s.is_active ? "Actief" : "Niet actief" }), _jsx("span", { className: "text-sm", children: s.setting_label })] }, s.id))) }) })) }), _jsx(TabsContent, { value: "locations", children: _jsx(DeliveryLocationsTab, { customerId: id }) }), _jsx(TabsContent, { value: "dashboard", children: _jsx(CustomerAnalyticsTab, { customerId: id }) }), _jsx(TabsContent, { value: "assortment", children: data.assortment.length === 0 ? (_jsx(EmptyState, { message: "Geen geselecteerd assortiment voor deze klant." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikelnummer" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Groep" }), _jsx(TableHead, { children: "Prijs" })] }) }), _jsx(TableBody, { children: data.assortment.map((a) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: a.article_number || "—" }), _jsx(TableCell, { children: a.product_name }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: a.product_group || "—" }), _jsx(TableCell, { children: a.price != null ? `€ ${Number(a.price).toFixed(2)}${a.unit ? ` / ${a.unit}` : ""}` : "—" })] }, a.id))) })] }) }) })) }), _jsx(TabsContent, { value: "orders", children: _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Datum" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Totaal" })] }) }), _jsx(TableBody, { children: data.orders.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center py-6 text-muted-foreground", children: "Geen bestellingen" }) })) : data.orders.map((o) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: o.order_number }), _jsx(TableCell, { children: o.order_date }), _jsx(TableCell, { children: _jsx(Badge, { children: o.status }) }), _jsx(TableCell, { children: o.total })] }, o.id))) })] }) }) }) }), _jsx(TabsContent, { value: "activity", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: data.audit.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen activiteit voor deze klant." })) : (data.audit.map((a) => _jsx(ActivityItem, { entry: a }, a.id))) }) }) }), _jsx(TabsContent, { value: "discounts", children: _jsx(LocalDiscountsTab, { customerId: id }) })] })] })] })] }));
|
|
49
|
+
}, onSelect: setTab }), _jsxs(Tabs, { value: tab, onValueChange: setTab, children: [_jsxs(TabsList, { className: "flex flex-wrap h-auto gap-1 mb-4", children: [_jsx(TabsTrigger, { value: "overview", children: "Overzicht" }), _jsx(TabsTrigger, { value: "dashboard", children: "Analyses" }), _jsxs(TabsTrigger, { value: "contacts", children: ["Contacten (", data.contacts.length, ")"] }), _jsxs(TabsTrigger, { value: "locations", children: ["Locaties (", data.delivery_locations.length, ")"] }), _jsxs(TabsTrigger, { value: "assortment", children: ["Assortiment (", data.assortment.length, ")"] }), c.floriday_organization_id && (_jsx(TabsTrigger, { value: "floriday-assortment", children: "Floriday-aanbod" })), _jsxs(TabsTrigger, { value: "orders", children: ["Bestellingen (", data.orders.length, ")"] }), _jsx(TabsTrigger, { value: "trade", children: "Handel" }), _jsx(TabsTrigger, { value: "discounts", children: "Kortingen & prijzen" }), _jsx(TabsTrigger, { value: "activity", children: "Activiteit" })] }), _jsxs(TabsContent, { value: "overview", className: "space-y-4", children: [c.bio && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Bio" }) }), _jsx(CardContent, { className: "text-sm whitespace-pre-wrap", children: c.bio })] })), (c.segments?.length || c.trade_forms?.length || c.markets?.length || c.product_groups?.length) ? (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Profiel" }) }), _jsxs(CardContent, { className: "grid gap-4 md:grid-cols-2", children: [_jsx(BadgeList, { label: "Segmenten", items: c.segments }), _jsx(BadgeList, { label: "Handelsvorm", items: c.trade_forms }), _jsx(BadgeList, { label: "Afzetmarkten", items: c.markets }), _jsx(BadgeList, { label: "Productgroepen", items: c.product_groups })] })] })) : null, (c.payment_methods?.length ?? 0) > 0 && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Financi\u00EBle afhandeling" }) }), _jsx(CardContent, { children: _jsx(BadgeList, { label: "Betaalmethoden", items: c.payment_methods }) })] })), c.notes && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Notities" }) }), _jsx(CardContent, { className: "text-sm whitespace-pre-wrap", children: c.notes })] })), !c.bio && !c.notes && !(c.segments?.length || c.trade_forms?.length || c.markets?.length || c.product_groups?.length) && !(c.payment_methods?.length) && (_jsx(EmptyState, { message: "Geen extra profielinformatie beschikbaar voor deze klant." }))] }), _jsx(TabsContent, { value: "contacts", children: data.contacts.length === 0 ? (_jsx(EmptyState, { message: "Geen contactpersonen gesynchroniseerd." })) : (_jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: data.contacts.map((p) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsx("div", { className: "font-semibold", children: p.full_name }), p.job_title && _jsx("div", { className: "text-sm text-muted-foreground", children: p.job_title }), p.phone && (_jsxs("a", { href: `tel:${p.phone}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Phone, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.phone] })), p.email && (_jsxs("a", { href: `mailto:${p.email}`, className: "text-sm flex items-center gap-2 hover:underline", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", p.email] })), p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "secondary", children: "Inactief" })] }) }, p.id))) })) }), _jsx(TabsContent, { value: "trade", children: data.trade_settings.length === 0 ? (_jsx(EmptyState, { message: "Handelsinstellingen zijn (nog) niet beschikbaar via de Suppliers API voor deze klant." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: data.trade_settings.map((s) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Badge, { variant: s.is_active ? "default" : "secondary", children: s.is_active ? "Actief" : "Niet actief" }), _jsx("span", { className: "text-sm", children: s.setting_label })] }, s.id))) }) })) }), _jsx(TabsContent, { value: "locations", children: _jsx(DeliveryLocationsTab, { customerId: id }) }), _jsx(TabsContent, { value: "dashboard", children: _jsx(CustomerAnalyticsTab, { customerId: id }) }), _jsx(TabsContent, { value: "assortment", children: data.assortment.length === 0 ? (_jsx(EmptyState, { message: "Geen geselecteerd assortiment voor deze klant." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikelnummer" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Groep" }), _jsx(TableHead, { children: "Prijs" })] }) }), _jsx(TableBody, { children: data.assortment.map((a) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: a.article_number || "—" }), _jsx(TableCell, { children: a.product_name }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: a.product_group || "—" }), _jsx(TableCell, { children: a.price != null ? `€ ${Number(a.price).toFixed(2)}${a.unit ? ` / ${a.unit}` : ""}` : "—" })] }, a.id))) })] }) }) })) }), c.floriday_organization_id && (_jsx(TabsContent, { value: "floriday-assortment", children: _jsx(FloridayAssortmentTab, { organizationId: c.floriday_organization_id }) })), _jsx(TabsContent, { value: "orders", children: _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Datum" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Totaal" })] }) }), _jsx(TableBody, { children: data.orders.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center py-6 text-muted-foreground", children: "Geen bestellingen" }) })) : data.orders.map((o) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: o.order_number }), _jsx(TableCell, { children: o.order_date }), _jsx(TableCell, { children: _jsx(Badge, { children: o.status }) }), _jsx(TableCell, { children: o.total })] }, o.id))) })] }) }) }) }), _jsx(TabsContent, { value: "activity", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6 space-y-2", children: data.audit.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen activiteit voor deze klant." })) : (data.audit.map((a) => _jsx(ActivityItem, { entry: a }, a.id))) }) }) }), _jsx(TabsContent, { value: "discounts", children: _jsx(LocalDiscountsTab, { customerId: id }) })] })] })] })] }));
|
|
44
50
|
}
|
|
@@ -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":"AA2CA,wBAAgB,gBAAgB,gCAia/B"}
|
|
@@ -10,6 +10,7 @@ import { listCustomers, listCustomerClasses } from "../../lib/customers.function
|
|
|
10
10
|
import { listExternalCustomers } from "../../lib/external-customers.functions";
|
|
11
11
|
import { listOverdueBalances } from "../../lib/overdue-invoices.functions";
|
|
12
12
|
import { CustomerAvatar } from "../CustomerAvatar";
|
|
13
|
+
import { PriceGroupBadges } from "./PriceGroupBadges";
|
|
13
14
|
import { formatCountry } from "./KlantDetailLayout";
|
|
14
15
|
export function KlantenOverzicht() {
|
|
15
16
|
const navigate = useNavigate();
|
|
@@ -64,6 +65,7 @@ export function KlantenOverzicht() {
|
|
|
64
65
|
logo_url: c.logo_url ?? null,
|
|
65
66
|
is_active: !!c.is_active,
|
|
66
67
|
overdue: null,
|
|
68
|
+
price_groups: c.price_groups ?? [],
|
|
67
69
|
}));
|
|
68
70
|
const ext = (externalData?.customers ?? []).map((c) => {
|
|
69
71
|
const provs = c.providers ?? [];
|
|
@@ -97,6 +99,7 @@ export function KlantenOverzicht() {
|
|
|
97
99
|
logo_url: c.logo_url,
|
|
98
100
|
is_active: c.is_active,
|
|
99
101
|
overdue,
|
|
102
|
+
price_groups: [],
|
|
100
103
|
};
|
|
101
104
|
});
|
|
102
105
|
return [...local, ...ext];
|
|
@@ -158,8 +161,8 @@ export function KlantenOverzicht() {
|
|
|
158
161
|
const toggleSort = (col) => setSort((s) => (s.col === col ? { col, dir: s.dir === "asc" ? "desc" : "asc" } : { col, dir: "asc" }));
|
|
159
162
|
const lastUpdated = externalData?.last_fetched_at ?? null;
|
|
160
163
|
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/netwerk" }), children: [_jsx(Network, { className: "h-4 w-4 mr-2" }), " Klanten toevoegen uit netwerk"] }), _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
|
|
161
|
-
? `Klanten met openstaande facturen waarvan de vervaldatum is verstreken.\nTotaal openstaand: ${formatEuro(overdueData.totals.amount)} · ${overdueData.totals.invoices} facturen
|
|
162
|
-
: "Klanten met openstaande facturen waarvan de vervaldatum is verstreken."), active: view === "overdue", onClick: () => { setView("overdue"); resetPage(); } })] }), _jsxs(Card, { children: [_jsx("div", { className: "border-b px-4 pt-3", children: _jsx(Tabs, { value: view, onValueChange: (v) => { setView(v); resetPage(); }, children: _jsxs(TabsList, { className: "flex-wrap", children: [_jsxs(TabsTrigger, { value: "all", children: ["Alle ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.all ?? 0 })] }), _jsxs(TabsTrigger, { value: "active", children: ["Actief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.active ?? 0 })] }), _jsxs(TabsTrigger, { value: "inactive", children: ["Inactief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.inactive ?? 0 })] }), _jsxs(TabsTrigger, { value: "overdue", children: ["Achterstallig ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.overdue ?? 0 })] })] }) }) }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap p-4 border-b", children: [_jsxs("div", { className: "relative flex-1 min-w-[240px] max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op bedrijf, contactpersoon of e-mail...", value: search, onChange: (e) => { setSearch(e.target.value); resetPage(); }, className: "pl-9" })] }), _jsxs(Select, { value: customerClass, onValueChange: (v) => { setCustomerClass(v); resetPage(); }, children: [_jsx(SelectTrigger, { className: "w-40", children: _jsx(SelectValue, { placeholder: "Klasse" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle klassen" }), (classData?.classes ?? []).map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] }), _jsxs(Select, { value: country, onValueChange: (v) => { setCountry(v); resetPage(); }, children: [_jsx(SelectTrigger, { className: "w-36", children: _jsx(SelectValue, { placeholder: "Land" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle landen" }), countries.map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] })] }), activeFilterChips.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap px-4 py-2 border-b bg-muted/30", children: [activeFilterChips.map((chip) => (_jsxs("button", { type: "button", onClick: chip.clear, className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1 text-xs hover:bg-muted", children: [chip.label, " ", _jsx(X, { className: "h-3 w-3" })] }, chip.key))), _jsx("button", { type: "button", onClick: () => { setSearch(""); setCustomerClass("__all__"); setCountry("__all__"); resetPage(); }, className: "text-xs text-muted-foreground hover:text-foreground underline", children: "Alles wissen" })] })), _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { 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: _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:
|
|
164
|
+
? `Klanten met openstaande facturen waarvan de vervaldatum is verstreken.\nTotaal openstaand: ${formatEuro(overdueData.totals.amount)} · ${overdueData.totals.invoices} facturen`
|
|
165
|
+
: "Klanten met openstaande facturen waarvan de vervaldatum is verstreken."), active: view === "overdue", onClick: () => { setView("overdue"); resetPage(); } })] }), _jsxs(Card, { children: [_jsx("div", { className: "border-b px-4 pt-3", children: _jsx(Tabs, { value: view, onValueChange: (v) => { setView(v); resetPage(); }, children: _jsxs(TabsList, { className: "flex-wrap", children: [_jsxs(TabsTrigger, { value: "all", children: ["Alle ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.all ?? 0 })] }), _jsxs(TabsTrigger, { value: "active", children: ["Actief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.active ?? 0 })] }), _jsxs(TabsTrigger, { value: "inactive", children: ["Inactief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.inactive ?? 0 })] }), _jsxs(TabsTrigger, { value: "overdue", children: ["Achterstallig ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: totals.overdue ?? 0 })] })] }) }) }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap p-4 border-b", children: [_jsxs("div", { className: "relative flex-1 min-w-[240px] max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op bedrijf, contactpersoon of e-mail...", value: search, onChange: (e) => { setSearch(e.target.value); resetPage(); }, className: "pl-9" })] }), _jsxs(Select, { value: customerClass, onValueChange: (v) => { setCustomerClass(v); resetPage(); }, children: [_jsx(SelectTrigger, { className: "w-40", children: _jsx(SelectValue, { placeholder: "Klasse" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle klassen" }), (classData?.classes ?? []).map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] }), _jsxs(Select, { value: country, onValueChange: (v) => { setCountry(v); resetPage(); }, children: [_jsx(SelectTrigger, { className: "w-36", children: _jsx(SelectValue, { placeholder: "Land" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle landen" }), countries.map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] })] }), activeFilterChips.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap px-4 py-2 border-b bg-muted/30", children: [activeFilterChips.map((chip) => (_jsxs("button", { type: "button", onClick: chip.clear, className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1 text-xs hover:bg-muted", children: [chip.label, " ", _jsx(X, { className: "h-3 w-3" })] }, chip.key))), _jsx("button", { type: "button", onClick: () => { setSearch(""); setCustomerClass("__all__"); setCountry("__all__"); resetPage(); }, className: "text-xs text-muted-foreground hover:text-foreground underline", children: "Alles wissen" })] })), _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: _jsx(SortHeader, { label: "Bedrijf", col: "company_name", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Contact" }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Klasse", col: "customer_class", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Prijscategorie" }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Land", col: "country", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: isLoading ? (Array.from({ length: 6 }).map((_, i) => (_jsx(TableRow, { children: Array.from({ length: 6 }).map((__, j) => (_jsx(TableCell, { className: "py-4", children: _jsx(Skeleton, { className: "h-4 w-full max-w-[160px]" }) }, j))) }, i)))) : pageRows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, children: _jsx(EmptyState, { onAdd: () => navigate({ to: "/klanten/nieuw" }) }) }) })) : pageRows.map((c) => {
|
|
163
166
|
const secondary = c.commercial_name && c.commercial_name !== c.company_name
|
|
164
167
|
? c.commercial_name
|
|
165
168
|
: c.city ?? c.email ?? null;
|
|
@@ -168,7 +171,7 @@ export function KlantenOverzicht() {
|
|
|
168
171
|
: navigate({
|
|
169
172
|
to: "/klanten/groep/$groupKey",
|
|
170
173
|
params: { groupKey: c.group_key },
|
|
171
|
-
}), 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 text-sm", children: c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(MapPin, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", formatCountry(c.country)] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(StatusPill, { active: c.is_active }) })] }, c.key));
|
|
174
|
+
}), children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(CustomerAvatar, { logoUrl: c.logo_url, name: c.company_name, size: "sm" }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-2", children: [_jsx("span", { className: "truncate", children: c.company_name }), c.overdue && c.overdue.count > 0 && (_jsxs("span", { title: `${c.overdue.count} openstaande factu${c.overdue.count === 1 ? "ur" : "ren"} · oudste vervallen op ${c.overdue.oldest_due}`, className: "inline-flex items-center gap-1 rounded-full bg-destructive/10 text-destructive px-2 py-0.5 text-[11px] font-medium shrink-0", children: [_jsx(AlertCircle, { className: "h-3 w-3" }), " ", formatEuro(c.overdue.total), " achterstallig"] }))] }), secondary && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: secondary })] })] }) }), _jsx(TableCell, { className: "py-3", children: c.contact_person || c.email ? (_jsxs("div", { className: "min-w-0", children: [c.contact_person && _jsx("div", { className: "text-sm truncate", children: c.contact_person }), c.email && (_jsxs("a", { href: `mailto:${c.email}`, onClick: (e) => e.stopPropagation(), className: "text-xs text-muted-foreground inline-flex items-center gap-1 hover:underline truncate", children: [_jsx(Mail, { className: "h-3 w-3" }), " ", c.email] }))] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: c.customer_class ? _jsx(Badge, { variant: "secondary", children: c.customer_class }) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(PriceGroupBadges, { groups: c.price_groups }) }), _jsx(TableCell, { className: "py-3 text-sm", children: c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(MapPin, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", formatCountry(c.country)] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(StatusPill, { active: c.is_active }) })] }, c.key));
|
|
172
175
|
}) })] }) }), _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" })] })] })] })] })] }) }));
|
|
173
176
|
}
|
|
174
177
|
function StatCard({ icon, label, value, active, onClick, tooltip, tint = "muted", disabled = false, }) {
|
|
@@ -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":"AAkCA,wBAAgB,aAAa,gCA4O5B"}
|