@flowselections/floriday-klanten-module 1.0.46 → 1.0.48

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.
Files changed (39) hide show
  1. package/dist-lib/components/klanten/BewerkKlant.d.ts.map +1 -1
  2. package/dist-lib/components/klanten/BewerkKlant.js +1 -1
  3. package/dist-lib/components/klanten/CustomerAnalyticsTab.d.ts.map +1 -1
  4. package/dist-lib/components/klanten/CustomerAnalyticsTab.js +2 -2
  5. package/dist-lib/components/klanten/FloridayNetworkStatus.d.ts +5 -0
  6. package/dist-lib/components/klanten/FloridayNetworkStatus.d.ts.map +1 -0
  7. package/dist-lib/components/klanten/FloridayNetworkStatus.js +18 -0
  8. package/dist-lib/components/klanten/KlantDetailLayout.d.ts +2 -0
  9. package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -1
  10. package/dist-lib/components/klanten/KlantDetailLayout.js +6 -1
  11. package/dist-lib/components/klanten/KlantFormulier.d.ts +3 -1
  12. package/dist-lib/components/klanten/KlantFormulier.d.ts.map +1 -1
  13. package/dist-lib/components/klanten/KlantFormulier.js +16 -4
  14. package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -1
  15. package/dist-lib/components/klanten/NetwerkPagina.js +9 -5
  16. package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts.map +1 -1
  17. package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +10 -2
  18. package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts.map +1 -1
  19. package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.js +4 -7
  20. package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.d.ts +11 -0
  21. package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.d.ts.map +1 -0
  22. package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.js +43 -0
  23. package/dist-lib/index.js +1 -1
  24. package/dist-lib/integrations/supabase/types.d.ts +126 -2
  25. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  26. package/dist-lib/integrations/supabase/types.js +1 -0
  27. package/dist-lib/lib/customers.functions.d.ts +3 -0
  28. package/dist-lib/lib/customers.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/customers.functions.js +6 -2
  30. package/dist-lib/lib/floriday-network-status.functions.d.ts +18 -0
  31. package/dist-lib/lib/floriday-network-status.functions.d.ts.map +1 -0
  32. package/dist-lib/lib/floriday-network-status.functions.js +41 -0
  33. package/dist-lib/lib/price-groups-local.functions.d.ts +42 -0
  34. package/dist-lib/lib/price-groups-local.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/price-groups-local.functions.js +120 -1
  36. package/dist-lib/lib/vat-format.d.ts +17 -0
  37. package/dist-lib/lib/vat-format.d.ts.map +1 -0
  38. package/dist-lib/lib/vat-format.js +64 -0
  39. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"BewerkKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/BewerkKlant.tsx"],"names":[],"mappings":"AAkEA,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,MAAM,EACN,QAAQ,EACR,YAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,+BAkFA"}
1
+ {"version":3,"file":"BewerkKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/BewerkKlant.tsx"],"names":[],"mappings":"AAkEA,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,MAAM,EACN,QAAQ,EACR,YAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,+BAmFA"}
@@ -98,7 +98,7 @@ export function BewerkKlant({ id, onDone, onCancel, hideBackLink, }) {
98
98
  return (_jsxs("main", { className: "p-6 space-y-3", children: [!hideBackLink && _jsx(BackLink, {}), _jsx("p", { className: "text-sm text-destructive", children: error instanceof Error ? error.message : "Klant niet gevonden." })] }));
99
99
  }
100
100
  const c = data.customer;
101
- return (_jsx(KlantFormulier, { title: `${c.company_name || "Klant"} bewerken`, description: "Pas de gegevens aan. Wijzigingen worden direct naar Twinfield doorgestuurd.", submitLabel: "Wijzigingen opslaan", submitting: mut.isPending, defaultValues: toFormValues(c), numberValue: c.twinfield_code ? String(c.twinfield_code) : undefined, numberHint: c.twinfield_code ? "Debiteurnummer in Twinfield" : undefined, creditInfo: {
101
+ return (_jsx(KlantFormulier, { title: `${c.company_name || "Klant"} bewerken`, description: "Pas de gegevens aan. Wijzigingen worden direct naar Twinfield doorgestuurd.", submitLabel: "Wijzigingen opslaan", submitting: mut.isPending, defaultValues: toFormValues(c), numberValue: c.twinfield_code ? String(c.twinfield_code) : undefined, numberHint: c.twinfield_code ? "Debiteurnummer in Twinfield" : undefined, syncError: c.twinfield_sync_error ?? null, creditInfo: {
102
102
  credit_manager: c.credit_manager,
103
103
  credit_manager_name: c.credit_manager_name,
104
104
  reminder_email: c.reminder_email,
@@ -1 +1 @@
1
- {"version":3,"file":"CustomerAnalyticsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/CustomerAnalyticsTab.tsx"],"names":[],"mappings":"AA0GA,wBAAgB,oBAAoB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCA6N1E"}
1
+ {"version":3,"file":"CustomerAnalyticsTab.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/CustomerAnalyticsTab.tsx"],"names":[],"mappings":"AA0GA,wBAAgB,oBAAoB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCAqO1E"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from "react";
3
3
  import { formatDate as formatDateFull } from "../../lib/format-date.js";
4
4
  import { formatEur } from "../../lib/format-currency.js";
@@ -100,7 +100,7 @@ export function CustomerAnalyticsTab({ customerId }) {
100
100
  return null;
101
101
  const a = data;
102
102
  const k = a.kpis;
103
- 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, margin: { left: 30, right: 10 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, width: 100, 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, margin: { left: 30, right: 10 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, width: 40, 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, margin: { left: 30, right: 10 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, width: 100, 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, margin: { left: 30, right: 10 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, width: 100, 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: displayProductName(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."] }) }) })] }));
103
+ 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("p", { className: "text-xs text-muted-foreground", children: ["Bron: ", formatNum(k.order_count), " eigen ERP-orders (ordertotaal)", k.first_order_date && k.last_order_date && (_jsxs(_Fragment, { children: [", periode ", new Date(k.first_order_date).toLocaleDateString("nl-NL"), " \u2013 ", new Date(k.last_order_date).toLocaleDateString("nl-NL")] })), k.order_count > 0 && _jsxs(_Fragment, { children: [", inclusief de grootste order van ", formatEUR(k.largest_order)] }), ". Facturen uit Floriday of Twinfield tellen hier niet mee."] }), _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, margin: { left: 30, right: 10 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, width: 100, 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, margin: { left: 30, right: 10 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, width: 40, 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, margin: { left: 30, right: 10 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, width: 100, 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, margin: { left: 30, right: 10 }, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--border))" }), _jsx(XAxis, { dataKey: "label", fontSize: 11 }), _jsx(YAxis, { fontSize: 11, width: 100, 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: displayProductName(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."] }) }) })] }));
104
104
  }
105
105
  function EmptyChart() {
106
106
  return _jsx("div", { className: "h-full flex items-center justify-center text-sm text-muted-foreground", children: "Geen data in deze periode." });
@@ -0,0 +1,5 @@
1
+ /** Alleen weergave: staat deze Floriday-organisatie in je Floriday-netwerk? Geen klantstatus. */
2
+ export declare function FloridayNetworkStatus({ organizationId }: {
3
+ organizationId: string;
4
+ }): import("react").JSX.Element | null;
5
+ //# sourceMappingURL=FloridayNetworkStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloridayNetworkStatus.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/FloridayNetworkStatus.tsx"],"names":[],"mappings":"AAKA,iGAAiG;AACjG,wBAAgB,qBAAqB,CAAC,EAAE,cAAc,EAAE,EAAE;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,sCAenF"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { useServerFn } from "@tanstack/react-start";
4
+ import { getFloridayNetworkStatus } from "../../lib/floriday-network-status.functions";
5
+ import { formatDate } from "../../lib/format-date";
6
+ /** Alleen weergave: staat deze Floriday-organisatie in je Floriday-netwerk? Geen klantstatus. */
7
+ export function FloridayNetworkStatus({ organizationId }) {
8
+ const fn = useServerFn(getFloridayNetworkStatus);
9
+ const { data } = useQuery({
10
+ queryKey: ["floriday-network-status", organizationId],
11
+ queryFn: () => fn({ data: { organizationId } }),
12
+ staleTime: 5 * 60 * 1000,
13
+ });
14
+ if (!data)
15
+ return null;
16
+ const label = data.inNetwork === null ? "onbekend" : data.inNetwork ? "ja" : "nee";
17
+ return (_jsxs("div", { className: "mt-2 text-xs text-muted-foreground space-y-0.5", children: [_jsxs("p", { children: ["In je Floriday-netwerk: ", _jsx("span", { className: "font-medium text-foreground", children: label })] }), data.endDate && _jsxs("p", { children: ["Einddatum in Floriday: ", formatDate(data.endDate)] })] }));
18
+ }
@@ -26,6 +26,8 @@ export type CustomerProfile = {
26
26
  email_from_contact: boolean;
27
27
  phone_from_contact: boolean;
28
28
  has_contacts: boolean;
29
+ /** Floriday-organisatie-id (alleen op id, nooit op naam). */
30
+ floriday_organization_id?: string | null;
29
31
  };
30
32
  export declare function adaptLocalToProfile(c: any, contacts?: any[]): CustomerProfile;
31
33
  export declare function adaptGroupedToProfile(g: GroupedCustomer): CustomerProfile;
@@ -1 +1 @@
1
- {"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"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"}
@@ -20,6 +20,7 @@ function firstNonEmpty(values) {
20
20
  return null;
21
21
  }
22
22
  import { formatPaymentMethodLabel } from "../../lib/payment-method";
23
+ import { FloridayNetworkStatus } from "./FloridayNetworkStatus";
23
24
  function fmtAddr(a) {
24
25
  if (!a)
25
26
  return null;
@@ -71,6 +72,7 @@ export function adaptLocalToProfile(c, contacts = []) {
71
72
  email_from_contact: !c.email && !!primary?.email,
72
73
  phone_from_contact: !c.phone && !!primary?.phone,
73
74
  has_contacts: contacts.length > 0,
75
+ floriday_organization_id: c.floriday_organization_id ?? null,
74
76
  };
75
77
  }
76
78
  export function adaptGroupedToProfile(g) {
@@ -104,6 +106,8 @@ export function adaptGroupedToProfile(g) {
104
106
  email_from_contact: false,
105
107
  phone_from_contact: false,
106
108
  has_contacts: !!(g.contact_person || pick("contact_person")),
109
+ floriday_organization_id: firstNonEmpty(sources.map((s) => (s.provider === "floriday" ? String(s.external_id ?? "") : null))) ??
110
+ (String(g.group_key ?? "").startsWith("fd:") ? String(g.group_key).slice(3) : null),
107
111
  };
108
112
  }
109
113
  export function adaptExternalToProfile(c) {
@@ -136,6 +140,7 @@ export function adaptExternalToProfile(c) {
136
140
  email_from_contact: false,
137
141
  phone_from_contact: false,
138
142
  has_contacts: !!c.contact_person,
143
+ floriday_organization_id: c.provider === "floriday" ? c.external_id ?? null : null,
139
144
  };
140
145
  }
141
146
  export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false, }) {
@@ -157,7 +162,7 @@ export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false,
157
162
  await onSaveNotes?.(draft);
158
163
  setIsEditing(false);
159
164
  };
160
- return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), (p.payment_providers?.length || p.mailing_address) ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [p.payment_providers && p.payment_providers.length > 0 && (_jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Financi\u00EBle afhandeling", value: p.payment_providers.join(", ") })), p.mailing_address && (_jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), label: "Postadres", value: p.mailing_address }))] })] })) : null, p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "Technische gegevens"] }), showTech && (_jsxs("div", { className: "space-y-3", children: [p.rfh_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-relatienr.", value: p.rfh_id, mono: true }), p.rfh_company_account_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-bedrijfsaccount", value: p.rfh_company_account_id, mono: true }), p.plantion_number && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Plantion-registratienr.", value: p.plantion_number, mono: true }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "GLN", value: p.gln, mono: true }), _jsx(InfoRow, { icon: _jsx(Building2, { className: "h-4 w-4" }), label: "Type", value: formatOrgType(p.organization_type) })] }))] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(SectionLabel, { children: "Notitie" }), editable && !isEditing && (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", "aria-label": "Notitie bewerken", children: [_jsx(Pencil, { className: "h-3 w-3" }), "Bewerken"] }))] }), isEditing ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Textarea, { value: draft, onChange: (e) => setDraft(e.target.value), rows: 4, placeholder: "Voeg een korte notitie over deze klant toe...", disabled: isSaving, className: "text-sm resize-none" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
165
+ return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] }), p.floriday_organization_id && _jsx(FloridayNetworkStatus, { organizationId: p.floriday_organization_id })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), (p.payment_providers?.length || p.mailing_address) ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [p.payment_providers && p.payment_providers.length > 0 && (_jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Financi\u00EBle afhandeling", value: p.payment_providers.join(", ") })), p.mailing_address && (_jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), label: "Postadres", value: p.mailing_address }))] })] })) : null, p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "Technische gegevens"] }), showTech && (_jsxs("div", { className: "space-y-3", children: [p.rfh_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-relatienr.", value: p.rfh_id, mono: true }), p.rfh_company_account_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-bedrijfsaccount", value: p.rfh_company_account_id, mono: true }), p.plantion_number && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Plantion-registratienr.", value: p.plantion_number, mono: true }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "GLN", value: p.gln, mono: true }), _jsx(InfoRow, { icon: _jsx(Building2, { className: "h-4 w-4" }), label: "Type", value: formatOrgType(p.organization_type) })] }))] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(SectionLabel, { children: "Notitie" }), editable && !isEditing && (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", "aria-label": "Notitie bewerken", children: [_jsx(Pencil, { className: "h-3 w-3" }), "Bewerken"] }))] }), isEditing ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Textarea, { value: draft, onChange: (e) => setDraft(e.target.value), rows: 4, placeholder: "Voeg een korte notitie over deze klant toe...", disabled: isSaving, className: "text-sm resize-none" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
161
166
  setDraft(notes ?? "");
162
167
  setIsEditing(false);
163
168
  }, disabled: isSaving, children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Annuleren"] }), _jsx(Button, { type: "button", size: "sm", onClick: handleSave, disabled: isSaving || draft === (notes ?? ""), children: isSaving ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" }), "Opslaan..."] })) : (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Opslaan"] })) })] })] })) : notes ? (_jsx("p", { className: "text-sm whitespace-pre-wrap text-foreground", children: notes })) : editable ? (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex w-full items-center gap-2 rounded-md border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted", children: [_jsx(StickyNote, { className: "h-4 w-4" }), "Notitie toevoegen"] })) : (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(StickyNote, { className: "h-4 w-4" }), "Geen notitie"] }))] })] }) }));
@@ -10,7 +10,7 @@ export declare const emptyKlantFormValues: KlantFormValues;
10
10
  * Herbruikbaar klantformulier voor zowel aanmaken als bewerken. De opbouw,
11
11
  * validatie en verplichte Twinfield-velden zijn in beide gevallen identiek.
12
12
  */
13
- export declare function KlantFormulier({ title, description, submitLabel, submitting, defaultValues, onSubmit, onCancel, extra, extraSections, notice, numberReadOnly, numberHint, numberValue, creditInfo, base, external, }: {
13
+ export declare function KlantFormulier({ title, description, submitLabel, submitting, defaultValues, onSubmit, onCancel, extra, extraSections, notice, numberReadOnly, numberHint, numberValue, syncError, creditInfo, base, external, }: {
14
14
  title: string;
15
15
  description?: string;
16
16
  /** Rustige melding boven het formulier (bv. ontbrekende standaardwaarde, alleen voor beheerders). */
@@ -28,6 +28,8 @@ export declare function KlantFormulier({ title, description, submitLabel, submit
28
28
  numberHint?: string;
29
29
  /** Vaste weergave in het nummerveld (Twinfield-debiteurnummer bij een gekoppelde klant). */
30
30
  numberValue?: string;
31
+ /** Laatste Twinfield-foutmelding van deze klant; een btw-fout (field4) wordt bij het btw-veld getoond. */
32
+ syncError?: string | null;
31
33
  /** Alleen-lezen kredietgegevens uit Twinfield. */
32
34
  creditInfo?: {
33
35
  credit_manager?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"KlantFormulier.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantFormulier.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAwB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAsBxF,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,eAuBf,CAAC;AAErB;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,aAAa,EACb,MAAM,EACN,cAAc,EACd,UAAU,EACV,WAAW,EACX,UAAU,EACV,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,eAAe,CAAC;IAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,iEAAiE;IACjE,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,UAAU,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACrH,4FAA4F;IAC5F,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,+BAgrBA"}
1
+ {"version":3,"file":"KlantFormulier.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantFormulier.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAwB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAuBxF,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,eAuBf,CAAC;AAErB;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,aAAa,EACb,MAAM,EACN,cAAc,EACd,UAAU,EACV,WAAW,EACX,SAAS,EACT,UAAU,EACV,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,eAAe,CAAC;IAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,iEAAiE;IACjE,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0GAA0G;IAC1G,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kDAAkD;IAClD,UAAU,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACrH,4FAA4F;IAC5F,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,+BA4sBA"}
@@ -12,7 +12,8 @@ import { listTwinfieldLedgerAccounts } from "../../lib/twinfield-ledger.function
12
12
  import { customerCreateSchema } from "../../lib/customer-schemas.js";
13
13
  import { requiresVatNumber } from "../../lib/twinfield-required-fields.js";
14
14
  import { TWINFIELD_FIELD_MAX, TWINFIELD_READONLY_FIELDS, TWINFIELD_READONLY_HINT } from "../../lib/twinfield-limits.js";
15
- import { countryNameToFlag, COMMON_COUNTRY_NAMES } from "../../lib/countries.js";
15
+ import { countryNameToFlag, COMMON_COUNTRY_NAMES, toIsoCountry } from "../../lib/countries.js";
16
+ import { checkVatFormat, isTwinfieldVatError } from "../../lib/vat-format.js";
16
17
  import { creditManagerDisplay } from "../../lib/credit-management.js";
17
18
  import { TWINFIELD_DEFAULTS, VAT_DISPLAY_OPTIONS, PAYMENT_SPEC_METHOD_OPTIONS, INVOICE_SEND_AS_OPTIONS, SEND_REMINDER_OPTIONS, DISCOUNT_SURCHARGE_OPTIONS, } from "../../lib/twinfield-defaults.js";
18
19
  /** Rustige aanwijzing per veld wanneer de waarde intussen elders is gewijzigd. */
@@ -53,7 +54,7 @@ export const emptyKlantFormValues = {
53
54
  * Herbruikbaar klantformulier voor zowel aanmaken als bewerken. De opbouw,
54
55
  * validatie en verplichte Twinfield-velden zijn in beide gevallen identiek.
55
56
  */
56
- export function KlantFormulier({ title, description, submitLabel, submitting, defaultValues, onSubmit, onCancel, extra, extraSections, notice, numberReadOnly, numberHint, numberValue, creditInfo, base, external, }) {
57
+ export function KlantFormulier({ title, description, submitLabel, submitting, defaultValues, onSubmit, onCancel, extra, extraSections, notice, numberReadOnly, numberHint, numberValue, syncError, creditInfo, base, external, }) {
57
58
  const classesFn = useServerFn(listCustomerClasses);
58
59
  const ledgerFn = useServerFn(listTwinfieldLedgerAccounts);
59
60
  const [open, setOpen] = useState({
@@ -116,7 +117,12 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
116
117
  }
117
118
  }
118
119
  /** Bij opslaan: niet-aangeraakte velden volgen de laatst bekende serverwaarde. */
120
+ const [vatOverride, setVatOverride] = useState(false);
119
121
  const handleSubmit = form.handleSubmit((values) => {
122
+ if (checkVatFormat(values.vat_number, toIsoCountry(values.country)).status === "invalid" && !vatOverride) {
123
+ form.setFocus("vat_number");
124
+ return;
125
+ }
120
126
  const merged = { ...values };
121
127
  for (const k of Object.keys(customerCreateSchema.shape)) {
122
128
  if (!form.getFieldState(k).isDirty && k in baseRef.current) {
@@ -213,7 +219,13 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
213
219
  "customer_number", "company_name", "language", "website", "vat_number",
214
220
  "coc_number", "contact_person", "attention_to", "email", "billing_email",
215
221
  "phone", "fax",
216
- ]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Field, { label: "Nummer", error: fieldErr("customer_number"), success: !numberReadOnly && isValid("customer_number"), hint: numberHint, children: numberValue ? (_jsx(Input, { readOnly: true, className: "bg-muted", value: numberValue })) : (_jsx(Input, { readOnly: numberReadOnly, ...form.register("customer_number") })) }), _jsx(Field, { label: "Bedrijfsnaam", required: true, error: fieldErr("company_name"), success: isValid("company_name"), hint: lengthWarning(values.company_name, 40, "de bedrijfsnaam"), children: _jsx(Input, { "aria-invalid": !!errors.company_name, ...form.register("company_name") }) }), _jsx(Field, { label: "Taal", error: fieldErr("language"), children: _jsxs(Select, { value: form.watch("language") || undefined, onValueChange: (v) => form.setValue("language", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Geen standaard" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nl", children: "nl" }), _jsx(SelectItem, { value: "en", children: "en" }), _jsx(SelectItem, { value: "de", children: "de" }), _jsx(SelectItem, { value: "fr", children: "fr" })] })] }) }), _jsx(Field, { label: "Website", error: fieldErr("website"), success: isValid("website"), children: _jsx(Input, { placeholder: "https://", ...normalizeOnBlur("website", normUrl) }) }), _jsx(Field, { label: "BTW-nummer", required: vatRequired, error: fieldErr("vat_number"), success: isValid("vat_number"), children: _jsx(Input, { "aria-invalid": !!errors.vat_number, ...normalizeOnBlur("vat_number", normVat) }) }), _jsx(Field, { label: "KvK-nummer", error: fieldErr("coc_number"), success: isValid("coc_number"), children: _jsx(Input, { ...form.register("coc_number") }) }), _jsx(Field, { label: "Contactpersoon", error: fieldErr("contact_person"), success: isValid("contact_person"), children: _jsx(Input, { ...form.register("contact_person") }) }), _jsx(Field, { label: "T.a.v.", error: fieldErr("attention_to"), children: _jsx(Input, { ...form.register("attention_to") }) }), _jsx(Field, { label: "E-mail", error: fieldErr("email"), success: isValid("email"), children: _jsx(Input, { type: "email", "aria-invalid": !!errors.email, placeholder: "naam@bedrijf.nl", ...form.register("email") }) }), _jsx(Field, { label: "E-mail voor facturering", error: fieldErr("billing_email"), success: isValid("billing_email"), children: _jsx(Input, { type: "email", placeholder: "facturen@bedrijf.nl", ...form.register("billing_email") }) }), _jsx(Field, { label: "Telefoon", error: fieldErr("phone"), success: isValid("phone"), children: _jsx(Input, { ...form.register("phone") }) }), _jsx(Field, { label: "Faxnummer", error: fieldErr("fax"), children: _jsx(Input, { ...form.register("fax") }) }), _jsx(Field, { label: "Klantklasse", children: _jsxs(Select, { value: form.watch("customer_class") || "__none__", onValueChange: (v) => form.setValue("customer_class", v === "__none__" ? "" : v, { shouldDirty: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies klasse..." }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "Geen klasse" }), (classData?.classes ?? []).map((c) => _jsx(SelectItem, { value: c, children: c }, c))] })] }) }), _jsx(Field, { label: "Status", hint: "Een inactieve klant wordt in Twinfield verborgen.", children: _jsxs(Select, { value: form.watch("is_active") === false ? "inactief" : "actief", onValueChange: (v) => form.setValue("is_active", v === "actief", { shouldDirty: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "actief", children: "Actief" }), _jsx(SelectItem, { value: "inactief", children: "Inactief" })] })] }) })] }) }), _jsx(Section, { k: "adres", title: "Adres", icon: _jsx(MapPin, { className: "h-5 w-5" }), open: open.adres, onToggle: toggle, hasError: sectionHasError([
222
+ ]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Field, { label: "Nummer", error: fieldErr("customer_number"), success: !numberReadOnly && isValid("customer_number"), hint: numberHint, children: numberValue ? (_jsx(Input, { readOnly: true, className: "bg-muted", value: numberValue })) : (_jsx(Input, { readOnly: numberReadOnly, ...form.register("customer_number") })) }), _jsx(Field, { label: "Bedrijfsnaam", required: true, error: fieldErr("company_name"), success: isValid("company_name"), hint: lengthWarning(values.company_name, 40, "de bedrijfsnaam"), children: _jsx(Input, { "aria-invalid": !!errors.company_name, ...form.register("company_name") }) }), _jsx(Field, { label: "Taal", error: fieldErr("language"), children: _jsxs(Select, { value: form.watch("language") || undefined, onValueChange: (v) => form.setValue("language", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Geen standaard" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nl", children: "nl" }), _jsx(SelectItem, { value: "en", children: "en" }), _jsx(SelectItem, { value: "de", children: "de" }), _jsx(SelectItem, { value: "fr", children: "fr" })] })] }) }), _jsx(Field, { label: "Website", error: fieldErr("website"), success: isValid("website"), children: _jsx(Input, { placeholder: "https://", ...normalizeOnBlur("website", normUrl) }) }), _jsxs(Field, { label: "BTW-nummer", required: vatRequired, error: fieldErr("vat_number"), success: isValid("vat_number"), children: [_jsx(Input, { "aria-invalid": !!errors.vat_number, ...normalizeOnBlur("vat_number", normVat) }), (() => {
223
+ const chk = checkVatFormat(values.vat_number, toIsoCountry(values.country));
224
+ const tfErr = isTwinfieldVatError(syncError) ? syncError : null;
225
+ if (chk.status !== "invalid" && !tfErr)
226
+ return null;
227
+ return (_jsxs("div", { className: "mt-1.5 rounded-md border border-warning/40 bg-warning/10 px-2.5 py-2 text-xs space-y-1.5", children: [_jsx("p", { className: "font-medium", children: chk.status === "invalid" ? chk.message : "Twinfield weigerde het btw-nummer" }), tfErr && _jsxs("p", { className: "text-muted-foreground", children: ["Melding van Twinfield: ", tfErr] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: () => { setVatOverride(false); form.setFocus("vat_number"); }, children: "Corrigeren" }), chk.status === "invalid" && (_jsx(Button, { type: "button", size: "sm", variant: vatOverride ? "secondary" : "ghost", onClick: () => setVatOverride(true), children: vatOverride ? "Wordt toch verstuurd" : "Toch versturen" }))] })] }));
228
+ })()] }), _jsx(Field, { label: "KvK-nummer", error: fieldErr("coc_number"), success: isValid("coc_number"), children: _jsx(Input, { ...form.register("coc_number") }) }), _jsx(Field, { label: "Contactpersoon", error: fieldErr("contact_person"), success: isValid("contact_person"), children: _jsx(Input, { ...form.register("contact_person") }) }), _jsx(Field, { label: "T.a.v.", error: fieldErr("attention_to"), children: _jsx(Input, { ...form.register("attention_to") }) }), _jsx(Field, { label: "E-mail", error: fieldErr("email"), success: isValid("email"), children: _jsx(Input, { type: "email", "aria-invalid": !!errors.email, placeholder: "naam@bedrijf.nl", ...form.register("email") }) }), _jsx(Field, { label: "E-mail voor facturering", error: fieldErr("billing_email"), success: isValid("billing_email"), children: _jsx(Input, { type: "email", placeholder: "facturen@bedrijf.nl", ...form.register("billing_email") }) }), _jsx(Field, { label: "Telefoon", error: fieldErr("phone"), success: isValid("phone"), children: _jsx(Input, { ...form.register("phone") }) }), _jsx(Field, { label: "Faxnummer", error: fieldErr("fax"), children: _jsx(Input, { ...form.register("fax") }) }), _jsx(Field, { label: "Klantklasse", children: _jsxs(Select, { value: form.watch("customer_class") || "__none__", onValueChange: (v) => form.setValue("customer_class", v === "__none__" ? "" : v, { shouldDirty: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies klasse..." }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "Geen klasse" }), (classData?.classes ?? []).map((c) => _jsx(SelectItem, { value: c, children: c }, c))] })] }) }), _jsx(Field, { label: "Status", hint: "Een inactieve klant wordt in Twinfield verborgen.", children: _jsxs(Select, { value: form.watch("is_active") === false ? "inactief" : "actief", onValueChange: (v) => form.setValue("is_active", v === "actief", { shouldDirty: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "actief", children: "Actief" }), _jsx(SelectItem, { value: "inactief", children: "Inactief" })] })] }) })] }) }), _jsx(Section, { k: "adres", title: "Adres", icon: _jsx(MapPin, { className: "h-5 w-5" }), open: open.adres, onToggle: toggle, hasError: sectionHasError([
217
229
  "address_type", "icp_country_code", "address_line1", "address_line2",
218
230
  "street", "house_number", "postal_code", "city", "country",
219
231
  ]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Straat", error: fieldErr("street"), success: isValid("street"), children: _jsx(Input, { "aria-invalid": !!errors.street, ...form.register("street") }) }), _jsx(Field, { label: "Huisnummer", error: fieldErr("house_number"), success: isValid("house_number"), children: _jsx(Input, { "aria-invalid": !!errors.house_number, ...form.register("house_number") }) }), _jsx(Field, { label: "Postcode", error: fieldErr("postal_code"), success: isValid("postal_code"), children: _jsx(Input, { "aria-invalid": !!errors.postal_code, ...normalizeOnBlur("postal_code", normPostal) }) }), _jsx(Field, { label: "Plaats", error: fieldErr("city"), success: isValid("city"), children: _jsx(Input, { "aria-invalid": !!errors.city, ...form.register("city") }) }), _jsx(Field, { label: "Land", error: fieldErr("country"), success: isValid("country"), children: _jsxs(Select, { value: form.watch("country") || undefined, onValueChange: (v) => form.setValue("country", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { children: (() => {
@@ -232,7 +244,7 @@ export function KlantFormulier({ title, description, submitLabel, submitting, de
232
244
  : undefined, children: _jsx(Input, { ...form.register("bank_bic") }) }), _jsx(Field, { label: "Bank - Nationale bankcode", children: _jsx(Input, { ...form.register("bank_national_code") }) }), _jsx(Field, { label: "Bank - Adresregel 1", children: _jsx(Input, { ...form.register("bank_address_line1") }) }), _jsx(Field, { label: "Bank - Adresregel 2", children: _jsx(Input, { ...form.register("bank_address_line2") }) }), _jsx(Field, { label: "Bank - Huisnummer", children: _jsx(Input, { ...form.register("bank_house_number") }) }), _jsx(Field, { label: "Bank - Postcode", error: fieldErr("bank_postal_code"), children: _jsx(Input, { ...normalizeOnBlur("bank_postal_code", normPostal) }) }), _jsx(Field, { label: "Bank - Plaats", children: _jsx(Input, { ...form.register("bank_city") }) }), _jsx(Field, { label: "Bank - Staat", children: _jsx(Input, { ...form.register("bank_state") }) }), _jsx("div", { className: "md:col-span-2", children: _jsx(Field, { label: "Bank - Land", children: _jsx(Input, { ...form.register("bank_country") }) }) })] }) }), _jsx(Section, { k: "facturering", title: "Facturering & betaling", icon: _jsx(ReceiptText, { className: "h-5 w-5" }), open: open.facturering, onToggle: toggle, summary: `${values.payment_term_days ?? "—"} vervaldagen · ${values.vat_display || TWINFIELD_DEFAULTS.vat_display}`, hasError: sectionHasError([
233
245
  "discount_percentage", "discount_days", "payment_term_days", "vat_display",
234
246
  "discount_surcharge", "payment_spec_method", "invoice_send_as", "notes",
235
- ]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kortingspercentage (%)", error: fieldErr("discount_percentage"), hint: "Alleen voor dit systeem; Twinfield accepteert dit niet op een debiteur.", children: _jsx(Input, { type: "number", step: "0.01", min: 0, max: 100, ...numberField("discount_percentage") }) }), _jsx(Field, { label: "Aantal kortingsdagen", error: fieldErr("discount_days"), hint: "Alleen voor dit systeem; gaat niet mee naar Twinfield.", children: _jsx(Input, { type: "number", min: 0, max: 365, ...numberField("discount_days") }) }), _jsx(Field, { label: "Aantal vervaldagen", error: fieldErr("payment_term_days"), children: _jsx(Input, { type: "number", min: 0, max: 365, ...numberField("payment_term_days") }) }), _jsx(Field, { label: "Btw-weergave op factuur", error: fieldErr("vat_display"), children: _jsxs(Select, { value: form.watch("vat_display") || TWINFIELD_DEFAULTS.vat_display, onValueChange: (v) => form.setValue("vat_display", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: VAT_DISPLAY_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Korting / opslag", error: fieldErr("discount_surcharge"), children: _jsxs(Select, { value: form.watch("discount_surcharge") || TWINFIELD_DEFAULTS.discount_surcharge, onValueChange: (v) => form.setValue("discount_surcharge", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DISCOUNT_SURCHARGE_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Verstuurmethode betaalspecificatie", error: fieldErr("payment_spec_method"), children: _jsxs(Select, { value: form.watch("payment_spec_method") || TWINFIELD_DEFAULTS.payment_spec_method, onValueChange: (v) => form.setValue("payment_spec_method", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: PAYMENT_SPEC_METHOD_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Elektronisch factureren", error: fieldErr("e_invoicing"), children: _jsxs(Select, { value: form.watch("e_invoicing") ? "ja" : "nee", onValueChange: (v) => form.setValue("e_invoicing", v === "ja"), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nee", children: "Nee" }), _jsx(SelectItem, { value: "ja", children: "Ja" })] })] }) }), _jsx(Field, { label: "Factuur versturen als", error: fieldErr("invoice_send_as"), children: _jsxs(Select, { value: form.watch("invoice_send_as") || TWINFIELD_DEFAULTS.invoice_send_as, onValueChange: (v) => form.setValue("invoice_send_as", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: INVOICE_SEND_AS_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsxs("div", { className: "md:col-span-2 space-y-1.5", children: [_jsx(Label, { children: "Notities" }), _jsx(Textarea, { rows: 4, ...form.register("notes") }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Interne notitie; wordt niet naar Twinfield gestuurd." })] })] }) }), _jsx(Section, { k: "boekhouding", title: "Kredietbeheer & rekeningschema", icon: _jsx(BookOpen, { className: "h-5 w-5" }), open: open.boekhouding, onToggle: toggle, summary: filled(values.ledger_account)
247
+ ]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kortingspercentage (%)", error: fieldErr("discount_percentage"), hint: "Alleen voor dit systeem; Twinfield accepteert dit niet op een debiteur.", children: _jsx(Input, { type: "number", step: "0.01", min: 0, max: 100, ...numberField("discount_percentage") }) }), _jsx(Field, { label: "Aantal kortingsdagen", error: fieldErr("discount_days"), hint: "Alleen voor dit systeem; gaat niet mee naar Twinfield.", children: _jsx(Input, { type: "number", min: 0, max: 365, ...numberField("discount_days") }) }), _jsx(Field, { label: "Aantal vervaldagen", error: fieldErr("payment_term_days"), children: _jsx(Input, { type: "number", min: 0, max: 365, ...numberField("payment_term_days") }) }), _jsx(Field, { label: "Btw-weergave op factuur", error: fieldErr("vat_display"), children: _jsxs(Select, { value: form.watch("vat_display") || TWINFIELD_DEFAULTS.vat_display, onValueChange: (v) => form.setValue("vat_display", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: VAT_DISPLAY_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Korting / opslag", error: fieldErr("discount_surcharge"), children: _jsxs(Select, { value: form.watch("discount_surcharge") || TWINFIELD_DEFAULTS.discount_surcharge, onValueChange: (v) => form.setValue("discount_surcharge", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DISCOUNT_SURCHARGE_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Verstuurmethode betaalspecificatie", error: fieldErr("payment_spec_method"), children: _jsxs(Select, { value: form.watch("payment_spec_method") || TWINFIELD_DEFAULTS.payment_spec_method, onValueChange: (v) => form.setValue("payment_spec_method", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: PAYMENT_SPEC_METHOD_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Elektronisch factureren", error: fieldErr("e_invoicing"), children: _jsxs(Select, { value: form.watch("e_invoicing") ? "ja" : "nee", onValueChange: (v) => form.setValue("e_invoicing", v === "ja"), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nee", children: "Nee" }), _jsx(SelectItem, { value: "ja", children: "Ja" })] })] }) }), _jsx(Field, { label: "Factuur versturen als", error: fieldErr("invoice_send_as"), children: _jsxs(Select, { value: form.watch("invoice_send_as") || TWINFIELD_DEFAULTS.invoice_send_as, onValueChange: (v) => form.setValue("invoice_send_as", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: INVOICE_SEND_AS_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsxs("div", { className: "md:col-span-2 space-y-1.5", children: [_jsx(Label, { children: "Notities" }), _jsx(Textarea, { rows: 4, ...form.register("notes") }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Interne notitie; wordt niet naar Twinfield gestuurd." })] })] }) }), _jsx(Section, { k: "boekhouding", title: "Boekhouding (krediet en grootboek)", icon: _jsx(BookOpen, { className: "h-5 w-5" }), open: open.boekhouding, onToggle: toggle, summary: filled(values.ledger_account)
236
248
  ? `Grootboek ${values.ledger_account}`
237
249
  : "Grootboekrekening nog niet gekozen", hasError: sectionHasError(["credit_limit", "send_reminder", "ledger_account"]), children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kredietlimiet (\u20AC)", error: fieldErr("credit_limit"), hint: TWINFIELD_READONLY_HINT, children: _jsx(Input, { type: "number", step: "0.01", min: 0, readOnly: true, className: "bg-muted", ...numberField("credit_limit") }) }), _jsx(Field, { label: "Aanmanen", error: fieldErr("send_reminder"), hint: TWINFIELD_READONLY_HINT, children: _jsxs(Select, { disabled: true, value: form.watch("send_reminder") || TWINFIELD_DEFAULTS.send_reminder, onValueChange: (v) => form.setValue("send_reminder", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SEND_REMINDER_OPTIONS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }) }), _jsx(Field, { label: "Kredietbeheerder", hint: TWINFIELD_READONLY_HINT, children: _jsx(Input, { readOnly: true, className: "bg-muted", value: creditManagerDisplay(creditInfo ?? {}), placeholder: "\u2014" }) }), _jsx(Field, { label: "E-mail voor aanmaningen", hint: TWINFIELD_READONLY_HINT, children: _jsx(Input, { readOnly: true, className: "bg-muted", value: creditInfo?.reminder_email ?? "", placeholder: "\u2014" }) }), _jsx(Field, { label: "Geblokkeerd", error: fieldErr("blocked"), hint: TWINFIELD_READONLY_HINT, children: _jsxs(Select, { disabled: true, value: form.watch("blocked") ? "ja" : "nee", onValueChange: (v) => form.setValue("blocked", v === "ja"), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nee", children: "Nee" }), _jsx(SelectItem, { value: "ja", children: "Ja" })] })] }) }), _jsx(Field, { label: "Grootboekrekening", error: fieldErr("ledger_account"), success: isValid("ledger_account"), hint: ledgerLoading
238
250
  ? "Rekeningschema wordt geladen uit Twinfield..."
@@ -1 +1 @@
1
- {"version":3,"file":"NetwerkPagina.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NetwerkPagina.tsx"],"names":[],"mappings":"AAqCA,wBAAgB,aAAa,gCAmP5B"}
1
+ {"version":3,"file":"NetwerkPagina.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NetwerkPagina.tsx"],"names":[],"mappings":"AAsCA,wBAAgB,aAAa,gCAmQ5B"}
@@ -3,7 +3,7 @@ import { formatDate } from "../../lib/format-date.js";
3
3
  import { useQuery } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { useState } from "react";
6
- import { Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Switch, Label, } from "@flowselections/core";
6
+ import { Input, Button, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Switch, Label, } from "@flowselections/core";
7
7
  import { Search, MapPin, Globe } from "lucide-react";
8
8
  import { BackLink } from "./BackLink.js";
9
9
  import { countryNameToFlag } from "../../lib/countries.js";
@@ -25,10 +25,13 @@ export function NetwerkPagina() {
25
25
  const [hideUnknown, setHideUnknown] = useState(false);
26
26
  const [types, setTypes] = useState(["CUSTOMER", "AGENT"]);
27
27
  const [includeExpired, setIncludeExpired] = useState(false);
28
+ const [page, setPage] = useState(0);
29
+ const PAGE_SIZE = 100;
28
30
  const list = useServerFn(listFloridayNetwork);
29
31
  const { data, isLoading } = useQuery({
30
- queryKey: ["floriday-network", search, types, includeExpired],
31
- queryFn: () => list({ data: { search, types, includeExpired, limit: 1000 } }).then(decodeDeep),
32
+ queryKey: ["floriday-network", search, types, includeExpired, page],
33
+ queryFn: () => list({ data: { search, types, includeExpired, limit: PAGE_SIZE, offset: page * PAGE_SIZE } }).then(decodeDeep),
34
+ placeholderData: (prev) => prev,
32
35
  });
33
36
  const all = data?.organizations ?? [];
34
37
  const named = all.filter((o) => o.organization_name && o.organization_name.trim() !== "");
@@ -38,14 +41,15 @@ export function NetwerkPagina() {
38
41
  const truncated = !!data?.truncated;
39
42
  const toggleType = (value) => {
40
43
  setTypes((prev) => prev.includes(value) ? prev.filter((t) => t !== value) : [...prev, value]);
44
+ setPage(0);
41
45
  };
42
- return (_jsx(TooltipPrimitive.Provider, { delayDuration: 200, children: _jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center justify-between gap-4", children: _jsxs("div", { children: [_jsx(BackLink, {}), _jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Klanten toevoegen uit netwerk" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Doorzoek alle organisaties die via het netwerk beschikbaar zijn." })] }) }), _jsxs("div", { className: "relative max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op organisatienaam of GLN...", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsx("div", { className: "flex items-center gap-2 flex-wrap", children: TYPE_OPTIONS.map((opt) => {
46
+ return (_jsx(TooltipPrimitive.Provider, { delayDuration: 200, children: _jsxs("main", { className: "p-4 sm:p-6 space-y-4 min-w-0 max-w-full overflow-x-hidden", children: [_jsx("div", { className: "flex items-center justify-between gap-4", children: _jsxs("div", { children: [_jsx(BackLink, {}), _jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Klanten toevoegen uit netwerk" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Doorzoek alle organisaties die via het netwerk beschikbaar zijn." })] }) }), _jsxs("div", { className: "relative max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op organisatienaam of GLN...", value: search, onChange: (e) => { setSearch(e.target.value); setPage(0); }, className: "pl-9" })] }), _jsx("div", { className: "flex items-center gap-2 flex-wrap", children: TYPE_OPTIONS.map((opt) => {
43
47
  const active = types.includes(opt.value);
44
48
  return (_jsx("button", { type: "button", onClick: () => toggleType(opt.value), className: "inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs transition-colors " +
45
49
  (active
46
50
  ? "bg-primary text-primary-foreground border-primary"
47
51
  : "bg-card text-muted-foreground border-border hover:bg-muted"), children: opt.label }, opt.value));
48
- }) }), _jsxs("div", { className: "flex items-center gap-6 flex-wrap text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "include-expired", checked: includeExpired, onCheckedChange: setIncludeExpired }), _jsx(Label, { htmlFor: "include-expired", className: "cursor-pointer", children: "Toon be\u00EBindigde relaties" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "hide-unknown", checked: hideUnknown, onCheckedChange: setHideUnknown }), _jsx(Label, { htmlFor: "hide-unknown", className: "cursor-pointer", children: "Verberg organisaties zonder naam" }), hideUnknown && hiddenCount > 0 ? (_jsxs("span", { className: "text-muted-foreground", children: ["(", hiddenCount, " verborgen)"] })) : null] })] }), _jsx("div", { className: "text-xs text-muted-foreground", children: isLoading ? ("Laden…") : (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium text-foreground", children: orgs.length.toLocaleString("nl-NL") }), " ", "weergegeven van", " ", _jsx("span", { className: "font-medium text-foreground", children: totalMatching.toLocaleString("nl-NL") }), " ", "organisaties \u00B7 bron: Floriday", truncated && (_jsx("span", { className: "ml-1 text-warning", children: "(verfijn de zoekopdracht om alles te zien)" }))] })) }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Organisatie" }), _jsx(TableHead, { children: "GLN" }), _jsx(TableHead, { children: "Adres" }), _jsx(TableHead, { children: "Land" }), _jsx(TableHead, { children: "Type" }), _jsx(TableHead, { children: "Prijscategorie" }), _jsx(TableHead, { children: "Bijgewerkt" })] }) }), _jsx(TableBody, { children: isLoading ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center py-8 text-muted-foreground", children: "Laden\u2026" }) })) : orgs.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center py-8 text-muted-foreground", children: "Geen organisaties gevonden." }) })) : (orgs.map((o) => {
52
+ }) }), _jsxs("div", { className: "flex items-center gap-6 flex-wrap text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "include-expired", checked: includeExpired, onCheckedChange: (v) => { setIncludeExpired(v); setPage(0); } }), _jsx(Label, { htmlFor: "include-expired", className: "cursor-pointer", children: "Toon be\u00EBindigde relaties" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "hide-unknown", checked: hideUnknown, onCheckedChange: setHideUnknown }), _jsx(Label, { htmlFor: "hide-unknown", className: "cursor-pointer", children: "Verberg organisaties zonder naam" }), hideUnknown && hiddenCount > 0 ? (_jsxs("span", { className: "text-muted-foreground", children: ["(", hiddenCount, " verborgen)"] })) : null] })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground", children: [_jsx("div", { children: isLoading ? ("Laden…") : totalMatching === 0 ? ("0 organisaties gevonden · bron: Floriday") : (_jsxs(_Fragment, { children: [_jsxs("span", { className: "font-medium text-foreground", children: [(page * PAGE_SIZE + 1).toLocaleString("nl-NL"), "\u2013", (page * PAGE_SIZE + all.length).toLocaleString("nl-NL")] }), " ", "van", " ", _jsx("span", { className: "font-medium text-foreground", children: totalMatching.toLocaleString("nl-NL") }), " ", "getoond \u00B7 bron: Floriday", totalMatching > PAGE_SIZE && _jsx("span", { className: "ml-1", children: "\u00B7 verfijn je zoekopdracht" }), hiddenCount > 0 && _jsxs("span", { className: "ml-1", children: ["(", hiddenCount, " zonder naam verborgen)"] })] })) }), totalMatching > PAGE_SIZE && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { size: "sm", variant: "outline", disabled: page === 0, onClick: () => setPage((p) => Math.max(0, p - 1)), children: "Vorige" }), _jsxs("span", { children: ["Pagina ", page + 1, " van ", Math.ceil(totalMatching / PAGE_SIZE)] }), _jsx(Button, { size: "sm", variant: "outline", disabled: !truncated, onClick: () => setPage((p) => p + 1), children: "Volgende" })] }))] }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Organisatie" }), _jsx(TableHead, { children: "GLN" }), _jsx(TableHead, { children: "Adres" }), _jsx(TableHead, { children: "Land" }), _jsx(TableHead, { children: "Type" }), _jsx(TableHead, { children: "Prijscategorie" }), _jsx(TableHead, { children: "Bijgewerkt" })] }) }), _jsx(TableBody, { children: isLoading ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center py-8 text-muted-foreground", children: "Laden\u2026" }) })) : orgs.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center py-8 text-muted-foreground", children: "Geen organisaties gevonden." }) })) : (orgs.map((o) => {
49
53
  const displayName = o.organization_name && o.organization_name.trim() !== ""
50
54
  ? o.organization_name
51
55
  : null;
@@ -1 +1 @@
1
- {"version":3,"file":"CustomerPickerDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/CustomerPickerDialog.tsx"],"names":[],"mappings":"AAcA,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,GAClD,EAAE;IACD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACrC,+BA+IA"}
1
+ {"version":3,"file":"CustomerPickerDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/CustomerPickerDialog.tsx"],"names":[],"mappings":"AAgBA,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,GAClD,EAAE;IACD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACrC,+BA6JA"}
@@ -5,14 +5,17 @@ import { useServerFn } from "@tanstack/react-start";
5
5
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Button, Input, Checkbox, Badge, } from "@flowselections/core";
6
6
  import { Search } from "lucide-react";
7
7
  import { listExternalCustomers } from "../../../lib/external-customers.functions";
8
- import { setPriceGroupMembers, getCustomerPriceGroup, } from "../../../lib/price-groups-local.functions";
8
+ import { setPriceGroupMembers, getCustomerPriceGroup, listUnknownFloridayMembers, } from "../../../lib/price-groups-local.functions";
9
9
  import { decodeDeep } from "../../../lib/decode-html";
10
10
  import { countryNameToFlag } from "../../../lib/countries";
11
+ import { formatDateTime } from "../../../lib/format-date";
12
+ import { UnknownFloridayMembers } from "./UnknownFloridayMembers";
11
13
  export function CustomerPickerDialog({ priceGroupId, open, onOpenChange, value, onChange, }) {
12
14
  const qc = useQueryClient();
13
15
  const listCustomers = useServerFn(listExternalCustomers);
14
16
  const getGroup = useServerFn(getCustomerPriceGroup);
15
17
  const setMembers = useServerFn(setPriceGroupMembers);
18
+ const listUnknown = useServerFn(listUnknownFloridayMembers);
16
19
  const [search, setSearch] = useState("");
17
20
  const [selected, setSelected] = useState(new Set());
18
21
  const isControlled = onChange !== undefined;
@@ -26,6 +29,11 @@ export function CustomerPickerDialog({ priceGroupId, open, onOpenChange, value,
26
29
  queryFn: () => getGroup({ data: { id: priceGroupId } }).then(decodeDeep),
27
30
  enabled: open && !!priceGroupId && !isControlled,
28
31
  });
32
+ const { data: unknown } = useQuery({
33
+ queryKey: ["unknown-floriday-members", priceGroupId],
34
+ queryFn: () => listUnknown({ data: { priceGroupId: priceGroupId } }).then(decodeDeep),
35
+ enabled: open && !!priceGroupId && !isControlled,
36
+ });
29
37
  useEffect(() => {
30
38
  if (!open)
31
39
  return;
@@ -73,5 +81,5 @@ export function CustomerPickerDialog({ priceGroupId, open, onOpenChange, value,
73
81
  return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-[720px]", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Klanten toewijzen" }) }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op naam, plaats of BTW-nummer\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [_jsxs("span", { children: [filtered.length, " klanten"] }), _jsxs("span", { children: [_jsx("strong", { className: "text-foreground", children: selected.size }), " geselecteerd"] })] }), _jsx("div", { className: "max-h-[380px] overflow-y-auto border rounded-md divide-y", children: isLoading ? (_jsx("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "Laden\u2026" })) : filtered.length === 0 ? (_jsx("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "Geen klanten gevonden." })) : filtered.map((c) => {
74
82
  const checked = selected.has(c.group_key);
75
83
  return (_jsxs("label", { className: "flex items-center gap-3 p-2.5 hover:bg-muted/50 cursor-pointer", children: [_jsx(Checkbox, { checked: checked, onCheckedChange: () => toggle(c.group_key) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: c.display_name }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c.city ? (_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { children: c.city }), c.country && (_jsxs(_Fragment, { children: [_jsx("span", { children: "\u00B7" }), _jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(c.country) ?? "🌐" }), _jsx("span", { children: c.country })] }))] })) : c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(c.country) ?? "🌐" }), _jsx("span", { children: c.country })] })) : "—" })] }), _jsx("div", { className: "flex gap-1", children: c.providers.slice(0, 3).map((p) => (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: p }, p))) })] }, c.group_key));
76
- }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsx(Button, { onClick: handleSave, disabled: !isControlled && saveMut.isPending, children: !isControlled && saveMut.isPending ? "Opslaan…" : "Selectie opslaan" })] })] }) }));
84
+ }) }), unknown?.lastModifiedInFloriday && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Laatst gewijzigd in Floriday: ", formatDateTime(unknown.lastModifiedInFloriday)] })), priceGroupId && unknown && unknown.members.length > 0 && (_jsx(UnknownFloridayMembers, { members: unknown.members, priceGroupIdFor: () => priceGroupId })), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsx(Button, { onClick: handleSave, disabled: !isControlled && saveMut.isPending, children: !isControlled && saveMut.isPending ? "Opslaan…" : "Selectie opslaan" })] })] }) }));
77
85
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PriceGroupsPage.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/PriceGroupsPage.tsx"],"names":[],"mappings":"AAuHA,wBAAgB,eAAe,gCA8U9B"}
1
+ {"version":3,"file":"PriceGroupsPage.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/PriceGroupsPage.tsx"],"names":[],"mappings":"AA2HA,wBAAgB,eAAe,gCAsV9B"}
@@ -12,6 +12,7 @@ import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrate
12
12
  import { CSS } from "@dnd-kit/utilities";
13
13
  import { listCustomerPriceGroups, deleteCustomerPriceGroup, reorderCustomerPriceGroups, updateCustomerPriceGroup, createCustomerPriceGroup, importFloridayPriceGroups, } from "../../../lib/price-groups-local.functions";
14
14
  import { CustomerPickerDialog } from "./CustomerPickerDialog";
15
+ import { UnknownFloridayMembers } from "./UnknownFloridayMembers";
15
16
  import { friendlyError } from "../../../lib/errors";
16
17
  import { toast } from "sonner";
17
18
  function unitLabel(u) {
@@ -34,7 +35,7 @@ function calcSummary(g) {
34
35
  function SortableRow({ g, onEdit, onMembers, onDelete, onDuplicate, onToggleActive }) {
35
36
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: g.id });
36
37
  const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.6 : 1 };
37
- return (_jsxs(TableRow, { ref: setNodeRef, style: style, className: isDragging ? "bg-muted/50" : undefined, children: [_jsx(TableCell, { className: "w-[36px] p-0 pl-2", children: _jsx("button", { type: "button", className: "cursor-grab touch-none text-muted-foreground hover:text-foreground p-2", "aria-label": "Verslepen", ...attributes, ...listeners, children: _jsx(GripVertical, { className: "h-4 w-4" }) }) }), _jsxs(TableCell, { children: [_jsxs("div", { className: "font-medium flex items-center gap-2", children: [g.name, g.source === "floriday" && _jsx(Badge, { variant: "outline", className: "text-[10px]", children: "Floriday" }), !g.is_active && _jsx(Badge, { variant: "secondary", className: "text-[10px]", children: "Inactief" })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Laatst gewijzigd: ", formatDateTime(g.updated_at)] })] }), _jsx(TableCell, { children: _jsx(Badge, { variant: g.pricing_mode === "calculated" ? "default" : "secondary", children: g.pricing_mode === "calculated" ? "Berekende prijzen" : "Handmatige prijzen" }) }), _jsxs(TableCell, { className: "tabular-nums", children: [g.member_count, " klanten"] }), _jsxs(TableCell, { className: "text-sm", children: [g.price_scope === "incl_delivery" ? "Levering en transport" : "Exclusief levering", g.include_stickers && _jsx("div", { className: "text-xs text-muted-foreground", children: "Incl. stickers" })] }), _jsx(TableCell, { className: "text-sm", children: unitLabel(g.unit) }), _jsx(TableCell, { className: "text-sm", children: g.currency }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: calcSummary(g) }), _jsx(TableCell, { className: "w-[52px] text-right pr-2", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Acties", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: onEdit, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), "Bewerken"] }), _jsxs(DropdownMenuItem, { onClick: onMembers, children: [_jsx(Users, { className: "h-4 w-4 mr-2" }), "Klanten toewijzen"] }), _jsxs(DropdownMenuItem, { onClick: onDuplicate, children: [_jsx(Copy, { className: "h-4 w-4 mr-2" }), "Dupliceren"] }), _jsx(DropdownMenuItem, { onClick: onToggleActive, children: g.is_active
38
+ return (_jsxs(TableRow, { ref: setNodeRef, style: style, className: isDragging ? "bg-muted/50" : undefined, children: [_jsx(TableCell, { className: "w-[36px] p-0 pl-2", children: _jsx("button", { type: "button", className: "cursor-grab touch-none text-muted-foreground hover:text-foreground p-2", "aria-label": "Verslepen", ...attributes, ...listeners, children: _jsx(GripVertical, { className: "h-4 w-4" }) }) }), _jsxs(TableCell, { children: [_jsxs("div", { className: "font-medium flex items-center gap-2", children: [g.name, g.source === "floriday" && _jsx(Badge, { variant: "outline", className: "text-[10px]", children: "Floriday" }), g.source === "floriday" && g.external_synced_at && (_jsxs("span", { className: "text-[10px] text-muted-foreground", children: ["Ge\u00EFmporteerd op ", formatDateTime(g.external_synced_at)] })), !g.is_active && _jsx(Badge, { variant: "secondary", className: "text-[10px]", children: "Inactief" })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Laatst gewijzigd: ", formatDateTime(g.updated_at)] })] }), _jsx(TableCell, { children: _jsx(Badge, { variant: g.pricing_mode === "calculated" ? "default" : "secondary", children: g.pricing_mode === "calculated" ? "Berekende prijzen" : "Handmatige prijzen" }) }), _jsxs(TableCell, { className: "tabular-nums", children: [g.member_count, " klanten"] }), _jsxs(TableCell, { className: "text-sm", children: [g.price_scope === "incl_delivery" ? "Levering en transport" : "Exclusief levering", g.include_stickers && _jsx("div", { className: "text-xs text-muted-foreground", children: "Incl. stickers" })] }), _jsx(TableCell, { className: "text-sm", children: unitLabel(g.unit) }), _jsx(TableCell, { className: "text-sm", children: g.currency }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: calcSummary(g) }), _jsx(TableCell, { className: "w-[52px] text-right pr-2", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Acties", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: onEdit, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), "Bewerken"] }), _jsxs(DropdownMenuItem, { onClick: onMembers, children: [_jsx(Users, { className: "h-4 w-4 mr-2" }), "Klanten toewijzen"] }), _jsxs(DropdownMenuItem, { onClick: onDuplicate, children: [_jsx(Copy, { className: "h-4 w-4 mr-2" }), "Dupliceren"] }), _jsx(DropdownMenuItem, { onClick: onToggleActive, children: g.is_active
38
39
  ? (_jsxs(_Fragment, { children: [_jsx(EyeOff, { className: "h-4 w-4 mr-2" }), "Op inactief zetten"] }))
39
40
  : (_jsxs(_Fragment, { children: [_jsx(Eye, { className: "h-4 w-4 mr-2" }), "Activeren"] })) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: onDelete, className: "text-destructive focus:text-destructive", children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), "Verwijderen"] })] })] }) })] }));
40
41
  }
@@ -69,13 +70,9 @@ export function PriceGroupsPage() {
69
70
  mutationFn: () => importFn({ data: { preview: false } }),
70
71
  onSuccess: (res) => {
71
72
  const r = res;
72
- setImportOpen(false);
73
- setPreview(null);
73
+ setPreview(r);
74
74
  qc.invalidateQueries({ queryKey: ["customer-price-groups"] });
75
75
  toast.success(`Import klaar: ${r.created} nieuw, ${r.updated} bijgewerkt, ${r.deactivated} op inactief, ${r.membersLinked} klantkoppelingen.`);
76
- if (r.unmatchedOrganizations.length > 0) {
77
- toast.warning(`${r.unmatchedOrganizations.length} Floriday-klant(en) zijn hier nog niet bekend en overgeslagen.`);
78
- }
79
76
  },
80
77
  onError: (e) => toast.error(friendlyError(e)),
81
78
  });
@@ -158,5 +155,5 @@ export function PriceGroupsPage() {
158
155
  setDeleteTarget(null); }, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { children: ["Prijsgroep \"", deleteTarget?.name, "\" verwijderen?"] }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setDeleteTarget(null), children: "Annuleren" }), _jsx(Button, { variant: "destructive", onClick: () => { if (deleteTarget)
159
156
  deleteMut.mutate(deleteTarget.id); setDeleteTarget(null); }, children: "Verwijderen" })] })] }) }), membersFor && (_jsx(CustomerPickerDialog, { priceGroupId: membersFor, open: !!membersFor, onOpenChange: (o) => { if (!o)
160
157
  setMembersFor(null); } })), _jsx(Dialog, { open: importOpen, onOpenChange: (o) => { setImportOpen(o); if (!o)
161
- setPreview(null); }, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Prijsgroepen importeren uit Floriday" }) }), preview && (_jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Badge, { variant: "default", children: [preview.created, " nieuw"] }), _jsxs(Badge, { variant: "secondary", children: [preview.updated, " bijwerken"] }), _jsxs(Badge, { variant: "outline", children: [preview.deactivated, " op inactief"] }), _jsxs(Badge, { variant: "outline", children: [preview.membersLinked, " klantkoppelingen"] })] }), preview.items.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Er zijn geen prijsgroepen uit Floriday om te importeren." })) : (_jsx("div", { className: "max-h-64 overflow-y-auto rounded-md border divide-y", children: preview.items.map((it) => (_jsxs("div", { className: "flex items-center justify-between gap-3 px-3 py-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium truncate", children: it.name }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [it.customers, " klant(en) in Floriday"] })] }), _jsx(Badge, { variant: it.action === "create" ? "default" : it.action === "update" ? "secondary" : "outline", children: it.action === "create" ? "Nieuw" : it.action === "update" ? "Bijwerken" : "Inactief" })] }, it.external_id))) })), preview.unmatchedOrganizations.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [preview.unmatchedOrganizations.length, " Floriday-klant(en) zijn hier nog niet bekend en worden overgeslagen."] })), _jsx("p", { className: "text-xs text-muted-foreground", children: "Kortingen, toeslagen en afrondingsregels blijven onaangeroerd; prijsgroepen die je zelf hebt gemaakt blijven ongewijzigd." })] })), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setImportOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: () => importMut.mutate(), disabled: importMut.isPending || !preview || preview.items.length === 0, children: importMut.isPending ? "Bezig…" : "Importeren" })] })] }) })] }));
158
+ setPreview(null); }, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: preview && !preview.preview ? "Uitkomst import uit Floriday" : "Prijsgroepen importeren uit Floriday" }) }), preview && (_jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Badge, { variant: "default", children: [preview.created, " nieuw"] }), _jsxs(Badge, { variant: "secondary", children: [preview.updated, " bijwerken"] }), _jsxs(Badge, { variant: "outline", children: [preview.deactivated, " op inactief"] }), _jsxs(Badge, { variant: "outline", children: [preview.membersLinked, " klantkoppelingen"] })] }), preview.items.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Er zijn geen prijsgroepen uit Floriday om te importeren." })) : (_jsx("div", { className: "max-h-64 overflow-y-auto rounded-md border divide-y", children: preview.items.map((it) => (_jsxs("div", { className: "flex items-center justify-between gap-3 px-3 py-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium truncate", children: it.name }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [it.customers, " klant(en) in Floriday", it.last_modified_in_floriday && _jsxs(_Fragment, { children: [" \u00B7 laatst gewijzigd in Floriday ", formatDateTime(it.last_modified_in_floriday)] })] })] }), _jsx(Badge, { variant: it.action === "create" ? "default" : it.action === "update" ? "secondary" : "outline", children: it.action === "create" ? "Nieuw" : it.action === "update" ? "Bijwerken" : "Inactief" })] }, it.external_id))) })), preview.unmatchedDetails.length > 0 && (preview.preview ? (_jsxs("p", { className: "text-xs text-muted-foreground", children: [preview.unmatchedDetails.length, " Floriday-klant(en) zijn nog geen klant in het ERP. Na het importeren kun je ze per klant toevoegen."] })) : (_jsx(UnknownFloridayMembers, { members: preview.unmatchedDetails, priceGroupIdFor: (ext) => groups.find((g) => g.source === "floriday" && g.external_id === ext)?.id ?? null }))), _jsx("p", { className: "text-xs text-muted-foreground", children: "Kortingen, toeslagen en afrondingsregels blijven onaangeroerd; prijsgroepen die je zelf hebt gemaakt blijven ongewijzigd." })] })), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setImportOpen(false), children: preview && !preview.preview ? "Sluiten" : "Annuleren" }), (!preview || preview.preview) && (_jsx(Button, { onClick: () => importMut.mutate(), disabled: importMut.isPending || !preview || preview.items.length === 0, children: importMut.isPending ? "Bezig…" : "Importeren" }))] })] }) })] }));
162
159
  }
@@ -0,0 +1,11 @@
1
+ import { type UnknownFloridayMember } from "../../../lib/price-groups-local.functions";
2
+ /**
3
+ * Floriday-leden van een prijsgroep die nog geen ERP-klant zijn. Er wordt niets vastgelegd
4
+ * tot iemand op "Toevoegen" klikt; dan ontstaat een klant zonder Twinfield-debiteur.
5
+ */
6
+ export declare function UnknownFloridayMembers({ members, priceGroupIdFor, }: {
7
+ members: UnknownFloridayMember[];
8
+ /** Geeft het ERP-prijsgroep-id voor een Floriday-prijsgroep, of null als die nog niet geïmporteerd is. */
9
+ priceGroupIdFor: (externalId: string) => string | null;
10
+ }): import("react").JSX.Element | null;
11
+ //# sourceMappingURL=UnknownFloridayMembers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UnknownFloridayMembers.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/UnknownFloridayMembers.tsx"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,2CAA2C,CAAC;AAGnD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,OAAO,EACP,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,0GAA0G;IAC1G,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACxD,sCAoEA"}