@flowselections/mailbox-orders 1.0.3 → 1.0.5

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 (30) 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/MailboxProcessedPage.d.ts.map +1 -1
  5. package/dist-lib/components/MailboxProcessedPage.js +25 -4
  6. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  7. package/dist-lib/components/MailboxProposalsPage.js +325 -60
  8. package/dist-lib/components/SearchableSelect.d.ts +2 -1
  9. package/dist-lib/components/SearchableSelect.d.ts.map +1 -1
  10. package/dist-lib/components/SearchableSelect.js +4 -3
  11. package/dist-lib/index.d.ts +0 -1
  12. package/dist-lib/index.d.ts.map +1 -1
  13. package/dist-lib/index.js +2 -4
  14. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
  15. package/dist-lib/integrations/supabase/auth-attacher.js +37 -3
  16. package/dist-lib/integrations/supabase/auth-middleware.d.ts +3460 -157
  17. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/client.d.ts +3460 -157
  19. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  20. package/dist-lib/integrations/supabase/client.server.d.ts +3460 -157
  21. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/types.d.ts +3553 -157
  23. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  24. package/dist-lib/integrations/supabase/types.js +2 -0
  25. package/dist-lib/lib/mailbox-template.functions.d.ts +43350 -23532
  26. package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -1
  27. package/dist-lib/lib/mailbox.functions.d.ts +142263 -33138
  28. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/mailbox.functions.js +392 -18
  30. package/package.json +2 -7
@@ -1,12 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useMemo, useState } from "react";
3
- import { useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
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 } from "lucide-react";
7
- import { listProposals, approveProposal, rejectProposal, listCustomersForPicker, listProductsForPicker, } from "../lib/mailbox.functions";
6
+ import { Check, X, Trash2, Plus, AlertTriangle, RefreshCw } from "lucide-react";
7
+ import { listProposals, approveProposal, rejectProposal, listCustomersForPicker, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getOrderPickDays, getActiveOrdersMailbox, ingestEmailEvent, backfillRecentEvents, } from "../lib/mailbox.functions";
8
8
  import { listTemplateFields, listPlantTypesForPicker, listSuppliersForPicker, } from "../lib/mailbox-template.functions";
9
9
  import { SearchableSelect } from "./SearchableSelect";
10
+ import { CartAssignmentWizard } from "./CartAssignmentWizard";
11
+ import { supabase } from "../integrations/supabase/client";
10
12
  export function MailboxProposalsPage() {
11
13
  const qc = useQueryClient();
12
14
  const fetchList = useServerFn(listProposals);
@@ -17,29 +19,121 @@ export function MailboxProposalsPage() {
17
19
  const fetchProducts = useServerFn(listProductsForPicker);
18
20
  const fetchPlantTypes = useServerFn(listPlantTypesForPicker);
19
21
  const fetchSuppliers = useServerFn(listSuppliersForPicker);
20
- const [status, setStatus] = useState("pending");
22
+ const fetchCarriers = useServerFn(listCarriersForPicker);
23
+ const fetchPickupLocations = useServerFn(listPickupLocations);
24
+ const fetchActive = useServerFn(getActiveOrdersMailbox);
25
+ const fetchIngest = useServerFn(ingestEmailEvent);
26
+ const fetchBackfill = useServerFn(backfillRecentEvents);
27
+ // Server fns met requireSupabaseAuth gooien een raw Response bij een stale
28
+ // bearer-token. Die bubbelt op als "[object Response]" en blankt het scherm.
29
+ // Vang elke fout en geef een lege fallback terug — UI blijft bruikbaar.
30
+ const safe = async (fn) => {
31
+ try {
32
+ return await fn();
33
+ }
34
+ catch {
35
+ return { items: [] };
36
+ }
37
+ };
38
+ // Bestelvoorstellen toont uitsluitend nieuwe, nog niet gecontroleerde
39
+ // bestellingen ("Te controleren"). Goedgekeurd/afgewezen verschijnt in
40
+ // de module Verwerkt.
21
41
  const { data, isLoading } = useQuery({
22
- queryKey: ["mailbox-proposals", status],
23
- queryFn: () => fetchList({ data: { status } }),
42
+ queryKey: ["mailbox-proposals", "pending"],
43
+ queryFn: () => safe(() => fetchList({ data: { status: "pending" } })),
24
44
  });
25
45
  const { data: templateData } = useQuery({
26
46
  queryKey: ["mailbox-template-fields"],
27
- queryFn: () => fetchTemplate(),
47
+ queryFn: () => safe(() => fetchTemplate()),
28
48
  });
29
- const { data: customersData } = useQuery({ queryKey: ["mailbox-customers"], queryFn: () => fetchCustomers() });
30
- const { data: productsData } = useQuery({ queryKey: ["mailbox-products"], queryFn: () => fetchProducts() });
31
- const { data: plantData } = useQuery({ queryKey: ["mailbox-plant-types"], queryFn: () => fetchPlantTypes() });
32
- const { data: supplierData } = useQuery({ queryKey: ["mailbox-suppliers"], queryFn: () => fetchSuppliers() });
49
+ const { data: customersData } = useQuery({ queryKey: ["mailbox-customers"], queryFn: () => safe(() => fetchCustomers()) });
50
+ const { data: productsData } = useQuery({ queryKey: ["mailbox-products"], queryFn: () => safe(() => fetchProducts()) });
51
+ const { data: plantData } = useQuery({ queryKey: ["mailbox-plant-types"], queryFn: () => safe(() => fetchPlantTypes()) });
52
+ const { data: supplierData } = useQuery({ queryKey: ["mailbox-suppliers"], queryFn: () => safe(() => fetchSuppliers()) });
53
+ const { data: carriersData } = useQuery({ queryKey: ["mailbox-carriers"], queryFn: () => safe(() => fetchCarriers()) });
54
+ const { data: pickupLocData } = useQuery({ queryKey: ["mailbox-pickup-locations"], queryFn: () => safe(() => fetchPickupLocations()) });
55
+ // Realtime ingestie + backfill — voorheen in de Inbox-pagina.
56
+ const { data: activeMailbox } = useQuery({
57
+ queryKey: ["mailbox-active-account"],
58
+ queryFn: () => fetchActive(),
59
+ refetchInterval: 60000,
60
+ retry: 1,
61
+ });
62
+ const activeMailboxId = activeMailbox?.connected ? activeMailbox.id : null;
63
+ const backfillMut = useMutation({
64
+ mutationFn: async () => {
65
+ try {
66
+ return await fetchBackfill();
67
+ }
68
+ catch {
69
+ return { ok: false, inserted: 0 };
70
+ }
71
+ },
72
+ onSuccess: (res) => {
73
+ if (res?.ok && (res.inserted ?? 0) > 0) {
74
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
75
+ }
76
+ },
77
+ });
78
+ const handleManualSync = () => {
79
+ const p = backfillMut.mutateAsync();
80
+ toast.promise(p, {
81
+ loading: "Mailbox synchroniseren...",
82
+ success: (res) => res?.ok
83
+ ? (res.inserted ?? 0) > 0
84
+ ? `${res.inserted} nieuwe bestelling(en) opgehaald`
85
+ : "Geen nieuwe bestellingen"
86
+ : `Sync mislukt: ${res?.error ?? "onbekende fout"}`,
87
+ error: "Sync mislukt",
88
+ });
89
+ };
90
+ useEffect(() => {
91
+ if (!activeMailboxId)
92
+ return;
93
+ const channel = supabase
94
+ .channel(`email_auth_events:${activeMailboxId}`)
95
+ .on("postgres_changes", {
96
+ event: "INSERT", schema: "public", table: "email_auth_events",
97
+ filter: `connection_id=eq.${activeMailboxId}`,
98
+ }, async (payload) => {
99
+ const evId = payload?.new?.id;
100
+ if (!evId)
101
+ return;
102
+ try {
103
+ const res = await fetchIngest({ data: { eventId: evId } });
104
+ if (res?.ok && (res.inserted ?? 0) > 0) {
105
+ toast.success("Nieuwe bestelling ontvangen");
106
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
107
+ }
108
+ }
109
+ catch { /* backfill vangt op */ }
110
+ })
111
+ .subscribe();
112
+ return () => { void supabase.removeChannel(channel); };
113
+ }, [activeMailboxId, fetchIngest, qc]);
114
+ const didInitialBackfill = useRef(false);
115
+ useEffect(() => {
116
+ if (!activeMailboxId || didInitialBackfill.current)
117
+ return;
118
+ didInitialBackfill.current = true;
119
+ backfillMut.mutate();
120
+ const t = setInterval(() => backfillMut.mutate(), 300000);
121
+ return () => clearInterval(t);
122
+ // eslint-disable-next-line react-hooks/exhaustive-deps
123
+ }, [activeMailboxId]);
33
124
  const items = Array.isArray(data?.items) ? data.items : [];
34
125
  const template = Array.isArray(templateData?.items) ? templateData.items : [];
35
126
  const customers = Array.isArray(customersData?.items) ? customersData.items : [];
36
127
  const products = Array.isArray(productsData?.items) ? productsData.items : [];
37
128
  const plantTypes = Array.isArray(plantData?.items) ? plantData.items : [];
38
129
  const suppliers = Array.isArray(supplierData?.items) ? supplierData.items : [];
130
+ const carriers = Array.isArray(carriersData?.items) ? carriersData.items : [];
131
+ const pickupLocations = Array.isArray(pickupLocData?.items) ? pickupLocData.items : [];
39
132
  const [activeId, setActiveId] = useState(null);
133
+ const [cartOrderId, setCartOrderId] = useState(null);
40
134
  const active = items.find((p) => p.id === activeId) ?? null;
41
- const statusLabel = (s) => s === "approved" ? "Goedgekeurd" : s === "rejected" ? "Afgewezen" : "Nieuw";
42
- const statusVariant = (s) => s === "approved" ? "default" : s === "rejected" ? "destructive" : "secondary";
135
+ const statusLabel = (_s) => "Te controleren";
136
+ const statusVariant = (_s) => "secondary";
43
137
  const customerNameFor = (p) => {
44
138
  const fvs = Array.isArray(p.mailbox_proposal_field_values) ? p.mailbox_proposal_field_values : [];
45
139
  const fv = fvs.find((v) => v.field_key === "customer");
@@ -52,35 +146,40 @@ export function MailboxProposalsPage() {
52
146
  return p.mailbox_messages?.from_name || p.mailbox_messages?.from_email || "Onbekend";
53
147
  };
54
148
  const orderNumberFor = (p) => `MB-${String(p.id).slice(0, 8).toUpperCase()}`;
55
- return (_jsxs("div", { className: "p-6 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Voorstellen" }), _jsxs(Select, { value: status, onValueChange: (v) => setStatus(v), children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "pending", children: "Nieuw / In behandeling" }), _jsx(SelectItem, { value: "approved", children: "Goedgekeurd" }), _jsx(SelectItem, { value: "rejected", children: "Afgekeurd" })] })] })] }), isLoading ? (_jsx("div", { className: "text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Geen voorstellen in deze categorie." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Klantnaam" }), _jsx(TableHead, { children: "Orderdatum" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "text-right", children: "Orderwaarde" })] }) }), _jsx(TableBody, { children: items.map((p) => {
149
+ return (_jsxs("div", { className: "p-6 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", 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." })] }), _jsxs(Button, { onClick: handleManualSync, disabled: backfillMut.isPending, variant: "outline", children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${backfillMut.isPending ? "animate-spin" : ""}` }), "Sync nu"] })] }), isLoading ? (_jsx("div", { className: "text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "Geen bestellingen te controleren." })) : (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Klantnaam" }), _jsx(TableHead, { children: "Orderdatum" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "text-right", children: "Orderwaarde" })] }) }), _jsx(TableBody, { children: items.map((p) => {
56
150
  const fvs = Array.isArray(p.mailbox_proposal_field_values) ? p.mailbox_proposal_field_values : [];
57
151
  const needsReview = fvs.some((v) => v.needs_review);
58
152
  const date = p.mailbox_messages?.received_at ?? p.created_at;
59
153
  return (_jsxs(TableRow, { className: "cursor-pointer hover:bg-muted/50", onClick: () => setActiveId(p.id), children: [_jsx(TableCell, { className: "font-mono text-xs", children: orderNumberFor(p) }), _jsx(TableCell, { className: "font-medium", children: customerNameFor(p) }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: date ? new Date(date).toLocaleDateString("nl-NL") : "-" }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Badge, { variant: statusVariant(p.status), children: statusLabel(p.status) }), needsReview && (_jsxs(Badge, { variant: "outline", className: "text-xs border-yellow-500 text-yellow-700", children: [_jsx(AlertTriangle, { className: "h-3 w-3 mr-1" }), "Controleren"] }))] }) }), _jsx(TableCell, { className: "text-right text-sm text-muted-foreground", children: "\u2014" })] }, p.id));
60
154
  }) })] }) }) })), _jsx(Dialog, { open: !!active, onOpenChange: (o) => { if (!o)
61
- setActiveId(null); }, children: _jsx(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] overflow-y-auto p-6", children: active && (_jsxs(_Fragment, { children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { children: [orderNumberFor(active), " \u00B7 ", customerNameFor(active)] }) }), _jsx(ProposalEditor, { proposal: active, template: template, customers: customers, products: products, plantTypes: plantTypes, suppliers: suppliers, onApprove: async (payload) => {
62
- try {
63
- await fetchApprove({ data: payload });
64
- toast.success("Bestelling aangemaakt");
65
- qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
66
- setActiveId(null);
67
- }
68
- catch (e) {
69
- toast.error(e?.message ?? "Goedkeuren mislukt");
70
- }
71
- }, onReject: async (reason) => {
72
- try {
73
- await fetchReject({ data: { proposalId: active.id, reason } });
74
- toast.success("Voorstel afgewezen");
75
- qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
76
- setActiveId(null);
77
- }
78
- catch (e) {
79
- toast.error(e?.message ?? "Afwijzen mislukt");
80
- }
81
- } }, active.id)] })) }) })] }));
155
+ setActiveId(null); }, children: _jsx(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] p-0 flex flex-col overflow-hidden", children: active && (_jsxs(_Fragment, { children: [_jsx(DialogHeader, { className: "px-6 pt-6 pb-3 shrink-0 border-b", children: _jsxs(DialogTitle, { children: [orderNumberFor(active), " \u00B7 ", customerNameFor(active)] }) }), _jsx("div", { className: "flex-1 min-h-0 overflow-y-auto overscroll-contain p-6", children: _jsx(ProposalEditor, { proposal: active, template: template, customers: customers, products: products, plantTypes: plantTypes, suppliers: suppliers, carriers: carriers, pickupLocations: pickupLocations, onApprove: async (payload) => {
156
+ try {
157
+ const res = await fetchApprove({ data: payload });
158
+ toast.success("Bestelling aangemaakt");
159
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
160
+ setActiveId(null);
161
+ if (res?.orderId)
162
+ setCartOrderId(res.orderId);
163
+ }
164
+ catch (e) {
165
+ toast.error(e?.message ?? "Goedkeuren mislukt");
166
+ }
167
+ }, onReject: async (reason) => {
168
+ try {
169
+ await fetchReject({ data: { proposalId: active.id, reason } });
170
+ toast.success("Voorstel afgewezen");
171
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
172
+ setActiveId(null);
173
+ }
174
+ catch (e) {
175
+ toast.error(e?.message ?? "Afwijzen mislukt");
176
+ }
177
+ } }, active.id) })] })) }) }), _jsx(CartAssignmentWizard, { orderId: cartOrderId, open: !!cartOrderId, onClose: () => setCartOrderId(null) })] }));
178
+ }
179
+ function ReadOnlyField({ label, value }) {
180
+ return (_jsxs("div", { className: "space-y-0.5", children: [_jsx("div", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: label }), _jsx("div", { className: "text-sm", children: value && String(value).trim() !== "" ? value : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) })] }));
82
181
  }
83
- function ProposalEditor({ proposal, template, customers, products, plantTypes, suppliers, onApprove, onReject, }) {
182
+ function ProposalEditor({ proposal, template, customers, products, plantTypes, suppliers, carriers, pickupLocations, onApprove, onReject, }) {
84
183
  const msg = proposal.mailbox_messages ?? {};
85
184
  const fieldValues = Array.isArray(proposal.mailbox_proposal_field_values)
86
185
  ? proposal.mailbox_proposal_field_values : [];
@@ -96,30 +195,136 @@ function ProposalEditor({ proposal, template, customers, products, plantTypes, s
96
195
  visibleFields.forEach((f) => {
97
196
  init[f.key] = fvMap[f.key]?.value ?? f.default_value ?? null;
98
197
  });
198
+ // Vul de auto-herkende klant in als er nog geen waarde uit het template komt
199
+ if (!init["customer"] && proposal.matched_customer_id) {
200
+ init["customer"] = proposal.matched_customer_id;
201
+ }
99
202
  return init;
100
203
  });
101
204
  const initialLines = Array.isArray(proposal.mailbox_proposal_lines)
102
- ? proposal.mailbox_proposal_lines.map((l) => ({
103
- id: l.id,
104
- product_id: l.product_id,
105
- raw_product_text: l.raw_product_text ?? "",
106
- quantity: l.quantity,
107
- unit: l.unit,
108
- }))
205
+ ? proposal.mailbox_proposal_lines.map((l) => {
206
+ const p = products.find((x) => x.id === l.product_id);
207
+ const ppc = Math.max(1, Number(p?.plants_per_crate ?? 1));
208
+ const q = Number(l.quantity ?? 0);
209
+ const normalized = q > 0 ? Math.ceil(q / ppc) * ppc : q;
210
+ return {
211
+ id: l.id,
212
+ product_id: l.product_id,
213
+ raw_product_text: l.raw_product_text ?? "",
214
+ quantity: normalized || null,
215
+ unit: l.unit,
216
+ };
217
+ })
109
218
  : [];
110
219
  const [lines, setLines] = useState(initialLines.length ? initialLines : [
111
- { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks" },
220
+ { product_id: null, raw_product_text: "", quantity: null, unit: "stuks" },
112
221
  ]);
113
222
  const customerVal = values["customer"];
114
223
  const deliveryVal = values["delivery_date"];
115
224
  const notesVal = values["notes"];
116
225
  const setVal = (key, v) => setValues((prev) => ({ ...prev, [key]: v }));
226
+ const fetchDeliveryLocations = useServerFn(listDeliveryLocationsForCustomer);
227
+ const { data: deliveryLocData } = useQuery({
228
+ queryKey: ["mailbox-delivery-locations", customerVal],
229
+ enabled: !!customerVal,
230
+ queryFn: async () => {
231
+ try {
232
+ return await fetchDeliveryLocations({ data: { customerId: customerVal } });
233
+ }
234
+ catch {
235
+ return { items: [] };
236
+ }
237
+ },
238
+ });
239
+ const deliveryLocations = Array.isArray(deliveryLocData?.items) ? deliveryLocData.items : [];
240
+ const deliveryLocationVal = values["delivery_location"];
241
+ const carrierVal = values["carrier"];
242
+ const selectedLocation = deliveryLocations.find((l) => l.id === deliveryLocationVal) ?? null;
243
+ // Reset delivery_location wanneer klant wijzigt en huidige waarde niet meer past
244
+ if (deliveryLocationVal && deliveryLocations.length > 0 &&
245
+ !deliveryLocations.some((l) => l.id === deliveryLocationVal)) {
246
+ // schedule reset via microtask om setState-in-render te vermijden
247
+ queueMicrotask(() => setVal("delivery_location", null));
248
+ }
249
+ // Auto-vul vervoerder zodra een nieuwe locatie wordt gekozen (één keer per wissel).
250
+ const lastLocRef = useRef(deliveryLocationVal);
251
+ useEffect(() => {
252
+ if (deliveryLocationVal !== lastLocRef.current) {
253
+ lastLocRef.current = deliveryLocationVal;
254
+ const loc = deliveryLocations.find((l) => l.id === deliveryLocationVal);
255
+ if (loc?.carrier_id)
256
+ setVal("carrier", loc.carrier_id);
257
+ else if (deliveryLocationVal === null)
258
+ setVal("carrier", null);
259
+ }
260
+ // eslint-disable-next-line react-hooks/exhaustive-deps
261
+ }, [deliveryLocationVal, deliveryLocations.length]);
262
+ // ─── Eerst mogelijke leverdatum ────────────────────────────────────────────
263
+ const fetchPickDays = useServerFn(getOrderPickDays);
264
+ const { data: pickDaysData } = useQuery({
265
+ queryKey: ["mailbox-order-pick-days"],
266
+ queryFn: async () => {
267
+ try {
268
+ return await fetchPickDays();
269
+ }
270
+ catch {
271
+ return { pickDays: 0 };
272
+ }
273
+ },
274
+ });
275
+ const pickDays = Math.max(0, Number(pickDaysData?.pickDays ?? 0));
276
+ const selectedCarrier = carriers.find((c) => c.id === carrierVal) ?? null;
277
+ const carrierDeliveryDays = Math.max(0, Number(selectedCarrier?.delivery_days ?? 0));
278
+ const pickupLocationVal = values["pickup_location"];
279
+ const canPickDate = !!customerVal && !!deliveryLocationVal && !!pickupLocationVal && !!carrierVal;
280
+ const fmtISO = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
281
+ const earliestDate = useMemo(() => {
282
+ const base = new Date();
283
+ base.setHours(0, 0, 0, 0);
284
+ base.setDate(base.getDate() + pickDays + carrierDeliveryDays);
285
+ return base;
286
+ }, [pickDays, carrierDeliveryDays]);
287
+ const earliestISO = fmtISO(earliestDate);
288
+ const earliestLabel = earliestDate.toLocaleDateString("nl-NL", {
289
+ day: "numeric", month: "long", year: "numeric",
290
+ });
291
+ // Synchroniseer delivery_date wanneer de eerst mogelijke datum verandert.
292
+ useEffect(() => {
293
+ if (!canPickDate)
294
+ return;
295
+ if (!deliveryVal || deliveryVal < earliestISO) {
296
+ setVal("delivery_date", earliestISO);
297
+ }
298
+ // eslint-disable-next-line react-hooks/exhaustive-deps
299
+ }, [canPickDate, earliestISO]);
117
300
  const handleApprove = () => {
118
301
  const missing = template.filter((f) => f.required && (values[f.key] === null || values[f.key] === undefined || values[f.key] === ""));
119
302
  if (missing.length > 0) {
120
303
  toast.error(`Verplichte velden ontbreken: ${missing.map((m) => m.label).join(", ")}`);
121
304
  return;
122
305
  }
306
+ if (!deliveryLocationVal) {
307
+ toast.error("Selecteer een afleverlocatie.");
308
+ return;
309
+ }
310
+ if (!pickupLocationVal) {
311
+ toast.error("Selecteer een afhaallocatie (eigen locatie / tuin).");
312
+ return;
313
+ }
314
+ const validLines = lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0);
315
+ if (validLines.length === 0) {
316
+ toast.error("Voeg minimaal één product toe aan de bestelling.");
317
+ return;
318
+ }
319
+ for (const l of validLines) {
320
+ const p = products.find((x) => x.id === l.product_id);
321
+ const ppc = Math.max(1, Number(p?.plants_per_crate ?? 1));
322
+ const qty = l.quantity ?? 0;
323
+ if (qty < ppc || qty % ppc !== 0) {
324
+ toast.error(`"${p?.product ?? l.raw_product_text}" moet per hele fust besteld worden (minimaal 1 fust = ${ppc} planten).`);
325
+ return;
326
+ }
327
+ }
123
328
  const cust = customers.find((c) => c.id === customerVal);
124
329
  onApprove({
125
330
  proposalId: proposal.id,
@@ -127,36 +332,96 @@ function ProposalEditor({ proposal, template, customers, products, plantTypes, s
127
332
  customerName: cust?.company_name ?? msg.from_name ?? msg.from_email ?? "Onbekend",
128
333
  deliveryDate: deliveryVal || new Date(Date.now() + 86400000).toISOString().slice(0, 10),
129
334
  notes: notesVal || null,
130
- lines: lines.filter((l) => l.raw_product_text.trim() !== ""),
335
+ lines: validLines,
131
336
  fieldValues: Object.entries(values).map(([field_key, value]) => ({ field_key, value })),
132
337
  });
133
338
  };
134
- return (_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: msg.subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [msg.from_name, " <", msg.from_email, "> \u00B7", " ", msg.received_at ? new Date(msg.received_at).toLocaleString("nl-NL") : ""] })] }), _jsx(CardContent, { children: _jsx("pre", { className: "text-sm whitespace-pre-wrap font-sans max-h-[60vh] overflow-y-auto", children: msg.body_text || "(geen tekst)" }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Bestelvoorstel" }) }), _jsxs(CardContent, { className: "space-y-4", children: [visibleFields.map((field) => (_jsx(DynamicField, { field: field, value: values[field.key], meta: fvMap[field.key], onChange: (v) => setVal(field.key, v), customers: customers, products: products, plantTypes: plantTypes, suppliers: suppliers }, field.key))), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bestelregels" }), lines.map((line, idx) => (_jsxs("div", { className: "grid grid-cols-12 gap-2 items-start", children: [_jsxs("div", { className: "col-span-6", children: [_jsx(SearchableSelect, { value: line.product_id, placeholder: "Kies product...", clearLabel: "\u2014 Handmatig \u2014", options: products.map((p) => ({ value: p.id, label: p.product, hint: p.unit })), onChange: (v) => {
339
+ return (_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-base", children: msg.subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [msg.from_name, " <", msg.from_email, "> \u00B7", " ", msg.received_at ? new Date(msg.received_at).toLocaleString("nl-NL") : ""] })] }), _jsx(CardContent, { children: _jsx("pre", { className: "text-sm whitespace-pre-wrap font-sans max-h-[60vh] overflow-y-auto", children: msg.body_text || "(geen tekst)" }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Bestelvoorstel" }) }), _jsxs(CardContent, { className: "space-y-4", children: [(() => {
340
+ const customerField = visibleFields.find((f) => f.key === "customer");
341
+ const deliveryDateField = visibleFields.find((f) => f.key === "delivery_date");
342
+ const otherFields = visibleFields.filter((f) => f.key !== "customer" && f.key !== "delivery_date");
343
+ const renderField = (field) => (_jsx(DynamicField, { field: field, value: values[field.key], meta: fvMap[field.key], onChange: (v) => setVal(field.key, v), customers: customers, products: products, plantTypes: plantTypes, suppliers: suppliers }, field.key));
344
+ const payload = (proposal.parsed_payload ?? {});
345
+ const autoMatched = payload.auto_matched_customer === true;
346
+ const matchWarning = typeof payload.auto_match_warning === "string" ? payload.auto_match_warning : null;
347
+ return (_jsxs(_Fragment, { children: [autoMatched && (_jsxs("div", { className: "rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs text-emerald-800", children: ["Klant automatisch herkend op basis van het e-mailadres van de afzender", payload.auto_match_email ? ` (${payload.auto_match_email})` : "", "."] })), 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), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Locatie" }), _jsx(SearchableSelect, { value: deliveryLocationVal, placeholder: !customerVal
348
+ ? "Selecteer eerst een klant"
349
+ : deliveryLocations.length
350
+ ? "Kies locatie..."
351
+ : "Geen locaties bekend voor deze klant", clearLabel: "\u2014 Geen \u2014", disabled: !customerVal || deliveryLocations.length === 0, options: deliveryLocations.map((l) => {
352
+ const addr = [
353
+ l.address_line || [l.street, l.house_number].filter(Boolean).join(" "),
354
+ l.postal_code,
355
+ l.city,
356
+ ].filter(Boolean).join(", ");
357
+ return {
358
+ value: l.id,
359
+ label: (l.name || addr || "Locatie") + (l.is_primary ? " ★" : ""),
360
+ hint: l.name && addr ? addr : undefined,
361
+ };
362
+ }), onChange: (v) => setVal("delivery_location", v) })] }), selectedLocation && (_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 space-y-2", children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Adresgegevens" }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsx(ReadOnlyField, { label: "Straat", value: selectedLocation.street }), _jsx(ReadOnlyField, { label: "Huisnummer", value: selectedLocation.house_number }), _jsx(ReadOnlyField, { label: "Postcode", value: selectedLocation.postal_code }), _jsx(ReadOnlyField, { label: "Plaats", value: selectedLocation.city }), _jsx(ReadOnlyField, { label: "Provincie", value: selectedLocation.province }), _jsx(ReadOnlyField, { label: "Land", value: selectedLocation.country })] })] })), _jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "flex items-center gap-2", children: ["Afhaallocatie / Tuin ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(SearchableSelect, { value: pickupLocationVal, placeholder: pickupLocations.length ? "Kies eigen locatie..." : "Geen eigen locaties bekend", clearLabel: "\u2014 Geen \u2014", allowClear: false, disabled: pickupLocations.length === 0, options: pickupLocations.map((l) => ({ value: l.id, label: l.name })), onChange: (v) => setVal("pickup_location", v) }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Locatie van waaruit de bestelling wordt geleverd of opgehaald." })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Vervoerder" }), _jsx(SearchableSelect, { value: carrierVal, placeholder: !customerVal
363
+ ? "Selecteer eerst een klant"
364
+ : !deliveryLocationVal
365
+ ? "Selecteer eerst een locatie"
366
+ : "Kies vervoerder...", clearLabel: "\u2014 Geen \u2014", disabled: !deliveryLocationVal, options: carriers.map((c) => ({
367
+ value: c.id,
368
+ label: c.name + (c.is_default ? " (standaard)" : ""),
369
+ })), 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: [deliveryDateField?.label ?? "Verzenddatum", deliveryDateField?.required && _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { type: "date", value: canPickDate ? (deliveryVal ?? "") : "", min: canPickDate ? earliestISO : undefined, disabled: !canPickDate, onChange: (e) => {
370
+ const v = e.target.value || null;
371
+ if (v && v < earliestISO) {
372
+ toast.error(`Eerst mogelijke leverdatum is ${earliestLabel}.`);
373
+ setVal("delivery_date", earliestISO);
374
+ return;
375
+ }
376
+ setVal("delivery_date", v);
377
+ } }), !canPickDate ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Selecteer eerst klant, afleverlocatie, afhaallocatie en vervoerder." })) : (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Eerst beschikbare leverdatum is ", earliestLabel, ", op basis van orderpicktijd (", pickDays, "d) en levertijd vervoerder (", carrierDeliveryDays, "d)."] }))] }), otherFields.map(renderField)] }));
378
+ })(), _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-5", children: "Product" }), _jsx("div", { className: "col-span-2", children: "Planten" }), _jsx("div", { className: "col-span-2", children: "Fusten" }), _jsx("div", { className: "col-span-2", children: "Eenheid" }), _jsx("div", { className: "col-span-1" })] }), lines.map((line, idx) => {
379
+ const product = line.product_id ? products.find((p) => p.id === line.product_id) : null;
380
+ const ppc = Math.max(1, Number(product?.plants_per_crate ?? 1));
381
+ const plants = line.quantity ?? null;
382
+ const cratesExact = plants !== null ? plants / ppc : null;
383
+ const cratesDisplay = cratesExact === null ? "" : String(Math.ceil(cratesExact));
384
+ const blockDecimal = (e) => {
385
+ if (e.key === "." || e.key === "," || e.key === "e" || e.key === "E" || e.key === "+" || e.key === "-") {
386
+ e.preventDefault();
387
+ }
388
+ };
389
+ return (_jsxs("div", { className: "grid grid-cols-12 gap-2 items-start", children: [_jsx("div", { className: "col-span-5", children: _jsx(SearchableSelect, { value: line.product_id, placeholder: "Kies product...", clearLabel: "\u2014 Handmatig \u2014", options: products.map((p) => ({ value: p.id, label: p.product, hint: p.unit })), onChange: (v) => {
135
390
  const next = [...lines];
136
391
  if (v === null)
137
392
  next[idx] = { ...next[idx], product_id: null };
138
393
  else {
139
394
  const p = products.find((x) => x.id === v);
395
+ const newPpc = Math.max(1, Number(p?.plants_per_crate ?? 1));
396
+ const currentQ = Number(next[idx].quantity ?? 0);
397
+ const normalizedQ = currentQ > 0 ? Math.ceil(currentQ / newPpc) * newPpc : newPpc;
140
398
  next[idx] = {
141
399
  ...next[idx], product_id: v,
142
400
  raw_product_text: p?.product ?? next[idx].raw_product_text,
143
401
  unit: next[idx].unit ?? p?.unit ?? "stuks",
402
+ quantity: normalizedQ,
144
403
  };
145
404
  }
146
405
  setLines(next);
147
- } }), _jsx(Input, { className: "mt-1", value: line.raw_product_text, onChange: (e) => {
148
- const next = [...lines];
149
- next[idx] = { ...next[idx], raw_product_text: e.target.value };
150
- setLines(next);
151
- }, placeholder: "Productomschrijving" })] }), _jsx(Input, { className: "col-span-2", type: "number", value: line.quantity ?? "", onChange: (e) => {
152
- const next = [...lines];
153
- next[idx] = { ...next[idx], quantity: e.target.value ? Number(e.target.value) : null };
154
- setLines(next);
155
- }, placeholder: "Aantal" }), _jsx(Input, { className: "col-span-3", value: line.unit ?? "", onChange: (e) => {
156
- const next = [...lines];
157
- next[idx] = { ...next[idx], unit: e.target.value };
158
- setLines(next);
159
- }, placeholder: "Eenheid" }), _jsx(Button, { variant: "ghost", size: "icon", className: "col-span-1", onClick: () => setLines(lines.filter((_, i) => i !== idx)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, idx))), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setLines([...lines, { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks" }]), children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Regel toevoegen"] })] }), _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" }), " Goedkeuren"] }), _jsxs(Button, { variant: "destructive", onClick: () => onReject(notesVal || ""), children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Afwijzen"] })] })] })] })] }));
406
+ } }) }), _jsx(Input, { className: "col-span-2", type: "number", min: ppc, step: ppc, inputMode: "numeric", onKeyDown: blockDecimal, value: plants ?? "", onChange: (e) => {
407
+ const next = [...lines];
408
+ const raw = e.target.value.replace(/[.,]/g, "");
409
+ const v = raw ? Number(raw) : null;
410
+ const rounded = v === null ? null : Math.max(1, Math.ceil(v / ppc)) * ppc;
411
+ next[idx] = { ...next[idx], quantity: rounded };
412
+ setLines(next);
413
+ }, placeholder: "Planten", title: `Verkoop per hele fust van ${ppc} planten` }), _jsx(Input, { className: "col-span-2", type: "number", min: 1, step: 1, inputMode: "numeric", onKeyDown: blockDecimal, value: cratesDisplay, onChange: (e) => {
414
+ const next = [...lines];
415
+ const raw = e.target.value.replace(/[.,]/g, "");
416
+ const cr = raw ? Math.max(1, Math.floor(Number(raw))) : null;
417
+ next[idx] = { ...next[idx], quantity: cr === null ? null : cr * ppc };
418
+ setLines(next);
419
+ }, placeholder: "Fusten", title: `${ppc} planten per fust — verkoop per hele fust` }), _jsx(Input, { className: "col-span-2", value: line.unit ?? "", onChange: (e) => {
420
+ const next = [...lines];
421
+ next[idx] = { ...next[idx], unit: e.target.value };
422
+ setLines(next);
423
+ }, placeholder: "Eenheid" }), _jsx(Button, { variant: "ghost", size: "icon", className: "col-span-1", onClick: () => setLines(lines.filter((_, i) => i !== idx)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, idx));
424
+ }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setLines([...lines, { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks" }]), children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Regel toevoegen"] })] }), _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" }), " Goedkeuren"] }), _jsxs(Button, { variant: "destructive", onClick: () => onReject(notesVal || ""), children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Afwijzen"] })] })] })] })] }));
160
425
  }
161
426
  function DynamicField({ field, value, meta, onChange, customers, products, plantTypes, suppliers, }) {
162
427
  const reviewClass = meta?.needs_review ? "border-yellow-500 ring-1 ring-yellow-500/40 rounded-md p-2" : "";
@@ -12,7 +12,8 @@ interface SearchableSelectProps {
12
12
  clearLabel?: string;
13
13
  onChange: (value: string | null) => void;
14
14
  className?: string;
15
+ disabled?: boolean;
15
16
  }
16
- export declare function SearchableSelect({ value, options, placeholder, emptyLabel, allowClear, clearLabel, onChange, className, }: SearchableSelectProps): import("react").JSX.Element;
17
+ export declare function SearchableSelect({ value, options, placeholder, emptyLabel, allowClear, clearLabel, onChange, className, disabled, }: SearchableSelectProps): import("react").JSX.Element;
17
18
  export {};
18
19
  //# sourceMappingURL=SearchableSelect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SearchableSelect.d.ts","sourceRoot":"","sources":["../../src/components/SearchableSelect.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,UAAU,qBAAqB;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EAAE,OAAO,EAAE,WAAuB,EAAE,UAA6B,EACtE,UAAiB,EAAE,UAAiC,EACpD,QAAQ,EAAE,SAAS,GACpB,EAAE,qBAAqB,+BAsFvB"}
1
+ {"version":3,"file":"SearchableSelect.d.ts","sourceRoot":"","sources":["../../src/components/SearchableSelect.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,UAAU,qBAAqB;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EAAE,OAAO,EAAE,WAAuB,EAAE,UAA6B,EACtE,UAAiB,EAAE,UAAiC,EACpD,QAAQ,EAAE,SAAS,EAAE,QAAgB,GACtC,EAAE,qBAAqB,+BAuFvB"}
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from "react";
3
3
  import { Button, Input, Popover, PopoverContent, PopoverTrigger, } from "@flowselections/core";
4
4
  import { Check, ChevronsUpDown, Search } from "lucide-react";
5
- export function SearchableSelect({ value, options, placeholder = "Kies...", emptyLabel = "Geen resultaat", allowClear = true, clearLabel = "— Geen koppeling —", onChange, className, }) {
5
+ export function SearchableSelect({ value, options, placeholder = "Kies...", emptyLabel = "Geen resultaat", allowClear = true, clearLabel = "— Geen koppeling —", onChange, className, disabled = false, }) {
6
6
  const [open, setOpen] = useState(false);
7
7
  const [q, setQ] = useState("");
8
8
  const selected = useMemo(() => options.find((o) => o.value === value) ?? null, [options, value]);
@@ -15,6 +15,7 @@ export function SearchableSelect({ value, options, placeholder = "Kies...", empt
15
15
  (o.hint ?? "").toLowerCase().includes(needle))
16
16
  .slice(0, 200);
17
17
  }, [options, q]);
18
- return (_jsxs(Popover, { open: open, onOpenChange: (o) => { setOpen(o); if (!o)
19
- setQ(""); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, className: `w-full justify-between font-normal ${className ?? ""}`, children: [_jsx("span", { className: `truncate ${selected ? "" : "text-muted-foreground"}`, children: selected?.label ?? placeholder }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "w-[--radix-popover-trigger-width] p-0", align: "start", children: [_jsx("div", { className: "p-2 border-b", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { autoFocus: true, value: q, onChange: (e) => setQ(e.target.value), placeholder: `Zoek in ${options.length} items...`, className: "pl-8 h-9" })] }) }), _jsxs("div", { className: "max-h-64 overflow-y-auto py-1", children: [allowClear && (_jsxs("button", { type: "button", className: "w-full text-left text-sm px-3 py-2 hover:bg-accent flex items-center gap-2", onClick: () => { onChange(null); setOpen(false); }, children: [value === null && _jsx(Check, { className: "h-3 w-3" }), _jsx("span", { className: "text-muted-foreground italic", children: clearLabel })] })), filtered.length === 0 ? (_jsx("div", { className: "px-3 py-6 text-sm text-muted-foreground text-center", children: emptyLabel })) : (filtered.map((o) => (_jsxs("button", { type: "button", className: "w-full text-left text-sm px-3 py-2 hover:bg-accent flex items-start gap-2", onClick: () => { onChange(o.value); setOpen(false); }, children: [_jsx(Check, { className: `h-3 w-3 mt-1 shrink-0 ${value === o.value ? "" : "invisible"}` }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "truncate", children: o.label }), o.hint && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: o.hint })] })] }, o.value)))), filtered.length === 200 && (_jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground text-center border-t", children: "Toon eerste 200 resultaten \u2014 verfijn je zoekopdracht" }))] })] })] }));
18
+ return (_jsxs(Popover, { open: open, onOpenChange: (o) => { if (disabled)
19
+ return; setOpen(o); if (!o)
20
+ setQ(""); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, disabled: disabled, className: `w-full justify-between font-normal ${className ?? ""}`, children: [_jsx("span", { className: `truncate ${selected ? "" : "text-muted-foreground"}`, children: selected?.label ?? placeholder }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "w-[--radix-popover-trigger-width] p-0", align: "start", children: [_jsx("div", { className: "p-2 border-b", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { autoFocus: true, value: q, onChange: (e) => setQ(e.target.value), placeholder: `Zoek in ${options.length} items...`, className: "pl-8 h-9" })] }) }), _jsxs("div", { className: "max-h-64 overflow-y-auto py-1", children: [allowClear && (_jsxs("button", { type: "button", className: "w-full text-left text-sm px-3 py-2 hover:bg-accent flex items-center gap-2", onClick: () => { onChange(null); setOpen(false); }, children: [value === null && _jsx(Check, { className: "h-3 w-3" }), _jsx("span", { className: "text-muted-foreground italic", children: clearLabel })] })), filtered.length === 0 ? (_jsx("div", { className: "px-3 py-6 text-sm text-muted-foreground text-center", children: emptyLabel })) : (filtered.map((o) => (_jsxs("button", { type: "button", className: "w-full text-left text-sm px-3 py-2 hover:bg-accent flex items-start gap-2", onClick: () => { onChange(o.value); setOpen(false); }, children: [_jsx(Check, { className: `h-3 w-3 mt-1 shrink-0 ${value === o.value ? "" : "invisible"}` }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "truncate", children: o.label }), o.hint && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: o.hint })] })] }, o.value)))), filtered.length === 200 && (_jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground text-center border-t", children: "Toon eerste 200 resultaten \u2014 verfijn je zoekopdracht" }))] })] })] }));
20
21
  }
@@ -1,6 +1,5 @@
1
1
  import type { FlowModule } from "@flowselections/core";
2
2
  export * from "./_core-safelist";
3
- export { MailboxInboxPage } from "./components/MailboxInboxPage";
4
3
  export { MailboxProposalsPage } from "./components/MailboxProposalsPage";
5
4
  export { SearchableSelect } from "./components/SearchableSelect";
6
5
  export { MailboxProcessedPage } from "./components/MailboxProcessedPage";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,eAAO,MAAM,mBAAmB,EAAE,UAmBjC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,eAAO,MAAM,mBAAmB,EAAE,UAkBjC,CAAC"}
package/dist-lib/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  // ============================================================================
2
2
  // src/index.ts — MODULE CONTRACT (Mailbox bestellingen)
3
3
  // ============================================================================
4
- import { Mail, Inbox, ClipboardCheck, CheckCircle2 } from "lucide-react";
4
+ import { Mail, ClipboardCheck, CheckCircle2 } from "lucide-react";
5
5
  import { MailboxOrderTemplateCard } from "./components/settings/MailboxOrderTemplateCard";
6
6
  export * from "./_core-safelist";
7
- export { MailboxInboxPage } from "./components/MailboxInboxPage";
8
7
  export { MailboxProposalsPage } from "./components/MailboxProposalsPage";
9
8
  export { SearchableSelect } from "./components/SearchableSelect";
10
9
  export { MailboxProcessedPage } from "./components/MailboxProcessedPage";
@@ -14,10 +13,9 @@ export const mailboxOrdersModule = {
14
13
  version: "1.2.0",
15
14
  nav: {
16
15
  label: "Mailbox",
17
- href: "/mailbox-orders",
16
+ href: "/mailbox-orders/voorstellen",
18
17
  icon: Mail,
19
18
  children: [
20
- { label: "Inbox", href: "/mailbox-orders", icon: Inbox },
21
19
  { label: "Voorstellen", href: "/mailbox-orders/voorstellen", icon: ClipboardCheck },
22
20
  { label: "Verwerkt", href: "/mailbox-orders/verwerkt", icon: CheckCircle2 },
23
21
  ],
@@ -1 +1 @@
1
- {"version":3,"file":"auth-attacher.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/auth-attacher.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB,mHAa9B,CAAA"}
1
+ {"version":3,"file":"auth-attacher.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/auth-attacher.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB,mHAyC9B,CAAA"}