@flowselections/mailbox-orders 1.0.4 → 1.0.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"AAmCA,wBAAgB,oBAAoB,gCA+PnC"}
1
+ {"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"AAmCA,wBAAgB,oBAAoB,gCAiQnC"}
@@ -152,29 +152,29 @@ export function MailboxProposalsPage() {
152
152
  const date = p.mailbox_messages?.received_at ?? p.created_at;
153
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));
154
154
  }) })] }) }) })), _jsx(Dialog, { open: !!active, onOpenChange: (o) => { if (!o)
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) => {
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) })] }));
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
178
  }
179
179
  function ReadOnlyField({ label, value }) {
180
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" }) })] }));
@@ -202,16 +202,22 @@ function ProposalEditor({ proposal, template, customers, products, plantTypes, s
202
202
  return init;
203
203
  });
204
204
  const initialLines = Array.isArray(proposal.mailbox_proposal_lines)
205
- ? proposal.mailbox_proposal_lines.map((l) => ({
206
- id: l.id,
207
- product_id: l.product_id,
208
- raw_product_text: l.raw_product_text ?? "",
209
- quantity: l.quantity,
210
- unit: l.unit,
211
- }))
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
+ })
212
218
  : [];
213
219
  const [lines, setLines] = useState(initialLines.length ? initialLines : [
214
- { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks" },
220
+ { product_id: null, raw_product_text: "", quantity: null, unit: "stuks" },
215
221
  ]);
216
222
  const customerVal = values["customer"];
217
223
  const deliveryVal = values["delivery_date"];
@@ -310,6 +316,15 @@ function ProposalEditor({ proposal, template, customers, products, plantTypes, s
310
316
  toast.error("Voeg minimaal één product toe aan de bestelling.");
311
317
  return;
312
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
+ }
313
328
  const cust = customers.find((c) => c.id === customerVal);
314
329
  onApprove({
315
330
  proposalId: proposal.id,
@@ -365,31 +380,43 @@ function ProposalEditor({ proposal, template, customers, products, plantTypes, s
365
380
  const ppc = Math.max(1, Number(product?.plants_per_crate ?? 1));
366
381
  const plants = line.quantity ?? null;
367
382
  const cratesExact = plants !== null ? plants / ppc : null;
368
- const cratesDisplay = cratesExact === null ? "" :
369
- Number.isInteger(cratesExact) ? String(cratesExact) : cratesExact.toFixed(2);
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
+ };
370
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) => {
371
390
  const next = [...lines];
372
391
  if (v === null)
373
392
  next[idx] = { ...next[idx], product_id: null };
374
393
  else {
375
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;
376
398
  next[idx] = {
377
399
  ...next[idx], product_id: v,
378
400
  raw_product_text: p?.product ?? next[idx].raw_product_text,
379
401
  unit: next[idx].unit ?? p?.unit ?? "stuks",
402
+ quantity: normalizedQ,
380
403
  };
381
404
  }
382
405
  setLines(next);
383
- } }) }), _jsx(Input, { className: "col-span-2", type: "number", min: 0, value: plants ?? "", onChange: (e) => {
406
+ } }) }), _jsx(Input, { className: "col-span-2", type: "number", min: ppc, step: ppc, inputMode: "numeric", onKeyDown: blockDecimal, value: plants ?? "", onChange: (e) => {
384
407
  const next = [...lines];
385
- next[idx] = { ...next[idx], quantity: e.target.value ? Number(e.target.value) : null };
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 };
386
412
  setLines(next);
387
- }, placeholder: "Planten" }), _jsx(Input, { className: "col-span-2", type: "number", min: 0, step: "0.01", value: cratesDisplay, onChange: (e) => {
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) => {
388
414
  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) };
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 };
391
418
  setLines(next);
392
- }, placeholder: "Fusten", title: `${ppc} planten per fust` }), _jsx(Input, { className: "col-span-2", value: line.unit ?? "", onChange: (e) => {
419
+ }, placeholder: "Fusten", title: `${ppc} planten per fust — verkoop per hele fust` }), _jsx(Input, { className: "col-span-2", value: line.unit ?? "", onChange: (e) => {
393
420
  const next = [...lines];
394
421
  next[idx] = { ...next[idx], unit: e.target.value };
395
422
  setLines(next);