@metron-labs/quote-react 0.1.62 → 0.1.64

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.
@@ -41,7 +41,10 @@ function ChevronIcon() {
41
41
  export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onResolveConversions, onRemove, onBack, onStartNewQuote, onQuoteRequested, internal = false, }) {
42
42
  // Both widgets can render a basket on the same page, so element ids are made unique.
43
43
  const uid = useId();
44
+ // Every customer sends the basket to the merchant (POST /v1/quote-requests). Materials Market
45
+ // calls it a quote request and keeps its own layout; every other client calls it a basket.
44
46
  const qzaBasketLayout = !internal && getBoqClientId() === "QZA6-MRA4";
47
+ const sendsRequest = !internal;
45
48
  const items = Object.values(cart);
46
49
  const [reference, setReference] = useState("");
47
50
  const [quoteRequestSent, setQuoteRequestSent] = useState(false);
@@ -178,7 +181,7 @@ export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onRe
178
181
  // shows once the API confirms the email went out; otherwise the customer is asked to retry.
179
182
  async function submitQuoteRequest() {
180
183
  if (pricedItems.some(({ pricing }) => pricing.lineTotal == null)) {
181
- setQuoteContactError("Confirm the highlighted quantities before requesting a quote.");
184
+ setQuoteContactError(qzaBasketLayout ? "Confirm the highlighted quantities before requesting a quote." : "Confirm the highlighted quantities before sending your basket.");
182
185
  return;
183
186
  }
184
187
  const email = reference.trim();
@@ -264,7 +267,7 @@ export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onRe
264
267
  const currentPage = Math.min(page, totalPages);
265
268
  const pagedItems = pricedItems.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
266
269
  if (quoteRequestSent) {
267
- return (_jsx("div", { className: "wizard-step entry-stage", children: _jsxs("div", { className: "quote-request-sent", role: "status", children: [_jsx("div", { className: "quote-request-sent-icon", "aria-hidden": "true", children: _jsx("svg", { width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "5 12.5 10 17.5 19 7" }) }) }), _jsx("h1", { children: "Quote requested" }), _jsxs("p", { children: ["You're all done. We'll email your sharper price to ", _jsx("strong", { children: reference.trim() }), ", usually within one working day."] }), _jsx("button", { type: "button", className: "cta-btn", onClick: onStartNewQuote, children: _jsx("span", { children: "Start a new quote" }) }), _jsx("p", { className: "quote-request-sent-note", children: "Nothing is charged until you approve the quote." })] }) }));
270
+ return (_jsx("div", { className: "wizard-step entry-stage", children: _jsxs("div", { className: "quote-request-sent", role: "status", children: [_jsx("div", { className: "quote-request-sent-icon", "aria-hidden": "true", children: _jsx("svg", { width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "5 12.5 10 17.5 19 7" }) }) }), _jsx("h1", { children: qzaBasketLayout ? "Quote requested" : "Basket sent" }), qzaBasketLayout ? (_jsxs("p", { children: ["You're all done. We'll email your sharper price to ", _jsx("strong", { children: reference.trim() }), ", usually within one working day."] })) : (_jsxs("p", { children: ["You're all done. We'll email ", _jsx("strong", { children: reference.trim() }), " to confirm prices, availability and delivery, usually within one working day."] })), _jsx("button", { type: "button", className: "cta-btn", onClick: onStartNewQuote, children: _jsx("span", { children: qzaBasketLayout ? "Start a new quote" : "Start a new basket" }) }), _jsx("p", { className: "quote-request-sent-note", children: qzaBasketLayout ? "Nothing is charged until you approve the quote." : "Nothing is charged until you approve the order." })] }) }));
268
271
  }
269
272
  // A customer's empty basket gets the same full-page treatment as the confirmation above.
270
273
  if (!internal && items.length === 0) {
@@ -274,7 +277,7 @@ export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onRe
274
277
  ? "Review the items you've added before exporting them for review."
275
278
  : qzaBasketLayout
276
279
  ? "Check your items and quantities below, and we'll do our best to beat list prices."
277
- : "Review your quantities and order summary before you buy." })] })] }), _jsx("div", { className: "quick-order-panel", style: { paddingTop: 16 }, children: waitingForConversions ? (_jsxs("div", { className: "basket-resolving", role: "status", "aria-label": "Calculating order quantities", children: [_jsx("span", { className: "basket-resolving-wheel", "aria-hidden": "true" }), _jsx("span", { children: "Calculating your quantities\u2026" })] })) : items.length === 0 ? (_jsxs("div", { className: "quick-order-history-empty", children: [_jsx("div", { className: "basket-empty-icon", "aria-hidden": "true", children: "\uD83D\uDCCB" }), _jsx("p", { children: "Your selection is empty." }), _jsx("p", { className: "basket-empty-hint", children: "Go back to your results and add quoted items." }), _jsx("div", { className: "btn-row", style: { marginTop: 20, justifyContent: "center" }, children: _jsx("button", { className: "cta-btn", style: { width: "auto", maxWidth: 280 }, onClick: onBack, "aria-label": "Back to results", children: _jsx("span", { children: "Back to Results" }) }) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "basket-layout", children: [_jsxs("div", { className: "basket-main", children: [_jsx("div", { className: "basket-list", "aria-label": internal ? "Selection items" : qzaBasketLayout ? "Quote request items" : "Basket items", children: pagedItems.map(({ item, pricing }) => {
280
+ : "Review your quantities and order summary, then send us your basket." })] })] }), _jsx("div", { className: "quick-order-panel", style: { paddingTop: 16 }, children: waitingForConversions ? (_jsxs("div", { className: "basket-resolving", role: "status", "aria-label": "Calculating order quantities", children: [_jsx("span", { className: "basket-resolving-wheel", "aria-hidden": "true" }), _jsx("span", { children: "Calculating your quantities\u2026" })] })) : items.length === 0 ? (_jsxs("div", { className: "quick-order-history-empty", children: [_jsx("div", { className: "basket-empty-icon", "aria-hidden": "true", children: "\uD83D\uDCCB" }), _jsx("p", { children: "Your selection is empty." }), _jsx("p", { className: "basket-empty-hint", children: "Go back to your results and add quoted items." }), _jsx("div", { className: "btn-row", style: { marginTop: 20, justifyContent: "center" }, children: _jsx("button", { className: "cta-btn", style: { width: "auto", maxWidth: 280 }, onClick: onBack, "aria-label": "Back to results", children: _jsx("span", { children: "Back to Results" }) }) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "basket-layout", children: [_jsxs("div", { className: "basket-main", children: [_jsx("div", { className: "basket-list", "aria-label": internal ? "Selection items" : qzaBasketLayout ? "Quote request items" : "Basket items", children: pagedItems.map(({ item, pricing }) => {
278
281
  const requestedQtyValue = qtyDrafts[item.lineId] ?? String(pricing.requestedAmount);
279
282
  const requestedLocked = pricing.requestedUnit === pricing.sellUnit || pricing.costingMode === "review";
280
283
  const rawDiffersFromOrder = pricing.requiredSellUnits == null || pricing.requestedAmount !== pricing.requiredSellUnits || pricing.requestedUnit !== pricing.sellUnit;
@@ -294,14 +297,16 @@ export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onRe
294
297
  if (e.key === "Enter")
295
298
  e.target.blur();
296
299
  }, "aria-label": `${item.description} order quantity` }), _jsxs("div", { className: "basket-qty-arrows", children: [_jsx("button", { type: "button", className: "basket-qty-arrow", onClick: () => stepSellUnits(item.lineId, parseFloat(sellUnitsDrafts[item.lineId] ?? String(pricing.requiredSellUnits ?? "0")) || 0, 1), "aria-label": `Increase ${item.description} order quantity`, children: _jsx(ChevronIcon, {}) }), _jsx("button", { type: "button", className: "basket-qty-arrow basket-qty-arrow--down", onClick: () => stepSellUnits(item.lineId, parseFloat(sellUnitsDrafts[item.lineId] ?? String(pricing.requiredSellUnits ?? "0")) || 0, -1), "aria-label": `Decrease ${item.description} order quantity`, children: _jsx(ChevronIcon, {}) })] })] }), _jsx("span", { className: "basket-metric-unit", children: renderDisplayUnit(pricing.sellUnit, parseFloat(sellUnitsDrafts[item.lineId] ?? String(pricing.requiredSellUnits ?? "0")) || 0) })] })] }), _jsxs("div", { className: "basket-stat basket-stat--total", children: [_jsx("span", { className: "basket-stat-label", children: "Total" }), _jsxs("div", { className: "basket-stat-value", children: [_jsx("strong", { children: pricing.lineTotal != null ? fmtCurrency(pricing.lineTotal) : "-" }), pricing.lineTotal != null && _jsx("span", { className: "basket-metric-unit", children: "ex. VAT" })] })] })] })] }, item.lineId));
297
- }) }), totalPages > 1 && (_jsxs("div", { className: "results-pagination", children: [_jsxs("span", { className: "results-pagination-count", children: ["Showing ", (currentPage - 1) * PAGE_SIZE + 1, "\u2013", Math.min(currentPage * PAGE_SIZE, pricedItems.length), " of ", pricedItems.length] }), _jsxs("div", { className: "results-pagination-controls", children: [_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setPage(currentPage - 1), disabled: currentPage === 1, children: "Previous" }), _jsxs("span", { className: "results-pagination-page", children: ["Page ", currentPage, " of ", totalPages] }), _jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setPage(currentPage + 1), disabled: currentPage === totalPages, children: "Next" })] })] }))] }), _jsxs("aside", { className: "basket-sidebar", children: [_jsx("div", { className: "basket-sidebar-title", children: qzaBasketLayout ? "Quote request summary" : "Order summary" }), _jsxs("div", { className: "basket-sidebar-row", children: [_jsx("span", { children: qzaBasketLayout ? "List price (ex. VAT)" : "Subtotal (ex. VAT)" }), _jsx("span", { children: subtotal == null ? "To confirm" : fmtCurrency(subtotal) })] }), _jsxs("div", { className: "basket-sidebar-row", children: [_jsx("span", { children: qzaBasketLayout ? "VAT (20%)" : "VAT (20%)" }), _jsx("span", { children: vat == null ? "To confirm" : fmtCurrency(vat) })] }), _jsxs("div", { className: "basket-sidebar-row basket-sidebar-row--total", children: [_jsx("span", { children: qzaBasketLayout ? "List price (inc. VAT)" : "Total" }), _jsx("span", { children: total == null ? "To confirm" : fmtCurrency(total) })] }), !qzaBasketLayout && (_jsx("div", { className: "basket-summary-waste", children: _jsxs("div", { className: "basket-waste-control", children: [_jsxs("label", { className: "basket-global-waste", children: [_jsx("input", { type: "checkbox", checked: wasteEnabled, onChange: (e) => setWasteEnabled(e.target.checked) }), _jsx("span", { children: "Add waste factor" })] }), _jsxs("label", { className: "basket-global-waste-percent", children: [_jsx("input", { type: "number", inputMode: "decimal", min: "0", step: "0.1", value: wastePercent, onChange: (e) => setWastePercent(e.target.value), disabled: !wasteEnabled, "aria-label": "Waste factor percent" }), _jsx("span", { children: "%" })] })] }) })), _jsx("label", { htmlFor: `${uid}-basket-reference`, className: "basket-sidebar-label", children: qzaBasketLayout ? "Enter email for your sharper quote" : "Your reference (optional)" }), _jsx("input", { id: `${uid}-basket-reference`, type: qzaBasketLayout ? "email" : "text", className: "basket-sidebar-input", value: reference, onChange: (e) => {
300
+ }) }), totalPages > 1 && (_jsxs("div", { className: "results-pagination", children: [_jsxs("span", { className: "results-pagination-count", children: ["Showing ", (currentPage - 1) * PAGE_SIZE + 1, "\u2013", Math.min(currentPage * PAGE_SIZE, pricedItems.length), " of ", pricedItems.length] }), _jsxs("div", { className: "results-pagination-controls", children: [_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setPage(currentPage - 1), disabled: currentPage === 1, children: "Previous" }), _jsxs("span", { className: "results-pagination-page", children: ["Page ", currentPage, " of ", totalPages] }), _jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setPage(currentPage + 1), disabled: currentPage === totalPages, children: "Next" })] })] }))] }), _jsxs("aside", { className: "basket-sidebar", children: [_jsx("div", { className: "basket-sidebar-title", children: qzaBasketLayout ? "Quote request summary" : "Order summary" }), _jsxs("div", { className: "basket-sidebar-row", children: [_jsx("span", { children: qzaBasketLayout ? "List price (ex. VAT)" : "Subtotal (ex. VAT)" }), _jsx("span", { children: subtotal == null ? "To confirm" : fmtCurrency(subtotal) })] }), _jsxs("div", { className: "basket-sidebar-row", children: [_jsx("span", { children: qzaBasketLayout ? "VAT (20%)" : "VAT (20%)" }), _jsx("span", { children: vat == null ? "To confirm" : fmtCurrency(vat) })] }), _jsxs("div", { className: "basket-sidebar-row basket-sidebar-row--total", children: [_jsx("span", { children: qzaBasketLayout ? "List price (inc. VAT)" : "Total" }), _jsx("span", { children: total == null ? "To confirm" : fmtCurrency(total) })] }), !qzaBasketLayout && (_jsx("div", { className: "basket-summary-waste", children: _jsxs("div", { className: "basket-waste-control", children: [_jsxs("label", { className: "basket-global-waste", children: [_jsx("input", { type: "checkbox", checked: wasteEnabled, onChange: (e) => setWasteEnabled(e.target.checked) }), _jsx("span", { children: "Add waste factor" })] }), _jsxs("label", { className: "basket-global-waste-percent", children: [_jsx("input", { type: "number", inputMode: "decimal", min: "0", step: "0.1", value: wastePercent, onChange: (e) => setWastePercent(e.target.value), disabled: !wasteEnabled, "aria-label": "Waste factor percent" }), _jsx("span", { children: "%" })] })] }) })), _jsx("label", { htmlFor: `${uid}-basket-reference`, className: "basket-sidebar-label", children: qzaBasketLayout ? "Enter email for your sharper quote" : sendsRequest ? "Your email" : "Your reference (optional)" }), _jsx("input", { id: `${uid}-basket-reference`, type: sendsRequest ? "email" : "text", className: "basket-sidebar-input", value: reference, onChange: (e) => {
298
301
  setReference(e.target.value);
299
- if (qzaBasketLayout)
302
+ if (sendsRequest)
300
303
  setQuoteContactError("");
301
- }, placeholder: qzaBasketLayout ? "you@example.com" : "PO number or job reference", "aria-invalid": qzaBasketLayout && quoteContactError === INVALID_EMAIL_MESSAGE, "aria-describedby": qzaBasketLayout && quoteContactError ? `${uid}-basket-reference-error` : undefined }), qzaBasketLayout && quoteContactError && (_jsx("p", { id: `${uid}-basket-reference-error`, className: "basket-quote-request-error", role: "alert", children: quoteContactError })), qzaBasketLayout ? (_jsx("button", { type: "button", className: "cta-btn", onClick: () => void submitQuoteRequest(), disabled: quoteRequestSending || unpriced > 0 || resolvingCount > 0, "aria-busy": quoteRequestSending, children: _jsx("span", { children: quoteRequestSending ? "Sending…" : "Request our best quote" }) })) : (_jsx("button", { className: "cta-btn", onClick: () => {
304
+ }, placeholder: sendsRequest ? "you@example.com" : "PO number or job reference", "aria-invalid": sendsRequest && quoteContactError === INVALID_EMAIL_MESSAGE, "aria-describedby": sendsRequest && quoteContactError ? `${uid}-basket-reference-error` : undefined }), sendsRequest && quoteContactError && (_jsx("p", { id: `${uid}-basket-reference-error`, className: "basket-quote-request-error", role: "alert", children: quoteContactError })), sendsRequest ? (_jsx("button", { type: "button", className: "cta-btn", onClick: () => void submitQuoteRequest(), disabled: quoteRequestSending || unpriced > 0 || resolvingCount > 0, "aria-busy": quoteRequestSending, children: _jsx("span", { children: quoteRequestSending ? "Sending…" : qzaBasketLayout ? "Request our best quote" : "Send basket" }) })) : (_jsx("button", { className: "cta-btn", onClick: () => {
302
305
  track("downloaded", { type: "basket", items: items.length });
303
306
  void exportBasketExcel(items);
304
- }, "aria-label": internal ? "Download selection" : "Download basket", children: _jsx("span", { children: internal ? "Download Quote" : "Buy Now" }) })), _jsxs("p", { className: "basket-sidebar-disclaimer", children: [_jsx("span", { "aria-hidden": "true", children: "\u26A0\uFE0F" }), qzaBasketLayout
307
+ }, "aria-label": "Download selection", children: _jsx("span", { children: "Download Quote" }) })), _jsxs("p", { className: "basket-sidebar-disclaimer", children: [_jsx("span", { "aria-hidden": "true", children: "\u26A0\uFE0F" }), qzaBasketLayout
305
308
  ? "This is a quote request, not an order. We'll confirm our best price, availability and delivery before you approve."
306
- : "Prices and availability are estimates and should be confirmed before procurement."] })] })] }), _jsx("div", { className: "btn-row results-actions", children: _jsxs("button", { className: "cta-btn cta-btn-secondary", style: { flex: "0 1 30%", gap: 10, justifyContent: "center" }, onClick: onBack, "aria-label": "Back to results", children: [_jsx(BackArrow, {}), _jsx("span", { children: "Back to results" })] }) })] })) })] }) }));
309
+ : sendsRequest
310
+ ? "Sending your basket isn't an order. We'll confirm prices, availability and delivery before you approve."
311
+ : "Prices and availability are estimates and should be confirmed before procurement."] })] })] }), _jsx("div", { className: "btn-row results-actions", children: _jsxs("button", { className: "cta-btn cta-btn-secondary", style: { flex: "0 1 30%", gap: 10, justifyContent: "center" }, onClick: onBack, "aria-label": "Back to results", children: [_jsx(BackArrow, {}), _jsx("span", { children: "Back to results" })] }) })] })) })] }) }));
307
312
  }
@@ -17,5 +17,5 @@ export declare function candidateFromCart(item: CartItem): MatchCandidate;
17
17
  export declare function refreshedCartItem(refreshed: CartItem, existing: CartItem): CartItem;
18
18
  export declare function displayLineLabel(item: LineItem, allItems: LineItem[]): string;
19
19
  export declare function formatResultsTotal(totalValue: number | null, needsPricingCount: number): string | null;
20
- export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal }: ResultsStageProps): import("react").JSX.Element;
20
+ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal, }: ResultsStageProps): import("react").JSX.Element;
21
21
  export {};
@@ -198,7 +198,9 @@ function curatedCandidates(candidates, result, selectedProductId) {
198
198
  const recommendedIds = result?.rerank?.recommendedCandidateIds ?? [];
199
199
  if (recommendedIds.length === 0) {
200
200
  const topCandidates = candidates.slice(0, 6);
201
- const pinnedSelected = !topCandidates.some((candidate) => candidate.product_id === selectedProductId) ? candidates.find((candidate) => candidate.product_id === selectedProductId) : undefined;
201
+ const pinnedSelected = !topCandidates.some((candidate) => candidate.product_id === selectedProductId)
202
+ ? candidates.find((candidate) => candidate.product_id === selectedProductId)
203
+ : undefined;
202
204
  return pinnedSelected ? [...topCandidates, pinnedSelected] : topCandidates;
203
205
  }
204
206
  const byId = new Map(candidates.map((candidate) => [candidate.product_id, candidate]));
@@ -326,7 +328,7 @@ function CandidateBreakdown({ item, candidates, result, decision, unmatchable, s
326
328
  }, "aria-label": isCurrent ? `${c.description} selected` : `Use ${c.description}` }), _jsx("span", { className: "switch-toggle-track", "aria-hidden": "true" })] }) })] }, `${c.product_id}-${i}`));
327
329
  }) })] })] }));
328
330
  }
329
- export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal = false }) {
331
+ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal = false, }) {
330
332
  const qzaQuoteRequest = !internal && getBoqClientId() === "QZA6-MRA4";
331
333
  const customerCollection = qzaQuoteRequest ? "quote" : "basket";
332
334
  const { results, lineItems, parseReport, timings } = scheduleResult;
@@ -368,7 +370,10 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
368
370
  const pick = pickOverrides[item.line_id];
369
371
  const automaticMatch = resolveBest(result, pick);
370
372
  const savedCartMatch = cart[item.line_id];
371
- const matched = pick === NO_SELECTION ? undefined : (pickOverrideCandidates[item.line_id] ?? (savedCartMatch && savedCartMatch.productId !== automaticMatch?.product_id ? candidateFromCart(savedCartMatch) : automaticMatch));
373
+ const matched = pick === NO_SELECTION
374
+ ? undefined
375
+ : (pickOverrideCandidates[item.line_id] ??
376
+ (savedCartMatch && savedCartMatch.productId !== automaticMatch?.product_id ? candidateFromCart(savedCartMatch) : automaticMatch));
372
377
  const best = unmatchable ? undefined : matched;
373
378
  const qty = item.qty ?? 0;
374
379
  return {
@@ -671,7 +676,9 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
671
676
  }));
672
677
  const reviewCandidates = reviewQuery ? catalogueSearchCandidates : llmReviewCandidates;
673
678
  const savedSearchCandidate = activeReviewRow ? savedSearchCandidates[activeReviewRow.item.line_id] : undefined;
674
- const reviewDisplayCandidates = !reviewQuery && savedSearchCandidate && !reviewCandidates.some((candidate) => candidate.product_id === savedSearchCandidate.product_id) ? [...reviewCandidates, savedSearchCandidate] : reviewCandidates;
679
+ const reviewDisplayCandidates = !reviewQuery && savedSearchCandidate && !reviewCandidates.some((candidate) => candidate.product_id === savedSearchCandidate.product_id)
680
+ ? [...reviewCandidates, savedSearchCandidate]
681
+ : reviewCandidates;
675
682
  const reviewOptionCandidates = (() => {
676
683
  const topCandidates = reviewDisplayCandidates.slice(0, reviewQuery ? 12 : 6);
677
684
  if (reviewQuery || !savedSearchCandidate || topCandidates.some((candidate) => candidate.product_id === savedSearchCandidate.product_id))
@@ -680,22 +687,22 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
680
687
  })();
681
688
  const activeReviewCartMatches = !!activeReviewRow?.best && cart[activeReviewRow.item.line_id]?.productId === activeReviewRow.best.product_id;
682
689
  const activeReviewHasPendingUpdate = !!activeReviewRow?.best && !!cart[activeReviewRow.item.line_id] && !activeReviewCartMatches;
683
- return (_jsxs("div", { className: "wizard-step entry-stage", children: [_jsxs("div", { className: `quick-order-card${internal ? "" : " quick-order-card--customer"}`, children: [_jsx("div", { className: `quick-order-hero${internal ? "" : " quick-order-hero--customer"}`, children: internal ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "quick-order-eyebrow", children: "Construction Services" }), _jsxs("div", { className: "quick-order-hero-title-row", children: [_jsx("h1", { children: "Your Results" }), _jsxs("span", { className: "summary-chip summary-chip--scope", children: [_jsx("strong", { children: totalLabel ?? "Review quantities" }), " Est. total"] })] }), _jsx("p", { children: "Each line item from your schedule matched against your catalogue, ranked by confidence." })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("h1", { children: "Review your matched products" }), _jsxs("p", { children: ["Check you're happy with these items, then click ", qzaQuoteRequest ? "View Quote" : "View Basket", " to continue."] })] }), _jsxs("button", { type: "button", className: "results-total-action", onClick: onGoToBasket, disabled: cartCount === 0, "aria-label": qzaQuoteRequest ? "View quote" : "View basket", children: [_jsxs("span", { children: [_jsx("small", { children: qzaQuoteRequest ? "View Quote" : "View Basket" }), totalLabel != null && _jsx("strong", { children: totalLabel })] }), _jsx("span", { className: "arrow", children: _jsx(ForwardArrow, {}) })] })] })) }), _jsxs("div", { className: "quick-order-panel", style: { paddingTop: 16 }, children: [debugEnabled && (_jsx("div", { className: "results-summary", style: { marginBottom: 12 }, children: _jsx("button", { type: "button", className: "bom-toolbar-btn", "aria-pressed": showTimings, onClick: () => setShowTimings((value) => !value), children: showTimings ? "Hide timings" : "Show timings" }) })), debugEnabled && showTimings && _jsx(TimingBreakdown, { ...timings }), !qzaQuoteRequest && (_jsx("div", { className: "results-toolbar-row", children: !activeReviewRow && (_jsxs("div", { className: "results-summary", style: { marginBottom: 0 }, children: [needsPricingCount > 0 && (_jsxs("span", { className: "summary-chip summary-chip--neutral", children: [_jsx("strong", { children: needsPricingCount }), " need pricing"] })), _jsxs("div", { style: {
684
- display: "flex",
685
- gap: 8,
686
- flexWrap: "wrap",
687
- alignItems: "center",
688
- }, children: [_jsx("input", { type: "text", className: "results-search-input", placeholder: "Search description...", value: search, onChange: (e) => setSearch(e.target.value), "aria-label": "Search line items" }), !internal && (_jsxs("div", { className: "results-view-toggle", "aria-label": "Results view", children: [_jsx("button", { type: "button", className: `results-view-toggle__btn${viewMode === "tile" ? " is-active" : ""}`, "aria-pressed": viewMode === "tile", "aria-label": "Tile view", title: "Tile view", onClick: () => setViewMode("tile"), children: _jsxs("svg", { width: "18", height: "12", viewBox: "0 0 15 10", fill: "currentColor", children: [_jsx("rect", { x: "0.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "5.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "10.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "0.5", y: "5.5", width: "4", height: "4" }), _jsx("rect", { x: "5.5", y: "5.5", width: "4", height: "4" }), _jsx("rect", { x: "10.5", y: "5.5", width: "4", height: "4" })] }) }), _jsx("button", { type: "button", className: `results-view-toggle__btn${viewMode === "list" ? " is-active" : ""}`, "aria-pressed": viewMode === "list", "aria-label": "List view", title: "List view", onClick: () => setViewMode("list"), children: _jsxs("svg", { width: "17", height: "17", viewBox: "0 0 15 15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: [_jsx("line", { x1: "0.5", y1: "2.5", x2: "14.5", y2: "2.5" }), _jsx("line", { x1: "0.5", y1: "7.5", x2: "14.5", y2: "7.5" }), _jsx("line", { x1: "0.5", y1: "12.5", x2: "14.5", y2: "12.5" })] }) })] })), debugEnabled && (_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setDetailMode((mode) => (mode === "simple" ? "detailed" : "simple")), children: detailMode === "detailed" ? "Hide detailed view" : "Show detailed view" })), _jsxs("details", { ref: sortMenuRef, style: { position: "relative" }, children: [_jsx("summary", { className: "bom-toolbar-btn sort-menu-summary", "aria-label": "Sort line items", style: { fontSize: 16 }, children: "\u21C5" }), _jsx("div", { className: "sort-menu-panel", children: SORT_OPTIONS.map((opt) => {
689
- const active = sortConfig === opt.dir;
690
- return (_jsxs("button", { type: "button", className: `sort-menu-item${active ? " is-active" : ""}`, onClick: () => {
691
- setSortConfig(active ? null : opt.dir);
692
- sortMenuRef.current?.removeAttribute("open");
693
- }, children: [_jsx("span", { className: "sort-menu-item__check", children: active ? "✓" : "" }), opt.label] }, opt.dir));
694
- }) })] })] })] })) })), viewMode === "tile" || activeReviewRow ? (_jsxs(_Fragment, { children: [activeReviewRow ? (_jsx("div", { className: "modal-backdrop quick-order-modal-backdrop", role: "dialog", "aria-modal": "true", "aria-label": "Product options", onClick: () => closeReview(), children: _jsxs("section", { className: "quick-order-workflow-modal results-review-modal", onClick: (event) => event.stopPropagation(), children: [_jsx("button", { type: "button", className: "quick-order-workflow-close results-review-modal__close", onClick: () => closeReview(), "aria-label": "Close product options", children: _jsx(CloseIcon, {}) }), _jsx("div", { className: "results-review-page", children: internal ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "results-review-page__top", children: [_jsxs("button", { type: "button", className: "bom-toolbar-btn bom-toolbar-btn--accent", onClick: () => closeReview(), children: [_jsx(BackArrow, {}), "Back to results"] }), _jsxs("div", { style: {
690
+ return (_jsxs("div", { className: "wizard-step entry-stage", children: [_jsxs("div", { className: `quick-order-card${internal ? "" : " quick-order-card--customer"}`, children: [_jsx("div", { className: `quick-order-hero${internal ? "" : " quick-order-hero--customer"}`, children: internal ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "quick-order-eyebrow", children: "Construction Services" }), _jsxs("div", { className: "quick-order-hero-title-row", children: [_jsx("h1", { children: "Your Results" }), _jsxs("span", { className: "summary-chip summary-chip--scope", children: [_jsx("strong", { children: totalLabel ?? "Review quantities" }), " Est. total"] })] }), _jsx("p", { children: "Each line item from your schedule matched against your catalogue, ranked by confidence." })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("h1", { children: "Review your matched products" }), _jsxs("p", { children: ["Check you're happy with these items, then click ", qzaQuoteRequest ? "View Quote" : "View Basket", " to continue."] })] }), _jsxs("button", { type: "button", className: "results-total-action", onClick: onGoToBasket, disabled: cartCount === 0, "aria-label": qzaQuoteRequest ? "View quote" : "View basket", children: [_jsxs("span", { children: [_jsx("small", { children: qzaQuoteRequest ? "View Quote" : "View Basket" }), totalLabel != null && _jsx("strong", { children: totalLabel })] }), _jsx("span", { className: "arrow", children: _jsx(ForwardArrow, {}) })] })] })) }), _jsxs("div", { className: "quick-order-panel", style: { paddingTop: 16 }, children: [debugEnabled && (_jsx("div", { className: "results-summary", style: { marginBottom: 12 }, children: _jsx("button", { type: "button", className: "bom-toolbar-btn", "aria-pressed": showTimings, onClick: () => setShowTimings((value) => !value), children: showTimings ? "Hide timings" : "Show timings" }) })), debugEnabled && showTimings && _jsx(TimingBreakdown, { ...timings }), !qzaQuoteRequest && (_jsx("div", { className: "results-toolbar-row", children: !activeReviewRow && (_jsx("div", { className: "results-summary", style: { marginBottom: 0 }, children: _jsxs("div", { style: {
691
+ display: "flex",
692
+ gap: 8,
693
+ flexWrap: "wrap",
694
+ alignItems: "center",
695
+ }, children: [_jsx("input", { type: "text", className: "results-search-input", placeholder: "Search description...", value: search, onChange: (e) => setSearch(e.target.value), "aria-label": "Search line items" }), !internal && (_jsxs("div", { className: "results-view-toggle", "aria-label": "Results view", children: [_jsx("button", { type: "button", className: `results-view-toggle__btn${viewMode === "tile" ? " is-active" : ""}`, "aria-pressed": viewMode === "tile", "aria-label": "Tile view", title: "Tile view", onClick: () => setViewMode("tile"), children: _jsxs("svg", { width: "18", height: "12", viewBox: "0 0 15 10", fill: "currentColor", children: [_jsx("rect", { x: "0.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "5.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "10.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "0.5", y: "5.5", width: "4", height: "4" }), _jsx("rect", { x: "5.5", y: "5.5", width: "4", height: "4" }), _jsx("rect", { x: "10.5", y: "5.5", width: "4", height: "4" })] }) }), _jsx("button", { type: "button", className: `results-view-toggle__btn${viewMode === "list" ? " is-active" : ""}`, "aria-pressed": viewMode === "list", "aria-label": "List view", title: "List view", onClick: () => setViewMode("list"), children: _jsxs("svg", { width: "17", height: "17", viewBox: "0 0 15 15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: [_jsx("line", { x1: "0.5", y1: "2.5", x2: "14.5", y2: "2.5" }), _jsx("line", { x1: "0.5", y1: "7.5", x2: "14.5", y2: "7.5" }), _jsx("line", { x1: "0.5", y1: "12.5", x2: "14.5", y2: "12.5" })] }) })] })), debugEnabled && (_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setDetailMode((mode) => (mode === "simple" ? "detailed" : "simple")), children: detailMode === "detailed" ? "Hide detailed view" : "Show detailed view" })), _jsxs("details", { ref: sortMenuRef, style: { position: "relative" }, children: [_jsx("summary", { className: "bom-toolbar-btn sort-menu-summary", "aria-label": "Sort line items", style: { fontSize: 16 }, children: "\u21C5" }), _jsx("div", { className: "sort-menu-panel", children: SORT_OPTIONS.map((opt) => {
696
+ const active = sortConfig === opt.dir;
697
+ return (_jsxs("button", { type: "button", className: `sort-menu-item${active ? " is-active" : ""}`, onClick: () => {
698
+ setSortConfig(active ? null : opt.dir);
699
+ sortMenuRef.current?.removeAttribute("open");
700
+ }, children: [_jsx("span", { className: "sort-menu-item__check", children: active ? "✓" : "" }), opt.label] }, opt.dir));
701
+ }) })] })] }) })) })), viewMode === "tile" || activeReviewRow ? (_jsxs(_Fragment, { children: [activeReviewRow ? (_jsx("div", { className: "modal-backdrop quick-order-modal-backdrop", role: "dialog", "aria-modal": "true", "aria-label": "Product options", onClick: () => closeReview(), children: _jsxs("section", { className: "quick-order-workflow-modal results-review-modal", onClick: (event) => event.stopPropagation(), children: [_jsx("button", { type: "button", className: "quick-order-workflow-close results-review-modal__close", onClick: () => closeReview(), "aria-label": "Close product options", children: _jsx(CloseIcon, {}) }), _jsx("div", { className: "results-review-page", children: internal ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "results-review-page__top", children: [_jsxs("button", { type: "button", className: "bom-toolbar-btn bom-toolbar-btn--accent", onClick: () => closeReview(), children: [_jsx(BackArrow, {}), "Back to results"] }), _jsxs("div", { style: {
695
702
  display: "flex",
696
703
  gap: 8,
697
704
  flexWrap: "wrap",
698
- }, children: [debugEnabled && (_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setDetailMode((mode) => (mode === "simple" ? "detailed" : "simple")), children: detailMode === "detailed" ? "Hide detailed info" : "Show detailed info" })), _jsx("div", { className: "results-review-searchbar", children: _jsx("input", { type: "text", className: "results-search-input", placeholder: "Search products in catalogue...", value: reviewSearch, onChange: (e) => setReviewSearch(e.target.value), "aria-label": "Search database candidates" }) }), _jsx("div", { className: "results-candidate-actions", children: _jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => clearPickedProduct(activeReviewRow.item.line_id), children: "Clear selected product" }) })] })] }), _jsxs("div", { className: "results-review-page__hero", children: [_jsx("div", { className: "results-review-page__image-wrap", children: _jsx("img", { className: "fill-img results-review-page__image", src: productImageSrc(previewCandidate(activeReviewRow)?.description || activeReviewRow.item.description_original, previewCandidate(activeReviewRow)?.product_id, previewCandidate(activeReviewRow)?.pdp_photo_url), alt: "" }) }), _jsxs("div", { className: "results-review-page__summary", children: [_jsx("h2", { children: displayLineLabel(activeReviewRow.item, lineItems) }), _jsxs("p", { className: "results-review-page__meta", children: [fmtQty(activeReviewRow.qty), " ", renderDisplayUnit(activeReviewRow.item.unit, activeReviewRow.qty)] }), activeReviewRow.best ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "results-review-page__product", children: activeReviewRow.best.description }), activeReviewRow.best.price != null ? _jsx("div", { className: "results-row-meta", children: fmtPrice(activeReviewRow.best.price, activeReviewRow.best.price_basis) }) : null] })) : previewCandidate(activeReviewRow) ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "results-review-page__product", children: previewCandidate(activeReviewRow)?.description }), previewCandidate(activeReviewRow)?.price != null ? (_jsx("div", { className: "results-row-meta", children: fmtPrice(previewCandidate(activeReviewRow)?.price ?? null, previewCandidate(activeReviewRow)?.price_basis ?? null) })) : null, _jsx("div", { className: "results-row-meta", children: "Best reviewed alternative - not yet selected" })] })) : (_jsx("div", { className: "results-empty-hint", children: "No product currently selected for this line item." })), hasVisibleLinkedGroup(activeReviewRow.item) ? (_jsxs("div", { style: {
705
+ }, children: [debugEnabled && (_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setDetailMode((mode) => (mode === "simple" ? "detailed" : "simple")), children: detailMode === "detailed" ? "Hide detailed info" : "Show detailed info" })), _jsx("div", { className: "results-review-searchbar", children: _jsx("input", { type: "text", className: "results-search-input", placeholder: "Search products in catalogue...", value: reviewSearch, onChange: (e) => setReviewSearch(e.target.value), "aria-label": "Search database candidates" }) }), _jsx("div", { className: "results-candidate-actions", children: _jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => clearPickedProduct(activeReviewRow.item.line_id), children: "Clear selected product" }) })] })] }), _jsxs("div", { className: "results-review-page__hero", children: [_jsx("div", { className: "results-review-page__image-wrap", children: _jsx("img", { className: "fill-img results-review-page__image", src: productImageSrc(previewCandidate(activeReviewRow)?.description || activeReviewRow.item.description_original, previewCandidate(activeReviewRow)?.product_id, previewCandidate(activeReviewRow)?.pdp_photo_url), alt: "" }) }), _jsxs("div", { className: "results-review-page__summary", children: [_jsx("h2", { children: displayLineLabel(activeReviewRow.item, lineItems) }), _jsxs("p", { className: "results-review-page__meta", children: [fmtQty(activeReviewRow.qty), " ", renderDisplayUnit(activeReviewRow.item.unit, activeReviewRow.qty)] }), activeReviewRow.best ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "results-review-page__product", children: activeReviewRow.best.description }), activeReviewRow.best.price != null ? (_jsx("div", { className: "results-row-meta", children: fmtPrice(activeReviewRow.best.price, activeReviewRow.best.price_basis) })) : null] })) : previewCandidate(activeReviewRow) ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "results-review-page__product", children: previewCandidate(activeReviewRow)?.description }), previewCandidate(activeReviewRow)?.price != null ? (_jsx("div", { className: "results-row-meta", children: fmtPrice(previewCandidate(activeReviewRow)?.price ?? null, previewCandidate(activeReviewRow)?.price_basis ?? null) })) : null, _jsx("div", { className: "results-row-meta", children: "Best reviewed alternative - not yet selected" })] })) : (_jsx("div", { className: "results-empty-hint", children: "No product currently selected for this line item." })), hasVisibleLinkedGroup(activeReviewRow.item) ? (_jsxs("div", { style: {
699
706
  display: "flex",
700
707
  alignItems: "center",
701
708
  gap: 8,
@@ -708,7 +715,19 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
708
715
  saveSearchCandidate(activeReviewRow.item.line_id, activeReviewRow.best);
709
716
  onAddToCart(toCartItem(activeReviewRow));
710
717
  }
711
- }, disabled: !activeReviewRow.best, "aria-label": cart[activeReviewRow.item.line_id] ? (internal ? "Remove from selection" : `Remove from ${customerCollection}`) : internal ? "Add to selection" : `Add to ${customerCollection}`, children: cart[activeReviewRow.item.line_id] ? "✓ Added" : internal ? "+ Add to selection" : `Add to ${customerCollection}` }) })] })] }), catalogueSearchLoading ? _jsx("div", { className: "results-empty-card", children: "Searching catalogue\u2026" }) : null, !catalogueSearchLoading && catalogueSearchError ? _jsx("div", { className: "results-empty-card", children: catalogueSearchError }) : null, !catalogueSearchLoading && !catalogueSearchError && reviewQuery && reviewCandidates.length === 0 ? (_jsx("div", { className: "results-empty-card", children: "No candidate products match this database search." })) : !catalogueSearchLoading && !catalogueSearchError ? (_jsx(CandidateBreakdown, { item: activeReviewRow.item, candidates: reviewCandidates, result: activeReviewRow.result, decision: activeReviewRow.decision, unmatchable: activeReviewRow.unmatchable, selectedProductId: activeReviewRow.best?.product_id ?? null, detailMode: detailMode, linkedGroupMeta: hasVisibleLinkedGroup(activeReviewRow.item) ? linkedGroupMetaById.get(activeReviewRow.item.linked_group_id) : undefined, onPick: (candidate) => handlePickProduct(activeReviewRow.item.line_id, candidate), onClear: () => clearPickedProduct(activeReviewRow.item.line_id), showAll: internal || debugEnabled || reviewQuery.length > 0, internal: internal, debugEnabled: debugEnabled, forceReviewNote: true })) : null] })) : (_jsxs(_Fragment, { children: [_jsxs("section", { className: "customer-review-request", children: [_jsx("span", { className: "customer-review-eyebrow", children: "Original request" }), _jsx("strong", { children: displayLineLabel(activeReviewRow.item, lineItems) || "Original request" })] }), activeReviewReason ? (_jsxs("section", { className: "customer-review-ai", children: [_jsx("strong", { children: hasNoSuitableCandidate ? "No automatic match:" : "AI review:" }), " ", activeReviewReason] })) : null, _jsxs("section", { className: "customer-review-options", children: [_jsx("div", { className: "customer-review-options__head", children: _jsxs("div", { children: [_jsx("h2", { children: hasNoSuitableCandidate ? (llmReviewCandidates.length > 0 ? "Choose a product" : "No suitable product found") : hasAiVerdict ? "Choose a product" : "We couldn't review this item" }), _jsx("p", { children: hasNoSuitableCandidate
718
+ }, disabled: !activeReviewRow.best, "aria-label": cart[activeReviewRow.item.line_id]
719
+ ? internal
720
+ ? "Remove from selection"
721
+ : `Remove from ${customerCollection}`
722
+ : internal
723
+ ? "Add to selection"
724
+ : `Add to ${customerCollection}`, children: cart[activeReviewRow.item.line_id] ? "✓ Added" : internal ? "+ Add to selection" : `Add to ${customerCollection}` }) })] })] }), catalogueSearchLoading ? _jsx("div", { className: "results-empty-card", children: "Searching catalogue\u2026" }) : null, !catalogueSearchLoading && catalogueSearchError ? _jsx("div", { className: "results-empty-card", children: catalogueSearchError }) : null, !catalogueSearchLoading && !catalogueSearchError && reviewQuery && reviewCandidates.length === 0 ? (_jsx("div", { className: "results-empty-card", children: "No candidate products match this database search." })) : !catalogueSearchLoading && !catalogueSearchError ? (_jsx(CandidateBreakdown, { item: activeReviewRow.item, candidates: reviewCandidates, result: activeReviewRow.result, decision: activeReviewRow.decision, unmatchable: activeReviewRow.unmatchable, selectedProductId: activeReviewRow.best?.product_id ?? null, detailMode: detailMode, linkedGroupMeta: hasVisibleLinkedGroup(activeReviewRow.item) ? linkedGroupMetaById.get(activeReviewRow.item.linked_group_id) : undefined, onPick: (candidate) => handlePickProduct(activeReviewRow.item.line_id, candidate), onClear: () => clearPickedProduct(activeReviewRow.item.line_id), showAll: internal || debugEnabled || reviewQuery.length > 0, internal: internal, debugEnabled: debugEnabled, forceReviewNote: true })) : null] })) : (_jsxs(_Fragment, { children: [_jsxs("section", { className: "customer-review-request", children: [_jsx("span", { className: "customer-review-eyebrow", children: "Original request" }), _jsx("strong", { children: displayLineLabel(activeReviewRow.item, lineItems) || "Original request" })] }), activeReviewReason ? (_jsxs("section", { className: "customer-review-ai", children: [_jsx("strong", { children: hasNoSuitableCandidate ? "No automatic match:" : "AI review:" }), " ", activeReviewReason] })) : null, _jsxs("section", { className: "customer-review-options", children: [_jsx("div", { className: "customer-review-options__head", children: _jsxs("div", { children: [_jsx("h2", { children: hasNoSuitableCandidate
725
+ ? llmReviewCandidates.length > 0
726
+ ? "Choose a product"
727
+ : "No suitable product found"
728
+ : hasAiVerdict
729
+ ? "Choose a product"
730
+ : "We couldn't review this item" }), _jsx("p", { children: hasNoSuitableCandidate
712
731
  ? llmReviewCandidates.length > 0
713
732
  ? "No product was selected automatically. Review these related options before adding one."
714
733
  : "Search the catalogue to choose an alternative."
@@ -717,7 +736,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
717
736
  : "Search the catalogue to choose a product." })] }) }), !reviewQuery && reviewDisplayCandidates.length > 0 ? (_jsx("div", { className: "customer-review-option-grid", children: reviewOptionCandidates.map((candidate) => {
718
737
  const selected = candidate.product_id === activeReviewRow.best?.product_id;
719
738
  return (_jsxs("button", { type: "button", className: `customer-review-option${selected ? " is-selected" : ""}`, onClick: () => handlePickProduct(activeReviewRow.item.line_id, candidate), "aria-pressed": selected, children: [_jsx("img", { className: "customer-review-option__image", src: productImageSrc(candidate.description, candidate.product_id, candidate.pdp_photo_url), alt: "", width: 64, height: 64 }), _jsx("span", { className: "customer-review-option__copy", children: _jsx("strong", { children: candidate.description }) }), _jsx("span", { className: "customer-review-option__price", children: candidate.price != null ? `£${fmtPrice(candidate.price, candidate.price_basis)}` : "-" }), _jsx("span", { className: "customer-review-option__state", children: selected ? "Selected" : "Choose" })] }, candidate.product_id));
720
- }) })) : null, reviewDisplayCandidates.length > 0 && (_jsx("div", { className: "customer-review-divider", children: _jsx("span", { children: "or" }) })), _jsxs("label", { className: "customer-review-search", children: [_jsx("span", { children: "Search our full catalogue" }), _jsx("input", { type: "text", className: "results-search-input", placeholder: "Search products...", value: reviewSearch, onChange: (e) => setReviewSearch(e.target.value), "aria-label": "Search catalogue" })] }), reviewQuery && catalogueSearchLoading ? _jsx("div", { className: "results-empty-card", children: "Searching catalogue\u2026" }) : null, reviewQuery && !catalogueSearchLoading && catalogueSearchError ? _jsx("div", { className: "results-empty-card", children: catalogueSearchError }) : null, reviewQuery && !catalogueSearchLoading && !catalogueSearchError && reviewDisplayCandidates.length === 0 ? _jsx("div", { className: "results-empty-card", children: "No products match this search." }) : null, reviewQuery && !catalogueSearchLoading && !catalogueSearchError && reviewDisplayCandidates.length > 0 ? (_jsx("div", { className: "customer-review-option-grid", children: reviewOptionCandidates.map((candidate) => {
739
+ }) })) : null, reviewDisplayCandidates.length > 0 && (_jsx("div", { className: "customer-review-divider", children: _jsx("span", { children: "or" }) })), _jsxs("label", { className: "customer-review-search", children: [_jsx("span", { children: "Search our full catalogue" }), _jsx("input", { type: "text", className: "results-search-input", placeholder: "Search products...", value: reviewSearch, onChange: (e) => setReviewSearch(e.target.value), "aria-label": "Search catalogue" })] }), reviewQuery && catalogueSearchLoading ? _jsx("div", { className: "results-empty-card", children: "Searching catalogue\u2026" }) : null, reviewQuery && !catalogueSearchLoading && catalogueSearchError ? _jsx("div", { className: "results-empty-card", children: catalogueSearchError }) : null, reviewQuery && !catalogueSearchLoading && !catalogueSearchError && reviewDisplayCandidates.length === 0 ? (_jsx("div", { className: "results-empty-card", children: "No products match this search." })) : null, reviewQuery && !catalogueSearchLoading && !catalogueSearchError && reviewDisplayCandidates.length > 0 ? (_jsx("div", { className: "customer-review-option-grid", children: reviewOptionCandidates.map((candidate) => {
721
740
  const selected = candidate.product_id === activeReviewRow.best?.product_id;
722
741
  return (_jsxs("button", { type: "button", className: `customer-review-option${selected ? " is-selected" : ""}`, onClick: () => handlePickProduct(activeReviewRow.item.line_id, candidate), "aria-pressed": selected, children: [_jsx("img", { className: "customer-review-option__image", src: productImageSrc(candidate.description, candidate.product_id, candidate.pdp_photo_url), alt: "", width: 64, height: 64 }), _jsx("span", { className: "customer-review-option__copy", children: _jsx("strong", { children: candidate.description }) }), _jsx("span", { className: "customer-review-option__price", children: candidate.price != null ? `£${fmtPrice(candidate.price, candidate.price_basis)}` : "-" }), _jsx("span", { className: "customer-review-option__state", children: selected ? "Selected" : "Choose" })] }, candidate.product_id));
723
742
  }) })) : null] }), activeReviewRow.best ? (activeReviewHasPendingUpdate ? (_jsxs("div", { style: { display: "grid", gap: 8 }, children: [_jsx("button", { type: "button", className: "cta-btn customer-review-add", onClick: () => {
@@ -746,7 +765,13 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
746
765
  else {
747
766
  onAddToCart(toCartItem(row));
748
767
  }
749
- }, disabled: !preview, "aria-label": cart[item.line_id] ? (internal ? "Remove from selection" : `Remove from ${customerCollection}`) : internal ? "Add to selection" : `Add to ${customerCollection}`, children: cart[item.line_id] ? "✓ Added" : internal ? "+ Add" : `Add to ${customerCollection}` })] })] }, item.line_id));
768
+ }, disabled: !preview, "aria-label": cart[item.line_id]
769
+ ? internal
770
+ ? "Remove from selection"
771
+ : `Remove from ${customerCollection}`
772
+ : internal
773
+ ? "Add to selection"
774
+ : `Add to ${customerCollection}`, children: cart[item.line_id] ? "✓ Added" : internal ? "+ Add" : `Add to ${customerCollection}` })] })] }, item.line_id));
750
775
  }) }) })) : null, unmatchedSection] })] })) : (_jsxs(_Fragment, { children: [hoveredRowBounds && (_jsx("div", { className: "results-table__hover-overlay", style: hoveredRowBounds, "aria-hidden": "true", children: "Click to view options" })), _jsx("div", { className: "bom-table-wrap", children: _jsxs("table", { className: "bom-table results-table", "aria-label": "Matched line items", children: [_jsxs("colgroup", { children: [_jsx("col", { className: "col-item" }), _jsx("col", { style: { width: "220px" } }), _jsx("col", { style: { width: "100px" } }), detailMode === "simple" && _jsx("col", { style: { width: "110px" } })] }), _jsx("thead", { children: _jsxs("tr", { className: "bom-col-header", children: [_jsx("th", { style: { textAlign: "left" }, children: "Description" }), _jsx("th", { style: { textAlign: "center" }, children: "Product Matched" }), _jsx("th", { style: { textAlign: "center" }, children: "Price" }), detailMode === "simple" && _jsx("th", { style: { textAlign: "center" }, children: internal ? "Selection" : qzaQuoteRequest ? "Quote" : "Basket" })] }) }), _jsxs("tbody", { children: [visibleRows.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: detailMode === "simple" ? 4 : 3, className: "results-empty-hint", style: { padding: "16px 8px" }, children: "No line items match the current search/filter." }) })), matchedDisplayRows.map((row) => {
751
776
  const { item, result, decision, best } = row;
752
777
  return (_jsxs("tr", { className: "bom-row results-table__review-row", onClick: () => openReview(item.line_id), onMouseEnter: (event) => {
@@ -767,7 +792,13 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
767
792
  else {
768
793
  onAddToCart(toCartItem(row));
769
794
  }
770
- }, "aria-label": cart[item.line_id] ? (internal ? "Remove from selection" : `Remove from ${customerCollection}`) : internal ? "Add to selection" : `Add to ${customerCollection}`, children: cart[item.line_id] ? "✓ Added" : internal ? "+ Add" : `Add to ${customerCollection}` }) }))] }, item.line_id));
795
+ }, "aria-label": cart[item.line_id]
796
+ ? internal
797
+ ? "Remove from selection"
798
+ : `Remove from ${customerCollection}`
799
+ : internal
800
+ ? "Add to selection"
801
+ : `Add to ${customerCollection}`, children: cart[item.line_id] ? "✓ Added" : internal ? "+ Add" : `Add to ${customerCollection}` }) }))] }, item.line_id));
771
802
  })] })] }) }), unmatchedSection] })), !activeReviewRow && totalPages > 1 && (_jsxs("div", { className: "results-pagination", children: [_jsxs("span", { className: "results-pagination-count", children: ["Showing ", (currentPage - 1) * PAGE_SIZE + 1, "\u2013", Math.min(currentPage * PAGE_SIZE, visibleRows.length), " of ", visibleRows.length] }), _jsxs("div", { className: "results-pagination-controls", children: [_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setPage(currentPage - 1), disabled: currentPage === 1, children: "Previous" }), _jsxs("span", { className: "results-pagination-page", children: ["Page ", currentPage, " of ", totalPages] }), _jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => setPage(currentPage + 1), disabled: currentPage === totalPages, children: "Next" })] })] })), (parseReport.itemCount != null || parseReport.headingCount != null || parseReport.skippedCount != null) && (_jsxs("div", { className: "results-summary", style: { marginTop: 16 }, children: [parseReport.itemCount != null && (_jsxs("span", { className: "summary-chip", children: [_jsx("strong", { children: parseReport.itemCount }), " items parsed"] })), parseReport.headingCount != null && (_jsxs("span", { className: "summary-chip", children: [_jsx("strong", { children: parseReport.headingCount }), " headings"] })), parseReport.skippedCount != null && (_jsxs("span", { className: "summary-chip", children: [_jsx("strong", { children: parseReport.skippedCount }), " rows skipped"] }))] })), _jsxs("div", { className: "disclaimer-box", children: [_jsx("span", { className: "disclaimer-icon", children: "\u26A0\uFE0F" }), _jsx("p", { className: "disclaimer-text", children: "These matches are generated automatically and should be reviewed before purchase." })] }), !activeReviewRow && (_jsxs("div", { className: "btn-row results-actions", children: [_jsxs("button", { className: "cta-btn cta-btn-secondary", style: {
772
803
  flex: "0 1 30%",
773
804
  gap: 10,
@@ -272,13 +272,13 @@ export default function EstimatorResults({ toolId, requirements, ready, onBack,
272
272
  const options = pickerResults.length > 0 ? pickerResults : row.candidates;
273
273
  return (_jsxs("div", { className: "estimator-products__picker", children: [_jsx("input", { type: "text", className: "results-search-input", value: pickerSearch, autoFocus: true, placeholder: "Search products in catalogue...", onChange: (event) => setPickerSearch(event.target.value), "aria-label": `Search a product for ${row.requirement.label}` }), _jsxs("div", { className: "estimator-products__options", children: [options.map((candidate) => (_jsxs("button", { type: "button", className: `estimator-products__option${candidate.product_id === row.chosen?.product_id ? " is-selected" : ""}`, onClick: () => chooseCandidate(row.requirement.key, candidate), children: [_jsx("span", { children: candidate.description }), _jsxs("span", { className: "results-row-meta", children: [candidate.product_id, candidate.price != null && ` · ${fmtCurrency(candidate.price)}`] })] }, candidate.product_id))), options.length === 0 && _jsx("p", { className: "results-empty-hint", children: "No matching products." })] })] }));
274
274
  };
275
- return (_jsxs("section", { className: "estimator-products results-tile-section", "aria-label": "Matched products", children: [!loading && (_jsxs("div", { className: "estimator-products__head", children: [_jsxs("div", { className: "results-tile-section__head", children: [_jsx("h3", { children: "Matched Products" }), _jsx("p", { children: "Products matched to your estimate, check before adding to your basket." })] }), !reviewRow && (_jsxs("div", { className: "results-summary estimator-products__toolbar", children: [needsPricingCount > 0 && (_jsxs("span", { className: "summary-chip summary-chip--neutral", children: [_jsx("strong", { children: needsPricingCount }), " need pricing"] })), _jsxs("div", { className: "estimator-products__controls", children: [_jsx("input", { type: "text", className: "results-search-input", placeholder: "Search description...", value: search, onChange: (event) => setSearch(event.target.value), "aria-label": "Search matched products" }), _jsxs("div", { className: "results-view-toggle", "aria-label": "Results view", children: [_jsx("button", { type: "button", className: `results-view-toggle__btn${viewMode === "tile" ? " is-active" : ""}`, "aria-pressed": viewMode === "tile", "aria-label": "Tile view", title: "Tile view", onClick: () => setViewMode("tile"), children: _jsxs("svg", { width: "18", height: "12", viewBox: "0 0 15 10", fill: "currentColor", children: [_jsx("rect", { x: "0.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "5.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "10.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "0.5", y: "5.5", width: "4", height: "4" }), _jsx("rect", { x: "5.5", y: "5.5", width: "4", height: "4" }), _jsx("rect", { x: "10.5", y: "5.5", width: "4", height: "4" })] }) }), _jsx("button", { type: "button", className: `results-view-toggle__btn${viewMode === "list" ? " is-active" : ""}`, "aria-pressed": viewMode === "list", "aria-label": "List view", title: "List view", onClick: () => setViewMode("list"), children: _jsxs("svg", { width: "17", height: "17", viewBox: "0 0 15 15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: [_jsx("line", { x1: "0.5", y1: "2.5", x2: "14.5", y2: "2.5" }), _jsx("line", { x1: "0.5", y1: "7.5", x2: "14.5", y2: "7.5" }), _jsx("line", { x1: "0.5", y1: "12.5", x2: "14.5", y2: "12.5" })] }) })] }), _jsxs("details", { ref: sortMenuRef, style: { position: "relative" }, children: [_jsx("summary", { className: "bom-toolbar-btn sort-menu-summary", "aria-label": "Sort materials", style: { fontSize: 16 }, children: "\u21C5" }), _jsx("div", { className: "sort-menu-panel", children: SORT_OPTIONS.map((option) => {
276
- const active = sortConfig === option.dir;
277
- return (_jsxs("button", { type: "button", className: `sort-menu-item${active ? " is-active" : ""}`, onClick: () => {
278
- setSortConfig(active ? null : option.dir);
279
- sortMenuRef.current?.removeAttribute("open");
280
- }, children: [_jsx("span", { className: "sort-menu-item__check", children: active ? "✓" : "" }), option.label] }, option.dir));
281
- }) })] })] })] }))] })), reviewRow && (_jsxs("div", { className: "results-review-page", children: [_jsxs("div", { className: "results-review-page__top", children: [_jsxs("button", { type: "button", className: "bom-toolbar-btn bom-toolbar-btn--accent", onClick: closeReview, children: [_jsx(BackArrow, {}), "Back to results"] }), _jsxs("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [_jsx("div", { className: "results-review-searchbar", children: _jsx("input", { type: "text", className: "results-search-input", placeholder: "Search products in catalogue...", value: reviewSearch, onChange: (event) => setReviewSearch(event.target.value), "aria-label": `Search a product for ${reviewRow.requirement.label}` }) }), reviewRow.chosen && (_jsx("div", { className: "results-candidate-actions", children: _jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => clearChosen(reviewRow.requirement.key), children: "Clear selected product" }) }))] })] }), _jsxs("div", { className: "results-review-page__hero", children: [_jsx("div", { className: "results-review-page__image-wrap", children: _jsx("img", { className: "fill-img results-review-page__image", src: productImageSrc(reviewRow.chosen?.description || reviewRow.requirement.label, reviewRow.chosen?.product_id, reviewRow.chosen?.pdp_photo_url), alt: "" }) }), _jsxs("div", { className: "results-review-page__summary", children: [_jsx("h2", { children: reviewRow.requirement.label }), _jsxs("p", { className: "results-review-page__meta", children: [fmtQty(reviewRow.requirement.qty), " ", renderDisplayUnit(reviewRow.requirement.unit, reviewRow.requirement.qty)] }), reviewRow.chosen ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "results-review-page__product", children: reviewRow.chosen.description }), _jsxs("div", { className: "results-row-meta", children: [reviewRow.chosen.product_id, reviewRow.chosen.price != null ? ` · ${fmtPrice(reviewRow.chosen.price, reviewRow.chosen.price_basis)}` : ""] })] })) : (_jsx("div", { className: "results-empty-hint", children: "No product currently selected for this line." })), _jsx("div", { className: "results-review-page__actions", children: _jsx("button", { type: "button", className: `bom-toolbar-btn${cart?.cart[lineIdFor(reviewRow.requirement.key)] ? " bom-toolbar-btn--added" : " bom-toolbar-btn--accent"}`, onClick: () => toggleInCart(reviewRow), disabled: !reviewRow.chosen || !cart, children: cart?.cart[lineIdFor(reviewRow.requirement.key)] ? "✓ Added" : "+ Add to basket" }) })] })] }), reviewCandidates.length === 0 ? (_jsx("p", { className: "results-empty-hint", children: "No matching products." })) : (_jsxs("table", { className: "score-breakdown-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Product" }), _jsx("th", { children: "Dims (mm)" }), _jsx("th", { children: "Price" }), _jsx("th", { "aria-label": "Actions" })] }) }), _jsx("tbody", { children: reviewCandidates.map((candidate) => {
275
+ return (_jsxs("section", { className: "estimator-products results-tile-section", "aria-label": "Matched products", children: [!loading && (_jsxs("div", { className: "estimator-products__head", children: [_jsxs("div", { className: "results-tile-section__head", children: [_jsx("h3", { children: "Matched Products" }), _jsx("p", { children: "Products matched to your estimate, check before adding to your basket." })] }), !reviewRow && (_jsx("div", { className: "results-summary estimator-products__toolbar", children: _jsxs("div", { className: "estimator-products__controls", children: [_jsx("input", { type: "text", className: "results-search-input", placeholder: "Search description...", value: search, onChange: (event) => setSearch(event.target.value), "aria-label": "Search matched products" }), _jsxs("div", { className: "results-view-toggle", "aria-label": "Results view", children: [_jsx("button", { type: "button", className: `results-view-toggle__btn${viewMode === "tile" ? " is-active" : ""}`, "aria-pressed": viewMode === "tile", "aria-label": "Tile view", title: "Tile view", onClick: () => setViewMode("tile"), children: _jsxs("svg", { width: "18", height: "12", viewBox: "0 0 15 10", fill: "currentColor", children: [_jsx("rect", { x: "0.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "5.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "10.5", y: "0.5", width: "4", height: "4" }), _jsx("rect", { x: "0.5", y: "5.5", width: "4", height: "4" }), _jsx("rect", { x: "5.5", y: "5.5", width: "4", height: "4" }), _jsx("rect", { x: "10.5", y: "5.5", width: "4", height: "4" })] }) }), _jsx("button", { type: "button", className: `results-view-toggle__btn${viewMode === "list" ? " is-active" : ""}`, "aria-pressed": viewMode === "list", "aria-label": "List view", title: "List view", onClick: () => setViewMode("list"), children: _jsxs("svg", { width: "17", height: "17", viewBox: "0 0 15 15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: [_jsx("line", { x1: "0.5", y1: "2.5", x2: "14.5", y2: "2.5" }), _jsx("line", { x1: "0.5", y1: "7.5", x2: "14.5", y2: "7.5" }), _jsx("line", { x1: "0.5", y1: "12.5", x2: "14.5", y2: "12.5" })] }) })] }), _jsxs("details", { ref: sortMenuRef, style: { position: "relative" }, children: [_jsx("summary", { className: "bom-toolbar-btn sort-menu-summary", "aria-label": "Sort materials", style: { fontSize: 16 }, children: "\u21C5" }), _jsx("div", { className: "sort-menu-panel", children: SORT_OPTIONS.map((option) => {
276
+ const active = sortConfig === option.dir;
277
+ return (_jsxs("button", { type: "button", className: `sort-menu-item${active ? " is-active" : ""}`, onClick: () => {
278
+ setSortConfig(active ? null : option.dir);
279
+ sortMenuRef.current?.removeAttribute("open");
280
+ }, children: [_jsx("span", { className: "sort-menu-item__check", children: active ? "✓" : "" }), option.label] }, option.dir));
281
+ }) })] })] }) }))] })), reviewRow && (_jsxs("div", { className: "results-review-page", children: [_jsxs("div", { className: "results-review-page__top", children: [_jsxs("button", { type: "button", className: "bom-toolbar-btn bom-toolbar-btn--accent", onClick: closeReview, children: [_jsx(BackArrow, {}), "Back to results"] }), _jsxs("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [_jsx("div", { className: "results-review-searchbar", children: _jsx("input", { type: "text", className: "results-search-input", placeholder: "Search products in catalogue...", value: reviewSearch, onChange: (event) => setReviewSearch(event.target.value), "aria-label": `Search a product for ${reviewRow.requirement.label}` }) }), reviewRow.chosen && (_jsx("div", { className: "results-candidate-actions", children: _jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => clearChosen(reviewRow.requirement.key), children: "Clear selected product" }) }))] })] }), _jsxs("div", { className: "results-review-page__hero", children: [_jsx("div", { className: "results-review-page__image-wrap", children: _jsx("img", { className: "fill-img results-review-page__image", src: productImageSrc(reviewRow.chosen?.description || reviewRow.requirement.label, reviewRow.chosen?.product_id, reviewRow.chosen?.pdp_photo_url), alt: "" }) }), _jsxs("div", { className: "results-review-page__summary", children: [_jsx("h2", { children: reviewRow.requirement.label }), _jsxs("p", { className: "results-review-page__meta", children: [fmtQty(reviewRow.requirement.qty), " ", renderDisplayUnit(reviewRow.requirement.unit, reviewRow.requirement.qty)] }), reviewRow.chosen ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "results-review-page__product", children: reviewRow.chosen.description }), _jsxs("div", { className: "results-row-meta", children: [reviewRow.chosen.product_id, reviewRow.chosen.price != null ? ` · ${fmtPrice(reviewRow.chosen.price, reviewRow.chosen.price_basis)}` : ""] })] })) : (_jsx("div", { className: "results-empty-hint", children: "No product currently selected for this line." })), _jsx("div", { className: "results-review-page__actions", children: _jsx("button", { type: "button", className: `bom-toolbar-btn${cart?.cart[lineIdFor(reviewRow.requirement.key)] ? " bom-toolbar-btn--added" : " bom-toolbar-btn--accent"}`, onClick: () => toggleInCart(reviewRow), disabled: !reviewRow.chosen || !cart, children: cart?.cart[lineIdFor(reviewRow.requirement.key)] ? "✓ Added" : "+ Add to basket" }) })] })] }), reviewCandidates.length === 0 ? (_jsx("p", { className: "results-empty-hint", children: "No matching products." })) : (_jsxs("table", { className: "score-breakdown-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Product" }), _jsx("th", { children: "Dims (mm)" }), _jsx("th", { children: "Price" }), _jsx("th", { "aria-label": "Actions" })] }) }), _jsx("tbody", { children: reviewCandidates.map((candidate) => {
282
282
  const isCurrent = candidate.product_id === reviewRow.chosen?.product_id;
283
283
  return (_jsxs("tr", { className: isCurrent ? "score-breakdown-row--best" : "", children: [_jsxs("td", { children: [_jsx("div", { children: candidate.description }), _jsx("div", { className: "results-row-meta", children: candidate.product_id })] }), _jsx("td", { children: _jsx("span", { className: "unit-badge", children: fmtMeasurements(candidate.measurements_mm) }) }), _jsx("td", { children: candidate.price != null ? fmtPrice(candidate.price, candidate.price_basis) : "-" }), _jsx("td", { children: _jsxs("label", { className: "switch-toggle", title: isCurrent ? "Currently selected" : "Use this candidate", children: [_jsx("input", { type: "checkbox", checked: isCurrent, onChange: () => {
284
284
  if (isCurrent)