@flowselections/floriday-verkoop-module 1.0.41 → 1.0.43

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 (69) hide show
  1. package/dist-lib/components/verkoop/SalesOrdersTable.d.ts +5 -2
  2. package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
  3. package/dist-lib/components/verkoop/SalesOrdersTable.js +27 -13
  4. package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
  5. package/dist-lib/components/verkoop/VerkoopOrdersPage.js +141 -27
  6. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1428 -172
  7. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  8. package/dist-lib/integrations/supabase/client.d.ts +1428 -172
  9. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  10. package/dist-lib/integrations/supabase/client.server.d.ts +1428 -172
  11. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  12. package/dist-lib/integrations/supabase/types.d.ts +1453 -170
  13. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/types.js +3 -0
  15. package/dist-lib/lib/cart-lock.functions.d.ts +1428 -172
  16. package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -1
  17. package/dist-lib/lib/cart-planning-settings.functions.d.ts +15224 -1408
  18. package/dist-lib/lib/cart-planning-settings.functions.d.ts.map +1 -1
  19. package/dist-lib/lib/cart-replan.functions.d.ts +4152 -384
  20. package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
  21. package/dist-lib/lib/create-order.functions.d.ts +65255 -1978
  22. package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
  23. package/dist-lib/lib/create-order.functions.js +48 -1
  24. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +4152 -384
  25. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +1428 -172
  27. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -1
  28. package/dist-lib/lib/floriday-mirror.functions.d.ts +1428 -172
  29. package/dist-lib/lib/floriday-mirror.functions.d.ts.map +1 -1
  30. package/dist-lib/lib/floriday-mirror.functions.js +6 -1
  31. package/dist-lib/lib/floriday-public.functions.d.ts +9688 -896
  32. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/floriday-writes.functions.d.ts +6920 -640
  34. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/invoice-timing.functions.d.ts +5536 -512
  36. package/dist-lib/lib/invoice-timing.functions.d.ts.map +1 -1
  37. package/dist-lib/lib/invoice-timing.server.d.ts.map +1 -1
  38. package/dist-lib/lib/invoice-timing.server.js +1 -0
  39. package/dist-lib/lib/load-carriers.functions.d.ts +1428 -172
  40. package/dist-lib/lib/load-carriers.functions.d.ts.map +1 -1
  41. package/dist-lib/lib/logistics-engine.functions.d.ts +6920 -640
  42. package/dist-lib/lib/logistics-engine.functions.d.ts.map +1 -1
  43. package/dist-lib/lib/logistics-stock.functions.d.ts +2768 -256
  44. package/dist-lib/lib/logistics-stock.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/order-addresses.functions.d.ts +1428 -172
  46. package/dist-lib/lib/order-addresses.functions.d.ts.map +1 -1
  47. package/dist-lib/lib/order-buffers.functions.d.ts +4152 -384
  48. package/dist-lib/lib/order-buffers.functions.d.ts.map +1 -1
  49. package/dist-lib/lib/order-cancel.server.d.ts +24 -0
  50. package/dist-lib/lib/order-cancel.server.d.ts.map +1 -0
  51. package/dist-lib/lib/order-cancel.server.js +130 -0
  52. package/dist-lib/lib/order-carrier.functions.d.ts +2768 -256
  53. package/dist-lib/lib/order-carrier.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/order-pickup.functions.d.ts +2768 -256
  55. package/dist-lib/lib/order-pickup.functions.d.ts.map +1 -1
  56. package/dist-lib/lib/order-shipments.functions.d.ts +5536 -512
  57. package/dist-lib/lib/order-shipments.functions.d.ts.map +1 -1
  58. package/dist-lib/lib/salesorders-list.functions.d.ts +1428 -172
  59. package/dist-lib/lib/salesorders-list.functions.d.ts.map +1 -1
  60. package/dist-lib/lib/salesorders-list.server.d.ts.map +1 -1
  61. package/dist-lib/lib/salesorders-list.server.js +4 -60
  62. package/dist-lib/lib/salesorders-mapping.d.ts +6 -0
  63. package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
  64. package/dist-lib/lib/salesorders-mapping.js +17 -0
  65. package/dist-lib/lib/salesorders.functions.d.ts +6920 -640
  66. package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
  67. package/dist-lib/lib/tradeitems.functions.d.ts +1428 -172
  68. package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
  69. package/package.json +1 -1
@@ -1,11 +1,14 @@
1
1
  import React from "react";
2
2
  import type { SalesOrderDTO } from "../../lib/salesorders-mapping";
3
- export declare function SalesOrdersTable({ orders, loading, onSelectOrder, onDeleteOrder, onEditOrder, onConfirmOrder, }: {
3
+ /** Stabiele rij-id: buffers zijn serverside al samengevouwen tot één rij. */
4
+ export declare function salesOrderRowId(row: SalesOrderDTO): string;
5
+ export declare function SalesOrdersTable({ orders, loading, onSelectOrder, onDeleteOrder, onEditOrder, selectedIds, onSelectionChange, }: {
4
6
  orders: SalesOrderDTO[];
5
7
  loading?: boolean;
6
8
  onSelectOrder?: (order: SalesOrderDTO) => void;
7
9
  onDeleteOrder?: (order: SalesOrderDTO) => void;
8
10
  onEditOrder?: (order: SalesOrderDTO) => void;
9
- onConfirmOrder?: (order: SalesOrderDTO) => void;
11
+ selectedIds?: string[];
12
+ onSelectionChange?: (ids: string[]) => void;
10
13
  }): React.JSX.Element;
11
14
  //# sourceMappingURL=SalesOrdersTable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SalesOrdersTable.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAuBxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAmCnE,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,WAAW,EACX,cAAc,GACf,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CACjD,qBAwOA"}
1
+ {"version":3,"file":"SalesOrdersTable.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAuBjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAmCnE,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAG1D;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,GAClB,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7C,qBAkPA"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useMemo, useState } from "react";
3
3
  import { Badge, Checkbox, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
4
4
  import { formatLoadUnits } from "../../lib/load-carrier-labels";
5
5
  import { useAllowedLoadCarriers } from "../../lib/useAllowedLoadCarriers";
@@ -36,19 +36,33 @@ function StatusTag({ order }) {
36
36
  const stage = order.stage ?? "incoming";
37
37
  return (_jsx(Badge, { variant: "outline", className: `border-transparent ${orderStageBadgeClass(stage)}`, children: orderStageLabel(stage) }));
38
38
  }
39
- export function SalesOrdersTable({ orders, loading, onSelectOrder, onDeleteOrder, onEditOrder, onConfirmOrder, }) {
40
- const [selected, setSelected] = useState(new Set());
39
+ /** Stabiele rij-id: buffers zijn serverside al samengevouwen tot één rij. */
40
+ export function salesOrderRowId(row) {
41
+ const bufferSize = row.bufferSize ?? 1;
42
+ return row.bufferId && bufferSize > 1 ? `buf-${row.bufferId}` : row.id;
43
+ }
44
+ export function SalesOrdersTable({ orders, loading, onSelectOrder, onDeleteOrder, onEditOrder, selectedIds, onSelectionChange, }) {
45
+ const [internalSelected, setInternalSelected] = useState([]);
46
+ const selected = useMemo(() => new Set(selectedIds ?? internalSelected), [selectedIds, internalSelected]);
41
47
  const allowedCarrierCodes = useAllowedLoadCarriers().allowed;
42
- const allChecked = orders.length > 0 && selected.size === orders.length;
48
+ const rowIds = useMemo(() => orders.map(salesOrderRowId), [orders]);
49
+ const allChecked = rowIds.length > 0 && rowIds.every((id) => selected.has(id));
50
+ const someChecked = !allChecked && rowIds.some((id) => selected.has(id));
43
51
  // Omzetkolom alleen tonen wanneer er daadwerkelijk bedragen bekend zijn.
44
52
  const showRevenue = orders.some((o) => (o.revenue ?? 0) > 0);
45
53
  const columnCount = showRevenue ? 10 : 9;
46
- const toggle = (id) => setSelected((s) => {
47
- const n = new Set(s);
48
- n.has(id) ? n.delete(id) : n.add(id);
49
- return n;
50
- });
51
- return (_jsx("div", { className: "rounded-lg border border-border bg-card", children: _jsx("div", { className: "max-h-[70vh] overflow-y-auto overflow-x-auto overscroll-contain [scrollbar-gutter:stable]", style: { WebkitOverflowScrolling: "touch" }, children: _jsxs(Table, { className: "min-w-[950px] [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-10 [&_thead_th]:bg-card", children: [_jsx(TableHeader, { children: _jsxs(TableRow, { className: "hover:bg-transparent", children: [_jsx(TableHead, { className: "w-10", children: _jsx(Checkbox, { checked: allChecked, onCheckedChange: (v) => setSelected(v === true ? new Set(orders.map((o) => o.id)) : new Set()) }) }), _jsx(TableHead, { children: "Levering" }), _jsx(TableHead, { children: "Klant" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Karren" }), _jsx(TableHead, { children: "Fusten" }), _jsx(TableHead, { children: "Vervoerder" }), _jsx(TableHead, { children: "Status" }), showRevenue && _jsx(TableHead, { children: "Omzet" }), _jsx(TableHead, { children: _jsxs("span", { className: "inline-flex items-center gap-1", children: ["Besteld ", _jsx(ArrowDown, { className: "h-3 w-3" })] }) }), _jsx(TableHead, { className: "w-10" })] }) }), _jsxs(TableBody, { children: [loading &&
54
+ const applySelection = (ids) => {
55
+ if (onSelectionChange)
56
+ onSelectionChange(ids);
57
+ else
58
+ setInternalSelected(ids);
59
+ };
60
+ const toggle = (id) => {
61
+ const next = new Set(selected);
62
+ next.has(id) ? next.delete(id) : next.add(id);
63
+ applySelection(Array.from(next));
64
+ };
65
+ return (_jsx("div", { className: "rounded-lg border border-border bg-card", children: _jsx("div", { className: "max-h-[70vh] overflow-y-auto overflow-x-auto overscroll-contain [scrollbar-gutter:stable]", style: { WebkitOverflowScrolling: "touch" }, children: _jsxs(Table, { className: "min-w-[950px] [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-10 [&_thead_th]:bg-card", children: [_jsx(TableHeader, { children: _jsxs(TableRow, { className: "hover:bg-transparent", children: [_jsx(TableHead, { className: "w-10", children: _jsx(Checkbox, { "aria-label": "Alle bestellingen selecteren", checked: allChecked ? true : someChecked ? "indeterminate" : false, onCheckedChange: (v) => applySelection(v === true ? rowIds : []) }) }), _jsx(TableHead, { children: "Levering" }), _jsx(TableHead, { children: "Klant" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Karren" }), _jsx(TableHead, { children: "Fusten" }), _jsx(TableHead, { children: "Vervoerder" }), _jsx(TableHead, { children: "Status" }), showRevenue && _jsx(TableHead, { children: "Omzet" }), _jsx(TableHead, { children: _jsxs("span", { className: "inline-flex items-center gap-1", children: ["Besteld ", _jsx(ArrowDown, { className: "h-3 w-3" })] }) }), _jsx(TableHead, { className: "w-10" })] }) }), _jsxs(TableBody, { children: [loading &&
52
66
  Array.from({ length: 6 }).map((_, i) => (_jsx(TableRow, { children: Array.from({ length: columnCount + 1 }).map((__, j) => (_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-6 w-full" }) }, j))) }, `s-${i}`))), !loading && orders.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columnCount + 1, className: "py-12 text-center text-muted-foreground", children: "Geen verkooporders gevonden." }) })), !loading &&
53
67
  orders.map((row) => {
54
68
  // Buffers zijn serverside al samengevouwen tot één rij.
@@ -70,8 +84,8 @@ export function SalesOrdersTable({ orders, loading, onSelectOrder, onDeleteOrder
70
84
  ? "Buffer-orders kunnen niet gebundeld bewerkt worden."
71
85
  : row.isLocal
72
86
  ? undefined
73
- : "FO-orders kunnen niet bewerkt worden.", children: "Bewerken" }), _jsx(DropdownMenuItem, { onClick: () => onSelectOrder?.(row), children: "Details bekijken" }), _jsx(DropdownMenuItem, { onClick: () => onConfirmOrder?.(row), children: "Bevestigen" }), !isBuffer && row.isLocal ? (_jsx(DropdownMenuItem, { className: "text-destructive focus:text-destructive", onClick: () => onDeleteOrder?.(row), children: "Verwijderen" })) : (_jsxs(DropdownMenuItem, { disabled: true, title: isBuffer
74
- ? "Buffer-orders kunnen niet gebundeld verwijderd worden."
75
- : "FO-orders kunnen niet verwijderd worden.", children: ["Verwijderen", isBuffer ? "" : " (FO)"] }))] })] }) })] }, rowId));
87
+ : "FO-orders kunnen niet bewerkt worden.", children: "Bewerken" }), _jsx(DropdownMenuItem, { onClick: () => onSelectOrder?.(row), children: "Details bekijken" }), !isBuffer && row.isLocal ? (_jsx(DropdownMenuItem, { className: "text-destructive focus:text-destructive", onClick: () => onDeleteOrder?.(row), children: row.stage === "cancelled" ? "Definitief verwijderen" : "Annuleren" })) : (_jsxs(DropdownMenuItem, { disabled: true, title: isBuffer
88
+ ? "Buffer-orders kunnen niet gebundeld geannuleerd worden."
89
+ : "FO-orders annuleer je in Floriday.", children: ["Annuleren", isBuffer ? "" : " (FO)"] }))] })] }) })] }, rowId));
76
90
  })] })] }) }) }));
77
91
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VerkoopOrdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/VerkoopOrdersPage.tsx"],"names":[],"mappings":"AA4DA,wBAAgB,iBAAiB,gCAoahC"}
1
+ {"version":3,"file":"VerkoopOrdersPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/VerkoopOrdersPage.tsx"],"names":[],"mappings":"AA6DA,wBAAgB,iBAAiB,gCA4mBhC"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
4
4
  import { Download, Plus, AlertTriangle, RefreshCw, ChevronLeft, ChevronRight, Filter, } from "lucide-react";
@@ -7,15 +7,15 @@ import { useNavigate } from "@tanstack/react-router";
7
7
  import { useQueryClient } from "@tanstack/react-query";
8
8
  import { supabase } from "../../integrations/supabase/client";
9
9
  import { runSalesOrdersSync } from "../../lib/salesorders.functions";
10
- import { deleteLocalOrder } from "../../lib/create-order.functions";
10
+ import { cancelLocalOrder, deleteLocalOrder } from "../../lib/create-order.functions";
11
11
  import { runTradeItemsSync } from "../../lib/tradeitems.functions";
12
- import { ordersToCsv, } from "../../lib/salesorders-mapping";
12
+ import { ordersToCsv, parseOrderRowId, } from "../../lib/salesorders-mapping";
13
13
  import { listSalesOrders } from "../../lib/salesorders-list.functions";
14
14
  import { derivePeriodWindow, useSalesOrdersQuery } from "../../lib/useSalesOrdersQuery";
15
15
  import { AnalyticsCards } from "./AnalyticsCards";
16
16
  import { SalesOrdersFilters } from "./SalesOrdersFilters";
17
17
  import { SalesOrdersFilterPanel, countActiveFilters, } from "./SalesOrdersFilterPanel";
18
- import { SalesOrdersTable } from "./SalesOrdersTable";
18
+ import { SalesOrdersTable, salesOrderRowId } from "./SalesOrdersTable";
19
19
  import { EditLocalOrderDialog } from "./EditLocalOrderDialog";
20
20
  import { ORDER_STAGES } from "../../lib/order-status";
21
21
  const STATUS_TABS = [
@@ -50,28 +50,109 @@ export function VerkoopOrdersPage() {
50
50
  const triggerSync = useServerFn(runSalesOrdersSync);
51
51
  const triggerTradeItemsSync = useServerFn(runTradeItemsSync);
52
52
  const triggerDeleteOrder = useServerFn(deleteLocalOrder);
53
- const [orderPendingDelete, setOrderPendingDelete] = useState(null);
53
+ const triggerCancelOrder = useServerFn(cancelLocalOrder);
54
+ const [pendingAction, setPendingAction] = useState(null);
55
+ const [cancelReason, setCancelReason] = useState("");
54
56
  const [deleting, setDeleting] = useState(false);
57
+ const [deleteProgress, setDeleteProgress] = useState(0);
58
+ const [selectedRowIds, setSelectedRowIds] = useState([]);
55
59
  const [editOrderId, setEditOrderId] = useState(null);
56
- const handleDeleteOrder = async () => {
57
- if (!orderPendingDelete)
60
+ // Rijen die daadwerkelijk geraakt worden: bij een samengevouwen buffer alle
61
+ // onderliggende orders, anders alleen de rij zelf.
62
+ const deleteTargets = (row) => {
63
+ const ids = (row.bufferSize ?? 1) > 1 && row.bufferOrderIds?.length
64
+ ? row.bufferOrderIds
65
+ : [row.id];
66
+ return Array.from(new Set(ids));
67
+ };
68
+ // Alle order-ids achter een selectie, gesplitst in bruikbaar en overgeslagen
69
+ // (Floriday-orders kunnen hier niet geannuleerd of verwijderd worden).
70
+ const collectDeletePlan = (rows) => {
71
+ const deletable = [];
72
+ let skipped = 0;
73
+ const seen = new Set();
74
+ for (const row of rows) {
75
+ for (const target of deleteTargets(row)) {
76
+ if (seen.has(target))
77
+ continue;
78
+ seen.add(target);
79
+ const parsed = parseOrderRowId(target);
80
+ if (!parsed || parsed.kind !== "order") {
81
+ skipped += 1;
82
+ continue;
83
+ }
84
+ deletable.push({ id: parsed.uuid, kind: parsed.kind });
85
+ }
86
+ }
87
+ return { deletable, skipped, total: seen.size };
88
+ };
89
+ const readableError = (e, mode) => {
90
+ const msg = typeof e?.message === "string" ? e.message.trim() : "";
91
+ if (!msg || msg.startsWith("[") || msg.startsWith("{"))
92
+ return mode === "cancel" ? "Annuleren mislukt" : "Verwijderen mislukt";
93
+ return msg;
94
+ };
95
+ const handleConfirmAction = async () => {
96
+ if (!pendingAction)
97
+ return;
98
+ const mode = pendingAction.mode;
99
+ const { deletable, skipped } = collectDeletePlan(pendingAction.rows);
100
+ if (deletable.length === 0) {
101
+ toast.error(mode === "cancel"
102
+ ? "Geen van de geselecteerde bestellingen kan geannuleerd worden."
103
+ : "Geen van de geselecteerde bestellingen kan verwijderd worden.");
104
+ setPendingAction(null);
58
105
  return;
59
- const rawId = orderPendingDelete.id.startsWith("local:")
60
- ? orderPendingDelete.id.slice("local:".length)
61
- : orderPendingDelete.id;
62
- try {
63
- setDeleting(true);
64
- const res = await triggerDeleteOrder({ data: { orderId: rawId } });
65
- toast.success(`Bestelling verwijderd — ${res.reversalEntries} voorraadmutatie(s) teruggedraaid`);
66
- setOrderPendingDelete(null);
67
- await refetch();
68
106
  }
69
- catch (e) {
70
- toast.error(e?.message ?? "Verwijderen mislukt");
107
+ setDeleting(true);
108
+ setDeleteProgress(0);
109
+ let done = 0;
110
+ let reversals = 0;
111
+ const failed = [];
112
+ for (const target of deletable) {
113
+ try {
114
+ if (mode === "cancel") {
115
+ await triggerCancelOrder({
116
+ data: { orderId: target.id, reason: cancelReason.trim() || undefined },
117
+ });
118
+ }
119
+ else {
120
+ const res = await triggerDeleteOrder({ data: { orderId: target.id } });
121
+ reversals += res.reversalEntries;
122
+ }
123
+ done += 1;
124
+ }
125
+ catch (e) {
126
+ failed.push(readableError(e, mode));
127
+ }
128
+ setDeleteProgress((p) => p + 1);
71
129
  }
72
- finally {
73
- setDeleting(false);
130
+ setDeleting(false);
131
+ setDeleteProgress(0);
132
+ if (done > 0) {
133
+ const extra = [];
134
+ if (mode === "cancel")
135
+ extra.push("voorraad teruggeboekt");
136
+ else
137
+ extra.push(`${reversals} voorraadmutatie(s) teruggedraaid`);
138
+ if (skipped > 0)
139
+ extra.push(`${skipped} overgeslagen`);
140
+ if (failed.length > 0)
141
+ extra.push(`${failed.length} mislukt`);
142
+ const verb = mode === "cancel" ? "geannuleerd" : "verwijderd";
143
+ toast.success(deletable.length > 1
144
+ ? `${done} van ${deletable.length} bestelling(en) ${verb} — ${extra.join(", ")}`
145
+ : `Bestelling ${verb} — ${extra.join(", ")}`);
146
+ }
147
+ if (failed.length > 0) {
148
+ toast.error(Array.from(new Set(failed)).join(" · "));
149
+ }
150
+ if (done > 0) {
151
+ setPendingAction(null);
152
+ setCancelReason("");
153
+ setSelectedRowIds([]);
74
154
  }
155
+ await refetch();
75
156
  };
76
157
  // Serverside gefilterd, gesorteerd en gepagineerd: de browser krijgt alleen
77
158
  // de zichtbare pagina plus totalen/tellingen.
@@ -81,6 +162,13 @@ export function VerkoopOrdersPage() {
81
162
  pageSize,
82
163
  });
83
164
  const paged = data?.orders ?? [];
165
+ // Selectie voor bulkacties: alleen rijen die nu zichtbaar zijn.
166
+ const selectedRows = paged.filter((o) => selectedRowIds.includes(salesOrderRowId(o)));
167
+ const selectedPlan = collectDeletePlan(selectedRows);
168
+ // Selectie leegmaken bij wisselen van tabblad, pagina of filters.
169
+ useEffect(() => {
170
+ setSelectedRowIds([]);
171
+ }, [tab, page, filters]);
84
172
  const activeEnvironment = data?.environment ?? "staging";
85
173
  const totals = data?.totals ?? {
86
174
  revenue: 0,
@@ -182,15 +270,24 @@ export function VerkoopOrdersPage() {
182
270
  (active
183
271
  ? "bg-primary text-primary-foreground"
184
272
  : "bg-muted text-muted-foreground"), children: count })] }, t.id));
185
- }) }) }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx(SalesOrdersFilters, { value: filters, onChange: setFilters }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setFilterPanelOpen(true), "aria-label": "Filters", children: [_jsx(Filter, { className: "mr-2 h-4 w-4" }), "Filters", activeFilterCount > 0 && (_jsx("span", { className: "ml-2 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground", children: activeFilterCount }))] }), _jsx(Button, { variant: "outline", size: "icon", onClick: handleSync, disabled: syncing, "aria-label": "Synchroniseren", title: "Synchroniseren", children: _jsx(RefreshCw, { className: "h-4 w-4 " + (syncing ? "animate-spin" : "") }) }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleExport, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), " Exporteren"] }), _jsxs(Button, { size: "sm", onClick: () => navigate({ to: "/floriday-verkoop/verkooporders/nieuw" }), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Bestelling aanmaken"] })] })] }), _jsx(AnalyticsCards, { totals: totals, loading: isLoading }), error && (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-5 w-5 shrink-0 text-destructive" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium text-destructive", children: "Floriday gegevens konden niet geladen worden" }), _jsx("div", { className: "mt-1 text-muted-foreground", children: error.message })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => refetch(), disabled: isFetching, children: "Opnieuw proberen" })] })), (warnings.length > 0 || truncated) && (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-sm", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" }), _jsxs("div", { className: "space-y-1 text-muted-foreground", children: [warnings.map((w) => (_jsx("div", { children: w }, w))), truncated && (_jsx("div", { children: "Niet alle orders konden worden meegenomen. Kies een kortere periode voor een volledig overzicht." }))] })] })), _jsx(SalesOrdersTable, { orders: paged, loading: isLoading, onSelectOrder: (o) => navigate({
273
+ }) }) }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx(SalesOrdersFilters, { value: filters, onChange: setFilters }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setFilterPanelOpen(true), "aria-label": "Filters", children: [_jsx(Filter, { className: "mr-2 h-4 w-4" }), "Filters", activeFilterCount > 0 && (_jsx("span", { className: "ml-2 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground", children: activeFilterCount }))] }), _jsx(Button, { variant: "outline", size: "icon", onClick: handleSync, disabled: syncing, "aria-label": "Synchroniseren", title: "Synchroniseren", children: _jsx(RefreshCw, { className: "h-4 w-4 " + (syncing ? "animate-spin" : "") }) }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleExport, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), " Exporteren"] }), _jsxs(Button, { size: "sm", onClick: () => navigate({ to: "/floriday-verkoop/verkooporders/nieuw" }), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Bestelling aanmaken"] })] })] }), _jsx(AnalyticsCards, { totals: totals, loading: isLoading }), error && (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-5 w-5 shrink-0 text-destructive" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium text-destructive", children: "Floriday gegevens konden niet geladen worden" }), _jsx("div", { className: "mt-1 text-muted-foreground", children: error.message })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => refetch(), disabled: isFetching, children: "Opnieuw proberen" })] })), (warnings.length > 0 || truncated) && (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-sm", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" }), _jsxs("div", { className: "space-y-1 text-muted-foreground", children: [warnings.map((w) => (_jsx("div", { children: w }, w))), truncated && (_jsx("div", { children: "Niet alle orders konden worden meegenomen. Kies een kortere periode voor een volledig overzicht." }))] })] })), selectedRows.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-lg border border-primary/30 bg-primary/5 p-3", children: [_jsxs("div", { className: "text-sm text-foreground", children: [_jsx("span", { className: "font-medium", children: selectedRows.length }), " geselecteerd", selectedPlan.total !== selectedRows.length && (_jsxs("span", { className: "text-muted-foreground", children: [" ", "\u00B7 ", selectedPlan.total, " onderliggende bestelling(en)"] })), selectedPlan.skipped > 0 && (_jsxs("span", { className: "text-muted-foreground", children: [" ", "\u00B7 ", selectedPlan.skipped, " Floriday-order(s) worden overgeslagen"] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setSelectedRowIds([]), children: "Selectie wissen" }), _jsx(Button, { size: "sm", className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", disabled: selectedPlan.deletable.length === 0, onClick: () => {
274
+ setCancelReason("");
275
+ setPendingAction({
276
+ rows: selectedRows,
277
+ mode: selectedRows.every((r) => r.stage === "cancelled") ? "delete" : "cancel",
278
+ });
279
+ }, children: selectedRows.every((r) => r.stage === "cancelled")
280
+ ? "Selectie definitief verwijderen"
281
+ : "Selectie annuleren" })] })] })), _jsx(SalesOrdersTable, { orders: paged, loading: isLoading, selectedIds: selectedRowIds, onSelectionChange: setSelectedRowIds, onSelectOrder: (o) => navigate({
186
282
  to: "/floriday-verkoop/verkooporders/$salesOrderId",
187
283
  params: { salesOrderId: o.id },
188
284
  }), onDeleteOrder: (o) => {
189
285
  if (!o.isLocal) {
190
- toast.error("FO-orders kunnen niet verwijderd worden.");
286
+ toast.error("FO-orders kunnen hier niet geannuleerd worden. Annuleer deze in Floriday.");
191
287
  return;
192
288
  }
193
- setOrderPendingDelete(o);
289
+ setCancelReason("");
290
+ setPendingAction({ rows: [o], mode: o.stage === "cancelled" ? "delete" : "cancel" });
194
291
  }, onEditOrder: (o) => {
195
292
  if (!o.isLocal) {
196
293
  toast.error("FO-orders kunnen niet bewerkt worden.");
@@ -198,10 +295,27 @@ export function VerkoopOrdersPage() {
198
295
  }
199
296
  const rawId = o.id.startsWith("local:") ? o.id.slice("local:".length) : o.id;
200
297
  setEditOrderId(rawId);
201
- }, onConfirmOrder: (o) => navigate({
202
- to: "/floriday-verkoop/verkooporders/$salesOrderId",
203
- params: { salesOrderId: o.id },
204
- }) }), orderPendingDelete && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", onClick: () => !deleting && setOrderPendingDelete(null), children: _jsxs("div", { className: "w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-lg", onClick: (e) => e.stopPropagation(), children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Bestelling verwijderen?" }), _jsxs("p", { className: "mt-2 text-sm text-muted-foreground", children: ["Weet je zeker dat je bestelling", " ", _jsx("span", { className: "font-medium text-foreground", children: orderPendingDelete.orderNumber || orderPendingDelete.id }), " ", "wilt verwijderen? Alle voorraadmutaties van deze bestelling worden automatisch teruggedraaid."] }), _jsxs("div", { className: "mt-5 flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOrderPendingDelete(null), disabled: deleting, children: "Annuleren" }), _jsx(Button, { size: "sm", onClick: handleDeleteOrder, disabled: deleting, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: deleting ? "Bezig…" : "Verwijderen" })] })] }) })), editOrderId && (_jsx(EditLocalOrderDialog, { orderId: editOrderId, open: !!editOrderId, onOpenChange: (o) => !o && setEditOrderId(null) })), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 text-sm text-muted-foreground", children: [_jsx("div", { children: totalCount === 0
298
+ } }), pendingAction && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", onClick: () => !deleting && setPendingAction(null), children: _jsxs("div", { className: "w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-lg", onClick: (e) => e.stopPropagation(), children: [(() => {
299
+ const rows = pendingAction.rows;
300
+ const isCancel = pendingAction.mode === "cancel";
301
+ const plan = collectDeletePlan(rows);
302
+ const single = rows[0];
303
+ const isBulk = rows.length > 1;
304
+ const verb = isCancel ? "annuleren" : "definitief verwijderen";
305
+ return (_jsxs(_Fragment, { children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: isBulk
306
+ ? `${rows.length} regels ${verb}?`
307
+ : plan.total > 1
308
+ ? `${plan.total} bestellingen ${verb}?`
309
+ : `Bestelling ${verb}?` }), _jsxs("p", { className: "mt-2 text-sm text-muted-foreground", children: [isBulk ? (_jsxs(_Fragment, { children: ["Weet je zeker dat je", " ", _jsxs("span", { className: "font-medium text-foreground", children: [rows.length, " geselecteerde regels"] }), " ", "wilt ", verb, "? Dit gaat om ", plan.total, " bestelling(en) in totaal."] })) : (_jsxs(_Fragment, { children: ["Weet je zeker dat je ", plan.total > 1 ? "deze buffer met " : "", _jsx("span", { className: "font-medium text-foreground", children: plan.total > 1
310
+ ? `${plan.total} bestellingen`
311
+ : single?.orderNumber || single?.id }), " ", "wilt ", verb, "?"] })), isCancel
312
+ ? " De bestelling blijft bewaard onder Geannuleerd, gaat uit de planning en facturatie, en de voorraad wordt teruggeboekt."
313
+ : " De bestelling wordt definitief gewist; resterende voorraadmutaties worden teruggedraaid."] }), isCancel && (_jsxs("label", { className: "mt-4 block text-sm text-foreground", children: ["Reden (optioneel)", _jsx("textarea", { value: cancelReason, onChange: (e) => setCancelReason(e.target.value), rows: 2, maxLength: 500, placeholder: "Bijv. klant heeft geannuleerd", className: "mt-1 w-full rounded-md border border-input bg-background p-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" })] })), plan.skipped > 0 && (_jsxs("p", { className: "mt-2 text-sm text-amber-600", children: [plan.skipped, " van de ", plan.total, " zijn Floriday-orders en worden overgeslagen."] })), plan.deletable.length === 0 && (_jsxs("p", { className: "mt-2 text-sm text-destructive", children: ["Geen van deze regels kan hier ", verb, "."] }))] }));
314
+ })(), _jsxs("div", { className: "mt-5 flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPendingAction(null), disabled: deleting, children: "Sluiten" }), _jsx(Button, { size: "sm", onClick: handleConfirmAction, disabled: deleting || collectDeletePlan(pendingAction.rows).deletable.length === 0, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: deleting
315
+ ? `Bezig… ${deleteProgress}/${collectDeletePlan(pendingAction.rows).deletable.length}`
316
+ : pendingAction.mode === "cancel"
317
+ ? "Order annuleren"
318
+ : "Definitief verwijderen" })] })] }) })), editOrderId && (_jsx(EditLocalOrderDialog, { orderId: editOrderId, open: !!editOrderId, onOpenChange: (o) => !o && setEditOrderId(null) })), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 text-sm text-muted-foreground", children: [_jsx("div", { children: totalCount === 0
205
319
  ? "Geen resultaten"
206
320
  : totalCount === 1
207
321
  ? "1 resultaat"