@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.
- 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 +325 -60
- 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 +3460 -157
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +3460 -157
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +3460 -157
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +3553 -157
- 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 +43350 -23532
- package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.d.ts +142263 -33138
- 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,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: "
|
|
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]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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:
|
|
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:
|
|
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: [
|
|
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: "
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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,
|
|
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"}
|