@flowselections/floriday-klanten-module 1.0.49 → 1.0.50

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 (24) hide show
  1. package/dist-lib/components/klanten/DeliveryLocationsTab.js +1 -1
  2. package/dist-lib/components/klanten/DiscountsTab.js +1 -1
  3. package/dist-lib/components/klanten/ExternalCacheBanner.d.ts.map +1 -1
  4. package/dist-lib/components/klanten/ExternalCacheBanner.js +2 -5
  5. package/dist-lib/components/klanten/FloridayNetworkStatus.d.ts.map +1 -1
  6. package/dist-lib/components/klanten/FloridayNetworkStatus.js +3 -1
  7. package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
  8. package/dist-lib/components/klanten/KlantDetail.js +15 -3
  9. package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -1
  10. package/dist-lib/components/klanten/KlantDetailLayout.js +3 -1
  11. package/dist-lib/components/klanten/KlantenOverzicht.js +2 -2
  12. package/dist-lib/components/klanten/SyncStatusChip.d.ts.map +1 -1
  13. package/dist-lib/components/klanten/SyncStatusChip.js +5 -4
  14. package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +2 -2
  15. package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.js +2 -2
  16. package/dist-lib/integrations/supabase/types.d.ts +104 -0
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/lib/floriday-network-status.functions.d.ts +2 -0
  19. package/dist-lib/lib/floriday-network-status.functions.d.ts.map +1 -1
  20. package/dist-lib/lib/floriday-network-status.functions.js +38 -17
  21. package/dist-lib/lib/friendly-twinfield-error.d.ts +8 -0
  22. package/dist-lib/lib/friendly-twinfield-error.d.ts.map +1 -0
  23. package/dist-lib/lib/friendly-twinfield-error.js +16 -0
  24. package/package.json +1 -1
@@ -60,7 +60,7 @@ export function DeliveryLocationsTab({ customerId, groupKey, externalProvider, e
60
60
  mutationFn: () => importFn({ data: { preview: true } }),
61
61
  onSuccess: (res) => {
62
62
  if (res.cacheEmpty) {
63
- toast.info("Nog geen Floriday-afleverlocaties in de cache — de Floriday-sync moet deze eerst aanleveren.");
63
+ toast.info("Er zijn nog geen afleverlocaties uit Floriday opgehaald.");
64
64
  return;
65
65
  }
66
66
  setImportPreview(res);
@@ -77,7 +77,7 @@ export function DiscountsTab({ buyerOrganizationId }) {
77
77
  }
78
78
  const s = data?.summary;
79
79
  const rows = data?.rows ?? [];
80
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsx(SummaryCard, { label: "Contracten", value: s?.agreements ?? 0, icon: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsregels", value: s?.sales_strategies ?? 0, icon: _jsx(Percent, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsgroep-regels", value: s?.price_groups ?? 0, icon: _jsx(Users, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Laatst gesynchroniseerd", value: formatDateTime(s?.last_fetched_at), icon: _jsx(Info, { className: "h-4 w-4" }), small: true })] }), rows.length === 0 ? (_jsxs("div", { className: "space-y-3", children: [_jsx(ExternalCacheBanner, { lastFetchedAt: s?.last_fetched_at ?? null, providers: [], title: "Floriday-kortingen", description: "De beschikbare kortings- en prijsgegevens worden automatisch bijgewerkt vanuit Floriday." }), _jsx(EmptyState, { message: "Voor deze klant zijn nog geen kortingen of prijsafspraken beschikbaar." })] })) : (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { placeholder: "Zoek op artikel of ID\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "max-w-xs" }), _jsxs(Select, { value: source, onValueChange: (v) => setSource(v), children: [_jsx(SelectTrigger, { className: "w-44", children: _jsx(SelectValue, { placeholder: "Bron" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle bronnen" }), _jsx(SelectItem, { value: "agreement", children: "Contract" }), _jsx(SelectItem, { value: "sales_strategy", children: "Sales Strategy" }), _jsx(SelectItem, { value: "price_group", children: "Prijsgroep" })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-muted-foreground ml-auto", children: [_jsx("input", { type: "checkbox", checked: onlyActive, onChange: (e) => setOnlyActive(e.target.checked) }), "Alleen actief vandaag"] })] }), _jsx("div", { className: "rounded-md border overflow-x-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel / Groep" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { className: "text-right", children: "Basisprijs" }), _jsx(TableHead, { className: "text-right", children: "Korting" }), _jsx(TableHead, { className: "text-right", children: "Netto" }), _jsx(TableHead, { children: "Geldig" }), _jsx(TableHead, { className: "text-right", children: "Min. hvh" })] }) }), _jsx(TableBody, { children: filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center text-muted-foreground py-6", children: "Geen kortingen die aan de filters voldoen." }) })) : filtered.map((r, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: r.trade_item_name ?? r.trade_item_id ?? r.product_group_id ?? "—" }), r.trade_item_name && (_jsx("div", { className: "text-xs text-muted-foreground font-mono truncate max-w-[280px]", children: r.trade_item_id }))] }), _jsx(TableCell, { children: _jsxs(Badge, { variant: "secondary", className: "gap-1", children: [SOURCE_ICON[r.source], SOURCE_LABEL[r.source]] }) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtMoney(r.base_price, r.currency) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtDiscount(r) }), _jsx(TableCell, { className: "text-right tabular-nums font-medium", children: fmtMoney(r.net_price, r.currency) }), _jsxs(TableCell, { className: "text-sm text-muted-foreground", children: [fmtDate(r.valid_from), " \u2013 ", fmtDate(r.valid_until)] }), _jsx(TableCell, { className: "text-right tabular-nums", children: r.min_quantity || "—" })] }, `${r.source_id}-${r.trade_item_id}-${i}`))) })] }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wijzigingen doe je in Floriday. Deze module leest de gesynchroniseerde kortingscache alleen." })] }) }))] }));
80
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsx(SummaryCard, { label: "Contracten", value: s?.agreements ?? 0, icon: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsregels", value: s?.sales_strategies ?? 0, icon: _jsx(Percent, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsgroep-regels", value: s?.price_groups ?? 0, icon: _jsx(Users, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Laatst gesynchroniseerd", value: formatDateTime(s?.last_fetched_at), icon: _jsx(Info, { className: "h-4 w-4" }), small: true })] }), rows.length === 0 ? (_jsxs("div", { className: "space-y-3", children: [_jsx(ExternalCacheBanner, { lastFetchedAt: s?.last_fetched_at ?? null, providers: [], title: "Floriday-kortingen", description: "De beschikbare kortings- en prijsgegevens worden automatisch bijgewerkt vanuit Floriday." }), _jsx(EmptyState, { message: "Voor deze klant zijn nog geen kortingen of prijsafspraken beschikbaar." })] })) : (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { placeholder: "Zoek op artikel of ID\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "max-w-xs" }), _jsxs(Select, { value: source, onValueChange: (v) => setSource(v), children: [_jsx(SelectTrigger, { className: "w-44", children: _jsx(SelectValue, { placeholder: "Bron" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle bronnen" }), _jsx(SelectItem, { value: "agreement", children: "Contract" }), _jsx(SelectItem, { value: "sales_strategy", children: "Sales Strategy" }), _jsx(SelectItem, { value: "price_group", children: "Prijsgroep" })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-muted-foreground ml-auto", children: [_jsx("input", { type: "checkbox", checked: onlyActive, onChange: (e) => setOnlyActive(e.target.checked) }), "Alleen actief vandaag"] })] }), _jsx("div", { className: "rounded-md border overflow-x-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel / Groep" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { className: "text-right", children: "Basisprijs" }), _jsx(TableHead, { className: "text-right", children: "Korting" }), _jsx(TableHead, { className: "text-right", children: "Netto" }), _jsx(TableHead, { children: "Geldig" }), _jsx(TableHead, { className: "text-right", children: "Min. hvh" })] }) }), _jsx(TableBody, { children: filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center text-muted-foreground py-6", children: "Geen kortingen die aan de filters voldoen." }) })) : filtered.map((r, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: r.trade_item_name ?? r.trade_item_id ?? r.product_group_id ?? "—" }), r.trade_item_name && (_jsx("div", { className: "text-xs text-muted-foreground font-mono truncate max-w-[280px]", children: r.trade_item_id }))] }), _jsx(TableCell, { children: _jsxs(Badge, { variant: "secondary", className: "gap-1", children: [SOURCE_ICON[r.source], SOURCE_LABEL[r.source]] }) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtMoney(r.base_price, r.currency) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtDiscount(r) }), _jsx(TableCell, { className: "text-right tabular-nums font-medium", children: fmtMoney(r.net_price, r.currency) }), _jsxs(TableCell, { className: "text-sm text-muted-foreground", children: [fmtDate(r.valid_from), " \u2013 ", fmtDate(r.valid_until)] }), _jsx(TableCell, { className: "text-right tabular-nums", children: r.min_quantity || "—" })] }, `${r.source_id}-${r.trade_item_id}-${i}`))) })] }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Kortingen pas je aan in Floriday; hier zie je ze alleen." })] }) }))] }));
81
81
  }
82
82
  function SummaryCard({ label, value, icon, small, }) {
83
83
  return (_jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs uppercase text-muted-foreground", children: [icon, label] }), _jsx("div", { className: small ? "text-sm mt-1" : "text-2xl font-semibold mt-1", children: value })] }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"ExternalCacheBanner.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ExternalCacheBanner.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAa/E,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,SAAS,EACT,KAA2C,EAC3C,WAAW,GACZ,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+BAsBA"}
1
+ {"version":3,"file":"ExternalCacheBanner.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ExternalCacheBanner.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAa/E,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,SAAS,EACT,KAA2C,EAC3C,WAAW,GACZ,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+BAmBA"}
@@ -14,9 +14,6 @@ function relativeTime(iso) {
14
14
  return `${day} dag${day === 1 ? "" : "en"} geleden`;
15
15
  }
16
16
  export function ExternalCacheBanner({ lastFetchedAt, providers, title = "Externe klantgegevens (read-only)", description, }) {
17
- const labels = providers
18
- .filter((p) => p.is_enabled)
19
- .map((p) => p.label)
20
- .join(", ");
21
- return (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-muted/40 px-4 py-3 text-sm", children: [_jsx(Cloud, { className: "h-4 w-4 mt-0.5 text-muted-foreground shrink-0" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium", children: title }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: description ?? (_jsxs(_Fragment, { children: ["Data wordt automatisch verfrist vanuit de gekoppelde bronnen", labels ? `: ${labels}` : "", "."] })) })] }), _jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground shrink-0", children: [_jsx(Clock, { className: "h-3.5 w-3.5" }), lastFetchedAt ? `Laatste cache: ${relativeTime(lastFetchedAt)}` : "Nog geen cache"] })] }));
17
+ void providers;
18
+ return (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-muted/40 px-4 py-3 text-sm", children: [_jsx(Cloud, { className: "h-4 w-4 mt-0.5 text-muted-foreground shrink-0" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium", children: title }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: description ?? (_jsx(_Fragment, { children: "Gegevens worden automatisch bijgewerkt uit Floriday en Twinfield." })) })] }), _jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground shrink-0", children: [_jsx(Clock, { className: "h-3.5 w-3.5" }), lastFetchedAt ? `Laatst bijgewerkt: ${relativeTime(lastFetchedAt)}` : "Nog niet opgehaald"] })] }));
22
19
  }
@@ -1 +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"}
1
+ {"version":3,"file":"FloridayNetworkStatus.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/FloridayNetworkStatus.tsx"],"names":[],"mappings":"AAMA,iGAAiG;AACjG,wBAAgB,qBAAqB,CAAC,EAAE,cAAc,EAAE,EAAE;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,sCAiBnF"}
@@ -3,9 +3,11 @@ import { useQuery } from "@tanstack/react-query";
3
3
  import { useServerFn } from "@tanstack/react-start";
4
4
  import { getFloridayNetworkStatus } from "../../lib/floriday-network-status.functions";
5
5
  import { formatDate } from "../../lib/format-date";
6
+ import { useCustomerDefaults } from "./useCustomerDefaults";
6
7
  /** Alleen weergave: staat deze Floriday-organisatie in je Floriday-netwerk? Geen klantstatus. */
7
8
  export function FloridayNetworkStatus({ organizationId }) {
8
9
  const fn = useServerFn(getFloridayNetworkStatus);
10
+ const { isAdmin } = useCustomerDefaults();
9
11
  const { data } = useQuery({
10
12
  queryKey: ["floriday-network-status", organizationId],
11
13
  queryFn: () => fn({ data: { organizationId } }),
@@ -14,5 +16,5 @@ export function FloridayNetworkStatus({ organizationId }) {
14
16
  if (!data)
15
17
  return null;
16
18
  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)] })] }));
19
+ 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 })] }), isAdmin === true && data.reason && _jsxs("p", { children: ["Details voor beheer: ", data.reason] }), data.endDate && _jsxs("p", { children: ["Einddatum in Floriday: ", formatDate(data.endDate)] })] }));
18
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"AA4CA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BA4UjD"}
1
+ {"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"AA6CA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAuXjD"}
@@ -4,8 +4,8 @@ import { REFRESH_MS } from "../../lib/refresh-intervals.js";
4
4
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
5
5
  import { useServerFn } from "@tanstack/react-start";
6
6
  import { useEffect, useState } from "react";
7
- import { Card, CardContent, CardHeader, CardTitle, Badge, Button, toast, Tabs, TabsContent, TabsList, TabsTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@flowselections/core";
8
- import { Trash2, Mail, Phone, Power, Pencil, } from "lucide-react";
7
+ import { Card, CardContent, CardHeader, CardTitle, Badge, Button, toast, Tabs, TabsContent, TabsList, TabsTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, } from "@flowselections/core";
8
+ import { Trash2, Mail, Phone, Power, Pencil, MoreHorizontal, } from "lucide-react";
9
9
  import { getCustomer, deleteCustomer, updateCustomer } from "../../lib/customers.functions.js";
10
10
  import { ProfileCard, KpiStrip, BadgeList, EmptyState, ActivityItem, adaptLocalToProfile, } from "./KlantDetailLayout.js";
11
11
  import { DeliveryLocationsTab } from "./DeliveryLocationsTab.js";
@@ -39,6 +39,7 @@ export function KlantDetail({ id }) {
39
39
  const saveNote = useServerFn(upsertCustomerNote);
40
40
  const [tab, setTab] = useState("overview");
41
41
  const [editOpen, setEditOpen] = useState(false);
42
+ const [confirm, setConfirm] = useState(null);
42
43
  const { data, isLoading } = useQuery({
43
44
  queryKey: ["customer", id],
44
45
  queryFn: () => get({ data: { id } }).then(decodeDeep),
@@ -103,9 +104,20 @@ export function KlantDetail({ id }) {
103
104
  if (!data)
104
105
  return _jsx("main", { className: "p-6", children: "Klant niet gevonden" });
105
106
  const c = data.customer;
107
+ const linkedToTwinfield = !!(c.twinfield_code ?? c.twinfield_debtor_code);
106
108
  const missingTwinfield = getMissingTwinfieldFields(c);
107
109
  const priceGroups = data.price_groups ?? c.price_groups ?? [];
108
- return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), missingTwinfield.length > 0 && (_jsxs("div", { className: "rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Onvolledig voor Twinfield" }), _jsxs("p", { className: "text-muted-foreground mt-0.5", children: ["Deze klant kan pas naar Twinfield worden gestuurd zodra de volgende gegevens zijn aangevuld: ", missingTwinfield.map((f) => f.label).join(", "), "."] })] })), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(BlockedBadge, { blocked: c.blocked }), _jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => toggleActive.mutate(!c.is_active), children: [_jsx(Power, { className: "h-4 w-4 mr-2" }), c.is_active ? "Op inactief zetten" : "Activeren"] }), _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), " Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je ", _jsx("strong", { children: c.company_name }), " wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => delMut.mutate(), children: "Verwijderen" })] })] })] })] }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: adaptLocalToProfile(c, data.contacts), notes: noteData?.note ?? null, onSaveNotes: (n) => noteMut.mutate(n), isSaving: noteMut.isPending || noteLoading }), _jsxs("div", { className: "space-y-4 min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [keyData?.customerKey ? (_jsx(CustomerPriceGroupSelect, { customerKey: keyData.customerKey })) : null, priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Floriday-prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] }))] }), _jsx(KpiStrip, { counts: {
110
+ return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), missingTwinfield.length > 0 && (_jsxs("div", { className: "rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Onvolledig voor Twinfield" }), _jsxs("p", { className: "text-muted-foreground mt-0.5", children: ["Deze klant kan pas naar Twinfield worden gestuurd zodra de volgende gegevens zijn aangevuld: ", missingTwinfield.map((f) => f.label).join(", "), "."] })] })), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(BlockedBadge, { blocked: c.blocked }), _jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(MoreHorizontal, { className: "h-4 w-4 mr-2" }), " Meer"] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onSelect: () => setConfirm("active"), children: [_jsx(Power, { className: "h-4 w-4 mr-2" }), c.is_active ? "Op inactief zetten" : "Activeren"] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-destructive", onSelect: () => setConfirm("delete"), children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), " Verwijderen"] })] })] }), _jsx(AlertDialog, { open: confirm !== null, onOpenChange: (o) => !o && setConfirm(null), children: _jsxs(AlertDialogContent, { children: [confirm === "delete" ? (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant verwijderen?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt alleen hier verwijderd. In de boekhouding (Twinfield) blijft de klant bestaan, en in Floriday verandert niets. Dit kun je niet ongedaan maken."] })] })) : c.is_active ? (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant op inactief zetten?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt hier inactief.", " ", linkedToTwinfield
111
+ ? "In de boekhouding (Twinfield) wordt de klant verborgen; de gegevens blijven daar bewaard."
112
+ : "Deze klant staat niet in de boekhouding, dus daar verandert niets.", " ", "In Floriday verandert niets. Je kunt de klant later weer activeren."] })] })) : (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant activeren?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt hier weer actief.", " ", linkedToTwinfield
113
+ ? "In de boekhouding (Twinfield) wordt de klant weer zichtbaar."
114
+ : "Deze klant staat niet in de boekhouding, dus daar verandert niets.", " ", "In Floriday verandert niets."] })] })), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => {
115
+ if (confirm === "delete")
116
+ delMut.mutate();
117
+ else
118
+ toggleActive.mutate(!c.is_active);
119
+ setConfirm(null);
120
+ }, children: confirm === "delete" ? "Verwijderen" : c.is_active ? "Op inactief zetten" : "Activeren" })] })] }) })] }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: adaptLocalToProfile(c, data.contacts), notes: noteData?.note ?? null, onSaveNotes: (n) => noteMut.mutate(n), isSaving: noteMut.isPending || noteLoading }), _jsxs("div", { className: "space-y-4 min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [keyData?.customerKey ? (_jsx(CustomerPriceGroupSelect, { customerKey: keyData.customerKey })) : null, priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Floriday-prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] }))] }), _jsx(KpiStrip, { counts: {
109
121
  contacts: data.contacts.length,
110
122
  locations: data.delivery_locations.length,
111
123
  assortment: null,
@@ -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":"AACA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAY5D,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EAChB,MAAM,wCAAwC,CAAC;AAShD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,6DAA6D;IAC7D,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C,CAAC;AA8BF,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,GAAE,GAAG,EAAO,GAAG,eAAe,CAkCjF;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,eAAe,GAAG,eAAe,CAyCzE;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAiC3E;AAED,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EAAE,CAAC,EACV,KAAK,EACL,WAAW,EACX,QAAgB,GACjB,EAAE;IACD,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,+BA0MA;AAGD,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEjE;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,+BAWjI;AAED,wBAAgB,OAAO,CAAC,EACtB,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,GAC/C,EAAE;IACD,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACrI,+BAyBA;AAED,wBAAgB,QAAQ,CAAC,EACvB,MAAM,EAAE,QAAQ,GACjB,EAAE;IACD,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3F,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,+BA6BA;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,+BAiBrG;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,+BAgBhG;AAED,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,+BAM1D"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCustomerDefaults } from "./useCustomerDefaults";
2
3
  import { useState, useEffect } from "react";
3
4
  import { formatDateTime } from "../../lib/format-date.js";
4
5
  import { Card, CardContent, Badge, Separator, Textarea, Button, } from "@flowselections/core";
@@ -152,6 +153,7 @@ export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false,
152
153
  const countryDisplay = countryFlag ? `${countryFlag} ${p.country}` : p.country;
153
154
  const fullAddress = [address, cityLine, countryDisplay].filter(Boolean).join(", ");
154
155
  const [showTech, setShowTech] = useState(false);
156
+ const { isAdmin } = useCustomerDefaults();
155
157
  const hasTech = !!(p.gln || p.rfh_id || p.organization_type || p.rfh_company_account_id || p.plantion_number);
156
158
  const editable = !!onSaveNotes;
157
159
  useEffect(() => {
@@ -162,7 +164,7 @@ export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false,
162
164
  await onSaveNotes?.(draft);
163
165
  setIsEditing(false);
164
166
  };
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: () => {
167
+ 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 && isAdmin === true && (_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" }), "Details voor beheer"] }), 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: () => {
166
168
  setDraft(notes ?? "");
167
169
  setIsEditing(false);
168
170
  }, 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"] }))] })] }) }));
@@ -482,7 +482,7 @@ export function KlantenOverzicht() {
482
482
  queryClient.invalidateQueries({ queryKey: ["overdue-balances"] });
483
483
  queryClient.invalidateQueries({ queryKey: ["customer-revenue-12m"] });
484
484
  }, children: "Opnieuw proberen" })] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3", children: [_jsx(StatCard, { icon: _jsx(Users, { className: "h-5 w-5" }), tint: "muted", label: "Alle klanten", value: totals.all, tooltip: "Totaal aantal klanten (lokaal + gekoppelde bronnen).", active: view === "all", onClick: () => { setView("all"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(UserCheck, { className: "h-5 w-5" }), tint: "success", label: "Met omzet", value: totals.active, tooltip: revenueData
485
- ? `Klanten met minimaal één bestelling of factuur sinds ${formatDateNl(revenueData.since)}.\nTotale omzet in die periode: ${formatEuro(revenueData.totals.amount)} · ${revenueData.totals.transactions} transacties`
485
+ ? `Klanten met minimaal één bestelling of factuur sinds ${formatDateNl(revenueData.since)}.\nTotale omzet in die periode: ${formatEuro(revenueData.totals.amount)} · ${revenueData.totals.transactions} ${revenueData.totals.transactions === 1 ? "transactie" : "transacties"}`
486
486
  : "Klanten met minimaal één bestelling in de afgelopen 12 maanden.", active: view === "active", onClick: () => { setView("active"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(UserX, { className: "h-5 w-5" }), tint: "warning", label: "Zonder omzet", value: totals.inactive, tooltip: revenueData
487
487
  ? `Klanten zonder enige bestelling of factuur sinds ${formatDateNl(revenueData.since)}.`
488
488
  : "Klanten zonder omzet in de afgelopen 12 maanden.", active: view === "inactive", onClick: () => { setView("inactive"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(AlertCircle, { className: "h-5 w-5" }), tint: "destructive", label: "Achterstallige betalingen", value: totals.overdue, tooltip: (overdueData?.totals
@@ -545,7 +545,7 @@ function SortHeader({ label, col, sort, onClick, }) {
545
545
  }
546
546
  function StatusPill({ active, missing }) {
547
547
  if (missing) {
548
- return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium bg-destructive/10 text-destructive", title: "Deze debiteur bestaat niet meer in Twinfield.", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-destructive" }), "Niet meer in Twinfield"] }));
548
+ return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium bg-destructive/10 text-destructive", title: "Deze klant staat niet meer in de boekhouding (Twinfield).", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-destructive" }), "Niet meer in Twinfield"] }));
549
549
  }
550
550
  return (_jsxs("span", { className: `inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ${active
551
551
  ? "bg-primary/10 text-primary"
@@ -1 +1 @@
1
- {"version":3,"file":"SyncStatusChip.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SyncStatusChip.tsx"],"names":[],"mappings":"AAYA,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCAoGpE"}
1
+ {"version":3,"file":"SyncStatusChip.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SyncStatusChip.tsx"],"names":[],"mappings":"AAaA,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCAsGpE"}
@@ -7,6 +7,7 @@ import { useServerFn } from "@tanstack/react-start";
7
7
  import { Badge } from "@flowselections/core";
8
8
  import { CheckCircle2, Clock, AlertTriangle, Loader2 } from "lucide-react";
9
9
  import { getTwinfieldSyncStatus, retryTwinfieldPush, } from "../../lib/accounting/twinfield/worker.functions.js";
10
+ import { friendlyTwinfieldError, GENERIC_BOOKKEEPING_ERROR } from "../../lib/friendly-twinfield-error.js";
10
11
  export function SyncStatusChip({ customerId }) {
11
12
  const qc = useQueryClient();
12
13
  const [retrying, setRetrying] = useState(false);
@@ -49,16 +50,16 @@ export function SyncStatusChip({ customerId }) {
49
50
  const failedAt = latest?.updated_at ?? latest?.created_at ?? null;
50
51
  const failedLongEnough = failedAt && Date.now() - new Date(failedAt).getTime() > FOUT_ZICHTBAAR_NA_MS;
51
52
  if ((status === "failed" || status === "dead") && failedLongEnough) {
52
- const reason = latest?.last_error ?? customer?.twinfield_sync_error;
53
- return (_jsx("button", { type: "button", onClick: () => !retrying && retry.mutate(), className: "inline-flex max-w-full items-center gap-1.5 text-xs", title: reason ?? "Onbekende fout", children: _jsxs(Badge, { variant: "destructive", className: "gap-1 max-w-full", children: [retrying ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(AlertTriangle, { className: "h-3 w-3" }), _jsxs("span", { className: "truncate", children: [reason ? `Twinfield: ${reason}` : "Synchronisatie mislukt", " \u2014 opnieuw proberen"] })] }) }));
53
+ const reason = friendlyTwinfieldError(latest?.last_error ?? customer?.twinfield_sync_error);
54
+ return (_jsx("button", { type: "button", onClick: () => !retrying && retry.mutate(), className: "inline-flex max-w-full items-center gap-1.5 text-xs", title: reason ?? GENERIC_BOOKKEEPING_ERROR, children: _jsxs(Badge, { variant: "destructive", className: "gap-1 max-w-full", children: [retrying ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(AlertTriangle, { className: "h-3 w-3" }), _jsx("span", { className: "truncate", children: "Niet bijgewerkt in de boekhouding \u2014 opnieuw proberen" })] }) }));
54
55
  }
55
56
  if (status === "pending" || status === "processing" || status === "failed" || status === "dead") {
56
- const waiting = latest?.last_error ?? null;
57
+ const waiting = friendlyTwinfieldError(latest?.last_error);
57
58
  return (_jsxs(Badge, { variant: "secondary", className: "gap-1 max-w-full", title: waiting ?? undefined, children: [status === "processing" ? (_jsx(Loader2, { className: "h-3 w-3 animate-spin" })) : (_jsx(Clock, { className: "h-3 w-3" })), _jsx("span", { className: "truncate", children: "Wordt bijgewerkt" })] }));
58
59
  }
59
60
  if (synced) {
60
61
  const when = lastPushAt ? formatDateTime(lastPushAt) : null;
61
- const warning = customer?.twinfield_sync_error;
62
+ const warning = friendlyTwinfieldError(customer?.twinfield_sync_error);
62
63
  return (_jsxs("span", { className: "inline-flex flex-wrap items-center gap-1.5", children: [_jsxs(Badge, { variant: "outline", className: "gap-1 text-success-soft-foreground border-success bg-success-soft", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), "Gesynchroniseerd", code ? ` · Twinfield ${code}` : "", when ? ` · laatst verstuurd ${when}` : ""] }), warning && (_jsxs(Badge, { variant: "secondary", className: "gap-1 max-w-full", title: warning, children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), _jsx("span", { className: "truncate", children: warning })] }))] }));
63
64
  }
64
65
  return null;
@@ -257,7 +257,7 @@ export function PriceGroupFormPage({ editingId }) {
257
257
  "De ingestelde transport- en afleverkosten in de levervoorwaarden worden in rekening gebracht",
258
258
  ] }))] }), _jsxs("div", { className: "pt-2", children: [_jsx("div", { className: "text-sm text-muted-foreground mb-2", children: "Diensten" }), _jsx(Separator, { className: "mb-3" }), _jsxs("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [_jsx(Checkbox, { checked: form.include_stickers, onCheckedChange: (v) => patch({ include_stickers: v === true }) }), _jsx("span", { className: "text-sm font-medium", children: "Inclusief stickeren" })] })] }), _jsxs("div", { className: "pt-2", children: [_jsx("div", { className: "text-sm text-muted-foreground mb-2", children: "Afname" }), _jsx(Separator, { className: "mb-3" }), _jsxs("div", { className: "max-w-sm", children: [_jsx(Label, { children: "Afname-eenheid" }), _jsxs(Select, { value: form.unit, onValueChange: (v) => patch({ unit: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "- Selecteer een eenheid -" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "per_fust", children: "Per fust" }), _jsx(SelectItem, { value: "per_laag", children: "Per laag" }), _jsx(SelectItem, { value: "per_ladingdrager", children: "Per ladingdrager" })] })] })] }), _jsxs("div", { className: "text-xs text-muted-foreground mt-2 leading-relaxed", children: ["Afname-eenheid per aangeboden artikel.", _jsx("br", {}), "Deze instelling geldt voor alle klanten in deze prijsgroep, per aangeboden artikel, niet per bestelling."] })] }), _jsx("div", { className: "pt-2 flex justify-end", children: _jsxs(Button, { onClick: () => nextStep(1), children: ["Volgende stap ", _jsx(ArrowRight, { className: "h-4 w-4 ml-1" })] }) })] }) }), _jsx(StepSection, { index: 2, title: STEP_TITLES[2], subtitle: summaries[2], open: openStep === 2, completed: form.pricing_mode === "calculated" && maxReached >= 2 && openStep !== 2, disabled: maxReached < 2 || form.pricing_mode !== "calculated", onToggle: () => goToStep(2), children: _jsx("div", { className: "space-y-4", children: form.pricing_mode !== "calculated" ? (_jsx("div", { className: "text-sm text-muted-foreground p-4 border rounded-md bg-muted/30", children: "Deze prijsgroep gebruikt handmatige prijzen. Prijsberekening is niet van toepassing." })) : (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx(Label, { children: "Korting/opslag per stuk" }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-3 mt-1.5", children: [_jsxs(Select, { value: form.calc_type ?? "discount", onValueChange: (v) => patch({ calc_type: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "discount", children: "Korting" }), _jsx(SelectItem, { value: "surcharge", children: "Opslag" })] })] }), _jsxs(Select, { value: form.calc_value_type ?? "fixed", onValueChange: (v) => patch({ calc_value_type: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "percentage", children: "Percentage" }), _jsx(SelectItem, { value: "fixed", children: "Vast bedrag" })] })] }), _jsxs("div", { className: "relative", children: [form.calc_value_type !== "percentage" && (_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" })), _jsx(Input, { type: "number", step: form.calc_value_type === "percentage" ? "1" : "0.001", value: form.calc_value ?? 0, onChange: (e) => patch({ calc_value: Number(e.target.value) }), className: form.calc_value_type === "percentage" ? "pr-8" : "pl-7" }), form.calc_value_type === "percentage" && (_jsx("span", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "%" }))] })] })] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsxs(Label, { className: "flex items-center gap-1.5", children: ["Afhandelingskosten (optioneel)", _jsx(Info, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-label": "Extra kosten die bovenop de prijs komen. Kies hoe deze verdeeld worden." })] }), _jsxs("div", { className: "relative mt-1.5", children: [_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: form.handling_fee ?? 0, onChange: (e) => patch({ handling_fee: Number(e.target.value) }), className: "pl-7" })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Verdeeld over het aantal planten of bloemen op een" }), _jsxs(Select, { value: form.distribute_over ?? "ladingdrager", onValueChange: (v) => patch({ distribute_over: v }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "ladingdrager", children: "Ladingdrager" }), _jsx(SelectItem, { value: "laag", children: "Laag" }), _jsx(SelectItem, { value: "fust", children: "Fust" })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { children: "Prijs afronden" }), _jsxs(Select, { value: form.rounding, onValueChange: (v) => patch({ rounding: v }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "none", children: "Niet afronden" }), _jsx(SelectItem, { value: "up", children: "Omhoog" }), _jsx(SelectItem, { value: "down", children: "Omlaag" }), _jsx(SelectItem, { value: "nearest", children: "Dichtstbijzijnde" })] })] })] }), form.rounding !== "none" && (_jsxs("div", { children: [_jsx(Label, { children: "Op" }), _jsxs(Select, { value: String(form.rounding_step ?? 0.05), onValueChange: (v) => patch({ rounding_step: Number(v) }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "0.01", children: "\u20AC 0,01" }), _jsx(SelectItem, { value: "0.05", children: "\u20AC 0,05" }), _jsx(SelectItem, { value: "0.1", children: "\u20AC 0,10" })] })] })] }))] }), _jsx("div", { className: "pt-2 flex justify-end", children: _jsxs(Button, { onClick: () => nextStep(2), children: ["Volgende stap ", _jsx(ArrowRight, { className: "h-4 w-4 ml-1" })] }) })] })) }) }), _jsxs(StepSection, { index: 3, title: STEP_TITLES[3], subtitle: summaries[3], open: openStep === 3, completed: form.group_type === "customer_specific" && maxReached >= 3 && openStep !== 3 && selectedCustomerKeys.length > 0, disabled: maxReached < 3 || form.group_type !== "customer_specific", onToggle: () => goToStep(3), children: [form.group_type !== "customer_specific" ? (_jsx("div", { className: "text-sm text-muted-foreground p-4 border rounded-md bg-muted/30", children: "Algemene prijsgroepen zijn zichtbaar voor alle klanten \u2014 een doelgroep is niet van toepassing." })) : selectedCustomerKeys.length === 0 ? (_jsxs("div", { className: "rounded-lg border bg-muted/20 p-8", children: [_jsxs("div", { className: "flex flex-col items-center text-center gap-3", children: [_jsxs("div", { className: "relative h-16 w-16 rounded-full bg-muted flex items-center justify-center", children: [_jsx(Users2, { className: "h-8 w-8 text-muted-foreground/60" }), _jsx("span", { className: "absolute -top-1 -right-1 h-6 w-6 rounded-full bg-background border flex items-center justify-center", children: _jsx(UserPlus, { className: "h-3.5 w-3.5 text-muted-foreground" }) })] }), _jsx("div", { children: _jsx("div", { className: "font-medium text-sm", children: "Nog geen klanten toegevoegd" }) })] }), _jsx("div", { className: "flex justify-end mt-4", children: _jsxs(Button, { variant: "outline", onClick: () => setCustomerPickerOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-1.5" }), " Organisaties wijzigen"] }) })] })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "text-sm text-muted-foreground", children: [_jsx("strong", { className: "text-foreground", children: selectedCustomerKeys.length }), " ", "klant", selectedCustomerKeys.length === 1 ? "" : "en", " gekoppeld"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setCustomerPickerOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-1.5" }), " Organisaties wijzigen"] })] }), _jsx("div", { className: "max-h-[320px] overflow-y-auto border rounded-md divide-y", children: selectedCustomerKeys.map((k) => {
259
259
  const c = customersByKey.get(k);
260
- return (_jsxs("div", { className: "flex items-center gap-3 p-2.5", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: c?.display_name ?? k }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c ? (c.city || c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1", children: [c.city && _jsx("span", { children: c.city }), c.city && c.country && _jsx("span", { children: "\u00B7" }), c.country && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(c.country) ?? "🌐" }), _jsx("span", { children: c.country })] }))] })) : "—") : "Niet in cache" })] }), _jsx("div", { className: "flex gap-1", children: (c?.providers ?? []).slice(0, 3).map((p) => (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: p }, p))) })] }, k));
260
+ return (_jsxs("div", { className: "flex items-center gap-3 p-2.5", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: c?.display_name ?? k }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c ? (c.city || c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1", children: [c.city && _jsx("span", { children: c.city }), c.city && c.country && _jsx("span", { children: "\u00B7" }), c.country && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(c.country) ?? "🌐" }), _jsx("span", { children: c.country })] }))] })) : "—") : "Nog niet opgehaald uit Floriday" })] }), _jsx("div", { className: "flex gap-1", children: (c?.providers ?? []).slice(0, 3).map((p) => (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: p }, p))) })] }, k));
261
261
  }) })] })), _jsx(CustomerPickerDialog, { open: customerPickerOpen, onOpenChange: setCustomerPickerOpen, value: selectedCustomerKeys, onChange: setSelectedCustomerKeys })] }), editingId && (_jsx(PriceGroupItemPrices, { priceGroupId: editingId, currency: form.currency })), error && _jsx("div", { className: "text-sm text-destructive", children: error })] }), form.pricing_mode === "calculated" && (_jsxs("aside", { className: "lg:sticky lg:top-4 space-y-3", children: [_jsx(Card, { children: _jsxs(CardContent, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-sm font-medium", children: "Voorbeeld prijscalculatie" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => cycleItem(-1), disabled: items.length < 2, "aria-label": "Vorige product", children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => cycleItem(1), disabled: items.length < 2, "aria-label": "Volgend product", children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] })] }), _jsxs("button", { type: "button", onClick: () => setPickerOpen(true), className: "w-full flex items-center gap-3 text-left hover:bg-muted/30 rounded-md p-2 -m-2", children: [_jsx("div", { className: "h-14 w-14 rounded bg-muted flex items-center justify-center overflow-hidden shrink-0", children: selectedItem?.image_url
262
262
  ? _jsx("img", { src: selectedItem.image_url, alt: "", className: "h-full w-full object-contain" })
263
263
  : _jsx(ImageIcon, { className: "h-6 w-6 text-muted-foreground" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-sm font-medium truncate", children: selectedItem?.name ?? "Geen product beschikbaar" }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: selectedItem
@@ -265,7 +265,7 @@ export function PriceGroupFormPage({ editingId }) {
265
265
  `${selectedItem.units_per_fust}×${selectedItem.packages_per_layer}×${selectedItem.layers_per_carrier}`,
266
266
  selectedItem.carrier_code]
267
267
  .filter(Boolean).join(" - ")
268
- : "Wacht op Floriday-cache" })] })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "Basisprijs" }), _jsxs("div", { className: "relative mt-1.5", children: [_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: basePrice, onChange: (e) => setBasePrice(Number(e.target.value)), className: "pl-7" })] })] }), _jsxs("div", { children: [_jsx("div", { className: "flex justify-end text-xs text-muted-foreground mb-1", children: "Prijzen" }), _jsxs("div", { className: "space-y-1.5 text-sm tabular-nums border-t pt-2", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Basisprijs" }), _jsx("span", { children: formatEur(basePrice) })] }), Math.abs(preview.discountAmount) > 0 && (_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { className: "text-muted-foreground", children: [form.calc_type === "surcharge" ? "Opslag" : "Korting", " ", form.calc_value_type === "percentage"
268
+ : "Nog niet opgehaald uit Floriday" })] })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "Basisprijs" }), _jsxs("div", { className: "relative mt-1.5", children: [_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: basePrice, onChange: (e) => setBasePrice(Number(e.target.value)), className: "pl-7" })] })] }), _jsxs("div", { children: [_jsx("div", { className: "flex justify-end text-xs text-muted-foreground mb-1", children: "Prijzen" }), _jsxs("div", { className: "space-y-1.5 text-sm tabular-nums border-t pt-2", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Basisprijs" }), _jsx("span", { children: formatEur(basePrice) })] }), Math.abs(preview.discountAmount) > 0 && (_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { className: "text-muted-foreground", children: [form.calc_type === "surcharge" ? "Opslag" : "Korting", " ", form.calc_value_type === "percentage"
269
269
  ? `${(form.calc_value ?? 0).toLocaleString("nl-NL")}%`
270
270
  : formatEur(form.calc_value ?? 0, { decimals: 3 })] }), _jsxs("span", { children: [preview.discountAmount < 0 ? "− " : "", formatEur(Math.abs(preview.discountAmount))] })] })), preview.handlingPerUnit > 0 && (_jsxs("div", { className: "flex justify-between items-baseline gap-2", children: [_jsxs("span", { className: "text-muted-foreground truncate", children: ["Afhandelingskosten (optioneel)", " ", _jsxs("span", { className: "italic text-muted-foreground/80", children: [formatEur(form.handling_fee ?? 0), handlingSuffix] })] }), _jsx("span", { children: formatEur(preview.handlingPerUnit, { decimals: 3 }) })] })), _jsxs("div", { className: "flex justify-between border-t pt-1.5", children: [_jsx("span", { className: form.rounding === "none" ? "font-medium" : "text-muted-foreground", children: "Berekende prijs" }), _jsx("span", { className: form.rounding === "none" ? "font-medium" : "", children: formatEur(preview.adjusted, { decimals: 3 }) })] }), form.rounding !== "none" && (_jsxs("div", { className: "flex justify-between font-medium border-t pt-1.5", children: [_jsx("span", { children: "Afgeronde prijs" }), _jsx("span", { children: formatEur(preview.rounded) })] }))] })] })] }) }), _jsx(ProductPickerDialog, { open: pickerOpen, onOpenChange: setPickerOpen, items: items, onSelect: (i) => setSelectedItemId(i.id) })] }))] })] }), _jsx("div", { className: "fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur border-t z-10", children: _jsxs("div", { className: "max-w-6xl mx-auto px-6 py-3 flex items-center justify-between", children: [_jsx(Button, { variant: "ghost", onClick: goBack, children: "Annuleren" }), _jsx(Button, { onClick: () => saveMut.mutate(), disabled: saveMut.isPending ||
271
271
  !form.name.trim() ||
@@ -29,7 +29,7 @@ export function UnknownFloridayMembers({ members, priceGroupIdFor, }) {
29
29
  qc.invalidateQueries({ queryKey: ["unknown-floriday-members"] });
30
30
  qc.invalidateQueries({ queryKey: ["customer-price-group"] });
31
31
  qc.invalidateQueries({ queryKey: ["external-customers-picker"] });
32
- toast.success(`${m.commercial_name ?? m.name} is toegevoegd als klant (zonder debiteur in Twinfield).`);
32
+ toast.success(`${m.commercial_name ?? m.name} is toegevoegd als klant en staat in deze prijsgroep.`);
33
33
  },
34
34
  onError: (e) => toast.error(friendlyError(e)),
35
35
  });
@@ -39,5 +39,5 @@ export function UnknownFloridayMembers({ members, priceGroupIdFor, }) {
39
39
  const added = done.has(m.organization_id);
40
40
  const canAdd = m.groups.some((g) => priceGroupIdFor(g.external_id));
41
41
  return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 px-3 py-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-sm font-medium truncate", children: m.commercial_name ?? m.name }), _jsxs("div", { className: "text-xs text-muted-foreground truncate", children: [m.commercial_name ? `${m.name} · ` : "", m.city ?? "—", " \u00B7 ", m.reason] }), m.groups.length > 0 && (_jsxs("div", { className: "text-xs text-muted-foreground truncate", children: ["In Floriday: ", m.groups.map((g) => g.name).join(", ")] }))] }), added ? (_jsx(Badge, { variant: "secondary", children: "Toegevoegd" })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: !canAdd || addMut.isPending, title: canAdd ? undefined : "Importeer eerst de prijsgroep", onClick: () => addMut.mutate(m), children: "Toevoegen" }))] }, m.organization_id));
42
- }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Toevoegen maakt een klant aan op organisatie-id, zonder debiteur in Twinfield, en deelt die in bij de prijsgroep." })] }));
42
+ }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Toevoegen maakt een nieuwe klant aan (nog niet in de boekhouding) en zet die in deze prijsgroep." })] }));
43
43
  }
@@ -1231,38 +1231,59 @@ export type Database = {
1231
1231
  Row: {
1232
1232
  carrier_id: string;
1233
1233
  cart_count: number;
1234
+ cart_ids: string[] | null;
1234
1235
  created_at: string;
1236
+ fingerprint: string | null;
1235
1237
  id: string;
1238
+ kind: string;
1236
1239
  order_count: number;
1237
1240
  outbox_id: string | null;
1238
1241
  pickup_date: string;
1239
1242
  pickup_moment_key: string;
1243
+ previous_cart_count: number | null;
1244
+ recipient: string | null;
1240
1245
  sent_at: string;
1241
1246
  signup_deadline: string;
1247
+ source: string;
1248
+ version: number;
1242
1249
  };
1243
1250
  Insert: {
1244
1251
  carrier_id: string;
1245
1252
  cart_count?: number;
1253
+ cart_ids?: string[] | null;
1246
1254
  created_at?: string;
1255
+ fingerprint?: string | null;
1247
1256
  id?: string;
1257
+ kind?: string;
1248
1258
  order_count?: number;
1249
1259
  outbox_id?: string | null;
1250
1260
  pickup_date: string;
1251
1261
  pickup_moment_key: string;
1262
+ previous_cart_count?: number | null;
1263
+ recipient?: string | null;
1252
1264
  sent_at?: string;
1253
1265
  signup_deadline: string;
1266
+ source?: string;
1267
+ version?: number;
1254
1268
  };
1255
1269
  Update: {
1256
1270
  carrier_id?: string;
1257
1271
  cart_count?: number;
1272
+ cart_ids?: string[] | null;
1258
1273
  created_at?: string;
1274
+ fingerprint?: string | null;
1259
1275
  id?: string;
1276
+ kind?: string;
1260
1277
  order_count?: number;
1261
1278
  outbox_id?: string | null;
1262
1279
  pickup_date?: string;
1263
1280
  pickup_moment_key?: string;
1281
+ previous_cart_count?: number | null;
1282
+ recipient?: string | null;
1264
1283
  sent_at?: string;
1265
1284
  signup_deadline?: string;
1285
+ source?: string;
1286
+ version?: number;
1266
1287
  };
1267
1288
  Relationships: [
1268
1289
  {
@@ -28564,6 +28585,18 @@ export type Database = {
28564
28585
  };
28565
28586
  Returns: string;
28566
28587
  };
28588
+ afh_html: {
28589
+ Args: {
28590
+ _s: string;
28591
+ };
28592
+ Returns: string;
28593
+ };
28594
+ afh_nl_date: {
28595
+ Args: {
28596
+ _d: string;
28597
+ };
28598
+ Returns: string;
28599
+ };
28567
28600
  afh_normalize_country: {
28568
28601
  Args: {
28569
28602
  _raw: string;
@@ -28576,6 +28609,46 @@ export type Database = {
28576
28609
  };
28577
28610
  Returns: string;
28578
28611
  };
28612
+ afh_signup_config: {
28613
+ Args: never;
28614
+ Returns: Json;
28615
+ };
28616
+ afh_signup_find_group: {
28617
+ Args: {
28618
+ p_carrier_id: string;
28619
+ p_moment_key: string;
28620
+ p_pickup_date: string;
28621
+ p_tz: string;
28622
+ };
28623
+ Returns: Json;
28624
+ };
28625
+ afh_signup_groups: {
28626
+ Args: {
28627
+ p_from: string;
28628
+ p_to: string;
28629
+ p_tz: string;
28630
+ };
28631
+ Returns: Json;
28632
+ };
28633
+ afh_signup_mail: {
28634
+ Args: {
28635
+ g: Json;
28636
+ p_prev: number;
28637
+ p_tz: string;
28638
+ };
28639
+ Returns: Json;
28640
+ };
28641
+ afh_signup_send_group: {
28642
+ Args: {
28643
+ g: Json;
28644
+ p_connection: string;
28645
+ p_purpose: string;
28646
+ p_source: string;
28647
+ p_tz: string;
28648
+ p_user: string;
28649
+ };
28650
+ Returns: Json;
28651
+ };
28579
28652
  afh_subtract_workdays: {
28580
28653
  Args: {
28581
28654
  _date: string;
@@ -28583,6 +28656,14 @@ export type Database = {
28583
28656
  };
28584
28657
  Returns: string;
28585
28658
  };
28659
+ afhandeling_signup_cron_status: {
28660
+ Args: never;
28661
+ Returns: Json;
28662
+ };
28663
+ afhandeling_sync_signup_cron: {
28664
+ Args: never;
28665
+ Returns: Json;
28666
+ };
28586
28667
  ai_usage_by_module: {
28587
28668
  Args: {
28588
28669
  _from: string;
@@ -28724,6 +28805,29 @@ export type Database = {
28724
28805
  id: string;
28725
28806
  }[];
28726
28807
  };
28808
+ carrier_signup_overview: {
28809
+ Args: {
28810
+ p_from: string;
28811
+ p_to: string;
28812
+ };
28813
+ Returns: Json;
28814
+ };
28815
+ carrier_signup_preview: {
28816
+ Args: {
28817
+ p_carrier_id: string;
28818
+ p_moment_key: string;
28819
+ p_pickup_date: string;
28820
+ };
28821
+ Returns: Json;
28822
+ };
28823
+ carrier_signup_send_now: {
28824
+ Args: {
28825
+ p_carrier_id: string;
28826
+ p_moment_key: string;
28827
+ p_pickup_date: string;
28828
+ };
28829
+ Returns: Json;
28830
+ };
28727
28831
  check_offer_product_available_stock: {
28728
28832
  Args: {
28729
28833
  p_campaign_product_id: string;