@flowselections/mailbox-orders 1.0.53 → 1.0.57

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 (98) hide show
  1. package/dist-lib/components/MailboxAuthBanner.d.ts +3 -3
  2. package/dist-lib/components/MailboxAuthBanner.d.ts.map +1 -1
  3. package/dist-lib/components/MailboxAuthBanner.js +20 -7
  4. package/dist-lib/components/MailboxPageFrame.d.ts +25 -0
  5. package/dist-lib/components/MailboxPageFrame.d.ts.map +1 -0
  6. package/dist-lib/components/MailboxPageFrame.js +103 -0
  7. package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
  8. package/dist-lib/components/MailboxProcessedPage.js +102 -61
  9. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  10. package/dist-lib/components/MailboxProposalsPage.js +191 -76
  11. package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -1
  12. package/dist-lib/components/MailboxSkippedPage.js +97 -19
  13. package/dist-lib/components/MailboxStatusLine.d.ts +8 -2
  14. package/dist-lib/components/MailboxStatusLine.d.ts.map +1 -1
  15. package/dist-lib/components/MailboxStatusLine.js +54 -8
  16. package/dist-lib/components/TablePagination.d.ts +2 -2
  17. package/dist-lib/components/TablePagination.d.ts.map +1 -1
  18. package/dist-lib/components/TablePagination.js +8 -12
  19. package/dist-lib/components/fs-ui/DataTable.d.ts +42 -0
  20. package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -0
  21. package/dist-lib/components/fs-ui/DataTable.js +40 -0
  22. package/dist-lib/components/fs-ui/EmptyState.d.ts +9 -0
  23. package/dist-lib/components/fs-ui/EmptyState.d.ts.map +1 -0
  24. package/dist-lib/components/fs-ui/EmptyState.js +5 -0
  25. package/dist-lib/components/fs-ui/FilterChips.d.ts +13 -0
  26. package/dist-lib/components/fs-ui/FilterChips.d.ts.map +1 -0
  27. package/dist-lib/components/fs-ui/FilterChips.js +8 -0
  28. package/dist-lib/components/fs-ui/FilterPanel.d.ts +32 -0
  29. package/dist-lib/components/fs-ui/FilterPanel.d.ts.map +1 -0
  30. package/dist-lib/components/fs-ui/FilterPanel.js +20 -0
  31. package/dist-lib/components/fs-ui/HoverTip.d.ts +15 -0
  32. package/dist-lib/components/fs-ui/HoverTip.d.ts.map +1 -0
  33. package/dist-lib/components/fs-ui/HoverTip.js +24 -0
  34. package/dist-lib/components/fs-ui/InlineAlert.d.ts +9 -0
  35. package/dist-lib/components/fs-ui/InlineAlert.d.ts.map +1 -0
  36. package/dist-lib/components/fs-ui/InlineAlert.js +6 -0
  37. package/dist-lib/components/fs-ui/KpiRow.d.ts +6 -0
  38. package/dist-lib/components/fs-ui/KpiRow.d.ts.map +1 -0
  39. package/dist-lib/components/fs-ui/KpiRow.js +7 -0
  40. package/dist-lib/components/fs-ui/KpiTile.d.ts +21 -0
  41. package/dist-lib/components/fs-ui/KpiTile.d.ts.map +1 -0
  42. package/dist-lib/components/fs-ui/KpiTile.js +40 -0
  43. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts +10 -0
  44. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts.map +1 -0
  45. package/dist-lib/components/fs-ui/MobilePrimaryAction.js +7 -0
  46. package/dist-lib/components/fs-ui/NoticeBar.d.ts +18 -0
  47. package/dist-lib/components/fs-ui/NoticeBar.d.ts.map +1 -0
  48. package/dist-lib/components/fs-ui/NoticeBar.js +24 -0
  49. package/dist-lib/components/fs-ui/PageHeader.d.ts +8 -0
  50. package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -0
  51. package/dist-lib/components/fs-ui/PageHeader.js +5 -0
  52. package/dist-lib/components/fs-ui/PillTabs.d.ts +16 -0
  53. package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -0
  54. package/dist-lib/components/fs-ui/PillTabs.js +29 -0
  55. package/dist-lib/components/fs-ui/RowMenu.d.ts +13 -0
  56. package/dist-lib/components/fs-ui/RowMenu.d.ts.map +1 -0
  57. package/dist-lib/components/fs-ui/RowMenu.js +10 -0
  58. package/dist-lib/components/fs-ui/SearchInput.d.ts +12 -0
  59. package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -0
  60. package/dist-lib/components/fs-ui/SearchInput.js +38 -0
  61. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts +7 -0
  62. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts.map +1 -0
  63. package/dist-lib/components/fs-ui/ShortcutHelp.js +6 -0
  64. package/dist-lib/components/fs-ui/StatusIcon.d.ts +8 -0
  65. package/dist-lib/components/fs-ui/StatusIcon.d.ts.map +1 -0
  66. package/dist-lib/components/fs-ui/StatusIcon.js +16 -0
  67. package/dist-lib/components/fs-ui/TableSkeleton.d.ts +6 -0
  68. package/dist-lib/components/fs-ui/TableSkeleton.d.ts.map +1 -0
  69. package/dist-lib/components/fs-ui/TableSkeleton.js +6 -0
  70. package/dist-lib/components/fs-ui/Toolbar.d.ts +13 -0
  71. package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -0
  72. package/dist-lib/components/fs-ui/Toolbar.js +10 -0
  73. package/dist-lib/components/fs-ui/UpdatedAt.d.ts +8 -0
  74. package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -0
  75. package/dist-lib/components/fs-ui/UpdatedAt.js +17 -0
  76. package/dist-lib/components/fs-ui/useListHotkeys.d.ts +18 -0
  77. package/dist-lib/components/fs-ui/useListHotkeys.d.ts.map +1 -0
  78. package/dist-lib/components/fs-ui/useListHotkeys.js +48 -0
  79. package/dist-lib/components/settings/MailboxAiPromptCard.d.ts +2 -1
  80. package/dist-lib/components/settings/MailboxAiPromptCard.d.ts.map +1 -1
  81. package/dist-lib/components/settings/MailboxAiPromptCard.js +9 -1
  82. package/dist-lib/components/settings/MailboxFilterSettingsCard.js +1 -1
  83. package/dist-lib/components/settings/MailboxTestSetCard.d.ts +2 -1
  84. package/dist-lib/components/settings/MailboxTestSetCard.d.ts.map +1 -1
  85. package/dist-lib/components/settings/MailboxTestSetCard.js +10 -2
  86. package/dist-lib/integrations/supabase/types.d.ts +372 -0
  87. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  88. package/dist-lib/lib/format-date.d.ts +5 -0
  89. package/dist-lib/lib/format-date.d.ts.map +1 -0
  90. package/dist-lib/lib/format-date.js +30 -0
  91. package/dist-lib/lib/labels.d.ts +15 -0
  92. package/dist-lib/lib/labels.d.ts.map +1 -0
  93. package/dist-lib/lib/labels.js +57 -0
  94. package/dist-lib/lib/mailbox-list.functions.d.ts +67 -0
  95. package/dist-lib/lib/mailbox-list.functions.d.ts.map +1 -0
  96. package/dist-lib/lib/mailbox-list.functions.js +61 -0
  97. package/dist-lib/lib/mailbox.functions.js +3 -3
  98. package/package.json +1 -1
@@ -2,18 +2,31 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
- import { Button, Badge, Card, CardContent, CardHeader, CardTitle, Input, Label, Textarea, Switch, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Dialog, DialogContent, DialogHeader, DialogTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
6
- import { Check, X, Trash2, Plus, AlertTriangle, RefreshCw } from "lucide-react";
7
- import { listProposals, getProposalDetail, approveProposal, rejectProposal, getStickerDefaults, listCustomersForPicker, createCustomerFromGroup, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getActiveOrdersMailbox, syncMailboxNow, refreshProposalsForAgentChange, reparseProposalNow, listBeladingOptionsForProducts, listAllowedLoadCarriers, getDeparturePreview, getAgreementPrices, countOutdatedProposals, } from "../lib/mailbox.functions.js";
5
+ import { useNavigate } from "@tanstack/react-router";
6
+ import { Button, Badge, Card, CardContent, CardHeader, CardTitle, Input, Label, Textarea, Switch, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Dialog, DialogContent, DialogHeader, DialogTitle, toast, } from "@flowselections/core";
7
+ import { Check, X, Trash2, Plus, AlertTriangle, RefreshCw, CalendarClock, UserX, Search, Inbox, ClipboardCheck } from "lucide-react";
8
+ import { getProposalDetail, approveProposal, rejectProposal, getStickerDefaults, listCustomersForPicker, createCustomerFromGroup, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getActiveOrdersMailbox, syncMailboxNow, refreshProposalsForAgentChange, reparseProposalNow, listBeladingOptionsForProducts, listAllowedLoadCarriers, getDeparturePreview, getAgreementPrices, countOutdatedProposals, } from "../lib/mailbox.functions.js";
8
9
  import { normalizeUnitKind, toPlants, describeConversion, lineTotal, UNIT_KIND_LABEL, } from "../lib/mailbox-units.js";
9
10
  import { enabledLoadCarriers, normalizeAllowedLoadCarriers, EMPTY_LOAD_CARRIERS } from "../lib/load-carriers.js";
10
11
  import { listTemplateFields, listPlantTypesForPicker, listSuppliersForPicker, } from "../lib/mailbox-template.functions.js";
11
12
  import { SearchableSelect } from "./SearchableSelect.js";
12
- import { MailboxAuthBanner } from "./MailboxAuthBanner.js";
13
- import { MailboxStatusLine, AI_AUTH_PATH } from "./MailboxStatusLine.js";
13
+ import { AI_AUTH_PATH } from "./MailboxStatusLine.js";
14
+ import { listProposalsPage } from "../lib/mailbox-list.functions.js";
15
+ import { MailboxPageFrame, browserTz } from "./MailboxPageFrame.js";
16
+ import { DataTable, InitialsAvatar, CellText } from "./fs-ui/DataTable.js";
17
+ import { FilterPanel, FilterOptions, PERIOD_OPTIONS } from "./fs-ui/FilterPanel.js";
18
+ import { fmtDayTime } from "../lib/format-date.js";
19
+ import { StatusIcon } from "./fs-ui/StatusIcon.js";
20
+ import { EmptyState } from "./fs-ui/EmptyState.js";
21
+ import { TableSkeleton } from "./fs-ui/TableSkeleton.js";
22
+ import { Toolbar } from "./fs-ui/Toolbar.js";
23
+ import { SearchInput } from "./fs-ui/SearchInput.js";
24
+ import { FilterChips } from "./fs-ui/FilterChips.js";
25
+ import { MobilePrimaryAction } from "./fs-ui/MobilePrimaryAction.js";
26
+ import { useListHotkeys } from "./fs-ui/useListHotkeys.js";
14
27
  import { EmailPreview } from "./EmailPreview.js";
15
28
  import { TablePagination } from "./TablePagination.js";
16
- import { MailboxFilterBar, usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar.js";
29
+ import { usePersistedState } from "./MailboxFilterBar.js";
17
30
  import { supabase } from "../integrations/supabase/client.js";
18
31
  import { canOverrideLateSignup, checkLinePrices } from "../lib/verkoop-order.js";
19
32
  const beladingKey = (b) => `${b.plants_per_crate}x${b.crates_per_layer}x${b.layers_per_cart}|${b.crate_code ?? ""}|${b.carrier_code ?? ""}`;
@@ -79,7 +92,7 @@ function resolveCustomerId(raw, customers, fallbackId) {
79
92
  }
80
93
  export function MailboxProposalsPage() {
81
94
  const qc = useQueryClient();
82
- const fetchList = useServerFn(listProposals);
95
+ const fetchList = useServerFn(listProposalsPage);
83
96
  const fetchTemplate = useServerFn(listTemplateFields);
84
97
  const fetchApprove = useServerFn(approveProposal);
85
98
  const fetchReject = useServerFn(rejectProposal);
@@ -91,6 +104,11 @@ export function MailboxProposalsPage() {
91
104
  const fetchPickupLocations = useServerFn(listPickupLocations);
92
105
  const fetchActive = useServerFn(getActiveOrdersMailbox);
93
106
  const fetchSync = useServerFn(syncMailboxNow);
107
+ const navigate = useNavigate();
108
+ /** Knop "Open order" in de melding na bevestigen (route van Verkooporders). */
109
+ const openOrderAction = (orderId) => orderId
110
+ ? { action: { label: "Open order", onClick: () => void navigate({ to: `/floriday-verkoop/verkooporders/${orderId}` }) } }
111
+ : undefined;
94
112
  // Server fns met requireSupabaseAuth gooien een raw Response bij een stale
95
113
  // bearer-token. Die bubbelt op als "[object Response]" en blankt het scherm.
96
114
  // Vang elke fout en geef een lege fallback terug — UI blijft bruikbaar.
@@ -106,11 +124,21 @@ export function MailboxProposalsPage() {
106
124
  // zodra de dialoog voor het eerst opengaat, en blijven daarna gecachet.
107
125
  const [activeId, setActiveId] = useState(null);
108
126
  const [page, setPage] = useState(1);
109
- const [pageSize, setPageSize] = useState(25);
127
+ const [pageSize, setPageSize] = useState(50);
110
128
  const [search, setSearch] = usePersistedState("mailbox-proposals:search", "");
111
129
  const [period, setPeriod] = usePersistedState("mailbox-proposals:period", "all");
112
130
  const [sortOrder, setSortOrder] = usePersistedState("mailbox-proposals:sort", "date-desc");
113
- const refsEnabled = true;
131
+ const [listFilter, setListFilter] = usePersistedState("mailbox-proposals:filter", null);
132
+ const [cursor, setCursor] = useState(null);
133
+ const [helpOpen, setHelpOpen] = useState(false);
134
+ const [filtersOpen, setFiltersOpen] = useState(false);
135
+ const searchRef = useRef(null);
136
+ // Zware keuzelijsten (klanten, artikelen, …) pas laden zodra een voorstel
137
+ // voor het eerst geopend wordt; daarna blijven ze in de cache.
138
+ const [refsWanted, setRefsWanted] = useState(false);
139
+ useEffect(() => { if (activeId)
140
+ setRefsWanted(true); }, [activeId]);
141
+ const refsEnabled = refsWanted;
114
142
  const refOpts = {
115
143
  enabled: refsEnabled,
116
144
  staleTime: 5 * 60000,
@@ -120,11 +148,23 @@ export function MailboxProposalsPage() {
120
148
  // Bestelvoorstellen toont uitsluitend nieuwe, nog niet gecontroleerde
121
149
  // bestellingen ("Te controleren"). Goedgekeurd/afgewezen verschijnt in
122
150
  // de module Verwerkt.
123
- const { data, isLoading, error: listError, refetch: refetchList } = useQuery({
124
- queryKey: ["mailbox-proposals", "pending"],
151
+ const listParams = {
152
+ statuses: ["pending"],
153
+ search,
154
+ period,
155
+ sort: sortOrder === "date-asc" ? "asc" : "desc",
156
+ dateField: "received",
157
+ tz: browserTz(),
158
+ filter: listFilter,
159
+ page,
160
+ pageSize,
161
+ };
162
+ const { data, isLoading, error: listError, refetch: refetchList, dataUpdatedAt } = useQuery({
163
+ queryKey: ["mailbox-proposals", "pending", listParams],
125
164
  // Bewust NIET stilzwijgend opvangen: als de server tijdelijk onbereikbaar is
126
165
  // moet de gebruiker dat zien in plaats van een lege lijst ("geen mailtjes").
127
- queryFn: () => fetchList({ data: { status: "pending" } }),
166
+ queryFn: () => fetchList({ data: listParams }),
167
+ placeholderData: (prev) => prev,
128
168
  retry: 3,
129
169
  retryDelay: (a) => Math.min(1000 * 2 ** a, 8000),
130
170
  refetchInterval: 60000,
@@ -171,6 +211,7 @@ export function MailboxProposalsPage() {
171
211
  onSuccess: (res) => {
172
212
  if (res?.ok && ((res.inserted ?? 0) > 0 || (res.processed ?? 0) > 0)) {
173
213
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
214
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
174
215
  }
175
216
  },
176
217
  });
@@ -221,8 +262,9 @@ export function MailboxProposalsPage() {
221
262
  },
222
263
  onSuccess: (res) => {
223
264
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
265
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
224
266
  void refetchOutdated();
225
- toast.success(`${res?.reparsed ?? 0} voorstel(len) opnieuw geanalyseerd`);
267
+ toast.success(`${res?.reparsed ?? 0} bestelling(en) opnieuw uitgelezen`);
226
268
  },
227
269
  });
228
270
  // Handmatig één voorstel opnieuw laten analyseren door de AI-agent.
@@ -237,22 +279,36 @@ export function MailboxProposalsPage() {
237
279
  if (res?.ok) {
238
280
  setReparseError(null);
239
281
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
282
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
240
283
  qc.invalidateQueries({ queryKey: ["mailbox-proposal-detail"] });
241
- toast.success("E-mail opnieuw geanalyseerd — bestelgegevens bijgewerkt");
284
+ toast.success("Mail opnieuw uitgelezen — gegevens bijgewerkt");
242
285
  }
243
286
  else {
244
- const msg = res?.error ?? "Opnieuw analyseren mislukt";
287
+ if (res?.error)
288
+ console.warn("[mailbox] opnieuw uitlezen", res.error);
289
+ const msg = "Opnieuw uitlezen lukt niet. Probeer het over een paar minuten opnieuw.";
245
290
  setReparseError(msg);
246
291
  toast.error(msg);
247
292
  }
248
293
  },
249
294
  onError: (e) => {
250
- const msg = e?.message ?? "Opnieuw analyseren mislukt";
295
+ console.warn("[mailbox] opnieuw uitlezen", e);
296
+ const msg = "Opnieuw uitlezen lukt niet. Probeer het over een paar minuten opnieuw.";
251
297
  setReparseError(msg);
252
298
  toast.error(msg);
253
299
  },
254
300
  });
255
- const items = Array.isArray(data?.items) ? data.items : [];
301
+ // Platte rijen uit de server terugzetten naar de vorm die de rest van de
302
+ // pagina (klantnaam, detailkop) al gebruikt.
303
+ const items = useMemo(() => (Array.isArray(data?.items) ? data.items : []).map((r) => ({
304
+ ...r,
305
+ mailbox_messages: {
306
+ subject: r.subject, from_email: r.from_email, from_name: r.from_name,
307
+ received_at: r.received_at, connection_id: r.connection_id,
308
+ account_email: r.account_email, preview: String(r.preview ?? "").slice(0, 180),
309
+ },
310
+ })), [data]);
311
+ const total = Number(data?.total ?? 0);
256
312
  const template = Array.isArray(templateData?.items) ? templateData.items : [];
257
313
  const customers = Array.isArray(customersData?.items) ? customersData.items : [];
258
314
  const products = Array.isArray(productsData?.items) ? productsData.items : [];
@@ -279,43 +335,99 @@ export function MailboxProposalsPage() {
279
335
  }
280
336
  return p.mailbox_messages?.from_name || p.mailbox_messages?.from_email || "Onbekend";
281
337
  };
282
- // --- Zoeken, filteren en sorteren --------------------------------------
283
- const filteredItems = useMemo(() => {
284
- const q = normalizeText(search).trim();
285
- const filtered = items.filter((p) => {
286
- const name = customerNameFor(p);
287
- if (!matchesPeriod(p.mailbox_messages?.received_at ?? p.created_at, period))
288
- return false;
289
- if (!q)
290
- return true;
291
- const haystack = normalizeText([
292
- name,
293
- p.mailbox_messages?.from_email,
294
- p.mailbox_messages?.from_name,
295
- p.mailbox_messages?.subject,
296
- p.mailbox_messages?.preview,
297
- ].join(" "));
298
- return q.split(/\s+/).every((token) => haystack.includes(token));
299
- });
300
- return filtered.sort((a, b) => {
301
- const aDate = a.mailbox_messages?.received_at ?? a.created_at ?? "";
302
- const bDate = b.mailbox_messages?.received_at ?? b.created_at ?? "";
303
- return sortOrder === "date-desc" ? bDate.localeCompare(aDate) : aDate.localeCompare(bDate);
304
- });
305
- }, [items, customers, search, sortOrder, period]);
306
- const hasActiveFilters = !!search || period !== "all" || sortOrder !== "date-desc";
307
- const clearFilters = () => { setSearch(""); setPeriod("all"); setSortOrder("date-desc"); };
308
- useEffect(() => { setPage(1); }, [search, period, sortOrder]);
309
- const pageCount = Math.max(1, Math.ceil(filteredItems.length / pageSize));
338
+ // --- Zoeken, filteren en sorteren gebeurt op de server -----------------
339
+ const hasActiveFilters = !!search || period !== "all" || !!listFilter;
340
+ const clearFilters = () => { setSearch(""); setPeriod("all"); setSortOrder("date-desc"); setListFilter(null); };
341
+ useEffect(() => { setPage(1); setCursor(null); }, [search, period, sortOrder, listFilter]);
342
+ const pageCount = Math.max(1, Math.ceil(total / pageSize));
310
343
  const currentPage = Math.min(page, pageCount);
311
- const pagedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
312
- return (_jsxs("div", { className: "flex h-[calc(100dvh-var(--app-header-height)-var(--core-topbar-h,0px))] max-h-[calc(100dvh-var(--app-header-height)-var(--core-topbar-h,0px))] min-h-0 w-full flex-col gap-4 overflow-hidden p-6", children: [_jsx(MailboxAuthBanner, { active: activeMailbox }), _jsx(MailboxStatusLine, {}), _jsx("div", { className: "flex items-center justify-between shrink-0", children: _jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Bestelvoorstellen" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Nieuw binnengekomen bestellingen die nog handmatig gecontroleerd moeten worden." })] }) }), outdatedCount > 0 && (_jsxs("div", { className: "shrink-0 flex items-center justify-between gap-3 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800", children: [_jsxs("span", { children: ["De AI-instelling is gewijzigd. ", outdatedCount, " voorstel(len) zijn nog met de vorige instelling gelezen."] }), _jsx(Button, { size: "sm", variant: "outline", disabled: agentRefreshMut.isPending, onClick: () => agentRefreshMut.mutate(), children: agentRefreshMut.isPending ? "Bezig…" : "Opnieuw analyseren" })] })), items.length > 0 && (_jsx(MailboxFilterBar, { search: search, onSearchChange: setSearch, period: period, onPeriodChange: setPeriod, sortOrder: sortOrder, onSortOrderChange: setSortOrder, filteredCount: filteredItems.length, totalCount: items.length, countLabel: "voorstellen", onClear: clearFilters, hasActiveFilters: hasActiveFilters })), _jsx("div", { className: "flex-1 min-h-0 overflow-y-auto overscroll-contain [scrollbar-gutter:stable]", children: isLoading ? (_jsx("div", { className: "text-muted-foreground", children: "Laden..." })) : listError ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-4 text-sm", children: [_jsx("p", { className: "font-medium text-destructive", children: "Bestellingen konden niet worden opgehaald." }), _jsxs("p", { className: "text-muted-foreground mt-1", children: [listError?.message ?? "Onbekende fout", " \u2014 de lijst is niet leeg, alleen niet geladen."] }), _jsx(Button, { size: "sm", variant: "outline", className: "mt-3", onClick: () => void refetchList(), children: "Opnieuw proberen" })] })) : items.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Geen bestellingen te controleren." })) : filteredItems.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Geen voorstellen gevonden voor deze zoekopdracht." })) : (_jsx(Card, { children: _jsxs(CardContent, { className: "p-0", children: [_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Klantnaam" }), _jsx(TableHead, { children: "Bericht" }), _jsx(TableHead, { children: "Orderdatum" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: pagedItems.map((p) => {
313
- const date = p.mailbox_messages?.received_at ?? p.created_at;
314
- return (_jsxs(TableRow, { className: "cursor-pointer hover:bg-muted/50", onClick: () => setActiveId(p.id), children: [_jsx(TableCell, { className: "font-medium", children: customerNameFor(p) }), _jsxs(TableCell, { className: "max-w-sm", children: [_jsx("div", { className: "truncate text-sm font-medium", children: p.mailbox_messages?.subject || "(geen onderwerp)" }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: p.mailbox_messages?.preview || "Geen voorbeeldtekst" }), p.mailbox_messages?.account_email && (_jsxs("div", { className: "truncate text-xs text-muted-foreground", children: ["Ontvangen op: ", p.mailbox_messages.account_email] }))] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: date ? new Date(date).toLocaleDateString("nl-NL") : "-" }), _jsx(TableCell, { children: _jsxs("div", { className: "flex flex-wrap gap-1", children: [p.readiness === "klaar" ? (_jsx(Badge, { variant: "outline", className: "border-emerald-300 bg-emerald-50 text-emerald-800", children: "Klaar om te bevestigen" })) : (_jsx(Badge, { variant: "secondary", children: "Controleren" })), p.duplicate && _jsx(Badge, { variant: "outline", className: "border-amber-300 bg-amber-50 text-amber-800", children: "Mogelijk dubbel" })] }) })] }, p.id));
315
- }) })] }), _jsx(TablePagination, { total: filteredItems.length, page: currentPage, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize })] }) })) }), _jsx(Dialog, { open: !!activeId, onOpenChange: (o) => { if (!o) {
344
+ // Oudste open voorstel openen (knop "Volgende controleren" op mobiel).
345
+ const openOldest = async () => {
346
+ try {
347
+ const res = await fetchList({ data: { statuses: ["pending"], sort: "asc", page: 1, pageSize: 10, tz: browserTz() } });
348
+ const first = Array.isArray(res?.items) ? res.items[0] : null;
349
+ if (first?.id)
350
+ setActiveId(first.id);
351
+ else
352
+ toast.info("Er zijn geen bestellingen meer om te controleren.");
353
+ }
354
+ catch {
355
+ toast.error("Openen lukt niet. Probeer het opnieuw.");
356
+ }
357
+ };
358
+ useListHotkeys({
359
+ disabled: !!activeId || helpOpen || filtersOpen,
360
+ onSearch: () => searchRef.current?.focus(),
361
+ onNext: () => setCursor((c) => (items.length ? Math.min((c ?? -1) + 1, items.length - 1) : null)),
362
+ onPrev: () => setCursor((c) => (items.length ? Math.max((c ?? 1) - 1, 0) : null)),
363
+ onOpen: cursor != null && items[cursor] ? () => setActiveId(items[cursor].id) : undefined,
364
+ onEscape: hasActiveFilters ? clearFilters : undefined,
365
+ onHelp: () => setHelpOpen(true),
366
+ });
367
+ const periodLabel = { today: "Vandaag", "7d": "Laatste 7 dagen", "30d": "Laatste 30 dagen" };
368
+ const chips = [
369
+ ...(period !== "all" ? [{ key: "period", label: periodLabel[period] ?? period, icon: _jsx(CalendarClock, {}), onRemove: () => setPeriod("all") }] : []),
370
+ ...(listFilter === "klant_onbekend" ? [{ key: "klant", label: "Klant onbekend", icon: _jsx(UserX, {}), onRemove: () => setListFilter(null) }] : []),
371
+ ];
372
+ const activeKpi = listFilter === "klant_onbekend" ? "customer_unknown" : period === "today" ? "today" : null;
373
+ const columns = [
374
+ {
375
+ key: "klant", header: "Klant", width: 220,
376
+ cell: (p) => {
377
+ const name = customerNameFor(p);
378
+ return (_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(InitialsAvatar, { name: name }), _jsx(CellText, { primary: name, secondary: p.customer_name ? undefined : "Klant nog niet herkend", strong: true })] }));
379
+ },
380
+ },
381
+ {
382
+ key: "bericht", header: "Bericht", width: 320,
383
+ cell: (p) => _jsx(CellText, { primary: p.mailbox_messages?.subject || "(geen onderwerp)", secondary: p.mailbox_messages?.preview || "Geen voorbeeldtekst" }),
384
+ },
385
+ {
386
+ key: "ontvangen_op", header: "Mailadres", width: 180,
387
+ cell: (p) => _jsx(CellText, { primary: p.mailbox_messages?.account_email || "–" }),
388
+ },
389
+ {
390
+ key: "datum", header: "Ontvangen", width: 132, sortable: true,
391
+ cell: (p) => _jsx("span", { className: "block truncate tabular-nums", children: fmtDayTime(p.mailbox_messages?.received_at ?? p.created_at) }),
392
+ },
393
+ {
394
+ key: "status", header: "Status", width: 120,
395
+ cell: (p) => (_jsxs("div", { className: "flex items-center gap-2", children: [p.readiness === "klaar"
396
+ ? _jsx(StatusIcon, { tone: "done", label: "Klaar om te bevestigen" })
397
+ : _jsx(StatusIcon, { tone: "open", label: "Te controleren", detail: "Controleer de gegevens en bevestig de bestelling." }), p.duplicate && _jsx(StatusIcon, { tone: "busy", label: "Mogelijk dubbel", detail: "Deze mail lijkt op een eerdere bestelling." })] })),
398
+ },
399
+ ];
400
+ const pageNotices = outdatedCount > 0 ? [{
401
+ key: "outdated",
402
+ tone: "warning",
403
+ title: `${outdatedCount} ${outdatedCount === 1 ? "bestelling is" : "bestellingen zijn"} met de vorige instelling uitgelezen.`,
404
+ action: (_jsx(Button, { size: "sm", variant: "outline", disabled: agentRefreshMut.isPending, onClick: () => agentRefreshMut.mutate(), children: agentRefreshMut.isPending ? "Bezig…" : "Opnieuw uitlezen" })),
405
+ }] : [];
406
+ return (_jsxs(MailboxPageFrame, { tab: "voorstellen", helpOpen: helpOpen, onHelpOpenChange: setHelpOpen, activeKpi: activeKpi, notices: pageNotices, onKpi: (k) => {
407
+ if (k === "today") {
408
+ setListFilter(null);
409
+ setPeriod("today");
410
+ return true;
411
+ }
412
+ if (k === "customer_unknown") {
413
+ setPeriod("all");
414
+ setListFilter("klant_onbekend");
415
+ return true;
416
+ }
417
+ if (k === "pending") {
418
+ clearFilters();
419
+ return true;
420
+ }
421
+ return false;
422
+ }, children: [_jsx(Toolbar, { onOpenFilters: () => setFiltersOpen(true), filterCount: chips.length, chips: _jsx(FilterChips, { chips: chips, onClearAll: clearFilters }), children: _jsx(SearchInput, { ref: searchRef, value: search, onChange: setSearch, placeholder: "Zoek op klant, afzender of onderwerp" }) }), _jsxs(FilterPanel, { open: filtersOpen, onOpenChange: setFiltersOpen, onClear: clearFilters, children: [_jsx(FilterOptions, { label: "Periode", value: period, options: PERIOD_OPTIONS, onChange: (v) => setPeriod(v) }), _jsx(FilterOptions, { label: "Klant", value: listFilter ?? "all", options: [{ value: "all", label: "Alle klanten" }, { value: "klant_onbekend", label: "Klant onbekend" }], onChange: (v) => setListFilter(v === "klant_onbekend" ? "klant_onbekend" : null) })] }), isLoading && !data ? (_jsx(TableSkeleton, { columns: [220, 320, 180, 132, 120] })) : listError && !data ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: _jsx(EmptyState, { icon: _jsx(Inbox, { className: "h-6 w-6" }), title: "De lijst kon niet worden geladen", text: "Je bestellingen zijn er nog; ze worden alleen nu niet getoond.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => void refetchList(), children: "Opnieuw proberen" }) }) })) : total === 0 ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: hasActiveFilters ? (_jsx(EmptyState, { icon: _jsx(Search, { className: "h-6 w-6" }), title: "Niets gevonden", text: "Er zijn geen bestellingen die bij je zoekopdracht of filters passen.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: clearFilters, children: "Filters wissen" }) })) : (_jsx(EmptyState, { icon: _jsx(Inbox, { className: "h-6 w-6" }), title: "Geen bestellingen om te controleren", text: "Nieuwe bestellingen uit je mailbox verschijnen hier vanzelf." })) })) : (_jsx(DataTable, { rows: items, columns: columns, rowKey: (p) => p.id, activeIndex: cursor, onRowClick: (p, i) => { setCursor(i); setActiveId(p.id); }, sort: { key: "datum", dir: sortOrder === "date-asc" ? "asc" : "desc" }, onSortChange: () => setSortOrder(sortOrder === "date-asc" ? "date-desc" : "date-asc"), mobileCard: (p) => {
423
+ const d = p.mailbox_messages?.received_at ?? p.created_at;
424
+ return (_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "truncate font-semibold", children: customerNameFor(p) }), p.readiness === "klaar"
425
+ ? _jsx(StatusIcon, { tone: "done", label: "Klaar om te bevestigen" })
426
+ : _jsx(StatusIcon, { tone: "open", label: "Te controleren" })] }), _jsx("div", { className: "truncate text-muted-foreground", children: p.mailbox_messages?.subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs tabular-nums text-muted-foreground", children: [fmtDayTime(d), p.duplicate ? " · mogelijk dubbel" : ""] })] }));
427
+ }, footer: _jsx(TablePagination, { total: total, page: currentPage, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize }) })), _jsx("span", { className: "sr-only", "aria-live": "polite", children: dataUpdatedAt ? `${total} bestellingen` : "" }), _jsx(MobilePrimaryAction, { label: "Volgende controleren", icon: _jsx(ClipboardCheck, {}), onClick: () => void openOldest(), hidden: !!activeId || total === 0 }), _jsx(Dialog, { open: !!activeId, onOpenChange: (o) => { if (!o) {
316
428
  setActiveId(null);
317
429
  setReparseError(null);
318
- } }, children: _jsx(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] p-0 flex flex-col overflow-hidden", children: !active ? (_jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 text-sm text-muted-foreground", children: [_jsx(DialogTitle, { className: "text-base", children: "Bestelling" }), detailLoading ? "Bestelling laden…" : "Bestelling niet gevonden."] })) : (_jsxs(_Fragment, { children: [_jsxs(DialogHeader, { className: "px-6 pt-6 pb-3 shrink-0 border-b", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsx(DialogTitle, { children: customerNameFor(activeSummary ?? active) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "mr-8", disabled: reparseMutation.isPending, onClick: () => reparseMutation.mutate(active.id), children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${reparseMutation.isPending ? "animate-spin" : ""}` }), reparseMutation.isPending ? "Analyseren…" : "Mail opnieuw analyseren"] })] }), reparseError && (_jsxs("div", { className: "mt-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "h-4 w-4 mt-0.5 shrink-0" }), _jsx("span", { children: reparseError })] }))] }), _jsx("div", { className: "flex-1 min-h-0 overflow-hidden p-6", children: _jsx(ProposalEditor, { proposal: active, template: template, customers: customers, products: products, allowBackorder: allowBackorder, plantTypes: plantTypes, suppliers: suppliers, carriers: carriers, pickupLocations: pickupLocations, onApprove: async (payload) => {
430
+ } }, children: _jsx(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] p-0 flex flex-col overflow-hidden", children: !active ? (_jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 text-sm text-muted-foreground", children: [_jsx(DialogTitle, { className: "text-base", children: "Bestelling" }), detailLoading ? "Bestelling laden…" : "Bestelling niet gevonden."] })) : (_jsxs(_Fragment, { children: [_jsxs(DialogHeader, { className: "px-6 pt-6 pb-3 shrink-0 border-b", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsx(DialogTitle, { children: customerNameFor(activeSummary ?? active) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "mr-8", disabled: reparseMutation.isPending, onClick: () => reparseMutation.mutate(active.id), children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${reparseMutation.isPending ? "animate-spin" : ""}` }), reparseMutation.isPending ? "Uitlezen…" : "Mail opnieuw uitlezen"] })] }), reparseError && (_jsxs("div", { className: "mt-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "h-4 w-4 mt-0.5 shrink-0" }), _jsx("span", { children: reparseError })] }))] }), _jsx("div", { className: "flex-1 min-h-0 overflow-hidden p-6", children: _jsx(ProposalEditor, { proposal: active, template: template, customers: customers, products: products, allowBackorder: allowBackorder, plantTypes: plantTypes, suppliers: suppliers, carriers: carriers, pickupLocations: pickupLocations, onApprove: async (payload) => {
319
431
  try {
320
432
  const res = await fetchApprove({ data: payload });
321
433
  if (res?.ok === false) {
@@ -323,35 +435,40 @@ export function MailboxProposalsPage() {
323
435
  return res;
324
436
  }
325
437
  if (res?.alreadyApproved) {
326
- toast.info(`Er bestaat al een order voor dit voorstel (${res.orderNumber}).`);
438
+ toast.info(`Er bestaat al een order voor deze bestelling (${res.orderNumber}).`, openOrderAction(res.orderId));
327
439
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
440
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
328
441
  setActiveId(null);
329
442
  return res;
330
443
  }
331
444
  const shortages = Array.isArray(res?.shortages) ? res.shortages : [];
332
- toast.success("Bestelling aangemaakt — karindeling wordt automatisch aangemaakt in de Verkoop-module");
445
+ toast.success(`Order ${res?.orderNumber ?? ""} aangemaakt.`.replace(" ", " "), openOrderAction(res?.orderId));
333
446
  if (shortages.length > 0) {
334
447
  toast.warning(`Tekorten (levering zonder voorraad staat aan): ${shortages
335
448
  .map((s) => `${s.product ?? s.productId}: besteld ${s.wanted}, beschikbaar ${s.available}, tekort ${s.missing}`)
336
449
  .join(" · ")}`);
337
450
  }
338
451
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
452
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
339
453
  setActiveId(null);
340
454
  return res;
341
455
  }
342
456
  catch (e) {
343
- toast.error(e?.message ?? "Goedkeuren mislukt");
344
- return { ok: false, reason: "other", message: e?.message ?? "Goedkeuren mislukt" };
457
+ console.warn("[mailbox] bevestigen", e);
458
+ toast.error("Bevestigen lukt niet. Probeer het opnieuw.");
459
+ return { ok: false, reason: "other", message: "Bevestigen lukt niet. Probeer het opnieuw." };
345
460
  }
346
461
  }, onReject: async (reason) => {
347
462
  try {
348
463
  await fetchReject({ data: { proposalId: active.id, reason } });
349
- toast.success("Voorstel afgewezen");
464
+ toast.success("Bestelling afgewezen");
350
465
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
466
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
351
467
  setActiveId(null);
352
468
  }
353
469
  catch (e) {
354
- toast.error(e?.message ?? "Afwijzen mislukt");
470
+ console.warn("[mailbox] afwijzen", e);
471
+ toast.error("Afwijzen lukt niet. Probeer het opnieuw.");
355
472
  }
356
473
  } }, active.id) })] })) }) })] }));
357
474
  }
@@ -678,11 +795,11 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
678
795
  return null;
679
796
  }
680
797
  if (!deliveryLocationVal) {
681
- toast.error("Selecteer een afleverlocatie.");
798
+ toast.error("Kies een afleverlocatie.");
682
799
  return null;
683
800
  }
684
801
  if (!pickupLocationVal) {
685
- toast.error("Selecteer een afhaallocatie (eigen locatie / tuin).");
802
+ toast.error("Kies een afhaallocatie (eigen locatie of tuin).");
686
803
  return null;
687
804
  }
688
805
  const validLines = lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0);
@@ -773,7 +890,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
773
890
  setSubmitting(false);
774
891
  }
775
892
  };
776
- return (_jsxs("div", { className: "grid grid-cols-2 gap-4 h-full min-h-0", children: [_jsx(Card, { className: "flex flex-col min-h-0 overflow-hidden", children: _jsxs(CardContent, { className: "flex-1 min-h-0 p-4 flex flex-col gap-2", children: [msg.account_email && (_jsxs("div", { className: "shrink-0 text-xs text-muted-foreground", children: ["Ontvangen op: ", msg.account_email] })), proposal.parsed_payload?.reason === "geen_ai_agent" && (_jsxs("div", { className: "shrink-0 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Automatisch uitlezen staat uit: koppel een AI-agent bij Instellingen \u203A AI-Authenticatie.", " ", _jsx("a", { href: AI_AUTH_PATH, className: "underline font-medium", children: "Naar AI-Authenticatie" })] })), _jsx(EmailPreview, { subject: msg.subject, fromName: msg.from_name, fromEmail: msg.from_email, receivedAt: msg.received_at, to: Array.isArray(msg.to_recipients) ? msg.to_recipients : [], cc: Array.isArray(msg.cc_recipients) ? msg.cc_recipients : [], bodyHtml: msg.body_html, bodyText: msg.body_text, attachments: Array.isArray(msg.attachments) ? msg.attachments : [], hasAttachments: msg.has_attachments === true })] }) }), _jsxs(Card, { className: "flex flex-col min-h-0 overflow-hidden", children: [_jsx(CardHeader, { className: "shrink-0", children: _jsx(CardTitle, { className: "text-base", children: "Bestelvoorstel" }) }), _jsxs(CardContent, { className: "space-y-4 flex-1 min-h-0 overflow-y-auto overscroll-contain", children: [(() => {
893
+ return (_jsxs("div", { className: "grid grid-cols-2 gap-4 h-full min-h-0", children: [_jsx(Card, { className: "flex flex-col min-h-0 overflow-hidden", children: _jsxs(CardContent, { className: "flex-1 min-h-0 p-4 flex flex-col gap-2", children: [msg.account_email && (_jsxs("div", { className: "shrink-0 text-xs text-muted-foreground", children: ["Ontvangen op: ", msg.account_email] })), proposal.parsed_payload?.reason === "geen_ai_agent" && (_jsxs("div", { className: "shrink-0 rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: ["Automatisch uitlezen staat uit. Vul de gegevens zelf in; een beheerder kan dit aanzetten bij Instellingen.", " ", _jsx("a", { href: AI_AUTH_PATH, className: "underline font-medium", children: "Naar Instellingen" })] })), _jsx(EmailPreview, { subject: msg.subject, fromName: msg.from_name, fromEmail: msg.from_email, receivedAt: msg.received_at, to: Array.isArray(msg.to_recipients) ? msg.to_recipients : [], cc: Array.isArray(msg.cc_recipients) ? msg.cc_recipients : [], bodyHtml: msg.body_html, bodyText: msg.body_text, attachments: Array.isArray(msg.attachments) ? msg.attachments : [], hasAttachments: msg.has_attachments === true })] }) }), _jsxs(Card, { className: "flex flex-col min-h-0 overflow-hidden", children: [_jsx(CardHeader, { className: "shrink-0", children: _jsx(CardTitle, { className: "text-base", children: "Bestelvoorstel" }) }), _jsxs(CardContent, { className: "space-y-4 flex-1 min-h-0 overflow-y-auto overscroll-contain", children: [(() => {
777
894
  // Klantveld staat altijd bovenaan, ook als het template (nog) leeg is.
778
895
  const customerField = visibleFields.find((f) => f.key === "customer") ?? {
779
896
  id: "customer",
@@ -800,9 +917,9 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
800
917
  const metaFor = (key) => fieldMeta[key] ?? (key === "customer" && proposal.matched_customer_id
801
918
  ? { confidence: payload.auto_match_warning ? 0.6 : 1, source: payload.auto_match_forwarded ? "afgeleid" : "uit mail" }
802
919
  : null);
803
- return (_jsxs(_Fragment, { children: [dup && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: [_jsx("strong", { children: "Mogelijk dubbel:" }), " ", dup.kind === "referentie" ? "dezelfde referentie" : "vrijwel dezelfde regels", " als ", dup.label, "."] })), payload.attachment_unread && (_jsx("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: "Deze mail heeft een bijlage. De bijlage is niet automatisch gelezen; controleer hem zelf." })), (missingList.length > 0 || uncertainList.length > 0) && (_jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [missingList.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs", children: [_jsx("div", { className: "font-medium text-destructive mb-1", children: "Ontbreekt" }), _jsx("ul", { className: "list-disc pl-4 space-y-0.5", children: missingList.map((m) => _jsx("li", { children: m }, m)) })] })), uncertainList.length > 0 && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: [_jsx("div", { className: "font-medium mb-1", children: "Twijfel" }), _jsx("ul", { className: "list-disc pl-4 space-y-0.5", children: uncertainList.map((m) => _jsx("li", { children: m }, m)) })] }))] })), ai && ai.ok === false && (_jsx("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ai.retry_pending
920
+ return (_jsxs(_Fragment, { children: [dup && (_jsxs("div", { className: "rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: [_jsx("strong", { children: "Mogelijk dubbel:" }), " ", dup.kind === "referentie" ? "dezelfde referentie" : "vrijwel dezelfde regels", " als ", dup.label, "."] })), payload.attachment_unread && (_jsx("div", { className: "rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: "Deze mail heeft een bijlage. De bijlage is niet automatisch gelezen; controleer hem zelf." })), (missingList.length > 0 || uncertainList.length > 0) && (_jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [missingList.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs", children: [_jsx("div", { className: "font-medium text-destructive mb-1", children: "Ontbreekt" }), _jsx("ul", { className: "list-disc pl-4 space-y-0.5", children: missingList.map((m) => _jsx("li", { children: m }, m)) })] })), uncertainList.length > 0 && (_jsxs("div", { className: "rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: [_jsx("div", { className: "font-medium mb-1", children: "Twijfel" }), _jsx("ul", { className: "list-disc pl-4 space-y-0.5", children: uncertainList.map((m) => _jsx("li", { children: m }, m)) })] }))] })), ai && ai.ok === false && (_jsx("div", { className: "rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: ai.retry_pending
804
921
  ? "De AI-dienst was even bezet — we proberen deze mail automatisch opnieuw. Je kunt de velden intussen zelf invullen."
805
- : "Deze mail is niet automatisch uitgelezen — vul de velden handmatig in." })), matchWarning && (_jsx("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: matchWarning })), customerField && renderField(customerField), _jsx(ConfBadge, { meta: metaFor("customer"), thresholds: thresholds }), customerNotLocal && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800 flex items-center justify-between gap-2", children: [_jsx("span", { children: "Deze klant bestaat nog niet lokaal. Maak de klant eerst aan." }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => setNewCustomer(customers.find((c) => c.id === customerVal) ?? null), children: "Klant aanmaken" })] })), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Locatie" }), _jsx(SearchableSelect, { value: deliveryLocationVal, placeholder: !customerVal
922
+ : "Deze mail is niet automatisch uitgelezen — vul de velden handmatig in." })), matchWarning && (_jsx("div", { className: "rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: matchWarning })), customerField && renderField(customerField), _jsx(ConfBadge, { meta: metaFor("customer"), thresholds: thresholds }), customerNotLocal && (_jsxs("div", { className: "rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground flex items-center justify-between gap-2", children: [_jsx("span", { children: "Deze klant bestaat nog niet lokaal. Maak de klant eerst aan." }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => setNewCustomer(customers.find((c) => c.id === customerVal) ?? null), children: "Klant aanmaken" })] })), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Locatie" }), _jsx(SearchableSelect, { value: deliveryLocationVal, placeholder: !customerVal
806
923
  ? "Selecteer eerst een klant"
807
924
  : deliveryLocations.length
808
925
  ? "Kies locatie..."
@@ -824,7 +941,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
824
941
  : "Kies vervoerder...", clearLabel: "\u2014 Geen \u2014", disabled: !deliveryLocationVal, options: carriers.map((c) => ({
825
942
  value: c.id,
826
943
  label: c.name + (c.is_default ? " (standaard)" : ""),
827
- })), onChange: (v) => setVal("carrier", v) }), selectedLocation?.carrier_id && carrierVal !== selectedLocation.carrier_id && (_jsx("div", { className: "text-xs text-muted-foreground", children: "Wijkt af van standaard vervoerder van deze locatie \u2014 geldt alleen voor deze bestelling." }))] }), _jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "flex items-center gap-2", children: ["Bezorgdatum ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { type: "date", value: deliveryVal ?? "", onChange: (e) => setVal("delivery_date", e.target.value || null) }), _jsx(ConfBadge, { meta: metaFor("delivery_date"), thresholds: thresholds }), !carrierVal || !deliveryLocationVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Kies een afleverlocatie en vervoerder; daarna controleren we of deze datum haalbaar is." })) : !deliveryVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "De mail noemt geen datum. Kies zelf een datum." })) : previewLoading ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Haalbaarheid controleren\u2026" })) : previewError ? (_jsxs("div", { className: "text-xs text-destructive", children: ["De haalbaarheid kon niet worden gecontroleerd: ", previewError] })) : dateFeasible ? (_jsxs("div", { className: "rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs text-emerald-800", children: [fmtDate(deliveryVal), " is haalbaar.", preview?.chosen?.departureAt ? ` Ophaalmoment: ${fmtDateTime(preview.chosen.departureAt)}` : "", preview?.chosen?.signupDeadline ? ` (aanmelden tot ${fmtDateTime(preview.chosen.signupDeadline)}).` : "."] })) : (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs space-y-2", children: [_jsxs("div", { className: "text-destructive", children: [fmtDate(deliveryVal), " is niet haalbaar", preview?.reason ? `: ${preview.reason}` : "."] }), preview?.firstFeasibleDeliveryDate && preview.firstFeasibleDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", preview.firstFeasibleDeliveryDate), children: ["Gebruik ", fmtDate(preview.firstFeasibleDeliveryDate)] })), preview?.ok === true && previewKind === "late_signup" && (_jsx(LateSignupChoice, { open: lateOpen, setOpen: setLateOpen, reason: lateReason, setReason: setLateReason }))] }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Referentie klant" }), _jsx(Input, { value: typeof values["reference"] === "string" ? values["reference"] : "", placeholder: "Bijv. ordernummer van de klant", onChange: (e) => setVal("reference", e.target.value) }), _jsx(ConfBadge, { meta: metaFor("reference"), thresholds: thresholds })] }), otherFields.map(renderField)] }));
944
+ })), onChange: (v) => setVal("carrier", v) }), selectedLocation?.carrier_id && carrierVal !== selectedLocation.carrier_id && (_jsx("div", { className: "text-xs text-muted-foreground", children: "Wijkt af van standaard vervoerder van deze locatie \u2014 geldt alleen voor deze bestelling." }))] }), _jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "flex items-center gap-2", children: ["Bezorgdatum ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { type: "date", value: deliveryVal ?? "", onChange: (e) => setVal("delivery_date", e.target.value || null) }), _jsx(ConfBadge, { meta: metaFor("delivery_date"), thresholds: thresholds }), !carrierVal || !deliveryLocationVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Kies een afleverlocatie en vervoerder; daarna controleren we of deze datum haalbaar is." })) : !deliveryVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "De mail noemt geen datum. Kies zelf een datum." })) : previewLoading ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Haalbaarheid controleren\u2026" })) : previewError ? (_jsxs("div", { className: "text-xs text-destructive", children: ["De haalbaarheid kon niet worden gecontroleerd: ", previewError] })) : dateFeasible ? (_jsxs("div", { className: "rounded-md border border-primary/30 bg-primary/10 px-3 py-2 text-xs text-primary", children: [fmtDate(deliveryVal), " is haalbaar.", preview?.chosen?.departureAt ? ` Ophaalmoment: ${fmtDateTime(preview.chosen.departureAt)}` : "", preview?.chosen?.signupDeadline ? ` (aanmelden tot ${fmtDateTime(preview.chosen.signupDeadline)}).` : "."] })) : (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs space-y-2", children: [_jsxs("div", { className: "text-destructive", children: [fmtDate(deliveryVal), " is niet haalbaar", preview?.reason ? `: ${preview.reason}` : "."] }), preview?.firstFeasibleDeliveryDate && preview.firstFeasibleDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", preview.firstFeasibleDeliveryDate), children: ["Gebruik ", fmtDate(preview.firstFeasibleDeliveryDate)] })), preview?.ok === true && previewKind === "late_signup" && (_jsx(LateSignupChoice, { open: lateOpen, setOpen: setLateOpen, reason: lateReason, setReason: setLateReason }))] }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Referentie klant" }), _jsx(Input, { value: typeof values["reference"] === "string" ? values["reference"] : "", placeholder: "Bijv. ordernummer van de klant", onChange: (e) => setVal("reference", e.target.value) }), _jsx(ConfBadge, { meta: metaFor("reference"), thresholds: thresholds })] }), otherFields.map(renderField)] }));
828
945
  })(), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bestelregels" }), _jsxs("div", { className: "grid grid-cols-12 gap-2 text-xs text-muted-foreground px-1", children: [_jsx("div", { className: "col-span-4", children: "Product" }), _jsx("div", { className: "col-span-3", children: "Belading" }), _jsx("div", { className: "col-span-2", children: "Fusten" }), _jsx("div", { className: "col-span-2", children: "Prijs per stuk" }), _jsx("div", { className: "col-span-1 text-center", children: stickerAvailable ? "Sticker" : "" })] }), lines.map((line, idx) => {
829
946
  const product = line.product_id ? products.find((p) => p.id === line.product_id) : null;
830
947
  const stdBelading = defaultBelading(product);
@@ -924,7 +1041,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
924
1041
  const pl = toPlants(next[idx].qty_in_mail, kind, effBelading);
925
1042
  next[idx] = { ...next[idx], unit_kind: kind, quantity: pl ?? next[idx].quantity };
926
1043
  setLines(next);
927
- }, children: [_jsx(SelectTrigger, { className: "h-8", children: _jsx(SelectValue, { placeholder: "Kies eenheid" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none", disabled: true, children: "Kies eenheid" }), ["fust", "laag", "kar", "stuk"].map((k) => (_jsx(SelectItem, { value: k, children: UNIT_KIND_LABEL[k] }, k)))] })] })] })), (line.warnings ?? []).filter((w) => !w.startsWith("Onbekend artikel") && !w.startsWith("Eenheid onbekend")).map((w) => (_jsx("div", { className: "mt-1 text-[11px] text-amber-700", children: w }, w)))] }), _jsx(Input, { className: "col-span-2", type: "number", min: 1, step: 1, inputMode: "numeric", onKeyDown: blockDecimal, value: cratesDisplay, onChange: (e) => {
1044
+ }, children: [_jsx(SelectTrigger, { className: "h-8", children: _jsx(SelectValue, { placeholder: "Kies eenheid" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none", disabled: true, children: "Kies eenheid" }), ["fust", "laag", "kar", "stuk"].map((k) => (_jsx(SelectItem, { value: k, children: UNIT_KIND_LABEL[k] }, k)))] })] })] })), (line.warnings ?? []).filter((w) => !w.startsWith("Onbekend artikel") && !w.startsWith("Eenheid onbekend")).map((w) => (_jsx("div", { className: "mt-1 text-[11px] text-warning-soft-foreground", children: w }, w)))] }), _jsx(Input, { className: "col-span-2", type: "number", min: 1, step: 1, inputMode: "numeric", onKeyDown: blockDecimal, value: cratesDisplay, onChange: (e) => {
928
1045
  const next = [...lines];
929
1046
  const raw = e.target.value.replace(/[.,]/g, "");
930
1047
  const cr = raw ? Math.max(1, Math.floor(Number(raw))) : null;
@@ -938,7 +1055,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
938
1055
  } }), (() => {
939
1056
  const ag = line.product_id ? agreements[line.product_id] : null;
940
1057
  const src = line.price_source;
941
- return (_jsxs("div", { className: "text-[11px] text-muted-foreground space-y-0.5", children: [line.unit_price === null || line.unit_price === undefined ? (_jsx("div", { className: "text-amber-700", children: "Geen prijs bekend" })) : (_jsxs("div", { children: [src === "uit mail" ? "Uit mail" : src === "afspraak" ? "Prijsafspraak" : "Zelf ingevuld", line.mail_price && line.price_per === "eenheid" ? ` (${euro(line.mail_price)} per ${UNIT_KIND_LABEL[(line.unit_kind ?? "fust")]})` : "", line.quantity ? ` · ${euro(lineTotal(line.quantity, line.unit_price))}` : ""] })), ag && Math.abs(ag.net_price - Number(line.unit_price ?? -1)) > 0.0001 && (_jsxs("div", { className: "text-amber-700", children: ["Afspraak: ", euro(ag.net_price), " ", _jsx("button", { type: "button", className: "underline", onClick: () => {
1058
+ return (_jsxs("div", { className: "text-[11px] text-muted-foreground space-y-0.5", children: [line.unit_price === null || line.unit_price === undefined ? (_jsx("div", { className: "text-warning-soft-foreground", children: "Geen prijs bekend" })) : (_jsxs("div", { children: [src === "uit mail" ? "Uit mail" : src === "afspraak" ? "Prijsafspraak" : "Zelf ingevuld", line.mail_price && line.price_per === "eenheid" ? ` (${euro(line.mail_price)} per ${UNIT_KIND_LABEL[(line.unit_kind ?? "fust")]})` : "", line.quantity ? ` · ${euro(lineTotal(line.quantity, line.unit_price))}` : ""] })), ag && Math.abs(ag.net_price - Number(line.unit_price ?? -1)) > 0.0001 && (_jsxs("div", { className: "text-warning-soft-foreground", children: ["Afspraak: ", euro(ag.net_price), " ", _jsx("button", { type: "button", className: "underline", onClick: () => {
942
1059
  const next = [...lines];
943
1060
  next[idx] = { ...next[idx], unit_price: ag.net_price, price_source: "afspraak" };
944
1061
  setLines(next);
@@ -948,13 +1065,13 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
948
1065
  next[idx] = { ...next[idx], needs_sticker: e.target.checked };
949
1066
  setLines(next);
950
1067
  } }), "Sticker"] })), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => setLines(lines.filter((_, i) => i !== idx)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }, idx));
951
- }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setLines([...lines, { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks", belading: null }]), children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Regel toevoegen"] }), _jsxs("div", { className: "flex justify-between border-t pt-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal" }), _jsx("span", { className: "font-medium", children: euro(orderTotal) })] }), linesWithoutPrice > 0 && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Geen prijs bekend voor ", linesWithoutPrice, " regel(s). Vul een prijs in; zonder prijs kan de bestelling niet worden aangemaakt."] })), zeroPriceLines > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive space-y-1", children: [_jsxs("div", { children: [zeroPriceLines, " regel(s) met prijs \u20AC 0. Vul de prijs in, of bevestig dat \u20AC 0 klopt."] }), _jsxs("label", { className: "flex items-center gap-2 text-foreground", children: [_jsx("input", { type: "checkbox", className: "h-4 w-4 accent-primary", checked: zeroConfirmed, onChange: (e) => setZeroConfirmed(e.target.checked) }), _jsx("span", { children: "Prijs \u20AC 0 klopt" }), "Prijs \u20AC 0 klopt"] })] }))] }), refusal && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm space-y-2", children: [_jsx("div", { className: "font-medium text-destructive", children: "Bestelling niet aangemaakt \u2014 het voorstel blijft open." }), _jsx("div", { className: "text-destructive", children: refusal.message }), _jsx("div", { className: "flex flex-wrap gap-2", children: refusal.nextDeliveryDate && refusal.nextDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", refusal.nextDeliveryDate), children: ["Volgend haalbaar moment: ", fmtDate(refusal.nextDeliveryDate)] })) }), refusal.kind === "late_signup" && (_jsx(LateSignupChoice, { open: lateOpen, setOpen: setLateOpen, reason: lateReason, setReason: setLateReason }))] })), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", onClick: handleApprove, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Controleren & bevestigen"] }), _jsxs(Button, { variant: "destructive", onClick: () => onReject(notesVal || ""), children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Afwijzen"] })] }), _jsx(Dialog, { open: confirmOpen, onOpenChange: (o) => {
1068
+ }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setLines([...lines, { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks", belading: null }]), children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Regel toevoegen"] }), _jsxs("div", { className: "flex justify-between border-t pt-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal" }), _jsx("span", { className: "font-medium", children: euro(orderTotal) })] }), linesWithoutPrice > 0 && (_jsxs("div", { className: "rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: ["Geen prijs bekend voor ", linesWithoutPrice, " regel(s). Vul een prijs in; zonder prijs kan de bestelling niet worden aangemaakt."] })), zeroPriceLines > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive space-y-1", children: [_jsxs("div", { children: [zeroPriceLines, " regel(s) met prijs \u20AC 0. Vul de prijs in, of bevestig dat \u20AC 0 klopt."] }), _jsxs("label", { className: "flex items-center gap-2 text-foreground", children: [_jsx("input", { type: "checkbox", className: "h-4 w-4 accent-primary", checked: zeroConfirmed, onChange: (e) => setZeroConfirmed(e.target.checked) }), _jsx("span", { children: "Prijs \u20AC 0 klopt" }), "Prijs \u20AC 0 klopt"] })] }))] }), refusal && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm space-y-2", children: [_jsx("div", { className: "font-medium text-destructive", children: "Bestelling niet aangemaakt \u2014 het voorstel blijft open." }), _jsx("div", { className: "text-destructive", children: refusal.message }), _jsx("div", { className: "flex flex-wrap gap-2", children: refusal.nextDeliveryDate && refusal.nextDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", refusal.nextDeliveryDate), children: ["Volgend haalbaar moment: ", fmtDate(refusal.nextDeliveryDate)] })) }), refusal.kind === "late_signup" && (_jsx(LateSignupChoice, { open: lateOpen, setOpen: setLateOpen, reason: lateReason, setReason: setLateReason }))] })), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", onClick: handleApprove, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Controleren & bevestigen"] }), _jsxs(Button, { variant: "destructive", onClick: () => onReject(notesVal || ""), children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Afwijzen"] })] }), _jsx(Dialog, { open: confirmOpen, onOpenChange: (o) => {
952
1069
  if (submitting)
953
1070
  return;
954
1071
  setConfirmOpen(o);
955
1072
  if (!o)
956
1073
  setCheckedOk(false);
957
- }, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Bestelling bevestigen" }) }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "De bestelling wordt pas aangemaakt in Bestellingen nadat je hem hebt gecontroleerd en bevestigd." }), _jsxs("div", { className: "rounded-md border p-3 space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Klant:" }), " ", customers.find((c) => c.id === customerVal)?.company_name ?? "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bezorgdatum:" }), " ", deliveryVal || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bestelregels:" }), " ", lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0).length] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Referentie:" }), " ", values["reference"] || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal:" }), " ", euro(orderTotal)] })] }), lateOverride && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 p-2 text-amber-800", children: ["Je maakt de order toch aan na de aanmeldtijd. Reden: ", lateReason.trim()] })), _jsxs("label", { className: "flex items-start gap-2 cursor-pointer", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: checkedOk, onChange: (e) => setCheckedOk(e.target.checked) }), _jsx("span", { children: "Ik heb deze bestelling gecontroleerd en de gegevens kloppen." })] })] }), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", disabled: !checkedOk || submitting, onClick: handleConfirm, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), submitting ? "Bezig…" : "Bevestigen en bestelling aanmaken"] }), _jsx(Button, { variant: "outline", disabled: submitting, onClick: () => setConfirmOpen(false), children: "Annuleren" })] })] }) }), _jsx(NewCustomerDialog, { candidate: newCustomer, onClose: () => setNewCustomer(null), onCreated: (id) => { setNewCustomer(null); setVal("customer", id); } })] })] })] }));
1074
+ }, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Bestelling bevestigen" }) }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "De bestelling wordt pas aangemaakt in Bestellingen nadat je hem hebt gecontroleerd en bevestigd." }), _jsxs("div", { className: "rounded-md border p-3 space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Klant:" }), " ", customers.find((c) => c.id === customerVal)?.company_name ?? "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bezorgdatum:" }), " ", deliveryVal || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bestelregels:" }), " ", lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0).length] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Referentie:" }), " ", values["reference"] || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal:" }), " ", euro(orderTotal)] })] }), lateOverride && (_jsxs("div", { className: "rounded-md border border-warning/30 bg-warning-soft p-2 text-warning-soft-foreground", children: ["Je maakt de order toch aan na de aanmeldtijd. Reden: ", lateReason.trim()] })), _jsxs("label", { className: "flex items-start gap-2 cursor-pointer", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: checkedOk, onChange: (e) => setCheckedOk(e.target.checked) }), _jsx("span", { children: "Ik heb deze bestelling gecontroleerd en de gegevens kloppen." })] })] }), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", disabled: !checkedOk || submitting, onClick: handleConfirm, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), submitting ? "Bezig…" : "Bevestigen en bestelling aanmaken"] }), _jsx(Button, { variant: "outline", disabled: submitting, onClick: () => setConfirmOpen(false), children: "Annuleren" })] })] }) }), _jsx(NewCustomerDialog, { candidate: newCustomer, onClose: () => setNewCustomer(null), onCreated: (id) => { setNewCustomer(null); setVal("customer", id); } })] })] })] }));
958
1075
  }
959
1076
  /** Verplichte bevestiging: externe klant eerst als lokale klant aanmaken. */
960
1077
  function NewCustomerDialog({ candidate, onClose, onCreated, }) {
@@ -1009,7 +1126,8 @@ function NewCustomerDialog({ candidate, onClose, onCreated, }) {
1009
1126
  onCreated(res.id);
1010
1127
  }
1011
1128
  catch (e) {
1012
- toast.error(e?.message ?? "Aanmaken mislukt.");
1129
+ console.warn("[mailbox] klant aanmaken", e);
1130
+ toast.error("Klant aanmaken lukt niet. Probeer het opnieuw.");
1013
1131
  }
1014
1132
  finally {
1015
1133
  setBusy(false);
@@ -1019,8 +1137,8 @@ function NewCustomerDialog({ candidate, onClose, onCreated, }) {
1019
1137
  onClose(); }, children: _jsxs(DialogContent, { className: "max-w-xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Nieuwe klant aanmaken" }) }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Deze klant bestaat alleen in ", (candidate?.providers ?? []).map(providerLabel).join(" + ") || "een externe bron", ". Controleer de gegevens en maak de klant aan voordat je de bestelling verwerkt."] }), _jsxs("div", { className: "grid grid-cols-2 gap-3 py-2", children: [field("companyName", "Bedrijfsnaam", true), field("email", "E-mail"), field("street", "Straat"), field("houseNumber", "Huisnummer"), field("postalCode", "Postcode"), field("city", "Plaats"), field("country", "Land"), field("gln", "GLN"), field("phone", "Telefoon")] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { className: "flex-1", disabled: busy, onClick: submit, children: busy ? "Bezig…" : "Klant aanmaken en koppelen" }), _jsx(Button, { variant: "outline", disabled: busy, onClick: onClose, children: "Annuleren" })] })] }) }));
1020
1138
  }
1021
1139
  function DynamicField({ field, value, meta, onChange, customers, products, plantTypes, suppliers, onSelectCustomer, }) {
1022
- const reviewClass = meta?.needs_review ? "border-yellow-500 ring-1 ring-yellow-500/40 rounded-md p-2" : "";
1023
- const label = (_jsxs(Label, { className: "flex items-center gap-2", children: [field.label, field.required && _jsx("span", { className: "text-destructive", children: "*" }), meta?.needs_review && (_jsx(Badge, { variant: "outline", className: "text-xs border-yellow-500 text-yellow-700", children: "Controleren" })), typeof meta?.ai_confidence === "number" && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [Math.round(meta.ai_confidence * 100), "%"] }))] }));
1140
+ const reviewClass = meta?.needs_review ? "border-warning/30 ring-1 ring-warning/40 rounded-md p-2" : "";
1141
+ const label = (_jsxs(Label, { className: "flex items-center gap-2", children: [field.label, field.required && _jsx("span", { className: "text-destructive", children: "*" }), meta?.needs_review && (_jsx(StatusIcon, { tone: "busy", label: "Controleren", detail: "Dit veld is niet zeker uit de mail gehaald." })), typeof meta?.ai_confidence === "number" && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [Math.round(meta.ai_confidence * 100), "%"] }))] }));
1024
1142
  const renderControl = () => {
1025
1143
  switch (field.field_type) {
1026
1144
  case "text":
@@ -1065,12 +1183,9 @@ function ConfBadge({ meta, thresholds, }) {
1065
1183
  if (!meta)
1066
1184
  return null;
1067
1185
  const c = Math.max(0, Math.min(1, Number(meta.confidence ?? 0)));
1068
- const cls = c >= thresholds.green
1069
- ? "border-emerald-300 bg-emerald-50 text-emerald-800"
1070
- : c >= thresholds.orange
1071
- ? "border-amber-300 bg-amber-50 text-amber-800"
1072
- : "border-destructive/40 bg-destructive/5 text-destructive";
1073
- return (_jsxs("span", { className: `inline-flex items-center rounded border px-1.5 py-0.5 text-[11px] ${cls}`, children: [Math.round(c * 100), "% \u00B7 ", meta.source] }));
1186
+ const tone = c >= thresholds.green ? "done" : c >= thresholds.orange ? "busy" : "error";
1187
+ const text = c >= thresholds.green ? "Zeker" : c >= thresholds.orange ? "Controleren" : "Onzeker";
1188
+ return (_jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] font-normal text-muted-foreground", children: [_jsx(StatusIcon, { tone: tone, label: text, detail: `${Math.round(c * 100)}% zeker · ${meta.source}` }), text] }));
1074
1189
  }
1075
1190
  function LateSignupChoice({ open, setOpen, reason, setReason }) {
1076
1191
  if (!open) {
@@ -1 +1 @@
1
- {"version":3,"file":"MailboxSkippedPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxSkippedPage.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,kBAAkB,gCAoJjC"}
1
+ {"version":3,"file":"MailboxSkippedPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxSkippedPage.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,kBAAkB,gCAoKjC"}