@flowselections/mailbox-orders 1.0.57 → 1.0.59

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 (36) hide show
  1. package/dist-lib/components/CartAssignmentWizard.d.ts +6 -0
  2. package/dist-lib/components/CartAssignmentWizard.d.ts.map +1 -0
  3. package/dist-lib/components/CartAssignmentWizard.js +199 -0
  4. package/dist-lib/components/EmailPreview.d.ts.map +1 -1
  5. package/dist-lib/components/EmailPreview.js +2 -1
  6. package/dist-lib/components/MailboxInboxPage.d.ts +2 -0
  7. package/dist-lib/components/MailboxInboxPage.d.ts.map +1 -0
  8. package/dist-lib/components/MailboxInboxPage.js +120 -0
  9. package/dist-lib/components/MailboxPageFrame.js +1 -1
  10. package/dist-lib/components/MailboxProposalsPage.js +3 -3
  11. package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -1
  12. package/dist-lib/components/MailboxSkippedPage.js +4 -4
  13. package/dist-lib/components/fs-ui/DataTable.js +3 -3
  14. package/dist-lib/components/settings/ImapAccountsCard.d.ts +2 -0
  15. package/dist-lib/components/settings/ImapAccountsCard.d.ts.map +1 -0
  16. package/dist-lib/components/settings/ImapAccountsCard.js +136 -0
  17. package/dist-lib/components/settings/ImapProfilesCard.d.ts +2 -0
  18. package/dist-lib/components/settings/ImapProfilesCard.d.ts.map +1 -0
  19. package/dist-lib/components/settings/ImapProfilesCard.js +101 -0
  20. package/dist-lib/components/settings/MailboxSettingsCard.d.ts +2 -0
  21. package/dist-lib/components/settings/MailboxSettingsCard.d.ts.map +1 -0
  22. package/dist-lib/components/settings/MailboxSettingsCard.js +85 -0
  23. package/dist-lib/integrations/supabase/client.server.js +1 -1
  24. package/dist-lib/integrations/supabase/types.d.ts +76 -0
  25. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  26. package/dist-lib/lib/imap.functions.d.ts +32852 -0
  27. package/dist-lib/lib/imap.functions.d.ts.map +1 -0
  28. package/dist-lib/lib/imap.functions.js +235 -0
  29. package/dist-lib/lib/mailbox-auto.server.d.ts +9 -0
  30. package/dist-lib/lib/mailbox-auto.server.d.ts.map +1 -0
  31. package/dist-lib/lib/mailbox-auto.server.js +198 -0
  32. package/dist-lib/lib/mailbox-filter-settings.functions.d.ts +2 -2
  33. package/dist-lib/lib/mailbox-filter-settings.functions.js +3 -3
  34. package/dist-lib/lib/mailbox.functions.d.ts +1 -1
  35. package/dist-lib/lib/mailbox.functions.js +6 -6
  36. package/package.json +2 -2
@@ -0,0 +1,6 @@
1
+ export declare function CartAssignmentWizard({ orderId, open, onClose, }: {
2
+ orderId: string | null;
3
+ open: boolean;
4
+ onClose: () => void;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=CartAssignmentWizard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartAssignmentWizard.d.ts","sourceRoot":"","sources":["../../src/components/CartAssignmentWizard.tsx"],"names":[],"mappings":"AA2CA,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EAAE,IAAI,EAAE,OAAO,GACvB,EAAE;IAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,+BAsUhE"}
@@ -0,0 +1,199 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Button, Badge, Card, CardContent, CardHeader, CardTitle, Input, Label, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
6
+ import { Plus, Trash2, AlertTriangle, CheckCircle2 } from "lucide-react";
7
+ import { planAndSaveAutoCarts, saveManualCarts } from "../lib/mailbox.functions.js";
8
+ export function CartAssignmentWizard({ orderId, open, onClose, }) {
9
+ const fetchPlan = useServerFn(planAndSaveAutoCarts);
10
+ const fetchSave = useServerFn(saveManualCarts);
11
+ const { data, isLoading, refetch } = useQuery({
12
+ queryKey: ["cart-plan", orderId],
13
+ enabled: !!orderId && open,
14
+ queryFn: async () => {
15
+ try {
16
+ return await fetchPlan({ data: { orderId: orderId } });
17
+ }
18
+ catch (e) {
19
+ toast.error(e?.message ?? "Indelen mislukt");
20
+ return { autoCarts: [], remainders: [], existingManualCarts: [], nextCartNumber: 1 };
21
+ }
22
+ },
23
+ });
24
+ const autoCarts = Array.isArray(data?.autoCarts) ? data.autoCarts : [];
25
+ const remainders = Array.isArray(data?.remainders) ? data.remainders : [];
26
+ const existingManualCarts = Array.isArray(data?.existingManualCarts) ? data.existingManualCarts : [];
27
+ // Pool met nog in te delen fusten per orderregel
28
+ const [pool, setPool] = useState({});
29
+ const [manual, setManual] = useState([]);
30
+ useEffect(() => {
31
+ const p = {};
32
+ remainders.forEach((r) => { p[r.order_item_id] = r.crates_remaining; });
33
+ setPool(p);
34
+ // Bestaande handmatige karren herstellen — capaciteitsmeta uit remainders aanvullen.
35
+ const remMeta = {};
36
+ remainders.forEach((r) => { remMeta[r.order_item_id] = r; });
37
+ setManual(existingManualCarts.map((c) => ({
38
+ id: crypto.randomUUID(),
39
+ items: c.items.map((i) => {
40
+ const meta = i.order_item_id ? remMeta[i.order_item_id] : null;
41
+ return {
42
+ ...i,
43
+ crates_per_layer: meta?.crates_per_layer ?? i.crates_per_layer ?? 1,
44
+ layers_per_cart: meta?.layers_per_cart ?? i.layers_per_cart ?? 1,
45
+ };
46
+ }),
47
+ })));
48
+ }, [data]);
49
+ const remByItem = useMemo(() => {
50
+ const m = {};
51
+ remainders.forEach((r) => { m[r.order_item_id] = r; });
52
+ return m;
53
+ }, [remainders]);
54
+ const addCart = () => setManual((m) => [...m, { id: crypto.randomUUID(), items: [] }]);
55
+ const addItemToCart = (cartId, orderItemId, crates) => {
56
+ const rem = remByItem[orderItemId];
57
+ if (!rem)
58
+ return;
59
+ const available = pool[orderItemId] ?? 0;
60
+ const take = Math.max(0, Math.min(crates, available));
61
+ if (take === 0)
62
+ return;
63
+ setPool((p) => ({ ...p, [orderItemId]: available - take }));
64
+ setManual((carts) => carts.map((c) => {
65
+ if (c.id !== cartId)
66
+ return c;
67
+ // merge in plaats van duplicate regel
68
+ const existingIdx = c.items.findIndex((i) => i.order_item_id === orderItemId);
69
+ if (existingIdx >= 0) {
70
+ const next = [...c.items];
71
+ next[existingIdx] = { ...next[existingIdx], crates: next[existingIdx].crates + take };
72
+ return { ...c, items: next };
73
+ }
74
+ return {
75
+ ...c,
76
+ items: [...c.items, {
77
+ order_item_id: orderItemId,
78
+ product_id: rem.product_id,
79
+ product_name: rem.product_name,
80
+ crates: take,
81
+ plants_per_crate: rem.plants_per_crate,
82
+ crates_per_layer: rem.crates_per_layer,
83
+ layers_per_cart: rem.layers_per_cart,
84
+ }],
85
+ };
86
+ }));
87
+ };
88
+ const removeItem = (cartId, orderItemId) => {
89
+ setManual((carts) => carts.map((c) => {
90
+ if (c.id !== cartId)
91
+ return c;
92
+ const item = c.items.find((i) => i.order_item_id === orderItemId);
93
+ if (!item)
94
+ return c;
95
+ setPool((p) => ({ ...p, [orderItemId]: (p[orderItemId] ?? 0) + item.crates }));
96
+ return { ...c, items: c.items.filter((i) => i.order_item_id !== orderItemId) };
97
+ }));
98
+ };
99
+ const removeCart = (cartId) => {
100
+ setManual((carts) => {
101
+ const c = carts.find((x) => x.id === cartId);
102
+ if (c) {
103
+ setPool((p) => {
104
+ const np = { ...p };
105
+ c.items.forEach((i) => {
106
+ if (!i.order_item_id)
107
+ return;
108
+ np[i.order_item_id] = (np[i.order_item_id] ?? 0) + i.crates;
109
+ });
110
+ return np;
111
+ });
112
+ }
113
+ return carts.filter((x) => x.id !== cartId);
114
+ });
115
+ };
116
+ // Capaciteit per kar = max van betrokken artikelen (gemengde karren toegestaan,
117
+ // pakken we de hoogste cratesPerCart als bovengrens; waarschuwing toont per item).
118
+ const cartCapacity = (cart) => cart.items.length === 0
119
+ ? 0
120
+ : Math.max(...cart.items.map((i) => i.crates_per_layer * i.layers_per_cart));
121
+ const cartUsed = (cart) => cart.items.reduce((s, i) => s + i.crates, 0);
122
+ const totalRemainingInPool = Object.values(pool).reduce((s, n) => s + n, 0);
123
+ const handleSave = async () => {
124
+ if (totalRemainingInPool > 0) {
125
+ toast.error(`Nog ${totalRemainingInPool} fust niet ingedeeld — verdeel alles over een kar voordat je opslaat`);
126
+ return;
127
+ }
128
+ if (manual.some((c) => c.items.length === 0)) {
129
+ toast.error("Lege karren niet toegestaan");
130
+ return;
131
+ }
132
+ const overfull = manual.find((c) => cartUsed(c) > cartCapacity(c));
133
+ if (overfull) {
134
+ toast.error("Een kar overschrijdt de capaciteit");
135
+ return;
136
+ }
137
+ try {
138
+ await fetchSave({
139
+ data: {
140
+ orderId: orderId,
141
+ carts: manual.map((c) => ({
142
+ is_complete: cartUsed(c) >= cartCapacity(c),
143
+ items: c.items.map((i) => ({
144
+ order_item_id: i.order_item_id,
145
+ product_id: i.product_id,
146
+ product_name: i.product_name,
147
+ crates: Math.max(1, Math.trunc(i.crates)),
148
+ plants_per_crate: Math.max(1, Math.trunc(i.plants_per_crate || 1)),
149
+ crates_per_layer: Math.max(1, Math.trunc(i.crates_per_layer || 1)),
150
+ })),
151
+ })),
152
+ },
153
+ });
154
+ toast.success("Karindeling opgeslagen");
155
+ onClose();
156
+ }
157
+ catch (e) {
158
+ // requireSupabaseAuth en input-validatie gooien een Response; toString
159
+ // daarvan is "[object Response]" — zet om in leesbare melding zodat
160
+ // de React error-overlay niet wordt getriggerd.
161
+ let msg = "Opslaan mislukt";
162
+ if (e instanceof Response) {
163
+ try {
164
+ msg = (await e.text()) || `HTTP ${e.status}`;
165
+ }
166
+ catch {
167
+ msg = `HTTP ${e.status}`;
168
+ }
169
+ if (e.status === 401)
170
+ msg = "Sessie verlopen — log opnieuw in";
171
+ }
172
+ else if (e?.message) {
173
+ msg = e.message;
174
+ }
175
+ toast.error(msg);
176
+ console.error("saveManualCarts failed:", e);
177
+ }
178
+ };
179
+ return (_jsx(Dialog, { open: open, onOpenChange: (o) => { if (!o)
180
+ onClose(); }, children: _jsxs(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] overflow-y-auto p-6", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Karindeling" }) }), isLoading ? (_jsx("div", { className: "text-muted-foreground", children: "Karren worden berekend..." })) : (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { className: "text-base", children: "Automatisch ingedeelde karren" }), _jsxs(Badge, { variant: "secondary", children: [autoCarts.length, " kar(ren)"] })] }), _jsx(CardContent, { children: autoCarts.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen volledige karren mogelijk." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Kar #" }), _jsx(TableHead, { children: "Artikel" }), _jsx(TableHead, { className: "text-right", children: "Fusten" }), _jsx(TableHead, { className: "text-right", children: "Planten" }), _jsx(TableHead, { className: "text-right", children: "Bezetting" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: autoCarts.map((c) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono", children: c.cart_number }), _jsx(TableCell, { children: c.product_name }), _jsx(TableCell, { className: "text-right", children: c.crates }), _jsx(TableCell, { className: "text-right", children: c.plants }), _jsx(TableCell, { className: "text-right", children: "100%" }), _jsx(TableCell, { children: _jsxs(Badge, { className: "gap-1", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), " Volledig"] }) })] }, c.cart_number))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Handmatig in te delen fusten" }) }), _jsx(CardContent, { className: "space-y-3", children: remainders.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen restant \u2014 alles past volledig op karren." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel" }), _jsx(TableHead, { className: "text-right", children: "Beschikbaar (fusten)" }), _jsx(TableHead, { className: "text-right", children: "Planten/fust" }), _jsx(TableHead, { className: "text-right", children: "Capaciteit/kar" })] }) }), _jsx(TableBody, { children: remainders.map((r) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: r.product_name }), _jsxs(TableCell, { className: "text-right font-mono", children: [pool[r.order_item_id] ?? 0, " / ", r.crates_remaining] }), _jsx(TableCell, { className: "text-right", children: r.plants_per_crate }), _jsx(TableCell, { className: "text-right", children: r.crates_per_layer * r.layers_per_cart })] }, r.order_item_id))) })] })) })] }), remainders.length > 0 && (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { className: "text-base", children: "Restant-karren samenstellen" }), _jsxs(Button, { size: "sm", onClick: addCart, children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Kar toevoegen"] })] }), _jsxs(CardContent, { className: "space-y-3", children: [manual.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Voeg een kar toe om restant-fusten in te delen." })), manual.map((cart, idx) => {
181
+ const used = cartUsed(cart);
182
+ const cap = cartCapacity(cart);
183
+ const pct = cap > 0 ? Math.round((used / cap) * 100) : 0;
184
+ const over = cap > 0 && used > cap;
185
+ return (_jsxs(Card, { className: over ? "border-destructive" : "", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "font-medium", children: ["Restant-kar ", idx + 1] }), _jsxs(Badge, { variant: over ? "destructive" : "secondary", children: [used, " / ", cap || "?", " fusten (", pct, "%)"] }), over && (_jsxs("span", { className: "text-xs text-destructive flex items-center gap-1", children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), " Capaciteit overschreden"] }))] }), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => removeCart(cart.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs(CardContent, { className: "space-y-2", children: [cart.items.map((i) => (_jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: i.product_name }), _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", i.crates, " fust (", i.crates * i.plants_per_crate, " planten)"] })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => removeItem(cart.id, i.order_item_id), children: _jsx(Trash2, { className: "h-3 w-3" }) })] }, i.order_item_id))), _jsx(AddItemRow, { remainders: remainders, pool: pool, onAdd: (itemId, crates) => addItemToCart(cart.id, itemId, crates) })] })] }, cart.id));
186
+ })] })] }))] })), _jsxs(DialogFooter, { className: "gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, children: "Annuleren" }), _jsxs(Button, { onClick: handleSave, disabled: isLoading || totalRemainingInPool > 0, children: ["Karindeling opslaan", totalRemainingInPool > 0 && (_jsxs(Badge, { variant: "destructive", className: "ml-2", children: ["Nog ", totalRemainingInPool, " fust in te delen"] }))] })] })] }) }));
187
+ }
188
+ function AddItemRow({ remainders, pool, onAdd, }) {
189
+ const available = remainders.filter((r) => (pool[r.order_item_id] ?? 0) > 0);
190
+ const [sel, setSel] = useState("");
191
+ const [qty, setQty] = useState(1);
192
+ if (available.length === 0)
193
+ return null;
194
+ return (_jsxs("div", { className: "flex items-end gap-2 pt-2 border-t", children: [_jsxs("div", { className: "flex-1", children: [_jsx(Label, { className: "text-xs", children: "Artikel" }), _jsxs(Select, { value: sel, onValueChange: setSel, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies restant-artikel..." }) }), _jsx(SelectContent, { children: available.map((r) => (_jsxs(SelectItem, { value: r.order_item_id, children: [r.product_name, " (", pool[r.order_item_id], " fust beschikbaar)"] }, r.order_item_id))) })] })] }), _jsxs("div", { className: "w-24", children: [_jsx(Label, { className: "text-xs", children: "Fusten" }), _jsx(Input, { type: "number", min: 1, value: qty, onChange: (e) => setQty(Math.max(1, Number(e.target.value) || 1)) })] }), _jsxs(Button, { size: "sm", disabled: !sel, onClick: () => { if (sel) {
195
+ onAdd(sel, qty);
196
+ setSel("");
197
+ setQty(1);
198
+ } }, children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Toevoegen"] })] }));
199
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"EmailPreview.d.ts","sourceRoot":"","sources":["../../src/components/EmailPreview.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC;IACtB,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAsEF,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,QAAQ,EACR,SAAS,EACT,UAAU,EACV,EAAO,EACP,EAAO,EACP,QAAQ,EACR,QAAQ,EACR,WAAgB,EAChB,cAAc,GACf,EAAE,KAAK,+BAsHP"}
1
+ {"version":3,"file":"EmailPreview.d.ts","sourceRoot":"","sources":["../../src/components/EmailPreview.tsx"],"names":[],"mappings":"AAKA,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC;IACtB,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAsEF,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,QAAQ,EACR,SAAS,EACT,UAAU,EACV,EAAO,EACP,EAAO,EACP,QAAQ,EACR,QAAQ,EACR,WAAgB,EAChB,cAAc,GACf,EAAE,KAAK,+BAsHP"}
@@ -1,4 +1,5 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { fmtDayTime } from "../lib/format-date.js";
2
3
  import { useEffect, useMemo, useState } from "react";
3
4
  import { Button, Badge } from "@flowselections/core";
4
5
  import { Paperclip, Download, Code, FileText, ShieldOff, Shield } from "lucide-react";
@@ -52,5 +53,5 @@ export function EmailPreview({ subject, fromName, fromEmail, receivedAt, to = []
52
53
  const rendered = blockRemoteContent ? blockRemote(html) : html;
53
54
  const safeAttachments = Array.isArray(attachments) ? attachments : [];
54
55
  const inlineOnly = safeAttachments.every((a) => !!a.contentId);
55
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-1 border-b pb-3", children: [_jsx("div", { className: "text-base font-semibold leading-snug", children: subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-foreground", children: fromName || fromEmail }), fromName && fromEmail ? ` <${fromEmail}>` : "", receivedAt ? ` · ${new Date(receivedAt).toLocaleString("nl-NL")}` : ""] }), _jsx(AddressLine, { label: "Aan", list: to }), _jsx(AddressLine, { label: "CC", list: cc }), (safeAttachments.length > 0 || hasAttachments) && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2", children: [safeAttachments.length === 0 ? (_jsxs(Badge, { variant: "outline", className: "gap-1 font-normal", children: [_jsx(Paperclip, { className: "h-3 w-3" }), " Deze mail bevat bijlagen (nog niet opgehaald)"] })) : (safeAttachments.map((a, i) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded border bg-muted/40 px-2 py-1 text-xs", children: [_jsx(Paperclip, { className: "h-3 w-3 shrink-0" }), _jsx("span", { className: "max-w-[16rem] truncate", children: a.name }), formatSize(a.size) && (_jsx("span", { className: "text-muted-foreground", children: formatSize(a.size) })), a.url && (_jsx("a", { href: a.url, target: "_blank", rel: "noopener noreferrer", className: "text-primary", "aria-label": `Download ${a.name}`, children: _jsx(Download, { className: "h-3 w-3" }) }))] }, `${a.name}-${i}`)))), safeAttachments.length > 0 && inlineOnly && (_jsx("span", { className: "text-[11px] text-muted-foreground", children: "(in de mail ingesloten afbeeldingen)" }))] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2", children: [_jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: () => setMode(mode === "html" ? "text" : "html"), disabled: !html, children: mode === "html" ? (_jsxs(_Fragment, { children: [_jsx(FileText, { className: "mr-1 h-3.5 w-3.5" }), " Platte tekst"] })) : (_jsxs(_Fragment, { children: [_jsx(Code, { className: "mr-1 h-3.5 w-3.5" }), " Opgemaakte mail"] })) }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", disabled: mode !== "html" || !html, onClick: () => setBlockRemoteContent((v) => !v), children: blockRemoteContent ? (_jsxs(_Fragment, { children: [_jsx(Shield, { className: "mr-1 h-3.5 w-3.5" }), " Externe inhoud tonen"] })) : (_jsxs(_Fragment, { children: [_jsx(ShieldOff, { className: "mr-1 h-3.5 w-3.5" }), " Externe inhoud blokkeren"] })) })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden pt-3", children: mode === "html" && html ? (_jsx(EmailFrame, { html: rendered })) : (_jsx("div", { className: "h-full w-full overflow-y-auto overscroll-contain rounded border bg-white p-3", children: _jsx("pre", { className: "whitespace-pre-wrap break-words font-sans text-sm leading-relaxed text-[#111]", children: bodyText || "(geen tekst)" }) })) })] }));
56
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-1 border-b pb-3", children: [_jsx("div", { className: "text-base font-semibold leading-snug", children: subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-foreground", children: fromName || fromEmail }), fromName && fromEmail ? ` <${fromEmail}>` : "", receivedAt ? ` · ${fmtDayTime(receivedAt)}` : ""] }), _jsx(AddressLine, { label: "Aan", list: to }), _jsx(AddressLine, { label: "CC", list: cc }), (safeAttachments.length > 0 || hasAttachments) && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2", children: [safeAttachments.length === 0 ? (_jsxs(Badge, { variant: "outline", className: "gap-1 font-normal", children: [_jsx(Paperclip, { className: "h-3 w-3" }), " Deze mail bevat bijlagen (nog niet opgehaald)"] })) : (safeAttachments.map((a, i) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded border bg-muted/40 px-2 py-1 text-xs", children: [_jsx(Paperclip, { className: "h-3 w-3 shrink-0" }), _jsx("span", { className: "max-w-[16rem] truncate", children: a.name }), formatSize(a.size) && (_jsx("span", { className: "text-muted-foreground", children: formatSize(a.size) })), a.url && (_jsx("a", { href: a.url, target: "_blank", rel: "noopener noreferrer", className: "text-primary", "aria-label": `Download ${a.name}`, children: _jsx(Download, { className: "h-3 w-3" }) }))] }, `${a.name}-${i}`)))), safeAttachments.length > 0 && inlineOnly && (_jsx("span", { className: "text-[11px] text-muted-foreground", children: "(in de mail ingesloten afbeeldingen)" }))] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2", children: [_jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: () => setMode(mode === "html" ? "text" : "html"), disabled: !html, children: mode === "html" ? (_jsxs(_Fragment, { children: [_jsx(FileText, { className: "mr-1 h-3.5 w-3.5" }), " Platte tekst"] })) : (_jsxs(_Fragment, { children: [_jsx(Code, { className: "mr-1 h-3.5 w-3.5" }), " Opgemaakte mail"] })) }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", disabled: mode !== "html" || !html, onClick: () => setBlockRemoteContent((v) => !v), children: blockRemoteContent ? (_jsxs(_Fragment, { children: [_jsx(Shield, { className: "mr-1 h-3.5 w-3.5" }), " Externe inhoud tonen"] })) : (_jsxs(_Fragment, { children: [_jsx(ShieldOff, { className: "mr-1 h-3.5 w-3.5" }), " Externe inhoud blokkeren"] })) })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden pt-3", children: mode === "html" && html ? (_jsx(EmailFrame, { html: rendered })) : (_jsx("div", { className: "h-full w-full overflow-y-auto overscroll-contain rounded border bg-white p-3", children: _jsx("pre", { className: "whitespace-pre-wrap break-words font-sans text-sm leading-relaxed text-[#111]", children: bodyText || "(geen tekst)" }) })) })] }));
56
57
  }
@@ -0,0 +1,2 @@
1
+ export declare function MailboxInboxPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=MailboxInboxPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MailboxInboxPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxInboxPage.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,gBAAgB,gCAuO/B"}
@@ -0,0 +1,120 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Button, Badge, Card, CardContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
6
+ import { Mail, Sparkles, AlertCircle, ExternalLink, RefreshCw } from "lucide-react";
7
+ import { listMessages, parseMessage, getActiveOrdersMailbox, ingestEmailEvent, backfillRecentEvents, } from "../lib/mailbox.functions.js";
8
+ import { supabase } from "../integrations/supabase/client.js";
9
+ const AUTH_MODULE_URL = "https://auth.flowselections.com/e-mail-authenticatie";
10
+ const STATUS_VARIANT = {
11
+ new: "secondary",
12
+ parsing: "outline",
13
+ parsed: "default",
14
+ failed: "destructive",
15
+ ignored: "outline",
16
+ converted: "default",
17
+ };
18
+ export function MailboxInboxPage() {
19
+ const qc = useQueryClient();
20
+ const fetchList = useServerFn(listMessages);
21
+ const fetchParse = useServerFn(parseMessage);
22
+ const fetchActive = useServerFn(getActiveOrdersMailbox);
23
+ const fetchIngest = useServerFn(ingestEmailEvent);
24
+ const fetchBackfill = useServerFn(backfillRecentEvents);
25
+ const { data, isLoading } = useQuery({
26
+ queryKey: ["mailbox-messages"],
27
+ queryFn: () => fetchList(),
28
+ });
29
+ const { data: active, isSuccess: activeLoaded } = useQuery({
30
+ queryKey: ["mailbox-active-account"],
31
+ queryFn: () => fetchActive(),
32
+ refetchInterval: 60000,
33
+ retry: 1,
34
+ });
35
+ // Realtime: nieuwe events uit gedeelde authenticatiemodule
36
+ const activeId = active?.connected ? active.id : null;
37
+ useEffect(() => {
38
+ if (!activeId)
39
+ return;
40
+ const channel = supabase
41
+ .channel(`email_auth_events:${activeId}`)
42
+ .on("postgres_changes", {
43
+ event: "INSERT",
44
+ schema: "public",
45
+ table: "email_auth_events",
46
+ filter: `connection_id=eq.${activeId}`,
47
+ }, async (payload) => {
48
+ const evId = payload?.new?.id;
49
+ if (!evId)
50
+ return;
51
+ try {
52
+ const res = await fetchIngest({ data: { eventId: evId } });
53
+ if (res?.ok && (res.inserted ?? 0) > 0) {
54
+ const app = res.autoApproved ?? 0;
55
+ if (app > 0)
56
+ toast.success(`Nieuwe e-mail — ${app} automatisch omgezet`);
57
+ else
58
+ toast.success("Nieuwe e-mail ontvangen");
59
+ qc.invalidateQueries({ queryKey: ["mailbox-messages"] });
60
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
61
+ }
62
+ }
63
+ catch {
64
+ /* stille fout — backfill vangt het later op */
65
+ }
66
+ })
67
+ .subscribe();
68
+ return () => {
69
+ void supabase.removeChannel(channel);
70
+ };
71
+ }, [activeId, fetchIngest, qc]);
72
+ // Backfill bij eerste mount + iedere 60s (vangt gemiste events op)
73
+ const didInitialBackfill = useRef(false);
74
+ const backfillMut = useMutation({
75
+ mutationFn: async () => {
76
+ try {
77
+ return await fetchBackfill();
78
+ }
79
+ catch {
80
+ // 401's (token verlopen) en tijdelijke fouten stil opvangen
81
+ return { ok: false, inserted: 0 };
82
+ }
83
+ },
84
+ onSuccess: (res) => {
85
+ if (res?.ok && (res.inserted ?? 0) > 0) {
86
+ qc.invalidateQueries({ queryKey: ["mailbox-messages"] });
87
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
88
+ }
89
+ },
90
+ onError: () => {
91
+ /* stille fout — volgende interval probeert opnieuw */
92
+ },
93
+ });
94
+ useEffect(() => {
95
+ if (!activeId || didInitialBackfill.current)
96
+ return;
97
+ didInitialBackfill.current = true;
98
+ backfillMut.mutate();
99
+ const t = setInterval(() => backfillMut.mutate(), 60000);
100
+ return () => clearInterval(t);
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, [activeId]);
103
+ const parseMut = useMutation({
104
+ mutationFn: (messageId) => fetchParse({ data: { messageId } }),
105
+ onSuccess: (res) => {
106
+ if (res?.ok) {
107
+ toast.success("E-mail geparsed naar voorstel");
108
+ qc.invalidateQueries({ queryKey: ["mailbox-messages"] });
109
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
110
+ }
111
+ else {
112
+ toast.error(res?.error ?? "Parsen mislukt");
113
+ }
114
+ },
115
+ onError: (e) => toast.error(e?.message ?? "Parsen mislukt"),
116
+ });
117
+ const items = Array.isArray(data?.items) ? data.items : [];
118
+ const needsReauth = active?.connected && active.needs_reauth;
119
+ return (_jsxs("div", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsxs("h1", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Mail, { className: "h-6 w-6 text-primary" }), " Inbox"] }), _jsx("p", { className: "text-muted-foreground text-sm mt-1", children: "Binnenkomende bestel-e-mails \u2014 geleverd door de E-mail Authenticatie module" })] }), _jsxs(Button, { variant: "outline", onClick: () => backfillMut.mutate(), disabled: backfillMut.isPending || !active?.connected, title: "Haal achterstallige events op", children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${backfillMut.isPending ? "animate-spin" : ""}` }), "Synchroniseer"] })] }), activeLoaded && !active?.connected ? (_jsx(Card, { children: _jsxs(CardContent, { className: "p-4 flex items-start gap-3 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive mt-0.5" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Nog geen e-mailadres gekoppeld" }), _jsxs("div", { className: "text-muted-foreground", children: ["Koppel in de module ", _jsx("strong", { children: "E-mail Authenticatie" }), " een adres met doeleinde", " ", _jsx("code", { children: "orders" }), " en richting ", _jsx("code", { children: "receive" }), "."] })] }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs("a", { href: AUTH_MODULE_URL, target: "_blank", rel: "noreferrer", children: ["Open module ", _jsx(ExternalLink, { className: "h-3 w-3 ml-1" })] }) })] }) })) : needsReauth ? (_jsx(Card, { className: "border-destructive", children: _jsxs(CardContent, { className: "p-4 flex items-start gap-3 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive mt-0.5" }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "font-medium", children: ["Heraanmelding vereist voor ", active.account_email] }), _jsx("div", { className: "text-muted-foreground", children: active.last_error ?? "De koppeling is verlopen. Log opnieuw in via de authenticatiemodule." })] }), _jsx(Button, { asChild: true, variant: "destructive", size: "sm", children: _jsxs("a", { href: `${AUTH_MODULE_URL}?focus=${active.id}`, target: "_blank", rel: "noreferrer", children: ["Opnieuw verbinden ", _jsx(ExternalLink, { className: "h-3 w-3 ml-1" })] }) })] }) })) : active?.connected ? (_jsx(Card, { children: _jsxs(CardContent, { className: "p-4 flex items-center gap-3 text-sm", children: [_jsx(Mail, { className: "h-4 w-4 text-primary" }), _jsx("span", { className: "text-muted-foreground", children: "Actieve mailbox:" }), _jsx("span", { className: "font-medium", children: active.account_email }), _jsx(Badge, { variant: "outline", className: "ml-2", children: active.provider }), active.status && active.status !== "ok" && active.status !== "verified" && (_jsx(Badge, { variant: "secondary", className: "ml-2", children: active.status }))] }) })) : null, _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: isLoading ? (_jsx("div", { className: "p-8 text-center text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("div", { className: "p-8 text-center text-muted-foreground", children: "Nog geen e-mails ontvangen. Nieuwe berichten verschijnen automatisch." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Afzender" }), _jsx(TableHead, { children: "Onderwerp" }), _jsx(TableHead, { children: "Ontvangen" }), _jsx(TableHead, { className: "text-right", children: "Actie" })] }) }), _jsx(TableBody, { children: items.map((m) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Badge, { variant: STATUS_VARIANT[m.status] ?? "secondary", children: m.status }) }), _jsxs(TableCell, { children: [_jsx("div", { className: "text-sm font-medium", children: m.from_name || m.from_email }), m.from_name && _jsx("div", { className: "text-xs text-muted-foreground", children: m.from_email })] }), _jsx(TableCell, { className: "max-w-md truncate", children: m.subject || "(geen onderwerp)" }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: m.received_at ? new Date(m.received_at).toLocaleString("nl-NL") : "-" }), _jsx(TableCell, { className: "text-right", children: m.status !== "converted" && (_jsxs(Button, { size: "sm", variant: "outline", disabled: parseMut.isPending, onClick: () => parseMut.mutate(m.id), children: [_jsx(Sparkles, { className: "h-3 w-3 mr-1" }), m.status === "parsed" ? "Opnieuw parsen" : "Parse"] })) })] }, m.id))) })] })) }) })] }));
120
+ }
@@ -95,7 +95,7 @@ export function MailboxPageFrame({ tab, children, onKpi, activeKpi, notices = []
95
95
  if (tab !== "voorstellen")
96
96
  go("voorstellen");
97
97
  };
98
- return (_jsxs("main", { 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 min-w-0 flex-col gap-4 overflow-hidden overflow-x-clip p-4 pb-24 sm:p-6 md:pb-6", children: [_jsx(PageHeader, { title: "Mailbox bestellingen", meta: _jsx(UpdatedAt, { at: kpiQ.dataUpdatedAt || null, stale: kpiQ.isError, info: statusInfo(status) }) }), _jsx(NoticeBar, { notices: allNotices }), _jsxs(KpiRow, { children: [_jsx(KpiTile, { title: "Te controleren", value: k?.pending ?? 0, sub: "Bestellingen uit e-mail", icon: _jsx(ClipboardCheck, {}), loading: loading, active: activeKpi === "pending", onClick: () => kpi("pending") }), _jsx(KpiTile, { title: "Vandaag binnen", value: k?.pending_today ?? 0, sub: "Nog te controleren", icon: _jsx(CalendarClock, {}), loading: loading, active: activeKpi === "today", onClick: () => kpi("today") }), _jsx(KpiTile, { title: "Vandaag verwerkt", value: k?.approved_today ?? 0, sub: "Orders aangemaakt", icon: _jsx(CheckCircle2, {}), loading: loading, active: activeKpi === "approved_today", onClick: () => kpi("approved_today") }), _jsx(KpiTile, { title: "Klant onbekend", value: k?.customer_unknown ?? 0, sub: (k?.customer_unknown ?? 0) > 0 ? _jsx("span", { className: "text-destructive", children: "Kies of maak de klant" }) : "Alles herkend", icon: _jsx(UserX, {}), tone: "warning", loading: loading, active: activeKpi === "customer_unknown", onClick: () => kpi("customer_unknown") })] }), _jsx(PillTabs, { ariaLabel: "Mailbox", active: tab, onSelect: (t) => go(t), tabs: [
98
+ return (_jsxs("main", { className: "flex w-full min-w-0 flex-col gap-4 overflow-x-clip p-4 pb-24 sm:p-6 md:pb-6", children: [_jsx(PageHeader, { title: "Mailbox bestellingen", meta: _jsx(UpdatedAt, { at: kpiQ.dataUpdatedAt || null, stale: kpiQ.isError, info: statusInfo(status) }) }), _jsx(NoticeBar, { notices: allNotices }), _jsxs(KpiRow, { children: [_jsx(KpiTile, { title: "Te controleren", value: k?.pending ?? 0, sub: "Bestellingen uit e-mail", icon: _jsx(ClipboardCheck, {}), loading: loading, active: activeKpi === "pending", onClick: () => kpi("pending") }), _jsx(KpiTile, { title: "Vandaag binnen", value: k?.pending_today ?? 0, sub: "Nog te controleren", icon: _jsx(CalendarClock, {}), loading: loading, active: activeKpi === "today", onClick: () => kpi("today") }), _jsx(KpiTile, { title: "Vandaag verwerkt", value: k?.approved_today ?? 0, sub: "Orders aangemaakt", icon: _jsx(CheckCircle2, {}), loading: loading, active: activeKpi === "approved_today", onClick: () => kpi("approved_today") }), _jsx(KpiTile, { title: "Klant onbekend", value: k?.customer_unknown ?? 0, sub: (k?.customer_unknown ?? 0) > 0 ? _jsx("span", { className: "text-destructive", children: "Kies of maak de klant" }) : "Alles herkend", icon: _jsx(UserX, {}), tone: "warning", loading: loading, active: activeKpi === "customer_unknown", onClick: () => kpi("customer_unknown") })] }), _jsx(PillTabs, { ariaLabel: "Mailbox", active: tab, onSelect: (t) => go(t), tabs: [
99
99
  { key: "voorstellen", label: "Te controleren", icon: _jsx(Inbox, {}), count: k ? k.pending : null },
100
100
  { key: "verwerkt", label: "Verwerkt", icon: _jsx(CheckCircle2, {}), count: k ? k.processed : null },
101
101
  { key: "overgeslagen", label: "Overgeslagen", icon: _jsx(MailX, {}), count: k ? k.skipped : null },
@@ -15,7 +15,7 @@ import { listProposalsPage } from "../lib/mailbox-list.functions.js";
15
15
  import { MailboxPageFrame, browserTz } from "./MailboxPageFrame.js";
16
16
  import { DataTable, InitialsAvatar, CellText } from "./fs-ui/DataTable.js";
17
17
  import { FilterPanel, FilterOptions, PERIOD_OPTIONS } from "./fs-ui/FilterPanel.js";
18
- import { fmtDayTime } from "../lib/format-date.js";
18
+ import { fmtDay, fmtDayTime } from "../lib/format-date.js";
19
19
  import { StatusIcon } from "./fs-ui/StatusIcon.js";
20
20
  import { EmptyState } from "./fs-ui/EmptyState.js";
21
21
  import { TableSkeleton } from "./fs-ui/TableSkeleton.js";
@@ -705,8 +705,8 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
705
705
  const preview = (previewData?.ok ? previewData.preview : null);
706
706
  const previewError = previewData?.ok === false ? String(previewData.error ?? "") : null;
707
707
  const dateFeasible = preview?.ok === true && preview?.deliveryDateFeasible === true;
708
- const fmtDate = (iso) => iso ? new Date(`${iso}T12:00:00`).toLocaleDateString("nl-NL", { weekday: "long", day: "numeric", month: "long", year: "numeric" }) : "";
709
- const fmtDateTime = (iso) => iso ? new Date(iso).toLocaleString("nl-NL", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }) : "";
708
+ const fmtDate = (iso) => iso ? fmtDay(iso) : "";
709
+ const fmtDateTime = (iso) => iso ? fmtDayTime(iso) : "";
710
710
  // ─── Prijsafspraak ter vergelijking ───────────────────────────────────────
711
711
  const fetchAgreement = useServerFn(getAgreementPrices);
712
712
  const agreementKey = lines.map((l) => `${l.product_id}:${l.quantity}`).join(",");
@@ -1 +1 @@
1
- {"version":3,"file":"MailboxSkippedPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxSkippedPage.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,kBAAkB,gCAoKjC"}
1
+ {"version":3,"file":"MailboxSkippedPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxSkippedPage.tsx"],"names":[],"mappings":"AA0CA,wBAAgB,kBAAkB,gCAoKjC"}
@@ -19,6 +19,7 @@ import { Toolbar } from "./fs-ui/Toolbar.js";
19
19
  import { SearchInput } from "./fs-ui/SearchInput.js";
20
20
  import { FilterChips } from "./fs-ui/FilterChips.js";
21
21
  import { useListHotkeys } from "./fs-ui/useListHotkeys.js";
22
+ import { RowMenu } from "./fs-ui/RowMenu.js";
22
23
  const statusOf = (m) => m.analysis_state === "wacht"
23
24
  ? { label: label(ANALYSIS_STATE, "wacht"), tone: "amber" }
24
25
  : m.filter_outcome === "te_oud" || m.filter_outcome === "geen_bestelling"
@@ -115,7 +116,7 @@ export function MailboxSkippedPage() {
115
116
  onEscape: hasActiveFilters ? clearFilters : undefined,
116
117
  onHelp: () => setHelpOpen(true),
117
118
  });
118
- const actionBtn = (m) => (_jsx(Button, { size: "sm", variant: "outline", className: "h-8", disabled: busyId === m.id, onClick: () => run(m.id), children: busyId === m.id ? "Bezig…" : m.analysis_state === "wacht" ? "Nu uitlezen" : "Toch uitlezen" }));
119
+ const rowAction = (m) => (_jsx(RowMenu, { label: `Acties voor ${m.from_name || m.from_email || "mail"}`, items: [{ label: busyId === m.id ? "Bezig…" : m.analysis_state === "wacht" ? "Nu uitlezen" : "Toch uitlezen", disabled: busyId === m.id, onSelect: () => void run(m.id) }] }));
119
120
  const columns = [
120
121
  {
121
122
  key: "afzender", header: "Afzender", width: 200,
@@ -136,7 +137,6 @@ export function MailboxSkippedPage() {
136
137
  return (_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(StatusIcon, { tone: st.tone === "amber" ? "busy" : "open", label: st.label, detail: reasonText(m) || undefined }), _jsx(CellText, { primary: st.label, secondary: reasonText(m) })] }));
137
138
  },
138
139
  },
139
- { key: "actie", header: "Actie", width: 136, cell: (m) => actionBtn(m) },
140
140
  ];
141
141
  const chips = period !== "all" ? [{ key: "period", label: PERIOD_LABEL[period] ?? period, icon: _jsx(CalendarClock, {}), onRemove: () => setPeriod("all") }] : [];
142
142
  const notices = waiting > 0 ? [{
@@ -145,7 +145,7 @@ export function MailboxSkippedPage() {
145
145
  title: `${waiting} ${waiting === 1 ? "mail wacht" : "mails wachten"} op uitlezen.`,
146
146
  details: "De limiet voor vandaag is bereikt of automatisch uitlezen staat uit. Je kunt een mail ook zelf laten uitlezen.",
147
147
  }] : [];
148
- return (_jsxs(MailboxPageFrame, { tab: "overgeslagen", helpOpen: helpOpen, onHelpOpenChange: setHelpOpen, notices: notices, children: [_jsx(Toolbar, { onOpenFilters: () => setFiltersOpen(true), filterCount: chips.length, chips: _jsx(FilterChips, { chips: chips, onClearAll: clearFilters }), children: _jsx(SearchInput, { ref: searchRef, value: search, onChange: setSearch, placeholder: "Zoek op afzender, onderwerp of reden" }) }), _jsx(FilterPanel, { open: filtersOpen, onOpenChange: setFiltersOpen, onClear: clearFilters, children: _jsx(FilterOptions, { label: "Periode", value: period, options: PERIOD_OPTIONS, onChange: (v) => setPeriod(v) }) }), isLoading && !data ? (_jsx(TableSkeleton, { columns: [200, 300, 132, 200, 136] })) : error && !data ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: _jsx(EmptyState, { icon: _jsx(MailX, { className: "h-6 w-6" }), title: "De lijst kon niet worden geladen", text: "Probeer het opnieuw.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => void refetch(), children: "Opnieuw proberen" }) }) })) : filtered.length === 0 ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: hasActiveFilters
148
+ return (_jsxs(MailboxPageFrame, { tab: "overgeslagen", helpOpen: helpOpen, onHelpOpenChange: setHelpOpen, notices: notices, children: [_jsx(Toolbar, { onOpenFilters: () => setFiltersOpen(true), filterCount: chips.length, chips: _jsx(FilterChips, { chips: chips, onClearAll: clearFilters }), children: _jsx(SearchInput, { ref: searchRef, value: search, onChange: setSearch, placeholder: "Zoek op afzender, onderwerp of reden" }) }), _jsx(FilterPanel, { open: filtersOpen, onOpenChange: setFiltersOpen, onClear: clearFilters, children: _jsx(FilterOptions, { label: "Periode", value: period, options: PERIOD_OPTIONS, onChange: (v) => setPeriod(v) }) }), isLoading && !data ? (_jsx(TableSkeleton, { columns: [200, 300, 132, 200] })) : error && !data ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: _jsx(EmptyState, { icon: _jsx(MailX, { className: "h-6 w-6" }), title: "De lijst kon niet worden geladen", text: "Probeer het opnieuw.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => void refetch(), children: "Opnieuw proberen" }) }) })) : filtered.length === 0 ? (_jsx("div", { className: "rounded-xl border border-border bg-card shadow-sm", children: hasActiveFilters
149
149
  ? _jsx(EmptyState, { icon: _jsx(Search, { className: "h-6 w-6" }), title: "Niets gevonden", text: "Er zijn geen overgeslagen mails die bij je zoekopdracht passen.", action: _jsx(Button, { size: "sm", variant: "outline", onClick: clearFilters, children: "Filters wissen" }) })
150
- : _jsx(EmptyState, { icon: _jsx(MailX, { className: "h-6 w-6" }), title: "Geen overgeslagen mails", text: "Mails die geen bestelling lijken, verschijnen hier met de reden." }) })) : (_jsx(DataTable, { rows: paged, columns: columns, rowKey: (m) => m.id, activeIndex: cursor, sort: { key: "datum", dir: sortOrder === "date-asc" ? "asc" : "desc" }, onSortChange: () => setSortOrder(sortOrder === "date-asc" ? "date-desc" : "date-asc"), mobileCard: (m) => (_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsx("div", { className: "truncate font-semibold", children: m.from_name || m.from_email || "Onbekend" }), _jsx("div", { className: "truncate text-muted-foreground", children: m.subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [fmtDayTime(m.received_at), " \u00B7 ", statusOf(m).label] }), _jsx("div", { className: "pt-1", children: actionBtn(m) })] })), footer: _jsx(TablePagination, { total: filtered.length, page: current, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize }) }))] }));
150
+ : _jsx(EmptyState, { icon: _jsx(MailX, { className: "h-6 w-6" }), title: "Geen overgeslagen mails", text: "Mails die geen bestelling lijken, verschijnen hier met de reden." }) })) : (_jsx(DataTable, { rows: paged, columns: columns, rowKey: (m) => m.id, activeIndex: cursor, rowAction: rowAction, sort: { key: "datum", dir: sortOrder === "date-asc" ? "asc" : "desc" }, onSortChange: () => setSortOrder(sortOrder === "date-asc" ? "date-desc" : "date-asc"), mobileCard: (m) => (_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsx("div", { className: "truncate font-semibold", children: m.from_name || m.from_email || "Onbekend" }), _jsx("div", { className: "truncate text-muted-foreground", children: m.subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [fmtDayTime(m.received_at), " \u00B7 ", statusOf(m).label] })] })), footer: _jsx(TablePagination, { total: filtered.length, page: current, pageSize: pageSize, onPageChange: setPage, onPageSizeChange: setPageSize }) }))] }));
151
151
  }
@@ -3,7 +3,7 @@ import { useEffect, useRef } from "react";
3
3
  import { ArrowDown, ArrowUp, ChevronRight } from "lucide-react";
4
4
  const TABLE_CLS = "w-full [&_thead_th]:h-10 [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:tracking-wide [&_thead_th]:text-muted-foreground min-w-[900px] table-fixed caption-bottom text-sm [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-10 [&_thead_th]:bg-card [&_tbody_tr]:transition-colors";
5
5
  /** Kop- en celruimte zijn gelijk, zodat koptekst en celtekst op dezelfde x beginnen. */
6
- const PAD = "px-4";
6
+ const PAD = "px-2";
7
7
  /**
8
8
  * Tabel exact als Verkooporders: eigen <table> met table-fixed en colgroup in px,
9
9
  * kop 40 px die plakt, rij 60 px, geselecteerde rij met 3 px groene streep.
@@ -13,7 +13,7 @@ export function DataTable({ rows, columns, rowKey, onRowClick, activeIndex, mobi
13
13
  const hasEnd = !!(rowAction || onRowClick);
14
14
  const activeRef = useRef(null);
15
15
  useEffect(() => { activeRef.current?.scrollIntoView({ block: "nearest" }); }, [activeIndex]);
16
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex min-h-0 w-full min-w-0 flex-1 flex-col overflow-clip rounded-xl border border-border bg-card shadow-sm", children: [_jsx("div", { className: "hidden min-h-0 flex-1 overflow-auto overscroll-contain md:block", children: _jsxs("table", { className: TABLE_CLS, children: [_jsxs("colgroup", { children: [columns.map((c) => _jsx("col", { style: { width: `${c.width}px` } }, c.key)), hasEnd && _jsx("col", { style: { width: "56px" } })] }), _jsx("thead", { className: "[&_tr]:border-b", children: _jsxs("tr", { className: "border-b border-border", children: [columns.map((c) => {
16
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex w-full min-w-0 flex-col overflow-clip rounded-xl border border-border bg-card shadow-sm", children: [_jsx("div", { className: "hidden md:block", children: _jsxs("table", { className: TABLE_CLS, children: [_jsxs("colgroup", { children: [columns.map((c) => _jsx("col", { style: { width: `${c.width}px` } }, c.key)), hasEnd && _jsx("col", { style: { width: "56px" } })] }), _jsx("thead", { className: "[&_tr]:border-b", children: _jsxs("tr", { className: "border-b border-border", children: [columns.map((c) => {
17
17
  const active = sort?.key === c.key;
18
18
  return (_jsx("th", { scope: "col", className: PAD + " text-left align-middle", "aria-sort": active ? (sort.dir === "asc" ? "ascending" : "descending") : undefined, children: c.sortable && onSortChange ? (_jsxs("button", { type: "button", onClick: () => onSortChange(c.key), className: "inline-flex max-w-full items-center gap-1 rounded-sm uppercase focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": `Sorteer op ${c.header}`, children: [_jsx("span", { className: "truncate", children: c.header }), active && (sort.dir === "asc" ? _jsx(ArrowUp, { className: "h-3 w-3 shrink-0", "aria-hidden": true }) : _jsx(ArrowDown, { className: "h-3 w-3 shrink-0", "aria-hidden": true }))] })) : _jsx("span", { className: "block truncate", children: c.header }) }, c.key));
19
19
  }), hasEnd && _jsx("th", { scope: "col", className: PAD, children: _jsx("span", { className: "sr-only", children: "Acties" }) })] }) }), _jsx("tbody", { children: rows.map((r, i) => {
@@ -24,7 +24,7 @@ export function DataTable({ rows, columns, rowKey, onRowClick, activeIndex, mobi
24
24
  } } : undefined, style: { height: 60 }, className: "group border-b border-border text-foreground last:border-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring " +
25
25
  (onRowClick ? "cursor-pointer hover:bg-muted/50 " : "") +
26
26
  (active ? "bg-muted/60" : ""), children: [columns.map((c, ci) => (_jsxs("td", { className: PAD + " relative overflow-hidden py-0 align-middle", children: [ci === 0 && active && _jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-[3px] bg-primary" }), c.cell(r)] }, c.key))), hasEnd && (_jsx("td", { className: PAD + " py-0 text-right align-middle", onClick: rowAction ? (e) => e.stopPropagation() : undefined, children: rowAction ? rowAction(r) : (_jsx(ChevronRight, { className: "ml-auto h-4 w-4 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100", "aria-hidden": true })) }))] }, rowKey(r)));
27
- }) })] }) }), _jsx("div", { className: "min-h-0 flex-1 divide-y divide-border overflow-y-auto overscroll-contain md:hidden", children: rows.map((r, i) => (_jsxs("div", { role: onRowClick ? "button" : undefined, tabIndex: onRowClick ? 0 : undefined, onClick: onRowClick ? () => onRowClick(r, i) : undefined, className: "relative min-h-11 p-4 text-sm transition-colors active:bg-muted " + (activeIndex === i ? "bg-muted/60" : ""), children: [activeIndex === i && _jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-[3px] bg-primary" }), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "min-w-0 flex-1", children: mobileCard(r) }), rowAction && _jsx("div", { onClick: (e) => e.stopPropagation(), children: rowAction(r) })] })] }, rowKey(r)))) })] }), footer] }));
27
+ }) })] }) }), _jsx("div", { className: "divide-y divide-border md:hidden", children: rows.map((r, i) => (_jsxs("div", { role: onRowClick ? "button" : undefined, tabIndex: onRowClick ? 0 : undefined, onClick: onRowClick ? () => onRowClick(r, i) : undefined, className: "relative min-h-11 p-4 text-sm transition-colors active:bg-muted " + (activeIndex === i ? "bg-muted/60" : ""), children: [activeIndex === i && _jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-[3px] bg-primary" }), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "min-w-0 flex-1", children: mobileCard(r) }), rowAction && _jsx("div", { onClick: (e) => e.stopPropagation(), children: rowAction(r) })] })] }, rowKey(r)))) })] }), footer] }));
28
28
  }
29
29
  /** Cel met hooguit twee regels: 14 px en 12 px grijs; afkappen met volledige tekst als infoballon. */
30
30
  export function CellText({ primary, secondary, strong }) {
@@ -0,0 +1,2 @@
1
+ export declare function ImapAccountsCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=ImapAccountsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImapAccountsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ImapAccountsCard.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,gBAAgB,gCAwP/B"}