@flowselections/floriday-klanten-module 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/klanten/CustomerAnalyticsTab.d.ts +4 -0
- package/dist-lib/components/klanten/CustomerAnalyticsTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/CustomerAnalyticsTab.js +90 -0
- package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts +4 -0
- package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/DeliveryLocationsTab.js +94 -0
- package/dist-lib/components/klanten/GroupedKlantDetail.d.ts +4 -0
- package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -0
- package/dist-lib/components/klanten/GroupedKlantDetail.js +152 -0
- package/dist-lib/components/klanten/KlantDetail.d.ts +4 -0
- package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -0
- package/dist-lib/components/klanten/KlantDetail.js +43 -0
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts +75 -0
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -0
- package/dist-lib/components/klanten/KlantDetailLayout.js +172 -0
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +12 -32
- package/dist-lib/components/klanten/NetwerkPagina.d.ts +2 -0
- package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -0
- package/dist-lib/components/klanten/NetwerkPagina.js +23 -0
- package/dist-lib/components/klanten/NieuweKlant.d.ts +2 -0
- package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -0
- package/dist-lib/components/klanten/NieuweKlant.js +57 -0
- package/dist-lib/index.d.ts +4 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +5 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2797 -144
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +2797 -144
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +2797 -144
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +2870 -134
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +2 -0
- package/dist-lib/lib/accounting.functions.d.ts +17599 -9640
- package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-analytics.functions.d.ts +9347 -0
- package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -0
- package/dist-lib/lib/customer-analytics.functions.js +165 -0
- package/dist-lib/lib/customers.functions.d.ts +42763 -24186
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/delivery-locations.functions.d.ts +37258 -0
- package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -0
- package/dist-lib/lib/delivery-locations.functions.js +150 -0
- package/dist-lib/lib/eu-regions.d.ts +14 -0
- package/dist-lib/lib/eu-regions.d.ts.map +1 -0
- package/dist-lib/lib/eu-regions.js +68 -0
- package/dist-lib/lib/external-customers.functions.d.ts +25112 -14500
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CustomerAnalyticsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/CustomerAnalyticsTab.tsx"],"names":[],"mappings":"AA0FA,wBAAgB,oBAAoB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCA6N1E"}
|
|
@@ -0,0 +1,90 @@
|
|
|
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, CardHeader, CardTitle, Badge, Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Input, } from "@flowselections/core";
|
|
6
|
+
import { ResponsiveContainer, LineChart, Line, BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, PieChart, Pie, Cell, Legend, } from "recharts";
|
|
7
|
+
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
|
|
8
|
+
import { getCustomerAnalytics } from "../../lib/customer-analytics.functions";
|
|
9
|
+
function presetRange(key) {
|
|
10
|
+
const now = new Date();
|
|
11
|
+
const y = now.getFullYear(), m = now.getMonth(), d = now.getDate();
|
|
12
|
+
switch (key) {
|
|
13
|
+
case "today": return { from: new Date(y, m, d), to: new Date(y, m, d + 1) };
|
|
14
|
+
case "week": {
|
|
15
|
+
const day = now.getDay() || 7;
|
|
16
|
+
const start = new Date(y, m, d - day + 1);
|
|
17
|
+
return { from: start, to: new Date(start.getTime() + 7 * 86400000) };
|
|
18
|
+
}
|
|
19
|
+
case "month": return { from: new Date(y, m, 1), to: new Date(y, m + 1, 1) };
|
|
20
|
+
case "quarter": {
|
|
21
|
+
const q = Math.floor(m / 3);
|
|
22
|
+
return { from: new Date(y, q * 3, 1), to: new Date(y, q * 3 + 3, 1) };
|
|
23
|
+
}
|
|
24
|
+
case "year": return { from: new Date(y, 0, 1), to: new Date(y + 1, 0, 1) };
|
|
25
|
+
case "last_year": return { from: new Date(y - 1, 0, 1), to: new Date(y, 0, 1) };
|
|
26
|
+
case "all":
|
|
27
|
+
default: return { from: new Date(2000, 0, 1), to: new Date(y + 1, 0, 1) };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const PRESETS = [
|
|
31
|
+
{ key: "today", label: "Vandaag" },
|
|
32
|
+
{ key: "week", label: "Deze week" },
|
|
33
|
+
{ key: "month", label: "Deze maand" },
|
|
34
|
+
{ key: "quarter", label: "Dit kwartaal" },
|
|
35
|
+
{ key: "year", label: "Dit jaar" },
|
|
36
|
+
{ key: "last_year", label: "Vorig jaar" },
|
|
37
|
+
{ key: "all", label: "Alles" },
|
|
38
|
+
{ key: "custom", label: "Aangepast" },
|
|
39
|
+
];
|
|
40
|
+
const PIE_COLORS = ["#2563eb", "#16a34a", "#f59e0b", "#dc2626", "#7c3aed", "#0891b2", "#db2777", "#65a30d"];
|
|
41
|
+
function formatEUR(n) {
|
|
42
|
+
return new Intl.NumberFormat("nl-NL", { style: "currency", currency: "EUR", maximumFractionDigits: 0 }).format(n);
|
|
43
|
+
}
|
|
44
|
+
function formatNum(n, d = 0) {
|
|
45
|
+
return new Intl.NumberFormat("nl-NL", { maximumFractionDigits: d }).format(n);
|
|
46
|
+
}
|
|
47
|
+
function formatDate(s) {
|
|
48
|
+
if (!s)
|
|
49
|
+
return "—";
|
|
50
|
+
return new Date(s).toLocaleDateString("nl-NL", { day: "2-digit", month: "short", year: "numeric" });
|
|
51
|
+
}
|
|
52
|
+
function Kpi({ label, value, hint, delta }) {
|
|
53
|
+
let deltaEl = null;
|
|
54
|
+
if (delta != null && isFinite(delta)) {
|
|
55
|
+
const Icon = delta > 0.5 ? TrendingUp : delta < -0.5 ? TrendingDown : Minus;
|
|
56
|
+
const color = delta > 0.5 ? "text-emerald-600" : delta < -0.5 ? "text-rose-600" : "text-muted-foreground";
|
|
57
|
+
deltaEl = (_jsxs("div", { className: `flex items-center gap-1 text-xs ${color}`, children: [_jsx(Icon, { className: "h-3 w-3" }), " ", delta > 0 ? "+" : "", formatNum(delta, 1), "%"] }));
|
|
58
|
+
}
|
|
59
|
+
return (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-4 pb-4 space-y-1", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: label }), _jsx("div", { className: "text-xl font-semibold tabular-nums", children: value }), _jsxs("div", { className: "flex items-center justify-between", children: [hint && _jsx("div", { className: "text-xs text-muted-foreground", children: hint }), deltaEl] })] }) }));
|
|
60
|
+
}
|
|
61
|
+
export function CustomerAnalyticsTab({ customerId }) {
|
|
62
|
+
const [preset, setPreset] = useState("year");
|
|
63
|
+
const [customFrom, setCustomFrom] = useState("");
|
|
64
|
+
const [customTo, setCustomTo] = useState("");
|
|
65
|
+
const range = useMemo(() => {
|
|
66
|
+
if (preset === "custom" && customFrom && customTo) {
|
|
67
|
+
return { from: new Date(customFrom), to: new Date(new Date(customTo).getTime() + 86400000) };
|
|
68
|
+
}
|
|
69
|
+
return presetRange(preset === "custom" ? "year" : preset);
|
|
70
|
+
}, [preset, customFrom, customTo]);
|
|
71
|
+
const fetchAnalytics = useServerFn(getCustomerAnalytics);
|
|
72
|
+
const { data, isLoading, error } = useQuery({
|
|
73
|
+
queryKey: ["customer-analytics", customerId, range.from.toISOString(), range.to.toISOString()],
|
|
74
|
+
queryFn: () => fetchAnalytics({ data: {
|
|
75
|
+
customerId, from: range.from.toISOString(), to: range.to.toISOString(),
|
|
76
|
+
} }),
|
|
77
|
+
});
|
|
78
|
+
if (isLoading)
|
|
79
|
+
return _jsx("div", { className: "text-sm text-muted-foreground p-4", children: "Analyses laden..." });
|
|
80
|
+
if (error)
|
|
81
|
+
return _jsxs("div", { className: "text-sm text-rose-600 p-4", children: ["Kon analyses niet laden: ", error.message] });
|
|
82
|
+
if (!data)
|
|
83
|
+
return null;
|
|
84
|
+
const a = data;
|
|
85
|
+
const k = a.kpis;
|
|
86
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(Card, { children: _jsx(CardContent, { className: "pt-4 pb-4", children: _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [PRESETS.map((p) => (_jsx(Button, { size: "sm", variant: preset === p.key ? "default" : "outline", onClick: () => setPreset(p.key), children: p.label }, p.key))), preset === "custom" && (_jsxs("div", { className: "flex items-center gap-2 ml-2", children: [_jsx(Input, { type: "date", value: customFrom, onChange: (e) => setCustomFrom(e.target.value), className: "h-8 w-40" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "tot" }), _jsx(Input, { type: "date", value: customTo, onChange: (e) => setCustomTo(e.target.value), className: "h-8 w-40" })] }))] }) }) }), _jsxs("div", { className: "grid gap-3 grid-cols-2 md:grid-cols-4", children: [_jsx(Kpi, { label: "Omzet in periode", value: formatEUR(k.revenue_period), delta: k.revenue_growth_pct, hint: `vs vorige periode: ${formatEUR(k.revenue_compare)}` }), _jsx(Kpi, { label: "Aantal bestellingen", value: formatNum(k.order_count_period), hint: `totaal: ${formatNum(k.order_count)}` }), _jsx(Kpi, { label: "Gem. orderwaarde", value: formatEUR(k.avg_order_value) }), _jsx(Kpi, { label: "Totale omzet (alle tijd)", value: formatEUR(k.total_revenue) }), _jsx(Kpi, { label: "Dit jaar", value: formatEUR(k.revenue_this_year), hint: `vorig jaar: ${formatEUR(k.revenue_last_year)}` }), _jsx(Kpi, { label: "Deze maand", value: formatEUR(k.revenue_this_month), hint: `vorige maand: ${formatEUR(k.revenue_last_month)}` }), _jsx(Kpi, { label: "Grootste bestelling", value: formatEUR(k.largest_order), hint: `kleinste: ${formatEUR(k.smallest_order)}` }), _jsx(Kpi, { label: "Gem. tijd tussen bestellingen", value: k.avg_days_between_orders != null ? `${formatNum(k.avg_days_between_orders, 1)} dgn` : "—", hint: k.avg_orders_per_month != null ? `${formatNum(k.avg_orders_per_month, 1)} / maand` : undefined })] }), _jsxs("div", { className: "grid gap-3 grid-cols-1 md:grid-cols-3 text-sm", children: [_jsx(Card, { children: _jsxs(CardContent, { className: "pt-4 pb-4", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Eerste bestelling" }), _jsx("div", { className: "font-medium", children: formatDate(k.first_order_date) })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "pt-4 pb-4", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Laatste bestelling" }), _jsx("div", { className: "font-medium", children: formatDate(k.last_order_date) })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "pt-4 pb-4", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Periode" }), _jsxs("div", { className: "font-medium", children: [formatDate(a.period.from), " \u2013 ", formatDate(new Date(new Date(a.period.to).getTime() - 86400000).toISOString())] })] }) })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Omzet per maand" }) }), _jsx(CardContent, { className: "h-72", children: a.monthly.length === 0 ? _jsx(EmptyChart, {}) : (_jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(LineChart, { data: a.monthly, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, tickFormatter: (v) => formatEUR(Number(v)) }), _jsx(Tooltip, { formatter: (v) => formatEUR(Number(v)) }), _jsx(Line, { type: "monotone", dataKey: "revenue", stroke: "#2563eb", strokeWidth: 2, dot: false, name: "Omzet" })] }) })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Aantal bestellingen per maand" }) }), _jsx(CardContent, { className: "h-72", children: a.monthly.length === 0 ? _jsx(EmptyChart, {}) : (_jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(BarChart, { data: a.monthly, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, allowDecimals: false }), _jsx(Tooltip, {}), _jsx(Bar, { dataKey: "orders", fill: "#16a34a", name: "Bestellingen" })] }) })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Gem. orderwaarde per maand" }) }), _jsx(CardContent, { className: "h-72", children: a.monthly.length === 0 ? _jsx(EmptyChart, {}) : (_jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(LineChart, { data: a.monthly, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, tickFormatter: (v) => formatEUR(Number(v)) }), _jsx(Tooltip, { formatter: (v) => formatEUR(Number(v)) }), _jsx(Line, { type: "monotone", dataKey: "aov", stroke: "#f59e0b", strokeWidth: 2, dot: false, name: "Gem. waarde" })] }) })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Omzet per jaar" }) }), _jsx(CardContent, { className: "h-72", children: a.yearly.length === 0 ? _jsx(EmptyChart, {}) : (_jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(BarChart, { data: a.yearly, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, tickFormatter: (v) => formatEUR(Number(v)) }), _jsx(Tooltip, { formatter: (v) => formatEUR(Number(v)) }), _jsx(Bar, { dataKey: "revenue", fill: "#7c3aed", name: "Omzet" })] }) })) })] })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-[2fr_1fr]", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Top artikelen in periode" }) }), _jsx(CardContent, { className: "p-0", children: a.topProducts.length === 0 ? (_jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Geen orderregels in deze periode." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Product" }), _jsx(TableHead, { className: "text-right", children: "Aantal" }), _jsx(TableHead, { children: "Eenheid" }), _jsx(TableHead, { className: "text-right", children: "Omzet" })] }) }), _jsx(TableBody, { children: a.topProducts.map((p) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: p.product_name }), _jsx(TableCell, { className: "text-right tabular-nums", children: formatNum(p.quantity) }), _jsx(TableCell, { className: "text-muted-foreground", children: p.unit ?? "—" }), _jsx(TableCell, { className: "text-right tabular-nums", children: formatEUR(p.revenue) })] }, p.product_name))) })] })) })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Verkocht per eenheid" }) }), _jsx(CardContent, { className: "space-y-2", children: a.unitTotals.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground", children: "Geen data." })) : a.unitTotals.map((u) => (_jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsx("span", { className: "capitalize", children: u.unit }), _jsx("span", { className: "tabular-nums font-medium", children: formatNum(u.quantity) })] }, u.unit))) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Status van bestellingen" }) }), _jsx(CardContent, { className: "h-56", children: a.statusBreakdown.length === 0 ? _jsx(EmptyChart, {}) : (_jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(PieChart, { children: [_jsx(Pie, { data: a.statusBreakdown, dataKey: "count", nameKey: "status", outerRadius: 70, label: true, children: a.statusBreakdown.map((_, i) => (_jsx(Cell, { fill: PIE_COLORS[i % PIE_COLORS.length] }, i))) }), _jsx(Tooltip, {}), _jsx(Legend, {})] }) })) })] })] })] }), _jsx(Card, { children: _jsx(CardContent, { className: "pt-4 pb-4 text-xs text-muted-foreground space-y-1", children: _jsxs("div", { children: [_jsx(Badge, { variant: "secondary", children: "Let op" }), " Sommige analyses zijn nog niet beschikbaar omdat de onderliggende data ontbreekt: btw-bedragen, marge en vervoerder/afhaallocatie per bestelling. Voeg deze velden toe aan orders/orderregels om die overzichten te activeren."] }) }) })] }));
|
|
87
|
+
}
|
|
88
|
+
function EmptyChart() {
|
|
89
|
+
return _jsx("div", { className: "h-full flex items-center justify-center text-sm text-muted-foreground", children: "Geen data in deze periode." });
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeliveryLocationsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/DeliveryLocationsTab.tsx"],"names":[],"mappings":"AAwDA,wBAAgB,oBAAoB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,+BAwS1E"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { Button, Card, CardContent, Badge, toast, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@flowselections/core";
|
|
6
|
+
import { MapPin, Plus, Pencil, Trash2, Truck, Sparkles, PackageCheck } from "lucide-react";
|
|
7
|
+
import { listCarriers, listDeliveryLocations, upsertDeliveryLocation, deleteDeliveryLocation, pickCarrierForLocation, } from "../../lib/delivery-locations.functions";
|
|
8
|
+
import { EU_COUNTRIES, findCountry } from "../../lib/eu-regions";
|
|
9
|
+
const EMPTY = {
|
|
10
|
+
name: "", street: "", house_number: "", postal_code: "",
|
|
11
|
+
city: "", province: "", country: "Nederland",
|
|
12
|
+
carrier_id: null, carrier_assignment_source: "auto", is_primary: false, self_pickup: false,
|
|
13
|
+
};
|
|
14
|
+
export function DeliveryLocationsTab({ customerId }) {
|
|
15
|
+
const qc = useQueryClient();
|
|
16
|
+
const listFn = useServerFn(listDeliveryLocations);
|
|
17
|
+
const carriersFn = useServerFn(listCarriers);
|
|
18
|
+
const upsertFn = useServerFn(upsertDeliveryLocation);
|
|
19
|
+
const deleteFn = useServerFn(deleteDeliveryLocation);
|
|
20
|
+
const { data: list } = useQuery({
|
|
21
|
+
queryKey: ["delivery-locations", customerId],
|
|
22
|
+
queryFn: () => listFn({ data: { customer_id: customerId } }),
|
|
23
|
+
});
|
|
24
|
+
const { data: carriersResp } = useQuery({
|
|
25
|
+
queryKey: ["carriers"],
|
|
26
|
+
queryFn: () => carriersFn(),
|
|
27
|
+
});
|
|
28
|
+
const carriers = carriersResp?.carriers ?? [];
|
|
29
|
+
const locations = (list?.locations ?? []);
|
|
30
|
+
const [open, setOpen] = useState(false);
|
|
31
|
+
const [form, setForm] = useState(EMPTY);
|
|
32
|
+
const autoSuggested = useMemo(() => {
|
|
33
|
+
if (form.carrier_assignment_source === "manual")
|
|
34
|
+
return null;
|
|
35
|
+
return pickCarrierForLocation({ country: form.country, province: form.province, city: form.city }, carriers);
|
|
36
|
+
}, [form.country, form.province, form.city, form.carrier_assignment_source, carriers]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (form.carrier_assignment_source !== "manual" && autoSuggested) {
|
|
39
|
+
setForm((f) => ({ ...f, carrier_id: autoSuggested.carrier_id }));
|
|
40
|
+
}
|
|
41
|
+
}, [autoSuggested?.carrier_id, form.carrier_assignment_source]);
|
|
42
|
+
const carrierName = (id) => carriers.find((c) => c.id === id)?.name ?? "—";
|
|
43
|
+
const startNew = () => { setForm(EMPTY); setOpen(true); };
|
|
44
|
+
const startEdit = (loc) => {
|
|
45
|
+
setForm({
|
|
46
|
+
id: loc.id,
|
|
47
|
+
name: loc.name ?? "",
|
|
48
|
+
street: loc.street ?? "",
|
|
49
|
+
house_number: loc.house_number ?? "",
|
|
50
|
+
postal_code: loc.postal_code ?? "",
|
|
51
|
+
city: loc.city ?? "",
|
|
52
|
+
province: loc.province ?? "",
|
|
53
|
+
country: loc.country ?? "Nederland",
|
|
54
|
+
carrier_id: loc.carrier_id,
|
|
55
|
+
carrier_assignment_source: (loc.carrier_assignment_source ?? "auto"),
|
|
56
|
+
is_primary: loc.is_primary,
|
|
57
|
+
self_pickup: !!loc.self_pickup,
|
|
58
|
+
});
|
|
59
|
+
setOpen(true);
|
|
60
|
+
};
|
|
61
|
+
const saveMut = useMutation({
|
|
62
|
+
mutationFn: () => upsertFn({ data: { ...form, customer_id: customerId } }),
|
|
63
|
+
onSuccess: () => {
|
|
64
|
+
toast.success(form.id ? "Locatie bijgewerkt" : "Locatie toegevoegd");
|
|
65
|
+
qc.invalidateQueries({ queryKey: ["delivery-locations", customerId] });
|
|
66
|
+
qc.invalidateQueries({ queryKey: ["customer", customerId] });
|
|
67
|
+
setOpen(false);
|
|
68
|
+
},
|
|
69
|
+
onError: (e) => toast.error(e?.message ?? "Opslaan mislukt"),
|
|
70
|
+
});
|
|
71
|
+
const delMut = useMutation({
|
|
72
|
+
mutationFn: (id) => deleteFn({ data: { id } }),
|
|
73
|
+
onSuccess: () => {
|
|
74
|
+
toast.success("Locatie verwijderd");
|
|
75
|
+
qc.invalidateQueries({ queryKey: ["delivery-locations", customerId] });
|
|
76
|
+
qc.invalidateQueries({ queryKey: ["customer", customerId] });
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-sm text-muted-foreground", children: "Voeg afleverlocaties toe; de vervoerder wordt automatisch gekoppeld op basis van het bedieningsgebied." }), _jsxs(Button, { size: "sm", onClick: startNew, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Locatie toevoegen"] })] }), locations.length === 0 ? (_jsx(Card, { children: _jsx(CardContent, { className: "py-10 text-center text-sm text-muted-foreground", children: "Nog geen afleverlocaties." }) })) : (_jsx("div", { className: "grid gap-3 md:grid-cols-2", children: locations.map((loc) => (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-5 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-semibold flex items-center gap-2", children: [_jsx(MapPin, { className: "h-4 w-4 text-muted-foreground" }), loc.name || [loc.street, loc.house_number].filter(Boolean).join(" ") || "Afleverlocatie"] }), _jsxs("div", { className: "text-sm text-muted-foreground mt-0.5", children: [[loc.street, loc.house_number].filter(Boolean).join(" "), (loc.street || loc.house_number) && (loc.postal_code || loc.city) ? ", " : "", [loc.postal_code, loc.city].filter(Boolean).join(" "), loc.province ? ` · ${loc.province}` : "", loc.country ? ` · ${loc.country}` : ""] })] }), _jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon", onClick: () => startEdit(loc), children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", children: _jsx(Trash2, { className: "h-4 w-4" }) }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Locatie verwijderen?" }), _jsx(AlertDialogDescription, { children: "Deze actie kan niet ongedaan worden gemaakt." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => delMut.mutate(loc.id), children: "Verwijderen" })] })] })] })] })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap text-sm", children: [loc.self_pickup ? (_jsxs(_Fragment, { children: [_jsx(PackageCheck, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: "Eigen vervoer" }), _jsx(Badge, { variant: "secondary", children: "Klant haalt zelf op" })] })) : (_jsxs(_Fragment, { children: [_jsx(Truck, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: carrierName(loc.carrier_id) }), loc.carrier_assignment_source === "auto" && loc.carrier_id && (_jsxs(Badge, { variant: "secondary", className: "gap-1", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "Automatisch"] })), loc.carrier_assignment_source === "manual" && (_jsx(Badge, { variant: "outline", children: "Handmatig" })), !loc.carrier_id && (_jsx(Badge, { variant: "outline", className: "text-amber-600 border-amber-300", children: "Geen match" }))] })), loc.is_primary && _jsx(Badge, { children: "Vaste locatie" })] })] }) }, loc.id))) })), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: form.id ? "Locatie bewerken" : "Nieuwe locatie" }), _jsx(DialogDescription, { children: "De vervoerder wordt automatisch gekozen op basis van plaats, provincie of land." })] }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsx(Label, { children: "Locatienaam (optioneel)" }), _jsx(Input, { value: form.name, onChange: (e) => setForm({ ...form, name: e.target.value }) })] }), _jsxs("div", { className: "md:col-span-2 grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "col-span-2", children: [_jsx(Label, { children: "Straat" }), _jsx(Input, { value: form.street, onChange: (e) => setForm({ ...form, street: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Huisnummer" }), _jsx(Input, { value: form.house_number, onChange: (e) => setForm({ ...form, house_number: e.target.value }) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Postcode" }), _jsx(Input, { value: form.postal_code, onChange: (e) => setForm({ ...form, postal_code: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Plaats" }), _jsx(Input, { value: form.city, onChange: (e) => setForm({ ...form, city: e.target.value }) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Land" }), _jsxs(Select, { value: findCountry(form.country)?.name ?? "", onValueChange: (v) => setForm({ ...form, country: v, province: "" }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een land" }) }), _jsx(SelectContent, { children: EU_COUNTRIES.map((c) => (_jsx(SelectItem, { value: c.name, children: c.name }, c.code))) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Provincie" }), (() => {
|
|
80
|
+
const provinces = findCountry(form.country)?.provinces ?? [];
|
|
81
|
+
return provinces.length > 0 ? (_jsxs(Select, { value: form.province || "", onValueChange: (v) => setForm({ ...form, province: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een provincie" }) }), _jsx(SelectContent, { children: provinces.map((p) => (_jsx(SelectItem, { value: p, children: p }, p))) })] })) : (_jsx(Input, { value: form.province, onChange: (e) => setForm({ ...form, province: e.target.value }), placeholder: "Kies eerst een land", disabled: !form.country }));
|
|
82
|
+
})()] }), _jsxs("div", { className: "md:col-span-2 space-y-2", children: [_jsx(Label, { children: "Vervoerder" }), _jsxs(Select, { value: form.self_pickup ? "__self__" : (form.carrier_id ?? "__none__"), onValueChange: (v) => {
|
|
83
|
+
if (v === "__self__") {
|
|
84
|
+
setForm({ ...form, self_pickup: true, carrier_id: null, carrier_assignment_source: "manual" });
|
|
85
|
+
}
|
|
86
|
+
else if (v === "__none__") {
|
|
87
|
+
setForm({ ...form, self_pickup: false, carrier_id: null, carrier_assignment_source: "manual" });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
setForm({ ...form, self_pickup: false, carrier_id: v, carrier_assignment_source: "manual" });
|
|
91
|
+
}
|
|
92
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een vervoerder" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__self__", children: "Eigen vervoer (klant haalt zelf op)" }), _jsx(SelectItem, { value: "__none__", children: "\u2014 Geen vervoerder \u2014" }), carriers.map((c) => (_jsx(SelectItem, { value: c.id, children: c.name }, c.id)))] })] }), form.self_pickup ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "De klant komt zelf de karren ophalen \u2014 er wordt geen vervoerder gekoppeld." })) : form.carrier_assignment_source === "manual" ? (_jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [_jsx("span", { children: "Handmatig overschreven." }), _jsx(Button, { variant: "link", size: "sm", className: "h-auto p-0", onClick: () => setForm({ ...form, carrier_assignment_source: "auto" }), children: "Terug naar automatisch" })] })) : autoSuggested?.carrier_id ? (_jsxs("div", { className: "text-xs text-muted-foreground flex items-center gap-1", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "Automatisch geselecteerd op basis van ", autoSuggested.matched_on === "city"
|
|
93
|
+
? "plaats" : autoSuggested.matched_on === "province" ? "provincie" : "land", "."] })) : (_jsx("div", { className: "text-xs text-amber-600", children: "Geen vervoerder gevonden voor dit bedieningsgebied \u2014 selecteer er handmatig \u00E9\u00E9n." }))] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: () => saveMut.mutate(), disabled: saveMut.isPending, children: form.id ? "Opslaan" : "Toevoegen" })] })] }) })] }));
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupedKlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/GroupedKlantDetail.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BA4MpE"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { Card, CardContent, Badge, Button, Tabs, TabsList, TabsTrigger, TabsContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
|
|
7
|
+
import { ArrowLeft, Mail, Phone } from "lucide-react";
|
|
8
|
+
import { getGroupedCustomerDetail, } from "../../lib/external-customers.functions";
|
|
9
|
+
import { ProfileCard, KpiStrip, BadgeList, EmptyState, adaptGroupedToProfile, } from "../../components/klanten/KlantDetailLayout";
|
|
10
|
+
export function GroupedKlantDetail({ groupKey }) {
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const get = useServerFn(getGroupedCustomerDetail);
|
|
13
|
+
const [tab, setTab] = useState("overview");
|
|
14
|
+
const { data, isLoading, error } = useQuery({
|
|
15
|
+
queryKey: ["grouped-customer", groupKey],
|
|
16
|
+
queryFn: () => get({ data: { groupKey } }),
|
|
17
|
+
});
|
|
18
|
+
if (isLoading)
|
|
19
|
+
return _jsx("main", { className: "p-6", children: "Laden..." });
|
|
20
|
+
if (error || !data) {
|
|
21
|
+
return (_jsxs("main", { className: "p-6 space-y-3", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate({ to: "/klanten" }), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-2" }), " Terug naar klanten"] }), _jsx("p", { className: "text-sm text-destructive", children: error instanceof Error ? error.message : "Klant niet gevonden." })] }));
|
|
22
|
+
}
|
|
23
|
+
const g = data.group;
|
|
24
|
+
const providerMap = new Map(data.providers.map((p) => [p.provider, p]));
|
|
25
|
+
const sources = g.sources ?? [];
|
|
26
|
+
const profile = adaptGroupedToProfile(g);
|
|
27
|
+
const contacts = collectContacts(sources, providerMap);
|
|
28
|
+
const locations = collectLocations(sources, providerMap);
|
|
29
|
+
const segments = collectArray(sources, ["segments"]);
|
|
30
|
+
const tradeForms = collectArray(sources, ["trade_forms", "tradeForms"]);
|
|
31
|
+
const markets = collectArray(sources, ["markets"]);
|
|
32
|
+
const productGroups = collectArray(sources, ["product_groups", "productGroups"]);
|
|
33
|
+
const paymentMethods = collectArray(sources, ["payment_methods", "paymentMethods"]);
|
|
34
|
+
const activity = sources
|
|
35
|
+
.filter((s) => s.fetched_at)
|
|
36
|
+
.map((s) => ({
|
|
37
|
+
id: `${s.provider}-${s.external_id}`,
|
|
38
|
+
action: "sync",
|
|
39
|
+
created_at: s.fetched_at,
|
|
40
|
+
provider: providerMap.get(s.provider)?.label ?? s.provider,
|
|
41
|
+
}))
|
|
42
|
+
.sort((a, b) => +new Date(b.created_at) - +new Date(a.created_at));
|
|
43
|
+
return (_jsxs("main", { className: "p-6 max-w-7xl mx-auto space-y-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate({ to: "/klanten" }), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-2" }), " Terug naar klanten"] }) }), _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: [_jsx(KpiStrip, { counts: {
|
|
44
|
+
contacts: contacts.length,
|
|
45
|
+
locations: locations.length,
|
|
46
|
+
assortment: 0,
|
|
47
|
+
orders: 0,
|
|
48
|
+
lastSync: g.fetched_at,
|
|
49
|
+
}, 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" }), _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." }) }), _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 }) }))] })] })] })] }));
|
|
50
|
+
}
|
|
51
|
+
function collectContacts(sources, providerMap) {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const s of sources) {
|
|
54
|
+
const label = providerMap.get(s.provider)?.label ?? s.provider;
|
|
55
|
+
const raw = s.raw ?? {};
|
|
56
|
+
const candidates = [];
|
|
57
|
+
if (raw.primaryContact)
|
|
58
|
+
candidates.push(raw.primaryContact);
|
|
59
|
+
if (Array.isArray(raw.contactPersons))
|
|
60
|
+
candidates.push(...raw.contactPersons);
|
|
61
|
+
if (Array.isArray(raw.contacts))
|
|
62
|
+
candidates.push(...raw.contacts);
|
|
63
|
+
if (candidates.length === 0 && s.contact_person) {
|
|
64
|
+
candidates.push({ name: s.contact_person, email: s.email, phone: s.phone });
|
|
65
|
+
}
|
|
66
|
+
for (const c of candidates) {
|
|
67
|
+
const full_name = c.fullName ?? c.name ?? [c.firstName, c.lastName].filter(Boolean).join(" ");
|
|
68
|
+
if (!full_name)
|
|
69
|
+
continue;
|
|
70
|
+
out.push({
|
|
71
|
+
full_name,
|
|
72
|
+
job_title: c.jobTitle ?? c.role ?? null,
|
|
73
|
+
email: c.email ?? c.emailAddress ?? null,
|
|
74
|
+
phone: c.phone ?? c.phoneNumber ?? null,
|
|
75
|
+
source_label: label,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
function collectLocations(sources, providerMap) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const s of sources) {
|
|
84
|
+
const label = providerMap.get(s.provider)?.label ?? s.provider;
|
|
85
|
+
const raw = s.raw ?? {};
|
|
86
|
+
const rawLocs = Array.isArray(raw.deliveryLocations)
|
|
87
|
+
? raw.deliveryLocations
|
|
88
|
+
: Array.isArray(raw.locations) ? raw.locations : [];
|
|
89
|
+
if (rawLocs.length > 0) {
|
|
90
|
+
for (const l of rawLocs) {
|
|
91
|
+
out.push({
|
|
92
|
+
name: l.name ?? null,
|
|
93
|
+
address_line: [l.street, l.houseNumber].filter(Boolean).join(" ") || l.addressLine || null,
|
|
94
|
+
city: l.city ?? null,
|
|
95
|
+
postal_code: l.postalCode ?? l.zipCode ?? null,
|
|
96
|
+
country: l.country ?? l.countryCode ?? null,
|
|
97
|
+
gln: l.gln ?? null,
|
|
98
|
+
source_label: label,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (s.street || s.city || s.postal_code) {
|
|
103
|
+
out.push({
|
|
104
|
+
name: null,
|
|
105
|
+
address_line: [s.street, s.house_number].filter(Boolean).join(" ") || null,
|
|
106
|
+
city: s.city,
|
|
107
|
+
postal_code: s.postal_code,
|
|
108
|
+
country: s.country,
|
|
109
|
+
gln: s.gln,
|
|
110
|
+
source_label: label,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function collectArray(sources, keys) {
|
|
117
|
+
const set = new Set();
|
|
118
|
+
for (const s of sources) {
|
|
119
|
+
for (const key of keys) {
|
|
120
|
+
const v = s.raw?.[key];
|
|
121
|
+
if (Array.isArray(v))
|
|
122
|
+
for (const x of v)
|
|
123
|
+
if (x)
|
|
124
|
+
set.add(String(x));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return Array.from(set);
|
|
128
|
+
}
|
|
129
|
+
function CompareCard({ sources, providerMap, }) {
|
|
130
|
+
const fields = [
|
|
131
|
+
{ key: "company_name", label: "Bedrijfsnaam" },
|
|
132
|
+
{ key: "commercial_name", label: "Handelsnaam" },
|
|
133
|
+
{ key: "contact_person", label: "Contactpersoon" },
|
|
134
|
+
{ key: "email", label: "E-mail" },
|
|
135
|
+
{ key: "phone", label: "Telefoon" },
|
|
136
|
+
{ key: "street", label: "Straat" },
|
|
137
|
+
{ key: "postal_code", label: "Postcode" },
|
|
138
|
+
{ key: "city", label: "Plaats" },
|
|
139
|
+
{ key: "country", label: "Land" },
|
|
140
|
+
{ key: "website", label: "Website" },
|
|
141
|
+
{ key: "vat_number", label: "BTW" },
|
|
142
|
+
{ key: "gln", label: "GLN" },
|
|
143
|
+
];
|
|
144
|
+
return (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6 overflow-auto", children: _jsxs("table", { className: "w-full text-sm border-collapse", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b", children: [_jsx("th", { className: "text-left py-2 pr-3 text-xs font-medium uppercase text-muted-foreground", children: "Veld" }), sources.map((s) => (_jsx("th", { className: "text-left py-2 px-3 text-xs font-medium", children: providerMap.get(s.provider)?.label ?? s.provider }, s.provider)))] }) }), _jsx("tbody", { children: fields.map((f) => {
|
|
145
|
+
const values = sources.map((s) => s[f.key] ?? null);
|
|
146
|
+
const firstNonEmpty = values.find((v) => v != null && v !== "");
|
|
147
|
+
return (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "py-2 pr-3 text-muted-foreground align-top", children: f.label }), values.map((v, i) => {
|
|
148
|
+
const differs = v != null && v !== "" && firstNonEmpty != null && v !== firstNonEmpty;
|
|
149
|
+
return (_jsx("td", { className: `py-2 px-3 align-top ${differs ? "bg-muted/40 rounded" : ""}`, children: v && v !== "" ? v : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }, i));
|
|
150
|
+
})] }, String(f.key)));
|
|
151
|
+
}) })] }) }) }));
|
|
152
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAkPjD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { useState } from "react";
|
|
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 { ArrowLeft, Trash2, Mail, Phone, Power, } from "lucide-react";
|
|
8
|
+
import { getCustomer, deleteCustomer, updateCustomer } from "../../lib/customers.functions";
|
|
9
|
+
import { ProfileCard, KpiStrip, BadgeList, EmptyState, ActivityItem, adaptLocalToProfile, } from "../../components/klanten/KlantDetailLayout";
|
|
10
|
+
import { DeliveryLocationsTab } from "../../components/klanten/DeliveryLocationsTab";
|
|
11
|
+
import { CustomerAnalyticsTab } from "../../components/klanten/CustomerAnalyticsTab";
|
|
12
|
+
export function KlantDetail({ id }) {
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
const qc = useQueryClient();
|
|
15
|
+
const get = useServerFn(getCustomer);
|
|
16
|
+
const del = useServerFn(deleteCustomer);
|
|
17
|
+
const upd = useServerFn(updateCustomer);
|
|
18
|
+
const [tab, setTab] = useState("overview");
|
|
19
|
+
const { data, isLoading } = useQuery({
|
|
20
|
+
queryKey: ["customer", id],
|
|
21
|
+
queryFn: () => get({ data: { id } }),
|
|
22
|
+
});
|
|
23
|
+
const delMut = useMutation({
|
|
24
|
+
mutationFn: () => del({ data: { id } }),
|
|
25
|
+
onSuccess: () => { toast.success("Verwijderd"); navigate({ to: "/klanten" }); },
|
|
26
|
+
});
|
|
27
|
+
const toggleActive = useMutation({
|
|
28
|
+
mutationFn: (active) => upd({ data: { id, is_active: active } }),
|
|
29
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ["customer", id] }),
|
|
30
|
+
});
|
|
31
|
+
if (isLoading)
|
|
32
|
+
return _jsx("main", { className: "p-6", children: "Laden..." });
|
|
33
|
+
if (!data)
|
|
34
|
+
return _jsx("main", { className: "p-6", children: "Klant niet gevonden" });
|
|
35
|
+
const c = data.customer;
|
|
36
|
+
return (_jsxs("main", { className: "p-6 max-w-7xl mx-auto space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate({ to: "/klanten" }), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-2" }), " Terug naar klanten"] }), _jsxs("div", { className: "flex gap-2", children: [_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: [_jsx(KpiStrip, { counts: {
|
|
37
|
+
contacts: data.contacts.length,
|
|
38
|
+
locations: data.delivery_locations.length,
|
|
39
|
+
assortment: data.assortment.length,
|
|
40
|
+
orders: data.orders.length,
|
|
41
|
+
lastSync: c.last_synced_at,
|
|
42
|
+
}, 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: "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))) }) }) })] })] })] })] }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { ExternalCustomer, GroupedCustomer } from "../../lib/external-customers.functions";
|
|
3
|
+
export type CustomerProfile = {
|
|
4
|
+
logo_url: string | null;
|
|
5
|
+
company_name: string;
|
|
6
|
+
commercial_name: string | null;
|
|
7
|
+
is_active: boolean;
|
|
8
|
+
class_badge: string | null;
|
|
9
|
+
email: string | null;
|
|
10
|
+
phone: string | null;
|
|
11
|
+
website: string | null;
|
|
12
|
+
street: string | null;
|
|
13
|
+
house_number: string | null;
|
|
14
|
+
postal_code: string | null;
|
|
15
|
+
city: string | null;
|
|
16
|
+
country: string | null;
|
|
17
|
+
vat_number: string | null;
|
|
18
|
+
gln: string | null;
|
|
19
|
+
rfh_id: string | null;
|
|
20
|
+
organization_type: string | null;
|
|
21
|
+
primary_contact_name: string | null;
|
|
22
|
+
email_from_contact: boolean;
|
|
23
|
+
phone_from_contact: boolean;
|
|
24
|
+
has_contacts: boolean;
|
|
25
|
+
};
|
|
26
|
+
export declare function formatCountry(value: string | null | undefined): string | null;
|
|
27
|
+
export declare function adaptLocalToProfile(c: any, contacts?: any[]): CustomerProfile;
|
|
28
|
+
export declare function adaptGroupedToProfile(g: GroupedCustomer): CustomerProfile;
|
|
29
|
+
export declare function adaptExternalToProfile(c: ExternalCustomer): CustomerProfile;
|
|
30
|
+
export declare function ProfileCard({ profile: p }: {
|
|
31
|
+
profile: CustomerProfile;
|
|
32
|
+
}): import("react").JSX.Element;
|
|
33
|
+
export declare function SectionLabel({ children }: {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
}): import("react").JSX.Element;
|
|
36
|
+
export declare function QuickAction({ icon, label, href, external }: {
|
|
37
|
+
icon: ReactNode;
|
|
38
|
+
label: string;
|
|
39
|
+
href?: string;
|
|
40
|
+
external?: boolean;
|
|
41
|
+
}): import("react").JSX.Element;
|
|
42
|
+
export declare function InfoRow({ icon, label, value, href, external, mono, hint, }: {
|
|
43
|
+
icon: ReactNode;
|
|
44
|
+
label?: string;
|
|
45
|
+
value: string | null | undefined;
|
|
46
|
+
href?: string;
|
|
47
|
+
external?: boolean;
|
|
48
|
+
mono?: boolean;
|
|
49
|
+
hint?: string;
|
|
50
|
+
}): import("react").JSX.Element;
|
|
51
|
+
export declare function KpiStrip({ counts, onSelect, }: {
|
|
52
|
+
counts: {
|
|
53
|
+
contacts: number;
|
|
54
|
+
locations: number;
|
|
55
|
+
assortment: number;
|
|
56
|
+
orders: number;
|
|
57
|
+
lastSync: string | null | undefined;
|
|
58
|
+
};
|
|
59
|
+
onSelect: (tab: string) => void;
|
|
60
|
+
}): import("react").JSX.Element;
|
|
61
|
+
export declare function ActivityItem({ entry }: {
|
|
62
|
+
entry: {
|
|
63
|
+
id?: string;
|
|
64
|
+
action: string;
|
|
65
|
+
created_at: string;
|
|
66
|
+
};
|
|
67
|
+
}): import("react").JSX.Element;
|
|
68
|
+
export declare function BadgeList({ label, items }: {
|
|
69
|
+
label: string;
|
|
70
|
+
items: string[] | null | undefined;
|
|
71
|
+
}): import("react").JSX.Element;
|
|
72
|
+
export declare function EmptyState({ message }: {
|
|
73
|
+
message: string;
|
|
74
|
+
}): import("react").JSX.Element;
|
|
75
|
+
//# sourceMappingURL=KlantDetailLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AASjD,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EAChB,MAAM,wCAAwC,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAgBF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAK7E;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,GAAE,GAAG,EAAO,GAAG,eAAe,CAgCjF;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,eAAe,GAAG,eAAe,CAqCzE;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,gBAAgB,GAAG,eAAe,CA+B3E;AAED,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;IAAE,OAAO,EAAE,eAAe,CAAA;CAAE,+BA6FvE;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEjE;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,+BAWjI;AAED,wBAAgB,OAAO,CAAC,EACtB,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,GAC/C,EAAE;IACD,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACrI,+BAyBA;AAED,wBAAgB,QAAQ,CAAC,EACvB,MAAM,EAAE,QAAQ,GACjB,EAAE;IACD,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;KAAE,CAAC;IACzH,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,+BAgCA;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,+BAiBrG;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,+BAgBhG;AAED,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,+BAM1D"}
|