@flowselections/mailbox-orders 1.0.53 → 1.0.55

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 (78) hide show
  1. package/dist-lib/components/MailboxAuthBanner.d.ts.map +1 -1
  2. package/dist-lib/components/MailboxAuthBanner.js +6 -5
  3. package/dist-lib/components/MailboxPageFrame.d.ts +19 -0
  4. package/dist-lib/components/MailboxPageFrame.d.ts.map +1 -0
  5. package/dist-lib/components/MailboxPageFrame.js +95 -0
  6. package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
  7. package/dist-lib/components/MailboxProcessedPage.js +94 -62
  8. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  9. package/dist-lib/components/MailboxProposalsPage.js +178 -71
  10. package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -1
  11. package/dist-lib/components/MailboxSkippedPage.js +80 -14
  12. package/dist-lib/components/MailboxStatusLine.d.ts +3 -1
  13. package/dist-lib/components/MailboxStatusLine.d.ts.map +1 -1
  14. package/dist-lib/components/MailboxStatusLine.js +31 -8
  15. package/dist-lib/components/fs-ui/DataTable.d.ts +36 -0
  16. package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -0
  17. package/dist-lib/components/fs-ui/DataTable.js +33 -0
  18. package/dist-lib/components/fs-ui/EmptyState.d.ts +9 -0
  19. package/dist-lib/components/fs-ui/EmptyState.d.ts.map +1 -0
  20. package/dist-lib/components/fs-ui/EmptyState.js +5 -0
  21. package/dist-lib/components/fs-ui/FilterChips.d.ts +13 -0
  22. package/dist-lib/components/fs-ui/FilterChips.d.ts.map +1 -0
  23. package/dist-lib/components/fs-ui/FilterChips.js +8 -0
  24. package/dist-lib/components/fs-ui/HoverTip.d.ts +15 -0
  25. package/dist-lib/components/fs-ui/HoverTip.d.ts.map +1 -0
  26. package/dist-lib/components/fs-ui/HoverTip.js +24 -0
  27. package/dist-lib/components/fs-ui/InlineAlert.d.ts +9 -0
  28. package/dist-lib/components/fs-ui/InlineAlert.d.ts.map +1 -0
  29. package/dist-lib/components/fs-ui/InlineAlert.js +14 -0
  30. package/dist-lib/components/fs-ui/KpiRow.d.ts +6 -0
  31. package/dist-lib/components/fs-ui/KpiRow.d.ts.map +1 -0
  32. package/dist-lib/components/fs-ui/KpiRow.js +7 -0
  33. package/dist-lib/components/fs-ui/KpiTile.d.ts +21 -0
  34. package/dist-lib/components/fs-ui/KpiTile.d.ts.map +1 -0
  35. package/dist-lib/components/fs-ui/KpiTile.js +40 -0
  36. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts +10 -0
  37. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts.map +1 -0
  38. package/dist-lib/components/fs-ui/MobilePrimaryAction.js +7 -0
  39. package/dist-lib/components/fs-ui/PageHeader.d.ts +8 -0
  40. package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -0
  41. package/dist-lib/components/fs-ui/PageHeader.js +5 -0
  42. package/dist-lib/components/fs-ui/PillTabs.d.ts +15 -0
  43. package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -0
  44. package/dist-lib/components/fs-ui/PillTabs.js +14 -0
  45. package/dist-lib/components/fs-ui/SearchInput.d.ts +12 -0
  46. package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -0
  47. package/dist-lib/components/fs-ui/SearchInput.js +33 -0
  48. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts +7 -0
  49. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts.map +1 -0
  50. package/dist-lib/components/fs-ui/ShortcutHelp.js +6 -0
  51. package/dist-lib/components/fs-ui/StatusIcon.d.ts +8 -0
  52. package/dist-lib/components/fs-ui/StatusIcon.d.ts.map +1 -0
  53. package/dist-lib/components/fs-ui/StatusIcon.js +16 -0
  54. package/dist-lib/components/fs-ui/TableSkeleton.d.ts +6 -0
  55. package/dist-lib/components/fs-ui/TableSkeleton.d.ts.map +1 -0
  56. package/dist-lib/components/fs-ui/TableSkeleton.js +6 -0
  57. package/dist-lib/components/fs-ui/Toolbar.d.ts +8 -0
  58. package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -0
  59. package/dist-lib/components/fs-ui/Toolbar.js +5 -0
  60. package/dist-lib/components/fs-ui/UpdatedAt.d.ts +7 -0
  61. package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -0
  62. package/dist-lib/components/fs-ui/UpdatedAt.js +9 -0
  63. package/dist-lib/components/fs-ui/useListHotkeys.d.ts +18 -0
  64. package/dist-lib/components/fs-ui/useListHotkeys.d.ts.map +1 -0
  65. package/dist-lib/components/fs-ui/useListHotkeys.js +48 -0
  66. package/dist-lib/components/settings/MailboxAiPromptCard.d.ts +2 -1
  67. package/dist-lib/components/settings/MailboxAiPromptCard.d.ts.map +1 -1
  68. package/dist-lib/components/settings/MailboxAiPromptCard.js +9 -1
  69. package/dist-lib/components/settings/MailboxFilterSettingsCard.js +1 -1
  70. package/dist-lib/components/settings/MailboxTestSetCard.d.ts +2 -1
  71. package/dist-lib/components/settings/MailboxTestSetCard.d.ts.map +1 -1
  72. package/dist-lib/components/settings/MailboxTestSetCard.js +10 -2
  73. package/dist-lib/integrations/supabase/types.d.ts +372 -0
  74. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  75. package/dist-lib/lib/mailbox-list.functions.d.ts +67 -0
  76. package/dist-lib/lib/mailbox-list.functions.d.ts.map +1 -0
  77. package/dist-lib/lib/mailbox-list.functions.js +63 -0
  78. package/package.json +1 -1
@@ -2,18 +2,30 @@ 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, Filter, 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 } from "./fs-ui/DataTable.js";
17
+ import { StatusIcon } from "./fs-ui/StatusIcon.js";
18
+ import { EmptyState } from "./fs-ui/EmptyState.js";
19
+ import { InlineAlert } from "./fs-ui/InlineAlert.js";
20
+ import { TableSkeleton } from "./fs-ui/TableSkeleton.js";
21
+ import { Toolbar } from "./fs-ui/Toolbar.js";
22
+ import { SearchInput } from "./fs-ui/SearchInput.js";
23
+ import { FilterChips } from "./fs-ui/FilterChips.js";
24
+ import { MobilePrimaryAction } from "./fs-ui/MobilePrimaryAction.js";
25
+ import { useListHotkeys } from "./fs-ui/useListHotkeys.js";
14
26
  import { EmailPreview } from "./EmailPreview.js";
15
27
  import { TablePagination } from "./TablePagination.js";
16
- import { MailboxFilterBar, usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar.js";
28
+ import { usePersistedState } from "./MailboxFilterBar.js";
17
29
  import { supabase } from "../integrations/supabase/client.js";
18
30
  import { canOverrideLateSignup, checkLinePrices } from "../lib/verkoop-order.js";
19
31
  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 +91,7 @@ function resolveCustomerId(raw, customers, fallbackId) {
79
91
  }
80
92
  export function MailboxProposalsPage() {
81
93
  const qc = useQueryClient();
82
- const fetchList = useServerFn(listProposals);
94
+ const fetchList = useServerFn(listProposalsPage);
83
95
  const fetchTemplate = useServerFn(listTemplateFields);
84
96
  const fetchApprove = useServerFn(approveProposal);
85
97
  const fetchReject = useServerFn(rejectProposal);
@@ -91,6 +103,11 @@ export function MailboxProposalsPage() {
91
103
  const fetchPickupLocations = useServerFn(listPickupLocations);
92
104
  const fetchActive = useServerFn(getActiveOrdersMailbox);
93
105
  const fetchSync = useServerFn(syncMailboxNow);
106
+ const navigate = useNavigate();
107
+ /** Knop "Open order" in de melding na bevestigen (route van Verkooporders). */
108
+ const openOrderAction = (orderId) => orderId
109
+ ? { action: { label: "Open order", onClick: () => void navigate({ to: `/floriday-verkoop/verkooporders/${orderId}` }) } }
110
+ : undefined;
94
111
  // Server fns met requireSupabaseAuth gooien een raw Response bij een stale
95
112
  // bearer-token. Die bubbelt op als "[object Response]" en blankt het scherm.
96
113
  // Vang elke fout en geef een lege fallback terug — UI blijft bruikbaar.
@@ -106,10 +123,14 @@ export function MailboxProposalsPage() {
106
123
  // zodra de dialoog voor het eerst opengaat, en blijven daarna gecachet.
107
124
  const [activeId, setActiveId] = useState(null);
108
125
  const [page, setPage] = useState(1);
109
- const [pageSize, setPageSize] = useState(25);
126
+ const [pageSize, setPageSize] = useState(50);
110
127
  const [search, setSearch] = usePersistedState("mailbox-proposals:search", "");
111
128
  const [period, setPeriod] = usePersistedState("mailbox-proposals:period", "all");
112
129
  const [sortOrder, setSortOrder] = usePersistedState("mailbox-proposals:sort", "date-desc");
130
+ const [listFilter, setListFilter] = usePersistedState("mailbox-proposals:filter", null);
131
+ const [cursor, setCursor] = useState(null);
132
+ const [helpOpen, setHelpOpen] = useState(false);
133
+ const searchRef = useRef(null);
113
134
  const refsEnabled = true;
114
135
  const refOpts = {
115
136
  enabled: refsEnabled,
@@ -120,11 +141,23 @@ export function MailboxProposalsPage() {
120
141
  // Bestelvoorstellen toont uitsluitend nieuwe, nog niet gecontroleerde
121
142
  // bestellingen ("Te controleren"). Goedgekeurd/afgewezen verschijnt in
122
143
  // de module Verwerkt.
123
- const { data, isLoading, error: listError, refetch: refetchList } = useQuery({
124
- queryKey: ["mailbox-proposals", "pending"],
144
+ const listParams = {
145
+ statuses: ["pending"],
146
+ search,
147
+ period,
148
+ sort: sortOrder === "date-asc" ? "asc" : "desc",
149
+ dateField: "received",
150
+ tz: browserTz(),
151
+ filter: listFilter,
152
+ page,
153
+ pageSize,
154
+ };
155
+ const { data, isLoading, error: listError, refetch: refetchList, dataUpdatedAt } = useQuery({
156
+ queryKey: ["mailbox-proposals", "pending", listParams],
125
157
  // Bewust NIET stilzwijgend opvangen: als de server tijdelijk onbereikbaar is
126
158
  // moet de gebruiker dat zien in plaats van een lege lijst ("geen mailtjes").
127
- queryFn: () => fetchList({ data: { status: "pending" } }),
159
+ queryFn: () => fetchList({ data: listParams }),
160
+ placeholderData: (prev) => prev,
128
161
  retry: 3,
129
162
  retryDelay: (a) => Math.min(1000 * 2 ** a, 8000),
130
163
  refetchInterval: 60000,
@@ -171,6 +204,7 @@ export function MailboxProposalsPage() {
171
204
  onSuccess: (res) => {
172
205
  if (res?.ok && ((res.inserted ?? 0) > 0 || (res.processed ?? 0) > 0)) {
173
206
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
207
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
174
208
  }
175
209
  },
176
210
  });
@@ -221,8 +255,9 @@ export function MailboxProposalsPage() {
221
255
  },
222
256
  onSuccess: (res) => {
223
257
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
258
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
224
259
  void refetchOutdated();
225
- toast.success(`${res?.reparsed ?? 0} voorstel(len) opnieuw geanalyseerd`);
260
+ toast.success(`${res?.reparsed ?? 0} bestelling(en) opnieuw uitgelezen`);
226
261
  },
227
262
  });
228
263
  // Handmatig één voorstel opnieuw laten analyseren door de AI-agent.
@@ -237,22 +272,36 @@ export function MailboxProposalsPage() {
237
272
  if (res?.ok) {
238
273
  setReparseError(null);
239
274
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
275
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
240
276
  qc.invalidateQueries({ queryKey: ["mailbox-proposal-detail"] });
241
- toast.success("E-mail opnieuw geanalyseerd — bestelgegevens bijgewerkt");
277
+ toast.success("Mail opnieuw uitgelezen — gegevens bijgewerkt");
242
278
  }
243
279
  else {
244
- const msg = res?.error ?? "Opnieuw analyseren mislukt";
280
+ if (res?.error)
281
+ console.warn("[mailbox] opnieuw uitlezen", res.error);
282
+ const msg = "Opnieuw uitlezen lukt niet. Probeer het over een paar minuten opnieuw.";
245
283
  setReparseError(msg);
246
284
  toast.error(msg);
247
285
  }
248
286
  },
249
287
  onError: (e) => {
250
- const msg = e?.message ?? "Opnieuw analyseren mislukt";
288
+ console.warn("[mailbox] opnieuw uitlezen", e);
289
+ const msg = "Opnieuw uitlezen lukt niet. Probeer het over een paar minuten opnieuw.";
251
290
  setReparseError(msg);
252
291
  toast.error(msg);
253
292
  },
254
293
  });
255
- const items = Array.isArray(data?.items) ? data.items : [];
294
+ // Platte rijen uit de server terugzetten naar de vorm die de rest van de
295
+ // pagina (klantnaam, detailkop) al gebruikt.
296
+ const items = useMemo(() => (Array.isArray(data?.items) ? data.items : []).map((r) => ({
297
+ ...r,
298
+ mailbox_messages: {
299
+ subject: r.subject, from_email: r.from_email, from_name: r.from_name,
300
+ received_at: r.received_at, connection_id: r.connection_id,
301
+ account_email: r.account_email, preview: String(r.preview ?? "").slice(0, 180),
302
+ },
303
+ })), [data]);
304
+ const total = Number(data?.total ?? 0);
256
305
  const template = Array.isArray(templateData?.items) ? templateData.items : [];
257
306
  const customers = Array.isArray(customersData?.items) ? customersData.items : [];
258
307
  const products = Array.isArray(productsData?.items) ? productsData.items : [];
@@ -279,43 +328,95 @@ export function MailboxProposalsPage() {
279
328
  }
280
329
  return p.mailbox_messages?.from_name || p.mailbox_messages?.from_email || "Onbekend";
281
330
  };
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));
331
+ // --- Zoeken, filteren en sorteren gebeurt op de server -----------------
332
+ const hasActiveFilters = !!search || period !== "all" || !!listFilter;
333
+ const clearFilters = () => { setSearch(""); setPeriod("all"); setSortOrder("date-desc"); setListFilter(null); };
334
+ useEffect(() => { setPage(1); setCursor(null); }, [search, period, sortOrder, listFilter]);
335
+ const pageCount = Math.max(1, Math.ceil(total / pageSize));
310
336
  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) {
337
+ // Oudste open voorstel openen (knop "Volgende controleren" op mobiel).
338
+ const openOldest = async () => {
339
+ try {
340
+ const res = await fetchList({ data: { statuses: ["pending"], sort: "asc", page: 1, pageSize: 10, tz: browserTz() } });
341
+ const first = Array.isArray(res?.items) ? res.items[0] : null;
342
+ if (first?.id)
343
+ setActiveId(first.id);
344
+ else
345
+ toast.info("Er zijn geen bestellingen meer om te controleren.");
346
+ }
347
+ catch {
348
+ toast.error("Openen lukt niet. Probeer het opnieuw.");
349
+ }
350
+ };
351
+ useListHotkeys({
352
+ disabled: !!activeId || helpOpen,
353
+ onSearch: () => searchRef.current?.focus(),
354
+ onNext: () => setCursor((c) => (items.length ? Math.min((c ?? -1) + 1, items.length - 1) : null)),
355
+ onPrev: () => setCursor((c) => (items.length ? Math.max((c ?? 1) - 1, 0) : null)),
356
+ onOpen: cursor != null && items[cursor] ? () => setActiveId(items[cursor].id) : undefined,
357
+ onEscape: hasActiveFilters ? clearFilters : undefined,
358
+ onHelp: () => setHelpOpen(true),
359
+ });
360
+ const periodLabel = { today: "Vandaag", "7d": "Laatste 7 dagen", "30d": "Laatste 30 dagen" };
361
+ const chips = [
362
+ ...(period !== "all" ? [{ key: "period", label: periodLabel[period] ?? period, icon: _jsx(CalendarClock, {}), onRemove: () => setPeriod("all") }] : []),
363
+ ...(listFilter === "klant_onbekend" ? [{ key: "klant", label: "Klant onbekend", icon: _jsx(UserX, {}), onRemove: () => setListFilter(null) }] : []),
364
+ ];
365
+ const columns = [
366
+ {
367
+ key: "klant", header: "Klant", width: "minmax(180px,1.4fr)",
368
+ cell: (p) => {
369
+ const name = customerNameFor(p);
370
+ return (_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(InitialsAvatar, { name: name }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: name }), !p.customer_name && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: "Klant nog niet herkend" })] })] }));
371
+ },
372
+ },
373
+ {
374
+ key: "bericht", header: "Bericht", width: "minmax(240px,2.2fr)",
375
+ cell: (p) => (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate 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" })] })),
376
+ },
377
+ {
378
+ key: "ontvangen_op", header: "Ontvangen op", width: "minmax(140px,1fr)",
379
+ cell: (p) => _jsx("span", { className: "block truncate text-xs text-muted-foreground", children: p.mailbox_messages?.account_email || "–" }),
380
+ },
381
+ {
382
+ key: "datum", header: "Ontvangen", width: "120px", sortable: true,
383
+ cell: (p) => {
384
+ const d = p.mailbox_messages?.received_at ?? p.created_at;
385
+ return _jsx("span", { className: "tabular-nums text-muted-foreground", children: d ? new Date(d).toLocaleDateString("nl-NL") : "–" });
386
+ },
387
+ },
388
+ {
389
+ key: "status", header: "Status", width: "150px",
390
+ cell: (p) => (_jsxs("div", { className: "flex items-center gap-2", children: [p.readiness === "klaar"
391
+ ? _jsx(StatusIcon, { tone: "done", label: "Klaar om te bevestigen" })
392
+ : _jsx(StatusIcon, { tone: "open", label: "Te controleren", detail: "Controleer de gegevens en bevestig de bestelling." }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: p.readiness === "klaar" ? "Klaar" : "Controleren" }), p.duplicate && _jsx(StatusIcon, { tone: "busy", label: "Mogelijk dubbel", detail: "Deze mail lijkt op een eerdere bestelling." })] })),
393
+ },
394
+ ];
395
+ return (_jsxs(MailboxPageFrame, { tab: "voorstellen", helpOpen: helpOpen, onHelpOpenChange: setHelpOpen, onKpi: (k) => {
396
+ if (k === "today") {
397
+ setListFilter(null);
398
+ setPeriod("today");
399
+ return true;
400
+ }
401
+ if (k === "customer_unknown") {
402
+ setPeriod("all");
403
+ setListFilter("klant_onbekend");
404
+ return true;
405
+ }
406
+ if (k === "pending") {
407
+ clearFilters();
408
+ return true;
409
+ }
410
+ return false;
411
+ }, children: [outdatedCount > 0 && (_jsx(InlineAlert, { tone: "warning", title: `${outdatedCount} bestelling(en) zijn met de vorige instelling uitgelezen.`, action: _jsx(Button, { size: "sm", variant: "outline", disabled: agentRefreshMut.isPending, onClick: () => agentRefreshMut.mutate(), children: agentRefreshMut.isPending ? "Bezig…" : "Opnieuw uitlezen" }) })), _jsx(Toolbar, { chips: _jsx(FilterChips, { chips: chips, onClearAll: clearFilters }), right: _jsxs(Select, { value: period, onValueChange: (v) => setPeriod(v), children: [_jsxs(SelectTrigger, { className: "h-9 w-[150px]", "aria-label": "Periode", children: [_jsx(Filter, { className: "mr-1 h-4 w-4" }), _jsx(SelectValue, {})] }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle periodes" }), _jsx(SelectItem, { value: "today", children: "Vandaag" }), _jsx(SelectItem, { value: "7d", children: "Laatste 7 dagen" }), _jsx(SelectItem, { value: "30d", children: "Laatste 30 dagen" })] })] }), children: _jsx(SearchInput, { ref: searchRef, value: search, onChange: setSearch, placeholder: "Zoek op klant, afzender of onderwerp" }) }), isLoading && !data ? (_jsx(TableSkeleton, { columns: ["1.4fr", "2.2fr", "1fr", "120px", "150px"] })) : listError && !data ? (_jsx(InlineAlert, { tone: "error", title: "De lijst kon niet worden geladen.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => void refetchList(), children: "Opnieuw proberen" }), children: "Je bestellingen zijn er nog; ze worden alleen nu niet getoond. Probeer het opnieuw." })) : 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) => {
412
+ const d = p.mailbox_messages?.received_at ?? p.created_at;
413
+ 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"
414
+ ? _jsx(StatusIcon, { tone: "done", label: "Klaar om te bevestigen" })
415
+ : _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: [d ? new Date(d).toLocaleDateString("nl-NL") : "–", p.duplicate ? " · mogelijk dubbel" : ""] })] }));
416
+ }, 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
417
  setActiveId(null);
317
418
  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) => {
419
+ } }, 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
420
  try {
320
421
  const res = await fetchApprove({ data: payload });
321
422
  if (res?.ok === false) {
@@ -323,35 +424,40 @@ export function MailboxProposalsPage() {
323
424
  return res;
324
425
  }
325
426
  if (res?.alreadyApproved) {
326
- toast.info(`Er bestaat al een order voor dit voorstel (${res.orderNumber}).`);
427
+ toast.info(`Er bestaat al een order voor deze bestelling (${res.orderNumber}).`, openOrderAction(res.orderId));
327
428
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
429
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
328
430
  setActiveId(null);
329
431
  return res;
330
432
  }
331
433
  const shortages = Array.isArray(res?.shortages) ? res.shortages : [];
332
- toast.success("Bestelling aangemaakt — karindeling wordt automatisch aangemaakt in de Verkoop-module");
434
+ toast.success(`Order ${res?.orderNumber ?? ""} aangemaakt.`.replace(" ", " "), openOrderAction(res?.orderId));
333
435
  if (shortages.length > 0) {
334
436
  toast.warning(`Tekorten (levering zonder voorraad staat aan): ${shortages
335
437
  .map((s) => `${s.product ?? s.productId}: besteld ${s.wanted}, beschikbaar ${s.available}, tekort ${s.missing}`)
336
438
  .join(" · ")}`);
337
439
  }
338
440
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
441
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
339
442
  setActiveId(null);
340
443
  return res;
341
444
  }
342
445
  catch (e) {
343
- toast.error(e?.message ?? "Goedkeuren mislukt");
344
- return { ok: false, reason: "other", message: e?.message ?? "Goedkeuren mislukt" };
446
+ console.warn("[mailbox] bevestigen", e);
447
+ toast.error("Bevestigen lukt niet. Probeer het opnieuw.");
448
+ return { ok: false, reason: "other", message: "Bevestigen lukt niet. Probeer het opnieuw." };
345
449
  }
346
450
  }, onReject: async (reason) => {
347
451
  try {
348
452
  await fetchReject({ data: { proposalId: active.id, reason } });
349
- toast.success("Voorstel afgewezen");
453
+ toast.success("Bestelling afgewezen");
350
454
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
455
+ qc.invalidateQueries({ queryKey: ["mailbox-kpis"] });
351
456
  setActiveId(null);
352
457
  }
353
458
  catch (e) {
354
- toast.error(e?.message ?? "Afwijzen mislukt");
459
+ console.warn("[mailbox] afwijzen", e);
460
+ toast.error("Afwijzen lukt niet. Probeer het opnieuw.");
355
461
  }
356
462
  } }, active.id) })] })) }) })] }));
357
463
  }
@@ -678,11 +784,11 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
678
784
  return null;
679
785
  }
680
786
  if (!deliveryLocationVal) {
681
- toast.error("Selecteer een afleverlocatie.");
787
+ toast.error("Kies een afleverlocatie.");
682
788
  return null;
683
789
  }
684
790
  if (!pickupLocationVal) {
685
- toast.error("Selecteer een afhaallocatie (eigen locatie / tuin).");
791
+ toast.error("Kies een afhaallocatie (eigen locatie of tuin).");
686
792
  return null;
687
793
  }
688
794
  const validLines = lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0);
@@ -773,7 +879,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
773
879
  setSubmitting(false);
774
880
  }
775
881
  };
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: [(() => {
882
+ 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
883
  // Klantveld staat altijd bovenaan, ook als het template (nog) leeg is.
778
884
  const customerField = visibleFields.find((f) => f.key === "customer") ?? {
779
885
  id: "customer",
@@ -800,9 +906,9 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
800
906
  const metaFor = (key) => fieldMeta[key] ?? (key === "customer" && proposal.matched_customer_id
801
907
  ? { confidence: payload.auto_match_warning ? 0.6 : 1, source: payload.auto_match_forwarded ? "afgeleid" : "uit mail" }
802
908
  : 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
909
+ 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
910
  ? "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
911
+ : "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
912
  ? "Selecteer eerst een klant"
807
913
  : deliveryLocations.length
808
914
  ? "Kies locatie..."
@@ -824,7 +930,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
824
930
  : "Kies vervoerder...", clearLabel: "\u2014 Geen \u2014", disabled: !deliveryLocationVal, options: carriers.map((c) => ({
825
931
  value: c.id,
826
932
  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)] }));
933
+ })), 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
934
  })(), _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
935
  const product = line.product_id ? products.find((p) => p.id === line.product_id) : null;
830
936
  const stdBelading = defaultBelading(product);
@@ -924,7 +1030,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
924
1030
  const pl = toPlants(next[idx].qty_in_mail, kind, effBelading);
925
1031
  next[idx] = { ...next[idx], unit_kind: kind, quantity: pl ?? next[idx].quantity };
926
1032
  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) => {
1033
+ }, 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
1034
  const next = [...lines];
929
1035
  const raw = e.target.value.replace(/[.,]/g, "");
930
1036
  const cr = raw ? Math.max(1, Math.floor(Number(raw))) : null;
@@ -938,7 +1044,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
938
1044
  } }), (() => {
939
1045
  const ag = line.product_id ? agreements[line.product_id] : null;
940
1046
  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: () => {
1047
+ 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
1048
  const next = [...lines];
943
1049
  next[idx] = { ...next[idx], unit_price: ag.net_price, price_source: "afspraak" };
944
1050
  setLines(next);
@@ -948,13 +1054,13 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
948
1054
  next[idx] = { ...next[idx], needs_sticker: e.target.checked };
949
1055
  setLines(next);
950
1056
  } }), "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) => {
1057
+ }), _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
1058
  if (submitting)
953
1059
  return;
954
1060
  setConfirmOpen(o);
955
1061
  if (!o)
956
1062
  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); } })] })] })] }));
1063
+ }, 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
1064
  }
959
1065
  /** Verplichte bevestiging: externe klant eerst als lokale klant aanmaken. */
960
1066
  function NewCustomerDialog({ candidate, onClose, onCreated, }) {
@@ -1009,7 +1115,8 @@ function NewCustomerDialog({ candidate, onClose, onCreated, }) {
1009
1115
  onCreated(res.id);
1010
1116
  }
1011
1117
  catch (e) {
1012
- toast.error(e?.message ?? "Aanmaken mislukt.");
1118
+ console.warn("[mailbox] klant aanmaken", e);
1119
+ toast.error("Klant aanmaken lukt niet. Probeer het opnieuw.");
1013
1120
  }
1014
1121
  finally {
1015
1122
  setBusy(false);
@@ -1019,8 +1126,8 @@ function NewCustomerDialog({ candidate, onClose, onCreated, }) {
1019
1126
  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
1127
  }
1021
1128
  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), "%"] }))] }));
1129
+ const reviewClass = meta?.needs_review ? "border-warning/30 ring-1 ring-warning/40 rounded-md p-2" : "";
1130
+ 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-warning/30 text-warning-soft-foreground", children: "Controleren" })), typeof meta?.ai_confidence === "number" && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [Math.round(meta.ai_confidence * 100), "%"] }))] }));
1024
1131
  const renderControl = () => {
1025
1132
  switch (field.field_type) {
1026
1133
  case "text":
@@ -1066,9 +1173,9 @@ function ConfBadge({ meta, thresholds, }) {
1066
1173
  return null;
1067
1174
  const c = Math.max(0, Math.min(1, Number(meta.confidence ?? 0)));
1068
1175
  const cls = c >= thresholds.green
1069
- ? "border-emerald-300 bg-emerald-50 text-emerald-800"
1176
+ ? "border-primary/30 bg-primary/10 text-primary"
1070
1177
  : c >= thresholds.orange
1071
- ? "border-amber-300 bg-amber-50 text-amber-800"
1178
+ ? "border-warning/30 bg-warning-soft text-warning-soft-foreground"
1072
1179
  : "border-destructive/40 bg-destructive/5 text-destructive";
1073
1180
  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] }));
1074
1181
  }
@@ -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":"AAuCA,wBAAgB,kBAAkB,gCAyLjC"}