@flowselections/floriday-klanten-module 1.0.49 → 1.0.51

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 (141) hide show
  1. package/dist-lib/components/CustomerAvatar.js +1 -1
  2. package/dist-lib/components/klanten/BewerkKlant.js +1 -1
  3. package/dist-lib/components/klanten/BewerkKlantRoute.js +1 -1
  4. package/dist-lib/components/klanten/BulkStickerUploadDialog.js +1 -1
  5. package/dist-lib/components/klanten/CustomerAnalyticsTab.js +2 -2
  6. package/dist-lib/components/klanten/CustomerAssortmentTab.js +4 -4
  7. package/dist-lib/components/klanten/DeliveryLocationsTab.js +6 -6
  8. package/dist-lib/components/klanten/DiscountsTab.js +3 -3
  9. package/dist-lib/components/klanten/ExternalCacheBanner.d.ts +1 -1
  10. package/dist-lib/components/klanten/ExternalCacheBanner.d.ts.map +1 -1
  11. package/dist-lib/components/klanten/ExternalCacheBanner.js +2 -5
  12. package/dist-lib/components/klanten/FloridayNetworkStatus.d.ts.map +1 -1
  13. package/dist-lib/components/klanten/FloridayNetworkStatus.js +5 -3
  14. package/dist-lib/components/klanten/GroupedKlantDetailRoute.js +1 -1
  15. package/dist-lib/components/klanten/ImportKlanten.js +1 -1
  16. package/dist-lib/components/klanten/KlantBewerkenDialog.js +2 -2
  17. package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
  18. package/dist-lib/components/klanten/KlantDetail.js +15 -3
  19. package/dist-lib/components/klanten/KlantDetailLayout.d.ts +1 -1
  20. package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -1
  21. package/dist-lib/components/klanten/KlantDetailLayout.js +7 -5
  22. package/dist-lib/components/klanten/KlantDetailRoute.js +1 -1
  23. package/dist-lib/components/klanten/KlantenOverzicht.js +18 -18
  24. package/dist-lib/components/klanten/LocalDiscountFormDialog.js +1 -1
  25. package/dist-lib/components/klanten/LocalDiscountsTab.js +2 -2
  26. package/dist-lib/components/klanten/NetwerkPagina.js +3 -3
  27. package/dist-lib/components/klanten/NieuweKlant.js +1 -1
  28. package/dist-lib/components/klanten/OvernemenTwinfieldKlant.js +1 -1
  29. package/dist-lib/components/klanten/OvernemenTwinfieldKlantRoute.js +1 -1
  30. package/dist-lib/components/klanten/RouteError.js +1 -1
  31. package/dist-lib/components/klanten/SyncStatusChip.d.ts.map +1 -1
  32. package/dist-lib/components/klanten/SyncStatusChip.js +5 -4
  33. package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +6 -6
  34. package/dist-lib/components/klanten/pricegroups/CustomerPriceGroupSelect.js +3 -3
  35. package/dist-lib/components/klanten/pricegroups/PriceGroupEditRoute.js +1 -1
  36. package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +13 -13
  37. package/dist-lib/components/klanten/pricegroups/PriceGroupNewRoute.js +1 -1
  38. package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.js +4 -4
  39. package/dist-lib/components/klanten/pricegroups/PriceGroupsRoute.js +1 -1
  40. package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts +1 -1
  41. package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.d.ts +1 -1
  42. package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.js +4 -4
  43. package/dist-lib/components/klanten/useCustomerDefaults.d.ts +1 -1
  44. package/dist-lib/index.d.ts +34 -34
  45. package/dist-lib/index.js +35 -35
  46. package/dist-lib/integrations/supabase/auth-attacher.js +1 -1
  47. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1 -1
  48. package/dist-lib/integrations/supabase/client.d.ts +1 -1
  49. package/dist-lib/integrations/supabase/client.server.d.ts +1 -1
  50. package/dist-lib/integrations/supabase/types.d.ts +450 -0
  51. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  52. package/dist-lib/lib/accounting/exact/client.server.d.ts +1 -1
  53. package/dist-lib/lib/accounting/exact/client.server.js +2 -2
  54. package/dist-lib/lib/accounting/exact/sync.server.d.ts +1 -1
  55. package/dist-lib/lib/accounting/exact/sync.server.js +3 -3
  56. package/dist-lib/lib/accounting/floriday/sync.server.d.ts +1 -1
  57. package/dist-lib/lib/accounting/floriday/sync.server.js +1 -1
  58. package/dist-lib/lib/accounting/registry.server.d.ts +1 -1
  59. package/dist-lib/lib/accounting/registry.server.js +2 -2
  60. package/dist-lib/lib/accounting/twinfield/client.server.js +2 -2
  61. package/dist-lib/lib/accounting/twinfield/field-coverage.test.js +6 -6
  62. package/dist-lib/lib/accounting/twinfield/mapping.js +3 -3
  63. package/dist-lib/lib/accounting/twinfield/rmw-push.server.d.ts +2 -2
  64. package/dist-lib/lib/accounting/twinfield/rmw-push.server.js +4 -4
  65. package/dist-lib/lib/accounting/twinfield/rmw.server.d.ts +1 -1
  66. package/dist-lib/lib/accounting/twinfield/rmw.server.js +2 -2
  67. package/dist-lib/lib/accounting/twinfield/sandbox-rmw.server.d.ts +6 -6
  68. package/dist-lib/lib/accounting/twinfield/sandbox-rmw.server.js +12 -12
  69. package/dist-lib/lib/accounting/twinfield/sandbox.server.js +3 -3
  70. package/dist-lib/lib/accounting/twinfield/sync.server.js +4 -4
  71. package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +7 -7
  72. package/dist-lib/lib/accounting/twinfield/worker.functions.js +6 -6
  73. package/dist-lib/lib/accounting/twinfield/worker.server.js +8 -8
  74. package/dist-lib/lib/accounting.functions.d.ts +4 -4
  75. package/dist-lib/lib/accounting.functions.js +2 -2
  76. package/dist-lib/lib/countries.test.js +3 -3
  77. package/dist-lib/lib/customer-analytics.functions.d.ts +1 -1
  78. package/dist-lib/lib/customer-analytics.functions.js +1 -1
  79. package/dist-lib/lib/customer-assortment.functions.d.ts +1 -1
  80. package/dist-lib/lib/customer-assortment.functions.js +3 -3
  81. package/dist-lib/lib/customer-defaults.test.js +3 -3
  82. package/dist-lib/lib/customer-insert.d.ts +1 -1
  83. package/dist-lib/lib/customer-insert.js +2 -2
  84. package/dist-lib/lib/customer-notes.functions.d.ts +2 -2
  85. package/dist-lib/lib/customer-notes.functions.js +1 -1
  86. package/dist-lib/lib/customer-orders.functions.d.ts +1 -1
  87. package/dist-lib/lib/customer-orders.functions.js +1 -1
  88. package/dist-lib/lib/customer-revenue.functions.d.ts +1 -1
  89. package/dist-lib/lib/customer-revenue.functions.js +2 -2
  90. package/dist-lib/lib/customer-schemas.js +1 -1
  91. package/dist-lib/lib/customer-schemas.test.js +1 -1
  92. package/dist-lib/lib/customer-stickers.functions.d.ts +9 -9
  93. package/dist-lib/lib/customer-stickers.functions.js +2 -2
  94. package/dist-lib/lib/customers.functions.d.ts +29 -29
  95. package/dist-lib/lib/customers.functions.js +11 -11
  96. package/dist-lib/lib/delivery-locations.functions.d.ts +7 -7
  97. package/dist-lib/lib/delivery-locations.functions.js +2 -2
  98. package/dist-lib/lib/external-customers.functions.d.ts +4 -4
  99. package/dist-lib/lib/external-customers.functions.js +2 -2
  100. package/dist-lib/lib/floriday-app-url.functions.d.ts +1 -1
  101. package/dist-lib/lib/floriday-app-url.functions.js +2 -2
  102. package/dist-lib/lib/floriday-assortment.functions.d.ts +1 -1
  103. package/dist-lib/lib/floriday-assortment.functions.js +2 -2
  104. package/dist-lib/lib/floriday-discounts.functions.d.ts +2 -2
  105. package/dist-lib/lib/floriday-discounts.functions.js +2 -2
  106. package/dist-lib/lib/floriday-network-status.functions.d.ts +3 -1
  107. package/dist-lib/lib/floriday-network-status.functions.d.ts.map +1 -1
  108. package/dist-lib/lib/floriday-network-status.functions.js +39 -18
  109. package/dist-lib/lib/floriday-price-import.functions.d.ts +2 -2
  110. package/dist-lib/lib/floriday-price-import.functions.js +2 -2
  111. package/dist-lib/lib/floriday-sticker-import.functions.d.ts +1 -1
  112. package/dist-lib/lib/floriday-sticker-import.functions.js +1 -1
  113. package/dist-lib/lib/floriday-trade-items.functions.d.ts +1 -1
  114. package/dist-lib/lib/floriday-trade-items.functions.js +1 -1
  115. package/dist-lib/lib/floriday-trade-settings.functions.d.ts +1 -1
  116. package/dist-lib/lib/floriday-trade-settings.functions.js +1 -1
  117. package/dist-lib/lib/friendly-twinfield-error.d.ts +8 -0
  118. package/dist-lib/lib/friendly-twinfield-error.d.ts.map +1 -0
  119. package/dist-lib/lib/friendly-twinfield-error.js +16 -0
  120. package/dist-lib/lib/local-discounts.functions.d.ts +7 -7
  121. package/dist-lib/lib/local-discounts.functions.js +1 -1
  122. package/dist-lib/lib/logistics-events.functions.d.ts +1 -1
  123. package/dist-lib/lib/logistics-events.functions.js +1 -1
  124. package/dist-lib/lib/logistics-strategy.functions.d.ts +4 -4
  125. package/dist-lib/lib/logistics-strategy.functions.js +1 -1
  126. package/dist-lib/lib/overdue-invoices.functions.d.ts +1 -1
  127. package/dist-lib/lib/overdue-invoices.functions.js +2 -2
  128. package/dist-lib/lib/price-groups-local.functions.d.ts +15 -15
  129. package/dist-lib/lib/price-groups-local.functions.js +1 -1
  130. package/dist-lib/lib/price-groups.functions.d.ts +1 -1
  131. package/dist-lib/lib/price-groups.functions.js +1 -1
  132. package/dist-lib/lib/twinfield-adopt.functions.d.ts +5 -5
  133. package/dist-lib/lib/twinfield-adopt.functions.js +7 -7
  134. package/dist-lib/lib/twinfield-customer-defaults.functions.d.ts +2 -2
  135. package/dist-lib/lib/twinfield-customer-defaults.functions.js +2 -2
  136. package/dist-lib/lib/twinfield-customer-defaults.server.js +1 -1
  137. package/dist-lib/lib/twinfield-ledger.functions.d.ts +2 -2
  138. package/dist-lib/lib/twinfield-ledger.functions.js +2 -2
  139. package/dist-lib/lib/twinfield-ledger.server.js +2 -2
  140. package/dist-lib/lib/twinfield-required-fields.js +1 -1
  141. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { cn } from "../lib/utils";
2
+ import { cn } from "../lib/utils.js";
3
3
  function initials(name) {
4
4
  if (!name)
5
5
  return "?";
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef } from "react";
3
3
  import { REFRESH_MS } from "../../lib/refresh-intervals.js";
4
- import { friendlyError } from "../../lib/errors";
4
+ import { friendlyError } from "../../lib/errors.js";
5
5
  import { useNavigate } from "@tanstack/react-router";
6
6
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
7
7
  import { useServerFn } from "@tanstack/react-start";
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useParams } from "@tanstack/react-router";
3
- import { BewerkKlant } from "./BewerkKlant";
3
+ import { BewerkKlant } from "./BewerkKlant.js";
4
4
  /** Route-onafhankelijk: leest `$id` uit welke route de shell ook gebruikt. */
5
5
  export function BewerkKlantRoute({ id: propId } = {}) {
6
6
  const params = useParams({ strict: false });
@@ -7,7 +7,7 @@ import { toast } from "sonner";
7
7
  import { supabase } from "../../integrations/supabase/client.js";
8
8
  import { ALLOWED_STICKER_MIME, commitStickerBatch, discardStickerBatch, suggestStickerMatches, } from "../../lib/customer-stickers.functions.js";
9
9
  import { friendlyError } from "../../lib/errors.js";
10
- import { SearchableSelect } from "./SearchableSelect";
10
+ import { SearchableSelect } from "./SearchableSelect.js";
11
11
  const BUCKET = "customer-stickers";
12
12
  const MAX_SIZE = 10 * 1024 * 1024;
13
13
  const ACCEPT = ".pdf,.png,.jpg,.jpeg,.svg";
@@ -7,8 +7,8 @@ import { useServerFn } from "@tanstack/react-start";
7
7
  import { Card, CardContent, CardHeader, CardTitle, Badge, Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Input, } from "@flowselections/core";
8
8
  import { ResponsiveContainer, LineChart, Line, BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, PieChart, Pie, Cell, Legend, } from "recharts";
9
9
  import { TrendingUp, TrendingDown, Minus } from "lucide-react";
10
- import { getCustomerAnalytics } from "../../lib/customer-analytics.functions";
11
- import { decodeDeep } from "../../lib/decode-html";
10
+ import { getCustomerAnalytics } from "../../lib/customer-analytics.functions.js";
11
+ import { decodeDeep } from "../../lib/decode-html.js";
12
12
  function presetRange(key) {
13
13
  const now = new Date();
14
14
  const y = now.getFullYear(), m = now.getMonth(), d = now.getDate();
@@ -12,10 +12,10 @@ import { importFloridayStickers } from "../../lib/floriday-sticker-import.functi
12
12
  import { decodeDeep } from "../../lib/decode-html.js";
13
13
  import { friendlyError } from "../../lib/errors.js";
14
14
  import { formatMoney } from "../../lib/format-currency.js";
15
- import { FloridayPriceImportDialog } from "./FloridayPriceImportDialog";
16
- import { CustomerStickerCell } from "./CustomerStickerCell";
17
- import { CustomerStickerFormatDialog } from "./CustomerStickerFormatDialog";
18
- import { BulkStickerUploadDialog } from "./BulkStickerUploadDialog";
15
+ import { FloridayPriceImportDialog } from "./FloridayPriceImportDialog.js";
16
+ import { CustomerStickerCell } from "./CustomerStickerCell.js";
17
+ import { CustomerStickerFormatDialog } from "./CustomerStickerFormatDialog.js";
18
+ import { BulkStickerUploadDialog } from "./BulkStickerUploadDialog.js";
19
19
  function money(value, currency) {
20
20
  if (value == null)
21
21
  return "—";
@@ -1,15 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { friendlyError } from "../../lib/errors";
2
+ import { friendlyError } from "../../lib/errors.js";
3
3
  import { useCustomerDefaults } from "./useCustomerDefaults.js";
4
4
  import { useEffect, useMemo, useRef, useState } from "react";
5
5
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
6
6
  import { useServerFn } from "@tanstack/react-start";
7
7
  import { Button, Card, CardContent, Badge, toast, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@flowselections/core";
8
8
  import { MapPin, Plus, Pencil, Trash2, Truck, Sparkles, PackageCheck, Download } from "lucide-react";
9
- import { listCarriers, listDeliveryLocations, upsertDeliveryLocation, deleteDeliveryLocation, pickCarrierForLocation, importFloridayDeliveryLocations, } from "../../lib/delivery-locations.functions";
10
- import { EU_COUNTRIES, findCountry } from "../../lib/eu-regions";
11
- import { decodeDeep } from "../../lib/decode-html";
12
- import { countryNameToFlag } from "../../lib/countries";
9
+ import { listCarriers, listDeliveryLocations, upsertDeliveryLocation, deleteDeliveryLocation, pickCarrierForLocation, importFloridayDeliveryLocations, } from "../../lib/delivery-locations.functions.js";
10
+ import { EU_COUNTRIES, findCountry } from "../../lib/eu-regions.js";
11
+ import { decodeDeep } from "../../lib/decode-html.js";
12
+ import { countryNameToFlag } from "../../lib/countries.js";
13
13
  const EMPTY = {
14
14
  name: "", street: "", house_number: "", postal_code: "",
15
15
  city: "", province: "", country: "",
@@ -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);
@@ -6,8 +6,8 @@ import { useServerFn } from "@tanstack/react-start";
6
6
  import { Card, CardContent, Badge, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
7
7
  import { Info, Percent, FileText, Users } from "lucide-react";
8
8
  import { listCustomerDiscounts, } from "../../lib/floriday-discounts.functions.js";
9
- import { EmptyState } from "./KlantDetailLayout";
10
- import { ExternalCacheBanner } from "./ExternalCacheBanner";
9
+ import { EmptyState } from "./KlantDetailLayout.js";
10
+ import { ExternalCacheBanner } from "./ExternalCacheBanner.js";
11
11
  import { decodeDeep } from "../../lib/decode-html.js";
12
12
  import { formatMoney } from "../../lib/format-currency.js";
13
13
  const SOURCE_LABEL = {
@@ -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,4 +1,4 @@
1
- import type { ExternalProvider } from "../../lib/external-customers.functions";
1
+ import type { ExternalProvider } from "../../lib/external-customers.functions.js";
2
2
  export declare function ExternalCacheBanner({ lastFetchedAt, providers, title, description, }: {
3
3
  lastFetchedAt: string | null;
4
4
  providers: ExternalProvider[];
@@ -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"}
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { useServerFn } from "@tanstack/react-start";
4
- import { getFloridayNetworkStatus } from "../../lib/floriday-network-status.functions";
5
- import { formatDate } from "../../lib/format-date";
4
+ import { getFloridayNetworkStatus } from "../../lib/floriday-network-status.functions.js";
5
+ import { formatDate } from "../../lib/format-date.js";
6
+ import { useCustomerDefaults } from "./useCustomerDefaults.js";
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,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useParams } from "@tanstack/react-router";
3
- import { GroupedKlantDetail } from "./GroupedKlantDetail";
3
+ import { GroupedKlantDetail } from "./GroupedKlantDetail.js";
4
4
  /** Route-onafhankelijk: leest `$groupKey` uit welke route de shell ook gebruikt. */
5
5
  export function GroupedKlantDetailRoute({ groupKey: propKey } = {}) {
6
6
  const params = useParams({ strict: false });
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { friendlyError } from "../../lib/errors";
2
+ import { friendlyError } from "../../lib/errors.js";
3
3
  import { useNavigate } from "@tanstack/react-router";
4
4
  import { useEffect, useMemo, useState } from "react";
5
5
  import { useMutation, useQueryClient } from "@tanstack/react-query";
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@flowselections/core";
4
- import { BewerkKlant } from "./BewerkKlant";
5
- import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant";
4
+ import { BewerkKlant } from "./BewerkKlant.js";
5
+ import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant.js";
6
6
  /**
7
7
  * Bewerken/overnemen in een dialoog, zodat de module geen eigen routes nodig
8
8
  * heeft. Werkt daardoor in elke shell, ongeacht welke routes die declareert.
@@ -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,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import type { ExternalCustomer, GroupedCustomer } from "../../lib/external-customers.functions";
2
+ import type { ExternalCustomer, GroupedCustomer } from "../../lib/external-customers.functions.js";
3
3
  export type CustomerProfile = {
4
4
  logo_url: string | null;
5
5
  company_name: string;
@@ -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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"}
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCustomerDefaults } from "./useCustomerDefaults.js";
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";
5
6
  import { Mail, Phone, Globe, MapPin, Hash, Building2, ChevronDown, ChevronRight, Users, Truck, Package, ShoppingCart, StickyNote, Pencil, Check, X, } from "lucide-react";
6
- import { CustomerAvatar } from "../CustomerAvatar";
7
- import { formatCountry, countryNameToFlag } from "../../lib/countries";
7
+ import { CustomerAvatar } from "../CustomerAvatar.js";
8
+ import { formatCountry, countryNameToFlag } from "../../lib/countries.js";
8
9
  function formatOrgType(t) {
9
10
  if (t === "CUSTOMER")
10
11
  return "Koper";
@@ -19,8 +20,8 @@ function firstNonEmpty(values) {
19
20
  }
20
21
  return null;
21
22
  }
22
- import { formatPaymentMethodLabel } from "../../lib/payment-method";
23
- import { FloridayNetworkStatus } from "./FloridayNetworkStatus";
23
+ import { formatPaymentMethodLabel } from "../../lib/payment-method.js";
24
+ import { FloridayNetworkStatus } from "./FloridayNetworkStatus.js";
24
25
  function fmtAddr(a) {
25
26
  if (!a)
26
27
  return null;
@@ -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"] }))] })] }) }));
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useParams } from "@tanstack/react-router";
3
- import { KlantDetail } from "./KlantDetail";
3
+ import { KlantDetail } from "./KlantDetail.js";
4
4
  /** Route-onafhankelijk: leest `$id` uit welke route de shell ook gebruikt. */
5
5
  export function KlantDetailRoute({ id: propId } = {}) {
6
6
  const params = useParams({ strict: false });
@@ -5,25 +5,25 @@ import { formatDate } from "../../lib/format-date.js";
5
5
  import { useQuery, useQueryClient } from "@tanstack/react-query";
6
6
  import { useServerFn } from "@tanstack/react-start";
7
7
  import { useEffect, useMemo, useRef, useState } from "react";
8
- import { usePersistedState, markReturnToList, consumeReturnToList } from "../../lib/persisted-state";
9
- import { formatEur } from "../../lib/format-currency";
8
+ import { usePersistedState, markReturnToList, consumeReturnToList } from "../../lib/persisted-state.js";
9
+ import { formatEur } from "../../lib/format-currency.js";
10
10
  import { Button, Input, Card, CardContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsList, TabsTrigger, Skeleton, } from "@flowselections/core";
11
11
  import { Plus, Search, Users, UserCheck, UserX, AlertCircle, X, ArrowUpDown, Mail, MapPin, Upload, ChevronLeft, ChevronRight, Pencil, } from "lucide-react";
12
12
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
13
- import { listCustomers } from "../../lib/customers.functions";
14
- import { listExternalCustomers } from "../../lib/external-customers.functions";
15
- import { listOverdueBalances } from "../../lib/overdue-invoices.functions";
16
- import { listRevenueActivity } from "../../lib/customer-revenue.functions";
17
- import { supabase } from "../../integrations/supabase/client";
18
- import { decodeDeep } from "../../lib/decode-html";
19
- import { friendlyError, isBackendUnavailable, retryOnTransient, retryBackoff } from "../../lib/errors";
20
- import { CustomerAvatar } from "../CustomerAvatar";
21
- import { PriceGroupBadges } from "./PriceGroupBadges";
22
- import { KlantBewerkenDialog } from "./KlantBewerkenDialog";
23
- import { ChannelBadges } from "./ChannelBadges";
24
- import { derivePaymentMethod, paymentToneClasses } from "../../lib/payment-method";
25
- import { formatCountry, getCountryFlagAndName, countryNameToFlag } from "../../lib/countries";
26
- import { MultiSearchableSelect } from "./MultiSearchableSelect";
13
+ import { listCustomers } from "../../lib/customers.functions.js";
14
+ import { listExternalCustomers } from "../../lib/external-customers.functions.js";
15
+ import { listOverdueBalances } from "../../lib/overdue-invoices.functions.js";
16
+ import { listRevenueActivity } from "../../lib/customer-revenue.functions.js";
17
+ import { supabase } from "../../integrations/supabase/client.js";
18
+ import { decodeDeep } from "../../lib/decode-html.js";
19
+ import { friendlyError, isBackendUnavailable, retryOnTransient, retryBackoff } from "../../lib/errors.js";
20
+ import { CustomerAvatar } from "../CustomerAvatar.js";
21
+ import { PriceGroupBadges } from "./PriceGroupBadges.js";
22
+ import { KlantBewerkenDialog } from "./KlantBewerkenDialog.js";
23
+ import { ChannelBadges } from "./ChannelBadges.js";
24
+ import { derivePaymentMethod, paymentToneClasses } from "../../lib/payment-method.js";
25
+ import { formatCountry, getCountryFlagAndName, countryNameToFlag } from "../../lib/countries.js";
26
+ import { MultiSearchableSelect } from "./MultiSearchableSelect.js";
27
27
  export function KlantenOverzicht() {
28
28
  const navigate = useNavigate();
29
29
  const queryClient = useQueryClient();
@@ -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,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { friendlyError } from "../../lib/errors";
2
+ import { friendlyError } from "../../lib/errors.js";
3
3
  import { useEffect, useState } from "react";
4
4
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
5
  import { useServerFn } from "@tanstack/react-start";
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { friendlyError } from "../../lib/errors";
2
+ import { friendlyError } from "../../lib/errors.js";
3
3
  import { formatDate } from "../../lib/format-date.js";
4
4
  import { useMemo, useState } from "react";
5
5
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -7,7 +7,7 @@ import { useServerFn } from "@tanstack/react-start";
7
7
  import { Card, CardContent, Badge, Input, Button, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
8
8
  import { Percent, FileText, Users, Plus, Pencil, Trash2 } from "lucide-react";
9
9
  import { listLocalCustomerDiscounts, deleteLocalDiscount, } from "../../lib/local-discounts.functions.js";
10
- import { LocalDiscountFormDialog } from "./LocalDiscountFormDialog";
10
+ import { LocalDiscountFormDialog } from "./LocalDiscountFormDialog.js";
11
11
  import { decodeDeep } from "../../lib/decode-html.js";
12
12
  import { formatMoney } from "../../lib/format-currency.js";
13
13
  const SOURCE_LABEL = {
@@ -8,9 +8,9 @@ import { Search, MapPin, Globe } from "lucide-react";
8
8
  import { BackLink } from "./BackLink.js";
9
9
  import { countryNameToFlag } from "../../lib/countries.js";
10
10
  import { listFloridayNetwork } from "../../lib/customers.functions.js";
11
- import { CustomerAvatar } from "../CustomerAvatar";
12
- import { PriceGroupBadges } from "./PriceGroupBadges";
13
- import { FloridayDeeplinkButton } from "./FloridayDeeplinkButton";
11
+ import { CustomerAvatar } from "../CustomerAvatar.js";
12
+ import { PriceGroupBadges } from "./PriceGroupBadges.js";
13
+ import { FloridayDeeplinkButton } from "./FloridayDeeplinkButton.js";
14
14
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
15
15
  import { decodeDeep } from "../../lib/decode-html.js";
16
16
  const TYPE_OPTIONS = [
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { friendlyError } from "../../lib/errors";
2
+ import { friendlyError } from "../../lib/errors.js";
3
3
  import { useNavigate } from "@tanstack/react-router";
4
4
  import { useState } from "react";
5
5
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { friendlyError } from "../../lib/errors";
2
+ import { friendlyError } from "../../lib/errors.js";
3
3
  import { useNavigate } from "@tanstack/react-router";
4
4
  import { useEffect } from "react";
5
5
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useParams } from "@tanstack/react-router";
3
- import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant";
3
+ import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant.js";
4
4
  /** Route-onafhankelijk: leest `$code` uit welke route de shell ook gebruikt. */
5
5
  export function OvernemenTwinfieldKlantRoute({ code: propCode } = {}) {
6
6
  const params = useParams({ strict: false });
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRouter } from "@tanstack/react-router";
3
3
  import { Button, Card, CardContent } from "@flowselections/core";
4
4
  import { AlertCircle, RefreshCw } from "lucide-react";
5
- import { friendlyError } from "../../lib/errors";
5
+ import { friendlyError } from "../../lib/errors.js";
6
6
  /** Nette foutweergave voor route-loaders en render-fouten binnen Klanten. */
7
7
  export function RouteError({ error }) {
8
8
  const router = useRouter();
@@ -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;