@flowselections/mailbox-orders 1.0.3 → 1.0.4
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.
- package/dist-lib/components/CartAssignmentWizard.d.ts +6 -0
- package/dist-lib/components/CartAssignmentWizard.d.ts.map +1 -0
- package/dist-lib/components/CartAssignmentWizard.js +199 -0
- package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProcessedPage.js +25 -4
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +272 -34
- package/dist-lib/components/SearchableSelect.d.ts +2 -1
- package/dist-lib/components/SearchableSelect.d.ts.map +1 -1
- package/dist-lib/components/SearchableSelect.js +4 -3
- package/dist-lib/index.d.ts +0 -1
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +2 -4
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-attacher.js +37 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2762 -145
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +2762 -145
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +2762 -145
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +2849 -149
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +2 -0
- package/dist-lib/lib/mailbox-template.functions.d.ts +34602 -18900
- package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.d.ts +130480 -33017
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +392 -18
- 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
|
|
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",
|
|
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 = (
|
|
42
|
-
const statusVariant = (
|
|
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,18 +146,20 @@ 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: "
|
|
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) => {
|
|
155
|
+
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, carriers: carriers, pickupLocations: pickupLocations, onApprove: async (payload) => {
|
|
62
156
|
try {
|
|
63
|
-
await fetchApprove({ data: payload });
|
|
157
|
+
const res = await fetchApprove({ data: payload });
|
|
64
158
|
toast.success("Bestelling aangemaakt");
|
|
65
159
|
qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
|
|
66
160
|
setActiveId(null);
|
|
161
|
+
if (res?.orderId)
|
|
162
|
+
setCartOrderId(res.orderId);
|
|
67
163
|
}
|
|
68
164
|
catch (e) {
|
|
69
165
|
toast.error(e?.message ?? "Goedkeuren mislukt");
|
|
@@ -78,9 +174,12 @@ export function MailboxProposalsPage() {
|
|
|
78
174
|
catch (e) {
|
|
79
175
|
toast.error(e?.message ?? "Afwijzen mislukt");
|
|
80
176
|
}
|
|
81
|
-
} }, active.id)] })) }) })] }));
|
|
177
|
+
} }, active.id)] })) }) }), _jsx(CartAssignmentWizard, { orderId: cartOrderId, open: !!cartOrderId, onClose: () => setCartOrderId(null) })] }));
|
|
82
178
|
}
|
|
83
|
-
function
|
|
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" }) })] }));
|
|
181
|
+
}
|
|
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,6 +195,10 @@ 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)
|
|
@@ -114,12 +217,99 @@ function ProposalEditor({ proposal, template, customers, products, plantTypes, s
|
|
|
114
217
|
const deliveryVal = values["delivery_date"];
|
|
115
218
|
const notesVal = values["notes"];
|
|
116
219
|
const setVal = (key, v) => setValues((prev) => ({ ...prev, [key]: v }));
|
|
220
|
+
const fetchDeliveryLocations = useServerFn(listDeliveryLocationsForCustomer);
|
|
221
|
+
const { data: deliveryLocData } = useQuery({
|
|
222
|
+
queryKey: ["mailbox-delivery-locations", customerVal],
|
|
223
|
+
enabled: !!customerVal,
|
|
224
|
+
queryFn: async () => {
|
|
225
|
+
try {
|
|
226
|
+
return await fetchDeliveryLocations({ data: { customerId: customerVal } });
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return { items: [] };
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
const deliveryLocations = Array.isArray(deliveryLocData?.items) ? deliveryLocData.items : [];
|
|
234
|
+
const deliveryLocationVal = values["delivery_location"];
|
|
235
|
+
const carrierVal = values["carrier"];
|
|
236
|
+
const selectedLocation = deliveryLocations.find((l) => l.id === deliveryLocationVal) ?? null;
|
|
237
|
+
// Reset delivery_location wanneer klant wijzigt en huidige waarde niet meer past
|
|
238
|
+
if (deliveryLocationVal && deliveryLocations.length > 0 &&
|
|
239
|
+
!deliveryLocations.some((l) => l.id === deliveryLocationVal)) {
|
|
240
|
+
// schedule reset via microtask om setState-in-render te vermijden
|
|
241
|
+
queueMicrotask(() => setVal("delivery_location", null));
|
|
242
|
+
}
|
|
243
|
+
// Auto-vul vervoerder zodra een nieuwe locatie wordt gekozen (één keer per wissel).
|
|
244
|
+
const lastLocRef = useRef(deliveryLocationVal);
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (deliveryLocationVal !== lastLocRef.current) {
|
|
247
|
+
lastLocRef.current = deliveryLocationVal;
|
|
248
|
+
const loc = deliveryLocations.find((l) => l.id === deliveryLocationVal);
|
|
249
|
+
if (loc?.carrier_id)
|
|
250
|
+
setVal("carrier", loc.carrier_id);
|
|
251
|
+
else if (deliveryLocationVal === null)
|
|
252
|
+
setVal("carrier", null);
|
|
253
|
+
}
|
|
254
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
255
|
+
}, [deliveryLocationVal, deliveryLocations.length]);
|
|
256
|
+
// ─── Eerst mogelijke leverdatum ────────────────────────────────────────────
|
|
257
|
+
const fetchPickDays = useServerFn(getOrderPickDays);
|
|
258
|
+
const { data: pickDaysData } = useQuery({
|
|
259
|
+
queryKey: ["mailbox-order-pick-days"],
|
|
260
|
+
queryFn: async () => {
|
|
261
|
+
try {
|
|
262
|
+
return await fetchPickDays();
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return { pickDays: 0 };
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
const pickDays = Math.max(0, Number(pickDaysData?.pickDays ?? 0));
|
|
270
|
+
const selectedCarrier = carriers.find((c) => c.id === carrierVal) ?? null;
|
|
271
|
+
const carrierDeliveryDays = Math.max(0, Number(selectedCarrier?.delivery_days ?? 0));
|
|
272
|
+
const pickupLocationVal = values["pickup_location"];
|
|
273
|
+
const canPickDate = !!customerVal && !!deliveryLocationVal && !!pickupLocationVal && !!carrierVal;
|
|
274
|
+
const fmtISO = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
275
|
+
const earliestDate = useMemo(() => {
|
|
276
|
+
const base = new Date();
|
|
277
|
+
base.setHours(0, 0, 0, 0);
|
|
278
|
+
base.setDate(base.getDate() + pickDays + carrierDeliveryDays);
|
|
279
|
+
return base;
|
|
280
|
+
}, [pickDays, carrierDeliveryDays]);
|
|
281
|
+
const earliestISO = fmtISO(earliestDate);
|
|
282
|
+
const earliestLabel = earliestDate.toLocaleDateString("nl-NL", {
|
|
283
|
+
day: "numeric", month: "long", year: "numeric",
|
|
284
|
+
});
|
|
285
|
+
// Synchroniseer delivery_date wanneer de eerst mogelijke datum verandert.
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
if (!canPickDate)
|
|
288
|
+
return;
|
|
289
|
+
if (!deliveryVal || deliveryVal < earliestISO) {
|
|
290
|
+
setVal("delivery_date", earliestISO);
|
|
291
|
+
}
|
|
292
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
293
|
+
}, [canPickDate, earliestISO]);
|
|
117
294
|
const handleApprove = () => {
|
|
118
295
|
const missing = template.filter((f) => f.required && (values[f.key] === null || values[f.key] === undefined || values[f.key] === ""));
|
|
119
296
|
if (missing.length > 0) {
|
|
120
297
|
toast.error(`Verplichte velden ontbreken: ${missing.map((m) => m.label).join(", ")}`);
|
|
121
298
|
return;
|
|
122
299
|
}
|
|
300
|
+
if (!deliveryLocationVal) {
|
|
301
|
+
toast.error("Selecteer een afleverlocatie.");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (!pickupLocationVal) {
|
|
305
|
+
toast.error("Selecteer een afhaallocatie (eigen locatie / tuin).");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const validLines = lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0);
|
|
309
|
+
if (validLines.length === 0) {
|
|
310
|
+
toast.error("Voeg minimaal één product toe aan de bestelling.");
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
123
313
|
const cust = customers.find((c) => c.id === customerVal);
|
|
124
314
|
onApprove({
|
|
125
315
|
proposalId: proposal.id,
|
|
@@ -127,11 +317,57 @@ function ProposalEditor({ proposal, template, customers, products, plantTypes, s
|
|
|
127
317
|
customerName: cust?.company_name ?? msg.from_name ?? msg.from_email ?? "Onbekend",
|
|
128
318
|
deliveryDate: deliveryVal || new Date(Date.now() + 86400000).toISOString().slice(0, 10),
|
|
129
319
|
notes: notesVal || null,
|
|
130
|
-
lines:
|
|
320
|
+
lines: validLines,
|
|
131
321
|
fieldValues: Object.entries(values).map(([field_key, value]) => ({ field_key, value })),
|
|
132
322
|
});
|
|
133
323
|
};
|
|
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: [
|
|
324
|
+
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: [(() => {
|
|
325
|
+
const customerField = visibleFields.find((f) => f.key === "customer");
|
|
326
|
+
const deliveryDateField = visibleFields.find((f) => f.key === "delivery_date");
|
|
327
|
+
const otherFields = visibleFields.filter((f) => f.key !== "customer" && f.key !== "delivery_date");
|
|
328
|
+
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));
|
|
329
|
+
const payload = (proposal.parsed_payload ?? {});
|
|
330
|
+
const autoMatched = payload.auto_matched_customer === true;
|
|
331
|
+
const matchWarning = typeof payload.auto_match_warning === "string" ? payload.auto_match_warning : null;
|
|
332
|
+
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
|
|
333
|
+
? "Selecteer eerst een klant"
|
|
334
|
+
: deliveryLocations.length
|
|
335
|
+
? "Kies locatie..."
|
|
336
|
+
: "Geen locaties bekend voor deze klant", clearLabel: "\u2014 Geen \u2014", disabled: !customerVal || deliveryLocations.length === 0, options: deliveryLocations.map((l) => {
|
|
337
|
+
const addr = [
|
|
338
|
+
l.address_line || [l.street, l.house_number].filter(Boolean).join(" "),
|
|
339
|
+
l.postal_code,
|
|
340
|
+
l.city,
|
|
341
|
+
].filter(Boolean).join(", ");
|
|
342
|
+
return {
|
|
343
|
+
value: l.id,
|
|
344
|
+
label: (l.name || addr || "Locatie") + (l.is_primary ? " ★" : ""),
|
|
345
|
+
hint: l.name && addr ? addr : undefined,
|
|
346
|
+
};
|
|
347
|
+
}), 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
|
|
348
|
+
? "Selecteer eerst een klant"
|
|
349
|
+
: !deliveryLocationVal
|
|
350
|
+
? "Selecteer eerst een locatie"
|
|
351
|
+
: "Kies vervoerder...", clearLabel: "\u2014 Geen \u2014", disabled: !deliveryLocationVal, options: carriers.map((c) => ({
|
|
352
|
+
value: c.id,
|
|
353
|
+
label: c.name + (c.is_default ? " (standaard)" : ""),
|
|
354
|
+
})), 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) => {
|
|
355
|
+
const v = e.target.value || null;
|
|
356
|
+
if (v && v < earliestISO) {
|
|
357
|
+
toast.error(`Eerst mogelijke leverdatum is ${earliestLabel}.`);
|
|
358
|
+
setVal("delivery_date", earliestISO);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
setVal("delivery_date", v);
|
|
362
|
+
} }), !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)] }));
|
|
363
|
+
})(), _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) => {
|
|
364
|
+
const product = line.product_id ? products.find((p) => p.id === line.product_id) : null;
|
|
365
|
+
const ppc = Math.max(1, Number(product?.plants_per_crate ?? 1));
|
|
366
|
+
const plants = line.quantity ?? null;
|
|
367
|
+
const cratesExact = plants !== null ? plants / ppc : null;
|
|
368
|
+
const cratesDisplay = cratesExact === null ? "" :
|
|
369
|
+
Number.isInteger(cratesExact) ? String(cratesExact) : cratesExact.toFixed(2);
|
|
370
|
+
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
371
|
const next = [...lines];
|
|
136
372
|
if (v === null)
|
|
137
373
|
next[idx] = { ...next[idx], product_id: null };
|
|
@@ -144,19 +380,21 @@ function ProposalEditor({ proposal, template, customers, products, plantTypes, s
|
|
|
144
380
|
};
|
|
145
381
|
}
|
|
146
382
|
setLines(next);
|
|
147
|
-
} }), _jsx(Input, { className: "
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
383
|
+
} }) }), _jsx(Input, { className: "col-span-2", type: "number", min: 0, value: plants ?? "", onChange: (e) => {
|
|
384
|
+
const next = [...lines];
|
|
385
|
+
next[idx] = { ...next[idx], quantity: e.target.value ? Number(e.target.value) : null };
|
|
386
|
+
setLines(next);
|
|
387
|
+
}, placeholder: "Planten" }), _jsx(Input, { className: "col-span-2", type: "number", min: 0, step: "0.01", value: cratesDisplay, onChange: (e) => {
|
|
388
|
+
const next = [...lines];
|
|
389
|
+
const cr = e.target.value ? Number(e.target.value) : null;
|
|
390
|
+
next[idx] = { ...next[idx], quantity: cr === null ? null : Math.round(cr * ppc) };
|
|
391
|
+
setLines(next);
|
|
392
|
+
}, placeholder: "Fusten", title: `${ppc} planten per fust` }), _jsx(Input, { className: "col-span-2", value: line.unit ?? "", onChange: (e) => {
|
|
393
|
+
const next = [...lines];
|
|
394
|
+
next[idx] = { ...next[idx], unit: e.target.value };
|
|
395
|
+
setLines(next);
|
|
396
|
+
}, 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));
|
|
397
|
+
}), _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
398
|
}
|
|
161
399
|
function DynamicField({ field, value, meta, onChange, customers, products, plantTypes, suppliers, }) {
|
|
162
400
|
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,
|
|
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) => {
|
|
19
|
-
|
|
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
|
}
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -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";
|
package/dist-lib/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
1
|
+
{"version":3,"file":"auth-attacher.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/auth-attacher.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB,mHAyC9B,CAAA"}
|
|
@@ -9,7 +9,41 @@ export const attachSupabaseAuth = createMiddleware({ type: 'function' }).client(
|
|
|
9
9
|
const { supabase } = await import('./client');
|
|
10
10
|
const { data } = await supabase.auth.getSession();
|
|
11
11
|
const token = data.session?.access_token;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const redirectToLogin = async () => {
|
|
13
|
+
try {
|
|
14
|
+
await supabase.auth.signOut();
|
|
15
|
+
}
|
|
16
|
+
catch { /* noop */ }
|
|
17
|
+
try {
|
|
18
|
+
window.localStorage.clear();
|
|
19
|
+
}
|
|
20
|
+
catch { /* noop */ }
|
|
21
|
+
if (!window.location.pathname.startsWith('/login')) {
|
|
22
|
+
window.location.replace('/login');
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
return await next({
|
|
27
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
// TanStack geeft door auth-middleware gegooide Response-objecten soms als
|
|
32
|
+
// raw Response terug aan de browser. Als we die opnieuw gooien, toont de
|
|
33
|
+
// runtime overlay alleen `[object Response]`. Zet ze daarom om naar óf een
|
|
34
|
+
// stille login-redirect (401) óf een normale Error met leesbare tekst.
|
|
35
|
+
if (err instanceof Response) {
|
|
36
|
+
if (err.status === 401) {
|
|
37
|
+
await redirectToLogin();
|
|
38
|
+
throw new Error('Sessie verlopen — log opnieuw in.');
|
|
39
|
+
}
|
|
40
|
+
let message = '';
|
|
41
|
+
try {
|
|
42
|
+
message = await err.clone().text();
|
|
43
|
+
}
|
|
44
|
+
catch { /* noop */ }
|
|
45
|
+
throw new Error(message || `Serverfout (${err.status})`);
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
15
49
|
});
|