@flowselections/mailbox-orders 1.0.34 → 1.0.37
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/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +89 -10
- package/dist-lib/integrations/supabase/types.d.ts +528 -18
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.d.ts +30 -0
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +125 -82
- package/dist-lib/lib/verkoop-order.d.ts +14 -0
- package/dist-lib/lib/verkoop-order.d.ts.map +1 -0
- package/dist-lib/lib/verkoop-order.js +24 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"AAgHA,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AA2CD,wBAAgB,oBAAoB,gCAmcnC"}
|
|
@@ -4,7 +4,7 @@ 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
6
|
import { Check, X, Trash2, Plus, AlertTriangle, RefreshCw } from "lucide-react";
|
|
7
|
-
import { listProposals, getProposalDetail, approveProposal, rejectProposal, listCustomersForPicker, createCustomerFromGroup, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getActiveOrdersMailbox, syncMailboxNow, refreshProposalsForAgentChange, reparseProposalNow, listBeladingOptionsForProducts, listAllowedLoadCarriers, getDeparturePreview, getAgreementPrices, countOutdatedProposals, } from "../lib/mailbox.functions.js";
|
|
7
|
+
import { listProposals, getProposalDetail, approveProposal, rejectProposal, getStickerDefaults, listCustomersForPicker, createCustomerFromGroup, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getActiveOrdersMailbox, syncMailboxNow, refreshProposalsForAgentChange, reparseProposalNow, listBeladingOptionsForProducts, listAllowedLoadCarriers, getDeparturePreview, getAgreementPrices, countOutdatedProposals, } from "../lib/mailbox.functions.js";
|
|
8
8
|
import { normalizeUnitKind, toPlants, describeConversion, lineTotal, UNIT_KIND_LABEL, } from "../lib/mailbox-units.js";
|
|
9
9
|
import { enabledLoadCarriers, normalizeAllowedLoadCarriers, EMPTY_LOAD_CARRIERS } from "../lib/load-carriers.js";
|
|
10
10
|
import { listTemplateFields, listPlantTypesForPicker, listSuppliersForPicker, } from "../lib/mailbox-template.functions.js";
|
|
@@ -14,6 +14,7 @@ import { EmailPreview } from "./EmailPreview.js";
|
|
|
14
14
|
import { TablePagination } from "./TablePagination.js";
|
|
15
15
|
import { MailboxFilterBar, usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar.js";
|
|
16
16
|
import { supabase } from "../integrations/supabase/client.js";
|
|
17
|
+
import { canOverrideLateSignup } from "../lib/verkoop-order.js";
|
|
17
18
|
const beladingKey = (b) => `${b.plants_per_crate}x${b.crates_per_layer}x${b.layers_per_cart}|${b.crate_code ?? ""}|${b.carrier_code ?? ""}`;
|
|
18
19
|
const beladingLabel = (b) => `${b.plants_per_crate} planten/fust · ${b.crates_per_layer} fusten/laag · ${b.layers_per_cart} lagen`;
|
|
19
20
|
// Extra regel onder het label: fusten/kar + fusttype + kartype
|
|
@@ -316,6 +317,16 @@ export function MailboxProposalsPage() {
|
|
|
316
317
|
} }, children: _jsx(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] p-0 flex flex-col overflow-hidden", children: !active ? (_jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 text-sm text-muted-foreground", children: [_jsx(DialogTitle, { className: "text-base", children: "Bestelling" }), detailLoading ? "Bestelling laden…" : "Bestelling niet gevonden."] })) : (_jsxs(_Fragment, { children: [_jsxs(DialogHeader, { className: "px-6 pt-6 pb-3 shrink-0 border-b", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsx(DialogTitle, { children: customerNameFor(activeSummary ?? active) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "mr-8", disabled: reparseMutation.isPending, onClick: () => reparseMutation.mutate(active.id), children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${reparseMutation.isPending ? "animate-spin" : ""}` }), reparseMutation.isPending ? "Analyseren…" : "Mail opnieuw analyseren"] })] }), reparseError && (_jsxs("div", { className: "mt-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "h-4 w-4 mt-0.5 shrink-0" }), _jsx("span", { children: reparseError })] }))] }), _jsx("div", { className: "flex-1 min-h-0 overflow-hidden p-6", children: _jsx(ProposalEditor, { proposal: active, template: template, customers: customers, products: products, allowBackorder: allowBackorder, plantTypes: plantTypes, suppliers: suppliers, carriers: carriers, pickupLocations: pickupLocations, onApprove: async (payload) => {
|
|
317
318
|
try {
|
|
318
319
|
const res = await fetchApprove({ data: payload });
|
|
320
|
+
if (res?.ok === false) {
|
|
321
|
+
toast.error(res.message ?? "Verkoop heeft de bestelling geweigerd.");
|
|
322
|
+
return res;
|
|
323
|
+
}
|
|
324
|
+
if (res?.alreadyApproved) {
|
|
325
|
+
toast.info(`Er bestaat al een order voor dit voorstel (${res.orderNumber}).`);
|
|
326
|
+
qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
|
|
327
|
+
setActiveId(null);
|
|
328
|
+
return res;
|
|
329
|
+
}
|
|
319
330
|
const shortages = Array.isArray(res?.shortages) ? res.shortages : [];
|
|
320
331
|
toast.success("Bestelling aangemaakt — karindeling wordt automatisch aangemaakt in de Verkoop-module");
|
|
321
332
|
if (shortages.length > 0) {
|
|
@@ -325,9 +336,11 @@ export function MailboxProposalsPage() {
|
|
|
325
336
|
}
|
|
326
337
|
qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
|
|
327
338
|
setActiveId(null);
|
|
339
|
+
return res;
|
|
328
340
|
}
|
|
329
341
|
catch (e) {
|
|
330
342
|
toast.error(e?.message ?? "Goedkeuren mislukt");
|
|
343
|
+
return { ok: false, reason: "other", message: e?.message ?? "Goedkeuren mislukt" };
|
|
331
344
|
}
|
|
332
345
|
}, onReject: async (reason) => {
|
|
333
346
|
try {
|
|
@@ -598,7 +611,50 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
|
|
|
598
611
|
},
|
|
599
612
|
});
|
|
600
613
|
const agreements = agreementData?.prices ?? {};
|
|
601
|
-
|
|
614
|
+
// ─── Stickervinkje per regel ─────────────────────────────────────────────
|
|
615
|
+
const fetchStickers = useServerFn(getStickerDefaults);
|
|
616
|
+
const stickerProductIds = Array.from(new Set(lines.map((l) => l.product_id).filter(Boolean))).sort();
|
|
617
|
+
const { data: stickerData } = useQuery({
|
|
618
|
+
queryKey: ["mailbox-sticker-defaults", customerVal, stickerProductIds.join(",")],
|
|
619
|
+
queryFn: async () => {
|
|
620
|
+
try {
|
|
621
|
+
return await fetchStickers({ data: { customerId: isLocalCustomerId(customerVal) ? customerVal : null, productIds: stickerProductIds } });
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
return { available: false, defaults: {} };
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
});
|
|
628
|
+
const stickerAvailable = stickerData?.available === true;
|
|
629
|
+
const stickerDefaults = stickerData?.defaults ?? {};
|
|
630
|
+
useEffect(() => {
|
|
631
|
+
if (!stickerAvailable)
|
|
632
|
+
return;
|
|
633
|
+
let changed = false;
|
|
634
|
+
const next = lines.map((l) => {
|
|
635
|
+
if (l.product_id && (l.needs_sticker === null || l.needs_sticker === undefined) && l.product_id in stickerDefaults) {
|
|
636
|
+
changed = true;
|
|
637
|
+
return { ...l, needs_sticker: stickerDefaults[l.product_id] === true };
|
|
638
|
+
}
|
|
639
|
+
return l;
|
|
640
|
+
});
|
|
641
|
+
if (changed)
|
|
642
|
+
setLines(next);
|
|
643
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
644
|
+
}, [stickerData, lines]);
|
|
645
|
+
const stickerCustomerRef = useRef(customerVal);
|
|
646
|
+
useEffect(() => {
|
|
647
|
+
if (stickerCustomerRef.current === customerVal)
|
|
648
|
+
return;
|
|
649
|
+
stickerCustomerRef.current = customerVal;
|
|
650
|
+
setLines((prev) => prev.map((l) => ({ ...l, needs_sticker: undefined })));
|
|
651
|
+
}, [customerVal]);
|
|
652
|
+
const [lateReason, setLateReason] = useState("");
|
|
653
|
+
const [lateOpen, setLateOpen] = useState(false);
|
|
654
|
+
const [refusal, setRefusal] = useState(null);
|
|
655
|
+
const previewKind = preview?.chosen?.departed === true ? "departed" : "late_signup";
|
|
656
|
+
const lateOverride = lateOpen && canOverrideLateSignup(refusal?.kind ?? previewKind, lateReason);
|
|
657
|
+
useEffect(() => { setRefusal(null); setLateOpen(false); setLateReason(""); }, [deliveryVal, carrierVal, deliveryLocationVal]);
|
|
602
658
|
const orderTotal = lines.reduce((sum, l) => sum + lineTotal(l.quantity ?? null, l.unit_price ?? null), 0);
|
|
603
659
|
const linesWithoutPrice = lines.filter((l) => l.product_id && (l.unit_price === null || l.unit_price === undefined)).length;
|
|
604
660
|
const euro = (n) => n.toLocaleString("nl-NL", { style: "currency", currency: "EUR" });
|
|
@@ -637,7 +693,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
|
|
|
637
693
|
toast.error("Kies een vervoerder.");
|
|
638
694
|
return null;
|
|
639
695
|
}
|
|
640
|
-
if (!dateFeasible) {
|
|
696
|
+
if (!dateFeasible && !lateOverride) {
|
|
641
697
|
toast.error(preview?.reason ? `Leverdatum niet haalbaar: ${preview.reason}` : "De leverdatum is nog niet gecontroleerd of niet haalbaar.");
|
|
642
698
|
return null;
|
|
643
699
|
}
|
|
@@ -651,6 +707,10 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
|
|
|
651
707
|
return null;
|
|
652
708
|
}
|
|
653
709
|
}
|
|
710
|
+
if (validLines.some((l) => l.unit_price === null || l.unit_price === undefined || l.unit_price <= 0)) {
|
|
711
|
+
toast.error("Vul bij elke regel een prijs per stuk in; Verkoop maakt geen order zonder prijs.");
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
654
714
|
const cust = customers.find((c) => c.id === customerVal);
|
|
655
715
|
return {
|
|
656
716
|
proposalId: proposal.id,
|
|
@@ -668,13 +728,14 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
|
|
|
668
728
|
quantity: l.quantity,
|
|
669
729
|
unit: "planten",
|
|
670
730
|
unit_price: l.unit_price ?? null,
|
|
731
|
+
needs_sticker: stickerAvailable ? l.needs_sticker === true : undefined,
|
|
671
732
|
belading: b ? { plants_per_crate: b.plants_per_crate, crates_per_layer: b.crates_per_layer, layers_per_cart: b.layers_per_cart } : null,
|
|
672
733
|
};
|
|
673
734
|
}),
|
|
674
735
|
fieldValues: Object.entries(values).map(([field_key, value]) => ({ field_key, value })),
|
|
675
736
|
customerReference: typeof values["reference"] === "string" ? values["reference"].trim() || null : null,
|
|
676
737
|
momentKey: preview?.chosen?.momentKey ?? null,
|
|
677
|
-
|
|
738
|
+
lateSignup: lateOverride ? { reason: lateReason.trim() } : null,
|
|
678
739
|
};
|
|
679
740
|
};
|
|
680
741
|
// Stap 1: controleren → bevestigingsdialoog. Pas na bevestigen wordt de
|
|
@@ -690,8 +751,16 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
|
|
|
690
751
|
return;
|
|
691
752
|
setSubmitting(true);
|
|
692
753
|
try {
|
|
693
|
-
await onApprove(payload);
|
|
754
|
+
const res = await onApprove(payload);
|
|
694
755
|
setConfirmOpen(false);
|
|
756
|
+
setCheckedOk(false);
|
|
757
|
+
if (res?.ok === false) {
|
|
758
|
+
setRefusal({
|
|
759
|
+
kind: res.reason ?? "other",
|
|
760
|
+
message: String(res.message ?? "Verkoop heeft de bestelling geweigerd."),
|
|
761
|
+
nextDeliveryDate: res.nextDeliveryDate ?? null,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
695
764
|
}
|
|
696
765
|
finally {
|
|
697
766
|
setSubmitting(false);
|
|
@@ -748,8 +817,8 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
|
|
|
748
817
|
: "Kies vervoerder...", clearLabel: "\u2014 Geen \u2014", disabled: !deliveryLocationVal, options: carriers.map((c) => ({
|
|
749
818
|
value: c.id,
|
|
750
819
|
label: c.name + (c.is_default ? " (standaard)" : ""),
|
|
751
|
-
})), 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: ["Bezorgdatum ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { type: "date", value: deliveryVal ?? "", onChange: (e) => setVal("delivery_date", e.target.value || null) }), _jsx(ConfBadge, { meta: metaFor("delivery_date"), thresholds: thresholds }), !carrierVal || !deliveryLocationVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Kies een afleverlocatie en vervoerder; daarna controleren we of deze datum haalbaar is." })) : !deliveryVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "De mail noemt geen datum. Kies zelf een datum." })) : previewLoading ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Haalbaarheid controleren\u2026" })) : previewError ? (_jsxs("div", { className: "text-xs text-destructive", children: ["De haalbaarheid kon niet worden gecontroleerd: ", previewError] })) : dateFeasible ? (_jsxs("div", { className: "rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs text-emerald-800", children: [fmtDate(deliveryVal), " is haalbaar.", preview?.chosen?.departureAt ? ` Ophaalmoment: ${fmtDateTime(preview.chosen.departureAt)}` : "", preview?.chosen?.signupDeadline ? ` (aanmelden tot ${fmtDateTime(preview.chosen.signupDeadline)}).` : "."] })) : (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs space-y-2", children: [_jsxs("div", { className: "text-destructive", children: [fmtDate(deliveryVal), " is niet haalbaar", preview?.reason ? `: ${preview.reason}` : "."] }), preview?.firstFeasibleDeliveryDate && preview.firstFeasibleDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", preview.firstFeasibleDeliveryDate), children: ["Gebruik ", fmtDate(preview.firstFeasibleDeliveryDate)] }))] }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Referentie klant" }), _jsx(Input, { value: typeof values["reference"] === "string" ? values["reference"] : "", placeholder: "Bijv. ordernummer van de klant", onChange: (e) => setVal("reference", e.target.value) }), _jsx(ConfBadge, { meta: metaFor("reference"), thresholds: thresholds })] }), otherFields.map(renderField)] }));
|
|
752
|
-
})(), _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-4", children: "Product" }), _jsx("div", { className: "col-span-3", children: "Belading" }), _jsx("div", { className: "col-span-2", children: "Fusten" }), _jsx("div", { className: "col-span-2", children: "Prijs per stuk" }), _jsx("div", { className: "col-span-1" })] }), lines.map((line, idx) => {
|
|
820
|
+
})), 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: ["Bezorgdatum ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { type: "date", value: deliveryVal ?? "", onChange: (e) => setVal("delivery_date", e.target.value || null) }), _jsx(ConfBadge, { meta: metaFor("delivery_date"), thresholds: thresholds }), !carrierVal || !deliveryLocationVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Kies een afleverlocatie en vervoerder; daarna controleren we of deze datum haalbaar is." })) : !deliveryVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "De mail noemt geen datum. Kies zelf een datum." })) : previewLoading ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Haalbaarheid controleren\u2026" })) : previewError ? (_jsxs("div", { className: "text-xs text-destructive", children: ["De haalbaarheid kon niet worden gecontroleerd: ", previewError] })) : dateFeasible ? (_jsxs("div", { className: "rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs text-emerald-800", children: [fmtDate(deliveryVal), " is haalbaar.", preview?.chosen?.departureAt ? ` Ophaalmoment: ${fmtDateTime(preview.chosen.departureAt)}` : "", preview?.chosen?.signupDeadline ? ` (aanmelden tot ${fmtDateTime(preview.chosen.signupDeadline)}).` : "."] })) : (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs space-y-2", children: [_jsxs("div", { className: "text-destructive", children: [fmtDate(deliveryVal), " is niet haalbaar", preview?.reason ? `: ${preview.reason}` : "."] }), preview?.firstFeasibleDeliveryDate && preview.firstFeasibleDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", preview.firstFeasibleDeliveryDate), children: ["Gebruik ", fmtDate(preview.firstFeasibleDeliveryDate)] })), preview?.ok === true && previewKind === "late_signup" && (_jsx(LateSignupChoice, { open: lateOpen, setOpen: setLateOpen, reason: lateReason, setReason: setLateReason }))] }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Referentie klant" }), _jsx(Input, { value: typeof values["reference"] === "string" ? values["reference"] : "", placeholder: "Bijv. ordernummer van de klant", onChange: (e) => setVal("reference", e.target.value) }), _jsx(ConfBadge, { meta: metaFor("reference"), thresholds: thresholds })] }), otherFields.map(renderField)] }));
|
|
821
|
+
})(), _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-4", children: "Product" }), _jsx("div", { className: "col-span-3", children: "Belading" }), _jsx("div", { className: "col-span-2", children: "Fusten" }), _jsx("div", { className: "col-span-2", children: "Prijs per stuk" }), _jsx("div", { className: "col-span-1 text-center", children: stickerAvailable ? "Sticker" : "" })] }), lines.map((line, idx) => {
|
|
753
822
|
const product = line.product_id ? products.find((p) => p.id === line.product_id) : null;
|
|
754
823
|
const stdBelading = defaultBelading(product);
|
|
755
824
|
const available = (line.product_id ? (Array.isArray(beladingByProduct[line.product_id]) ? beladingByProduct[line.product_id] : []) : []).map((b) => ({
|
|
@@ -867,14 +936,18 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
|
|
|
867
936
|
next[idx] = { ...next[idx], unit_price: ag.net_price, price_source: "afspraak" };
|
|
868
937
|
setLines(next);
|
|
869
938
|
}, children: "gebruiken" })] }))] }));
|
|
870
|
-
})()] }),
|
|
871
|
-
|
|
939
|
+
})()] }), _jsxs("div", { className: "col-span-1 flex flex-col items-center gap-1", children: [stickerAvailable && (_jsxs("label", { className: "flex flex-col items-center text-[11px] text-muted-foreground cursor-pointer", title: "Sticker nodig op deze orderregel", children: [_jsx("input", { type: "checkbox", "aria-label": "Sticker", checked: line.needs_sticker === true, onChange: (e) => {
|
|
940
|
+
const next = [...lines];
|
|
941
|
+
next[idx] = { ...next[idx], needs_sticker: e.target.checked };
|
|
942
|
+
setLines(next);
|
|
943
|
+
} }), "Sticker"] })), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => setLines(lines.filter((_, i) => i !== idx)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }, idx));
|
|
944
|
+
}), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setLines([...lines, { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks", belading: null }]), children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Regel toevoegen"] }), _jsxs("div", { className: "flex justify-between border-t pt-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal" }), _jsx("span", { className: "font-medium", children: euro(orderTotal) })] }), linesWithoutPrice > 0 && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Geen prijs bekend voor ", linesWithoutPrice, " regel(s). Vul een prijs in; zonder prijs kan de bestelling niet worden aangemaakt."] }))] }), refusal && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm space-y-2", children: [_jsx("div", { className: "font-medium text-destructive", children: "Bestelling niet aangemaakt \u2014 het voorstel blijft open." }), _jsx("div", { className: "text-destructive", children: refusal.message }), _jsx("div", { className: "flex flex-wrap gap-2", children: refusal.nextDeliveryDate && refusal.nextDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", refusal.nextDeliveryDate), children: ["Volgend haalbaar moment: ", fmtDate(refusal.nextDeliveryDate)] })) }), refusal.kind === "late_signup" && (_jsx(LateSignupChoice, { open: lateOpen, setOpen: setLateOpen, reason: lateReason, setReason: setLateReason }))] })), _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" }), " Controleren & bevestigen"] }), _jsxs(Button, { variant: "destructive", onClick: () => onReject(notesVal || ""), children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Afwijzen"] })] }), _jsx(Dialog, { open: confirmOpen, onOpenChange: (o) => {
|
|
872
945
|
if (submitting)
|
|
873
946
|
return;
|
|
874
947
|
setConfirmOpen(o);
|
|
875
948
|
if (!o)
|
|
876
949
|
setCheckedOk(false);
|
|
877
|
-
}, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Bestelling bevestigen" }) }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "De bestelling wordt pas aangemaakt in Bestellingen nadat je hem hebt gecontroleerd en bevestigd." }), _jsxs("div", { className: "rounded-md border p-3 space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Klant:" }), " ", customers.find((c) => c.id === customerVal)?.company_name ?? "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bezorgdatum:" }), " ", deliveryVal || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bestelregels:" }), " ", lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0).length] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Referentie:" }), " ", values["reference"] || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal:" }), " ", euro(orderTotal)] })] }),
|
|
950
|
+
}, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Bestelling bevestigen" }) }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "De bestelling wordt pas aangemaakt in Bestellingen nadat je hem hebt gecontroleerd en bevestigd." }), _jsxs("div", { className: "rounded-md border p-3 space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Klant:" }), " ", customers.find((c) => c.id === customerVal)?.company_name ?? "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bezorgdatum:" }), " ", deliveryVal || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bestelregels:" }), " ", lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0).length] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Referentie:" }), " ", values["reference"] || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal:" }), " ", euro(orderTotal)] })] }), lateOverride && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 p-2 text-amber-800", children: ["Je maakt de order toch aan na de aanmeldtijd. Reden: ", lateReason.trim()] })), _jsxs("label", { className: "flex items-start gap-2 cursor-pointer", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: checkedOk, onChange: (e) => setCheckedOk(e.target.checked) }), _jsx("span", { children: "Ik heb deze bestelling gecontroleerd en de gegevens kloppen." })] })] }), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", disabled: !checkedOk || submitting, onClick: handleConfirm, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), submitting ? "Bezig…" : "Bevestigen en bestelling aanmaken"] }), _jsx(Button, { variant: "outline", disabled: submitting, onClick: () => setConfirmOpen(false), children: "Annuleren" })] })] }) }), _jsx(NewCustomerDialog, { candidate: newCustomer, onClose: () => setNewCustomer(null), onCreated: (id) => { setNewCustomer(null); setVal("customer", id); } })] })] })] }));
|
|
878
951
|
}
|
|
879
952
|
/** Verplichte bevestiging: externe klant eerst als lokale klant aanmaken. */
|
|
880
953
|
function NewCustomerDialog({ candidate, onClose, onCreated, }) {
|
|
@@ -992,3 +1065,9 @@ function ConfBadge({ meta, thresholds, }) {
|
|
|
992
1065
|
: "border-destructive/40 bg-destructive/5 text-destructive";
|
|
993
1066
|
return (_jsxs("span", { className: `inline-flex items-center rounded border px-1.5 py-0.5 text-[11px] ${cls}`, children: [Math.round(c * 100), "% \u00B7 ", meta.source] }));
|
|
994
1067
|
}
|
|
1068
|
+
function LateSignupChoice({ open, setOpen, reason, setReason }) {
|
|
1069
|
+
if (!open) {
|
|
1070
|
+
return (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setOpen(true), children: "Toch aanmaken" }));
|
|
1071
|
+
}
|
|
1072
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "text-xs", children: ["Reden om toch aan te maken na de aanmeldtijd ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Textarea, { rows: 2, value: reason, placeholder: "Bijv. afgestemd met de vervoerder", onChange: (e) => setReason(e.target.value) }), _jsx("div", { className: "text-[11px] text-muted-foreground", children: "Wie, wanneer en de reden worden op de order vastgelegd. Daarna \"Controleren & bevestigen\"." }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => { setOpen(false); setReason(""); }, children: "Annuleren" })] }));
|
|
1073
|
+
}
|