@metron-labs/quote-react 0.1.13 โ†’ 0.1.15

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,6 +41,7 @@ export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onRe
41
41
  const items = Object.values(cart);
42
42
  const [reference, setReference] = useState("");
43
43
  const [quoteRequestSent, setQuoteRequestSent] = useState(false);
44
+ const [quoteRequestSending, setQuoteRequestSending] = useState(false);
44
45
  const [quoteContactError, setQuoteContactError] = useState("");
45
46
  const [qtyDrafts, setQtyDrafts] = useState({});
46
47
  const [sellUnitsDrafts, setSellUnitsDrafts] = useState({});
@@ -59,7 +60,26 @@ export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onRe
59
60
  void fetch(apiUrl("/api/catalogue/resolve-conversions"), {
60
61
  method: "POST",
61
62
  headers: { "Content-Type": "application/json", ...clientHeaders() },
62
- body: JSON.stringify({ lines: unresolved.map((item) => ({ line_id: item.lineId, product_id: item.productId, requested_description: item.requestedDescription ?? item.description, requested_qty: item.qty, requested_uom: item.unit, selected_description: item.description, selected_uoi: item.uoi, selected_uoi_count: item.uoiCount, selected_conversion: { sales_uom_normalised: item.salesUomNormalised, conversion_type: item.conversionType, sales_unit_size: item.salesUnitSize, sales_unit_size_uom: item.salesUnitSizeUom, coverage_qty: item.coverageQty, coverage_uom: item.coverageUom } })) }),
63
+ body: JSON.stringify({
64
+ lines: unresolved.map((item) => ({
65
+ line_id: item.lineId,
66
+ product_id: item.productId,
67
+ requested_description: item.requestedDescription ?? item.description,
68
+ requested_qty: item.qty,
69
+ requested_uom: item.unit,
70
+ selected_description: item.description,
71
+ selected_uoi: item.uoi,
72
+ selected_uoi_count: item.uoiCount,
73
+ selected_conversion: {
74
+ sales_uom_normalised: item.salesUomNormalised,
75
+ conversion_type: item.conversionType,
76
+ sales_unit_size: item.salesUnitSize,
77
+ sales_unit_size_uom: item.salesUnitSizeUom,
78
+ coverage_qty: item.coverageQty,
79
+ coverage_uom: item.coverageUom,
80
+ },
81
+ })),
82
+ }),
63
83
  })
64
84
  .then((response) => (response.ok ? readJson(response) : null))
65
85
  .then((payload) => {
@@ -120,13 +140,44 @@ export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onRe
120
140
  onUpdateSellUnits(lineId, Math.max(0, current + delta));
121
141
  clearSellUnitsDraft(lineId);
122
142
  }
123
- function submitQuoteRequest() {
124
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(reference.trim())) {
143
+ // Sends the basket to the merchant as a quote request (POST /v1/quote-requests). Success only
144
+ // shows once the API confirms the email went out; otherwise the customer is asked to retry.
145
+ async function submitQuoteRequest() {
146
+ const email = reference.trim();
147
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
125
148
  setQuoteContactError("Enter a valid email address so we can send your better price.");
126
149
  return;
127
150
  }
128
151
  setQuoteContactError("");
129
- setQuoteRequestSent(true);
152
+ setQuoteRequestSending(true);
153
+ try {
154
+ const res = await fetch(apiUrl("/api/quote-requests"), {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json", ...clientHeaders() },
157
+ body: JSON.stringify({
158
+ email,
159
+ // The quantity the basket shows to order: sale units where it could work them out,
160
+ // otherwise what the customer asked for.
161
+ items: pricedItems.map(({ item, pricing }) => {
162
+ const quantity = pricing.requiredSellUnits ?? pricing.requestedAmount;
163
+ const unit = pricing.requiredSellUnits != null ? pricing.sellUnit : pricing.requestedUnit;
164
+ return { product_id: item.productId, description: item.description, quantity, unit: unitDisplayText(unit, quantity) };
165
+ }),
166
+ }),
167
+ });
168
+ if (!res.ok) {
169
+ const payload = await readJson(res);
170
+ setQuoteContactError(res.status === 429 ? "You've sent a few requests already. Please try again in a few minutes." : (payload?.message ?? "Your request couldn't be sent. Please try again."));
171
+ return;
172
+ }
173
+ setQuoteRequestSent(true);
174
+ }
175
+ catch {
176
+ setQuoteContactError("Your request couldn't be sent. Check your connection and try again.");
177
+ }
178
+ finally {
179
+ setQuoteRequestSending(false);
180
+ }
130
181
  }
131
182
  const pricedItems = items.map((item) => ({
132
183
  item,
@@ -141,17 +192,35 @@ export default function BasketStage({ cart, onUpdateQty, onUpdateSellUnits, onRe
141
192
  const totalPages = Math.max(1, Math.ceil(pricedItems.length / PAGE_SIZE));
142
193
  const currentPage = Math.min(page, totalPages);
143
194
  const pagedItems = pricedItems.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
144
- return (_jsx("div", { className: `wizard-step entry-stage${qzaBasketLayout ? " basket-layout--qza6-mra4" : ""}`, children: _jsxs("div", { className: `quick-order-card${internal ? "" : " quick-order-card--customer"}`, children: [_jsxs("div", { className: `quick-order-hero${internal ? "" : " quick-order-hero--customer"}`, children: [internal && _jsx("div", { className: "quick-order-eyebrow", children: "Construction Services" }), _jsxs("div", { className: "basket-hero-copy", children: [qzaBasketLayout && _jsxs("button", { type: "button", className: "cta-btn cta-btn-secondary basket-hero-back", onClick: onBack, children: [_jsx(BackArrow, {}), _jsx("span", { children: "Back to results" })] }), _jsxs("div", { className: "quick-order-hero-title-row", children: [_jsx("h1", { children: internal ? "Your Selection" : qzaBasketLayout ? "Request a better price" : "Your Basket" }), internal && (_jsxs("span", { className: "summary-chip summary-chip--scope", children: [_jsx("strong", { children: items.length }), " ", items.length === 1 ? "item" : "items"] }))] }), _jsx("p", { children: internal ? "Review the items you've added before exporting them for review." : qzaBasketLayout ? "Check your quantities, then request a better price." : "Review your quantities and order summary before you buy." })] })] }), _jsx("div", { className: "quick-order-panel", style: { paddingTop: 16 }, children: items.length === 0 ? (_jsxs("div", { className: "quick-order-history-empty", children: [_jsx("div", { className: "basket-empty-icon", "aria-hidden": "true", children: internal ? "๐Ÿ“‹" : "๐Ÿงบ" }), _jsx("p", { children: internal ? "Your selection is empty." : qzaBasketLayout ? "Your quote request is empty." : "Your basket is empty." }), _jsx("p", { className: "basket-empty-hint", children: qzaBasketLayout ? "Go back to your results and add items for your quote." : "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 }) => {
195
+ return (_jsx("div", { className: `wizard-step entry-stage${qzaBasketLayout ? " basket-layout--qza6-mra4" : ""}`, children: _jsxs("div", { className: `quick-order-card${internal ? "" : " quick-order-card--customer"}`, children: [_jsxs("div", { className: `quick-order-hero${internal ? "" : " quick-order-hero--customer"}`, children: [internal && _jsx("div", { className: "quick-order-eyebrow", children: "Construction Services" }), _jsxs("div", { className: "basket-hero-copy", children: [qzaBasketLayout && (_jsxs("button", { type: "button", className: "cta-btn cta-btn-secondary basket-hero-back", onClick: onBack, children: [_jsx(BackArrow, {}), _jsx("span", { children: "Back to results" })] })), _jsxs("div", { className: "quick-order-hero-title-row", children: [_jsx("h1", { children: internal ? "Your Selection" : qzaBasketLayout ? "Request a quote" : "Your Basket" }), internal && (_jsxs("span", { className: "summary-chip summary-chip--scope", children: [_jsx("strong", { children: items.length }), " ", items.length === 1 ? "item" : "items"] }))] }), _jsx("p", { children: internal
196
+ ? "Review the items you've added before exporting them for review."
197
+ : qzaBasketLayout
198
+ ? "Check your items & quantities below, and we'll do our best to beat list prices."
199
+ : "Review your quantities and order summary before you buy." })] })] }), _jsx("div", { className: "quick-order-panel", style: { paddingTop: 16 }, children: items.length === 0 ? (_jsxs("div", { className: "quick-order-history-empty", children: [_jsx("div", { className: "basket-empty-icon", "aria-hidden": "true", children: internal ? "๐Ÿ“‹" : "๐Ÿงบ" }), _jsx("p", { children: internal ? "Your selection is empty." : qzaBasketLayout ? "Your quote request is empty." : "Your basket is empty." }), _jsx("p", { className: "basket-empty-hint", children: qzaBasketLayout ? "Go back to your results and add items for your quote." : "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 }) => {
145
200
  const requestedQtyValue = qtyDrafts[item.lineId] ?? String(pricing.requestedAmount);
146
201
  const requestedLocked = pricing.requestedUnit === pricing.sellUnit || pricing.costingMode === "review";
147
202
  const rawDiffersFromOrder = pricing.requiredSellUnits == null || pricing.requestedAmount !== pricing.requiredSellUnits || pricing.requestedUnit !== pricing.sellUnit;
148
- return (_jsxs("article", { className: "basket-card", children: [_jsxs("div", { className: "basket-card-top", children: [_jsx("img", { className: "basket-card-thumb", src: productImageSrc(item.description, item.productId, item.pdpPhotoUrl), alt: "", width: 48, height: 48 }), _jsxs("div", { className: "basket-card-info", children: [_jsx("div", { className: "bom-component-name", children: item.description }), rawDiffersFromOrder && pricing.costingMode !== "review" && (_jsxs("button", { type: "button", className: "basket-calc-toggle", onClick: () => setCalcOpen((prev) => ({ ...prev, [item.lineId]: !prev[item.lineId] })), "aria-expanded": !!calcOpen[item.lineId], children: ["How we calculated this", _jsx("span", { className: `basket-calc-toggle-chevron${calcOpen[item.lineId] ? " basket-calc-toggle-chevron--open" : ""}`, children: _jsx(ChevronIcon, {}) })] }))] }), _jsx("div", { className: "basket-card-actions", children: _jsx("button", { type: "button", className: "mf-remove-small-btn basket-card-remove", onClick: () => onRemove(item.lineId), "aria-label": `Remove ${item.description}`, children: _jsx(TrashIcon, {}) }) })] }), internal && (_jsxs("div", { className: "results-detail-block basket-internal-detail", children: [_jsxs("dl", { className: "results-detail-grid", children: [_jsxs("div", { children: [_jsx("dt", { children: "Conversion type" }), _jsx("dd", { children: item.conversionType ?? "-" })] }), _jsxs("div", { children: [_jsx("dt", { children: "Conversion confidence" }), _jsx("dd", { children: item.conversionConfidence ?? "-" })] }), _jsxs("div", { children: [_jsx("dt", { children: "Sales unit" }), _jsx("dd", { children: item.salesUnitSize != null ? (_jsxs(_Fragment, { children: [item.salesUnitSize, " ", renderDisplayUnit(item.salesUnitSizeUom, item.salesUnitSize)] })) : ("-") })] }), _jsxs("div", { children: [_jsx("dt", { children: "Coverage" }), _jsx("dd", { children: item.coverageQty != null ? (_jsxs(_Fragment, { children: [item.coverageQty, " ", renderDisplayUnit(item.coverageUom, item.coverageQty)] })) : ("-") })] }), _jsxs("div", { children: [_jsx("dt", { children: "Waste factor" }), _jsx("dd", { children: item.wasteFactorDefault ?? "-" })] }), _jsxs("div", { children: [_jsx("dt", { children: "Rounding rule" }), _jsx("dd", { children: item.roundingRule ?? "-" })] })] }), item.conversionNotes && _jsx("p", { className: "results-rerank-note", children: item.conversionNotes })] })), pricing.costingMode === "review" ? (_jsxs("p", { className: "basket-calc-text basket-calc-text--review", children: [_jsx("strong", { children: "Quantity needs confirmation." }), " ", reviewMessage(pricing)] })) : rawDiffersFromOrder && calcOpen[item.lineId] ? (_jsx("p", { className: "basket-calc-text", children: orderSummary(item, pricing) })) : null, _jsxs("div", { className: "basket-card-stats", children: [_jsxs("div", { className: "basket-stat", children: [_jsx("span", { className: "basket-stat-label", children: "Requested qty" }), _jsxs("div", { className: "basket-stat-value", children: [_jsxs("div", { className: "basket-qty-field", children: [_jsx("input", { className: "bom-qty-edit", type: "number", inputMode: "decimal", min: "0", placeholder: "-", value: requestedQtyValue, disabled: requestedLocked, onChange: (e) => setQtyDrafts((prev) => ({ ...prev, [item.lineId]: e.target.value })), onBlur: (e) => commitRequestedQty(item.lineId, e.target.value), onKeyDown: (e) => {
203
+ return (_jsxs("article", { className: "basket-card", children: [_jsxs("div", { className: "basket-card-top", children: [_jsx("img", { className: "basket-card-thumb", src: productImageSrc(item.description, item.productId, item.pdpPhotoUrl), alt: "", width: 48, height: 48 }), _jsxs("div", { className: "basket-card-info", children: [_jsx("div", { className: "bom-component-name", children: item.description }), rawDiffersFromOrder && pricing.costingMode !== "review" && (_jsxs("button", { type: "button", className: "basket-calc-toggle", onClick: () => setCalcOpen((prev) => ({
204
+ ...prev,
205
+ [item.lineId]: !prev[item.lineId],
206
+ })), "aria-expanded": !!calcOpen[item.lineId], children: ["How we calculated this", _jsx("span", { className: `basket-calc-toggle-chevron${calcOpen[item.lineId] ? " basket-calc-toggle-chevron--open" : ""}`, children: _jsx(ChevronIcon, {}) })] }))] }), _jsx("div", { className: "basket-card-actions", children: _jsx("button", { type: "button", className: "mf-remove-small-btn basket-card-remove", onClick: () => onRemove(item.lineId), "aria-label": `Remove ${item.description}`, children: _jsx(TrashIcon, {}) }) })] }), internal && (_jsxs("div", { className: "results-detail-block basket-internal-detail", children: [_jsxs("dl", { className: "results-detail-grid", children: [_jsxs("div", { children: [_jsx("dt", { children: "Conversion type" }), _jsx("dd", { children: item.conversionType ?? "-" })] }), _jsxs("div", { children: [_jsx("dt", { children: "Conversion confidence" }), _jsx("dd", { children: item.conversionConfidence ?? "-" })] }), _jsxs("div", { children: [_jsx("dt", { children: "Sales unit" }), _jsx("dd", { children: item.salesUnitSize != null ? (_jsxs(_Fragment, { children: [item.salesUnitSize, " ", renderDisplayUnit(item.salesUnitSizeUom, item.salesUnitSize)] })) : ("-") })] }), _jsxs("div", { children: [_jsx("dt", { children: "Coverage" }), _jsx("dd", { children: item.coverageQty != null ? (_jsxs(_Fragment, { children: [item.coverageQty, " ", renderDisplayUnit(item.coverageUom, item.coverageQty)] })) : ("-") })] }), _jsxs("div", { children: [_jsx("dt", { children: "Waste factor" }), _jsx("dd", { children: item.wasteFactorDefault ?? "-" })] }), _jsxs("div", { children: [_jsx("dt", { children: "Rounding rule" }), _jsx("dd", { children: item.roundingRule ?? "-" })] })] }), item.conversionNotes && _jsx("p", { className: "results-rerank-note", children: item.conversionNotes })] })), pricing.costingMode === "review" ? (_jsxs("p", { className: "basket-calc-text basket-calc-text--review", children: [_jsx("strong", { children: "Quantity needs confirmation." }), " ", reviewMessage(pricing)] })) : rawDiffersFromOrder && calcOpen[item.lineId] ? (_jsx("p", { className: "basket-calc-text", children: orderSummary(item, pricing) })) : null, _jsxs("div", { className: "basket-card-stats", children: [_jsxs("div", { className: "basket-stat", children: [_jsx("span", { className: "basket-stat-label", children: "Requested qty" }), _jsxs("div", { className: "basket-stat-value", children: [_jsxs("div", { className: "basket-qty-field", children: [_jsx("input", { className: "bom-qty-edit", type: "number", inputMode: "decimal", min: "0", placeholder: "-", value: requestedQtyValue, disabled: requestedLocked, onChange: (e) => setQtyDrafts((prev) => ({
207
+ ...prev,
208
+ [item.lineId]: e.target.value,
209
+ })), onBlur: (e) => commitRequestedQty(item.lineId, e.target.value), onKeyDown: (e) => {
149
210
  if (e.key === "Enter")
150
211
  e.target.blur();
151
- }, "aria-label": `${item.description} requested quantity` }), _jsxs("div", { className: "basket-qty-arrows", children: [_jsx("button", { type: "button", className: "basket-qty-arrow", disabled: requestedLocked, onClick: () => stepRequestedQty(item.lineId, parseFloat(requestedQtyValue) || 0, 1), "aria-label": `Increase ${item.description} quantity`, children: _jsx(ChevronIcon, {}) }), _jsx("button", { type: "button", className: "basket-qty-arrow basket-qty-arrow--down", disabled: requestedLocked, onClick: () => stepRequestedQty(item.lineId, parseFloat(requestedQtyValue) || 0, -1), "aria-label": `Decrease ${item.description} quantity`, children: _jsx(ChevronIcon, {}) })] })] }), _jsx("span", { className: "basket-metric-unit", children: renderDisplayUnit(pricing.requestedUnit, parseFloat(requestedQtyValue) || 0) })] })] }), _jsxs("div", { className: "basket-stat basket-stat--right", children: [_jsx("span", { className: "basket-stat-label", children: "Order qty" }), _jsxs("div", { className: "basket-stat-value", children: [_jsxs("div", { className: "basket-qty-field", children: [_jsx("input", { className: "bom-qty-edit", type: "number", inputMode: "decimal", min: "0", placeholder: "-", value: sellUnitsDrafts[item.lineId] ?? String(pricing.requiredSellUnits ?? ""), onChange: (e) => setSellUnitsDrafts((prev) => ({ ...prev, [item.lineId]: e.target.value })), onBlur: (e) => commitSellUnits(item.lineId, e.target.value), onKeyDown: (e) => {
212
+ }, "aria-label": `${item.description} requested quantity` }), _jsxs("div", { className: "basket-qty-arrows", children: [_jsx("button", { type: "button", className: "basket-qty-arrow", disabled: requestedLocked, onClick: () => stepRequestedQty(item.lineId, parseFloat(requestedQtyValue) || 0, 1), "aria-label": `Increase ${item.description} quantity`, children: _jsx(ChevronIcon, {}) }), _jsx("button", { type: "button", className: "basket-qty-arrow basket-qty-arrow--down", disabled: requestedLocked, onClick: () => stepRequestedQty(item.lineId, parseFloat(requestedQtyValue) || 0, -1), "aria-label": `Decrease ${item.description} quantity`, children: _jsx(ChevronIcon, {}) })] })] }), _jsx("span", { className: "basket-metric-unit", children: renderDisplayUnit(pricing.requestedUnit, parseFloat(requestedQtyValue) || 0) })] })] }), _jsxs("div", { className: "basket-stat basket-stat--right", children: [_jsx("span", { className: "basket-stat-label", children: "Order qty" }), _jsxs("div", { className: "basket-stat-value", children: [_jsxs("div", { className: "basket-qty-field", children: [_jsx("input", { className: "bom-qty-edit", type: "number", inputMode: "decimal", min: "0", placeholder: "-", value: sellUnitsDrafts[item.lineId] ?? String(pricing.requiredSellUnits ?? ""), onChange: (e) => setSellUnitsDrafts((prev) => ({
213
+ ...prev,
214
+ [item.lineId]: e.target.value,
215
+ })), onBlur: (e) => commitSellUnits(item.lineId, e.target.value), onKeyDown: (e) => {
152
216
  if (e.key === "Enter")
153
217
  e.target.blur();
154
218
  }, "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));
155
- }) }), 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 ? "Standard price (ex. VAT)" : "Subtotal (ex. VAT)" }), _jsx("span", { children: fmtCurrency(subtotal) })] }), _jsxs("div", { className: "basket-sidebar-row", children: [_jsx("span", { children: qzaBasketLayout ? "VAT (20%)" : "VAT (20%)" }), _jsx("span", { children: fmtCurrency(vat) })] }), _jsxs("div", { className: "basket-sidebar-row basket-sidebar-row--total", children: [_jsx("span", { children: qzaBasketLayout ? "Standard price (inc. VAT)" : "Total" }), _jsx("span", { children: 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 ? "Email for your better price" : "Your reference (optional)" }), _jsx("input", { id: `${uid}-basket-reference`, type: qzaBasketLayout ? "email" : "text", className: "basket-sidebar-input", value: reference, onChange: (e) => { setReference(e.target.value); if (qzaBasketLayout)
156
- setQuoteContactError(""); }, placeholder: qzaBasketLayout ? "you@example.com" : "PO number or job reference" }), qzaBasketLayout && quoteContactError && _jsx("p", { className: "basket-quote-request-error", role: "alert", children: quoteContactError }), qzaBasketLayout ? (quoteRequestSent ? (_jsxs("div", { className: "basket-quote-request-success", role: "status", children: [_jsx("strong", { children: "Better price requested" }), _jsx("span", { children: "We'll use this email address to prepare your better price." })] })) : (_jsx("button", { type: "button", className: "cta-btn", onClick: submitQuoteRequest, children: _jsx("span", { children: "Get a better price" }) }))) : (_jsx("button", { className: "cta-btn", onClick: () => void exportBasketExcel(items), "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 ? "This is a quote request, not an order. We'll confirm your better price, availability and delivery before you approve." : "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" })] }) })] })) })] }) }));
219
+ }) }), 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: fmtCurrency(subtotal) })] }), _jsxs("div", { className: "basket-sidebar-row", children: [_jsx("span", { children: qzaBasketLayout ? "VAT (20%)" : "VAT (20%)" }), _jsx("span", { children: 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: 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) => {
220
+ setReference(e.target.value);
221
+ if (qzaBasketLayout)
222
+ setQuoteContactError("");
223
+ }, placeholder: qzaBasketLayout ? "you@example.com" : "PO number or job reference" }), qzaBasketLayout && quoteContactError && (_jsx("p", { className: "basket-quote-request-error", role: "alert", children: quoteContactError })), qzaBasketLayout ? (quoteRequestSent ? (_jsxs("div", { className: "basket-quote-request-success", role: "status", children: [_jsx("strong", { children: "Better price requested" }), _jsx("span", { children: "We'll use this email address to prepare your better price." })] })) : (_jsx("button", { type: "button", className: "cta-btn", onClick: () => void submitQuoteRequest(), disabled: quoteRequestSending, "aria-busy": quoteRequestSending, children: _jsx("span", { children: quoteRequestSending ? "Sendingโ€ฆ" : "Request our best quote" }) }))) : (_jsx("button", { className: "cta-btn", onClick: () => void exportBasketExcel(items), "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
224
+ ? "This is a quote request, not an order. We'll confirm our best price, availability and delivery before you approve."
225
+ : "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" })] }) })] })) })] }) }));
157
226
  }
@@ -1,4 +1,4 @@
1
- import type { CartItem, MatchCandidate, ScheduleResult } from "../../util/types";
1
+ import type { CartItem, LineItem, MatchCandidate, ScheduleResult } from "../../util/types";
2
2
  interface ResultsStageProps {
3
3
  scheduleResult: ScheduleResult;
4
4
  onStartOver: () => void;
@@ -13,6 +13,6 @@ interface ResultsStageProps {
13
13
  internal?: boolean;
14
14
  }
15
15
  export declare function candidateFromCart(item: CartItem): MatchCandidate;
16
- export declare function withoutQuantityPrefix(description: string, qty: number | null | undefined): string;
17
- export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal, }: ResultsStageProps): import("react").JSX.Element;
16
+ export declare function displayLineLabel(item: LineItem, allItems: LineItem[]): string;
17
+ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal }: ResultsStageProps): import("react").JSX.Element;
18
18
  export {};
@@ -114,23 +114,9 @@ function fmtTextList(values) {
114
114
  const items = (values ?? []).map((value) => value.trim()).filter(Boolean);
115
115
  return items.length > 0 ? items.join(", ") : "-";
116
116
  }
117
- export function withoutQuantityPrefix(description, qty) {
118
- if (!description || qty == null || !Number.isFinite(qty))
119
- return description;
120
- const value = String(qty).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
121
- const withoutCount = description.replace(new RegExp(`^\\s*${value}(?:\\s+(?:x|ร—)?\\s*|\\s*[xร—]\\s*)`, "i"), "");
122
- if (withoutCount === description)
123
- return description;
124
- const withoutUnit = withoutCount.replace(/^(?:pallets?|palets?|packs?|sheets?|bags?|rolls?|boxes?|each|pcs?)\s+/i, "").trim();
125
- return withoutUnit || withoutCount.trim() || description;
126
- }
127
- function displayLineLabel(item, allItems) {
128
- const siblings = allItems.filter((candidate) => candidate.source_line_id === item.source_line_id && candidate.description_original === item.description_original);
129
- const description = withoutQuantityPrefix(item.description_original || "", item.qty);
130
- if (siblings.length <= 1 || !item.procurement_intent) {
131
- return description || "(no description)";
132
- }
133
- return `${description} -> ${item.procurement_intent}`;
117
+ export function displayLineLabel(item, allItems) {
118
+ void allItems;
119
+ return item.customer_display_label?.trim() || item.description_original || "(no description)";
134
120
  }
135
121
  const LINK_GROUP_BUBBLE_STYLES = [
136
122
  { background: "#fff7ed", borderColor: "#fdba74", color: "#9a3412" },
@@ -174,9 +160,7 @@ function curatedCandidates(candidates, result, selectedProductId) {
174
160
  const recommendedIds = result?.rerank?.recommendedCandidateIds ?? [];
175
161
  if (recommendedIds.length === 0) {
176
162
  const top5 = candidates.slice(0, 5);
177
- const pinnedSelected = !top5.some((candidate) => candidate.product_id === selectedProductId)
178
- ? candidates.find((candidate) => candidate.product_id === selectedProductId)
179
- : undefined;
163
+ const pinnedSelected = !top5.some((candidate) => candidate.product_id === selectedProductId) ? candidates.find((candidate) => candidate.product_id === selectedProductId) : undefined;
180
164
  return pinnedSelected ? [...top5, pinnedSelected] : top5;
181
165
  }
182
166
  const byId = new Map(candidates.map((candidate) => [candidate.product_id, candidate]));
@@ -268,9 +252,7 @@ function AdjudicationDebugBlock({ result }) {
268
252
  function DetailedDecision({ item, decision, result, showRetrievalDebug, reviewOnly = false, linkedGroupMeta, }) {
269
253
  if (!decision)
270
254
  return null;
271
- const reviewLabel = decision.technical_classification === "no_suitable_candidate"
272
- ? "Exact match not found:"
273
- : `AI review (${decision.confidence ?? "confidence not stated"}):`;
255
+ const reviewLabel = decision.technical_classification === "no_suitable_candidate" ? "Exact match not found:" : `AI review (${decision.confidence ?? "confidence not stated"}):`;
274
256
  if (reviewOnly) {
275
257
  return decision.reason ? (_jsxs("div", { className: "results-detail-block", children: [_jsxs("p", { className: `basket-calc-text basket-calc-text--review`, children: [_jsx("strong", { children: reviewLabel }), "\u00A0", decision.reason] }), hasVisibleLinkedGroup(item) ? (_jsxs("p", { className: "results-row-meta", style: { marginTop: 8 }, children: ["Linked context: ", linkedGroupMeta ? `Link ${linkedGroupMeta.token} - ` : "", linkedGroupSummary(item)] })) : null] })) : null;
276
258
  }
@@ -294,7 +276,11 @@ function CandidateBreakdown({ item, candidates, result, decision, unmatchable, s
294
276
  const shown = showAll || internal || detailMode === "detailed" ? candidates : curatedCandidates(candidates, result, selectedProductId);
295
277
  return (_jsxs(_Fragment, { children: [detailBlock, _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" }), showScoreColumns && (_jsxs(_Fragment, { children: [_jsx("th", { children: "dimension" }), _jsx("th", { children: "grade" }), _jsx("th", { children: "brand" }), _jsx("th", { children: "fuzzy" }), _jsx("th", { children: "bm25" }), _jsx("th", { children: "overall" })] })), _jsx("th", { "aria-label": "Actions" })] }) }), _jsx("tbody", { children: shown.map((c, i) => {
296
278
  const isCurrent = c.product_id === selectedProductId;
297
- return (_jsxs("tr", { className: isCurrent ? "score-breakdown-row--best" : "", children: [_jsxs("td", { children: [_jsx("div", { children: c.description }), _jsx("div", { className: "results-row-meta", children: c.product_id })] }), _jsx("td", { children: _jsx("span", { className: "unit-badge", children: fmtMeasurements(c.measurements_mm ?? c.debug?.candidate.measurements_mm) }) }), _jsx("td", { children: fmtPrice(c.price, c.price_basis) }), showScoreColumns && (_jsxs(_Fragment, { children: [_jsx("td", { children: (c.scores?.dimension ?? 0).toFixed(2) }), _jsx("td", { children: (c.scores?.grade ?? 0).toFixed(2) }), _jsx("td", { children: (c.scores?.brand ?? 0).toFixed(2) }), _jsx("td", { children: (c.scores?.fuzzy ?? 0).toFixed(2) }), _jsx("td", { children: (c.scores?.bm25 ?? 0).toFixed(2) }), _jsx("td", { children: _jsxs("div", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [_jsx(ConfChip, { band: bandFor(c.scores?.overall) }), _jsx("span", { className: "results-overall-cell", children: (c.scores?.overall ?? 0).toFixed(2) })] }) })] })), _jsx("td", { children: _jsxs("label", { className: "switch-toggle", title: isCurrent ? "Currently selected" : "Use this candidate", children: [_jsx("input", { type: "checkbox", checked: isCurrent, onChange: () => {
279
+ return (_jsxs("tr", { className: isCurrent ? "score-breakdown-row--best" : "", children: [_jsxs("td", { children: [_jsx("div", { children: c.description }), _jsx("div", { className: "results-row-meta", children: c.product_id })] }), _jsx("td", { children: _jsx("span", { className: "unit-badge", children: fmtMeasurements(c.measurements_mm ?? c.debug?.candidate.measurements_mm) }) }), _jsx("td", { children: fmtPrice(c.price, c.price_basis) }), showScoreColumns && (_jsxs(_Fragment, { children: [_jsx("td", { children: (c.scores?.dimension ?? 0).toFixed(2) }), _jsx("td", { children: (c.scores?.grade ?? 0).toFixed(2) }), _jsx("td", { children: (c.scores?.brand ?? 0).toFixed(2) }), _jsx("td", { children: (c.scores?.fuzzy ?? 0).toFixed(2) }), _jsx("td", { children: (c.scores?.bm25 ?? 0).toFixed(2) }), _jsx("td", { children: _jsxs("div", { style: {
280
+ display: "inline-flex",
281
+ alignItems: "center",
282
+ gap: 6,
283
+ }, children: [_jsx(ConfChip, { band: bandFor(c.scores?.overall) }), _jsx("span", { className: "results-overall-cell", children: (c.scores?.overall ?? 0).toFixed(2) })] }) })] })), _jsx("td", { children: _jsxs("label", { className: "switch-toggle", title: isCurrent ? "Currently selected" : "Use this candidate", children: [_jsx("input", { type: "checkbox", checked: isCurrent, onChange: () => {
298
284
  if (isCurrent)
299
285
  onClear?.();
300
286
  else
@@ -302,7 +288,7 @@ function CandidateBreakdown({ item, candidates, result, decision, unmatchable, s
302
288
  }, "aria-label": isCurrent ? `${c.description} selected` : `Use ${c.description}` }), _jsx("span", { className: "switch-toggle-track", "aria-hidden": "true" })] }) })] }, `${c.product_id}-${i}`));
303
289
  }) })] })] }));
304
290
  }
305
- export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal = false, }) {
291
+ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal = false }) {
306
292
  const qzaQuoteRequest = !internal && getBoqClientId() === "QZA6-MRA4";
307
293
  const customerCollection = qzaQuoteRequest ? "quote" : "basket";
308
294
  const { results, lineItems, parseReport, timings } = scheduleResult;
@@ -351,7 +337,15 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
351
337
  const matched = pick === NO_SELECTION ? undefined : (pickOverrideCandidates[item.line_id] ?? (savedCartMatch && savedCartMatch.productId !== automaticMatch?.product_id ? candidateFromCart(savedCartMatch) : automaticMatch));
352
338
  const best = unmatchable ? undefined : matched;
353
339
  const qty = item.qty ?? 0;
354
- return { item, result, decision, unmatchable, best, qty, band: bandFor(best?.scores?.overall) };
340
+ return {
341
+ item,
342
+ result,
343
+ decision,
344
+ unmatchable,
345
+ best,
346
+ qty,
347
+ band: bandFor(best?.scores?.overall),
348
+ };
355
349
  });
356
350
  let totalValue = 0;
357
351
  let needsPricingCount = 0;
@@ -394,7 +388,12 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
394
388
  const { top, left, width, height } = row.getBoundingClientRect();
395
389
  const actionCell = row.querySelector(".bom-toolbar-btn")?.closest("td");
396
390
  const actionLeft = actionCell?.getBoundingClientRect().left;
397
- setHoveredRowBounds({ top, left, width: actionLeft ? actionLeft - left : width, height });
391
+ setHoveredRowBounds({
392
+ top,
393
+ left,
394
+ width: actionLeft ? actionLeft - left : width,
395
+ height,
396
+ });
398
397
  };
399
398
  useEffect(() => {
400
399
  window.addEventListener("scroll", updateHoveredRowBounds, true);
@@ -423,8 +422,14 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
423
422
  return;
424
423
  const saved = cart[selectedReviewId];
425
424
  if (saved) {
426
- setPickOverrides((prev) => ({ ...prev, [selectedReviewId]: saved.productId }));
427
- setPickOverrideCandidates((prev) => ({ ...prev, [selectedReviewId]: candidateFromCart(saved) }));
425
+ setPickOverrides((prev) => ({
426
+ ...prev,
427
+ [selectedReviewId]: saved.productId,
428
+ }));
429
+ setPickOverrideCandidates((prev) => ({
430
+ ...prev,
431
+ [selectedReviewId]: candidateFromCart(saved),
432
+ }));
428
433
  return;
429
434
  }
430
435
  setPickOverrides((prev) => {
@@ -472,6 +477,16 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
472
477
  setSkippedReviewIds((ids) => new Set([...ids, ...pendingReviewRows.map((row) => row.item.line_id)]));
473
478
  onReviewGateComplete();
474
479
  };
480
+ const addReviewedProduct = () => {
481
+ if (!activeReviewRow?.best)
482
+ return;
483
+ saveSearchCandidate(activeReviewRow.item.line_id, activeReviewRow.best);
484
+ onAddToCart(toCartItem(activeReviewRow));
485
+ if (reviewFlowActive)
486
+ completeReviewAndReturnToGate(activeReviewRow.item.line_id);
487
+ else
488
+ closeReview(true);
489
+ };
475
490
  const unmatchedSection = unmatchedTileRows.length > 0 ? (_jsxs("section", { className: "results-tile-section results-tile-section--unmatched", "aria-label": "Items requiring matching", children: [_jsxs("div", { className: "results-tile-section__head", children: [_jsx("h3", { children: "Require Matching" }), _jsx("p", { children: "We couldn\u2019t confidently match these to a product. Open one to pick from the closest options." })] }), _jsx("div", { className: "results-unmatched-list", children: unmatchedTileRows.map((row) => {
476
491
  const { item, qty } = row;
477
492
  return (_jsxs("button", { type: "button", className: "results-unmatched-card", onClick: () => openReview(item.line_id), children: [_jsx("span", { className: "results-unmatched-card__main", children: _jsx("span", { className: "results-unmatched-card__title", children: displayLineLabel(item, lineItems) }) }), _jsx("span", { className: "bom-toolbar-btn results-unmatched-card__prompt", children: "Review" })] }, item.line_id));
@@ -492,7 +507,14 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
492
507
  }
493
508
  };
494
509
  function handleExport() {
495
- void exportScheduleExcel(rows.map(({ item, best, qty, band, unmatchable, decision }) => ({ item, best, qty, band, unmatchable, decision })));
510
+ void exportScheduleExcel(rows.map(({ item, best, qty, band, unmatchable, decision }) => ({
511
+ item,
512
+ best,
513
+ qty,
514
+ band,
515
+ unmatchable,
516
+ decision,
517
+ })));
496
518
  }
497
519
  function toCartItem(row) {
498
520
  const candidate = row.best ?? recommendedCandidates(row.result)[0];
@@ -525,7 +547,11 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
525
547
  if (!existing)
526
548
  continue;
527
549
  const refreshed = toCartItem(row);
528
- onAddToCart({ ...refreshed, qty: existing.qty, sellUnitsOverride: existing.sellUnitsOverride ?? null });
550
+ onAddToCart({
551
+ ...refreshed,
552
+ qty: existing.qty,
553
+ sellUnitsOverride: existing.sellUnitsOverride ?? null,
554
+ });
529
555
  }
530
556
  // eslint-disable-next-line react-hooks/exhaustive-deps
531
557
  }, [scheduleResult]);
@@ -588,14 +614,19 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
588
614
  pdp_photo_url: candidate.pdp_photo_url ?? null,
589
615
  conversion: candidate.conversion,
590
616
  measurements_mm: candidate.measurements_mm ?? [],
591
- scores: { dimension: 0, grade: 0, brand: 0, fuzzy: 0, bm25: 0, overall: 0 },
617
+ scores: {
618
+ dimension: 0,
619
+ grade: 0,
620
+ brand: 0,
621
+ fuzzy: 0,
622
+ bm25: 0,
623
+ overall: 0,
624
+ },
592
625
  method: "catalogue_search",
593
626
  }));
594
627
  const reviewCandidates = reviewQuery ? catalogueSearchCandidates : llmReviewCandidates;
595
628
  const savedSearchCandidate = activeReviewRow ? savedSearchCandidates[activeReviewRow.item.line_id] : undefined;
596
- const reviewDisplayCandidates = !reviewQuery && savedSearchCandidate && !reviewCandidates.some((candidate) => candidate.product_id === savedSearchCandidate.product_id)
597
- ? [...reviewCandidates, savedSearchCandidate]
598
- : reviewCandidates;
629
+ const reviewDisplayCandidates = !reviewQuery && savedSearchCandidate && !reviewCandidates.some((candidate) => candidate.product_id === savedSearchCandidate.product_id) ? [...reviewCandidates, savedSearchCandidate] : reviewCandidates;
599
630
  const reviewOptionCandidates = (() => {
600
631
  const topCandidates = reviewDisplayCandidates.slice(0, reviewQuery ? 12 : 3);
601
632
  if (reviewQuery || !savedSearchCandidate || topCandidates.some((candidate) => candidate.product_id === savedSearchCandidate.product_id))
@@ -604,13 +635,27 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
604
635
  })();
605
636
  const activeReviewCartMatches = !!activeReviewRow?.best && cart[activeReviewRow.item.line_id]?.productId === activeReviewRow.best.product_id;
606
637
  const activeReviewHasPendingUpdate = !!activeReviewRow?.best && !!cart[activeReviewRow.item.line_id] && !activeReviewCartMatches;
607
- 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: fmtCurrency(totalValue) }), " 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, "aria-label": qzaQuoteRequest ? "View quote" : "View basket", children: [_jsxs("span", { children: [_jsx("small", { children: "Total cost" }), _jsx("strong", { children: fmtCurrency(totalValue) })] }), _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: { display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }, 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) => {
638
+ 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: fmtCurrency(totalValue) }), " 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, "aria-label": qzaQuoteRequest ? "View quote" : "View basket", children: [_jsxs("span", { children: [_jsx("small", { children: qzaQuoteRequest ? "View Quote" : "View Basket" }), _jsx("strong", { children: fmtCurrency(totalValue) })] }), _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: {
639
+ display: "flex",
640
+ gap: 8,
641
+ flexWrap: "wrap",
642
+ alignItems: "center",
643
+ }, 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) => {
608
644
  const active = sortConfig === opt.dir;
609
645
  return (_jsxs("button", { type: "button", className: `sort-menu-item${active ? " is-active" : ""}`, onClick: () => {
610
646
  setSortConfig(active ? null : opt.dir);
611
647
  sortMenuRef.current?.removeAttribute("open");
612
648
  }, children: [_jsx("span", { className: "sort-menu-item__check", children: active ? "โœ“" : "" }), opt.label] }, opt.dir));
613
- }) })] })] })] })) }), 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: { display: "flex", gap: 8, flexWrap: "wrap" }, 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: { display: "flex", alignItems: "center", gap: 8, marginTop: 8 }, children: [_jsx(LinkedGroupPill, { meta: linkedGroupMetaById.get(activeReviewRow.item.linked_group_id) }), _jsx("div", { className: "results-row-meta", style: { marginTop: 0 }, children: linkedGroupSummary(activeReviewRow.item) })] })) : null, _jsx("div", { className: "results-review-page__actions", children: _jsx("button", { type: "button", className: `bom-toolbar-btn${cart[activeReviewRow.item.line_id] ? " bom-toolbar-btn--added" : " bom-toolbar-btn--accent"}`, onClick: () => {
649
+ }) })] })] })] })) })), 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: {
650
+ display: "flex",
651
+ gap: 8,
652
+ flexWrap: "wrap",
653
+ }, 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: {
654
+ display: "flex",
655
+ alignItems: "center",
656
+ gap: 8,
657
+ marginTop: 8,
658
+ }, children: [_jsx(LinkedGroupPill, { meta: linkedGroupMetaById.get(activeReviewRow.item.linked_group_id) }), _jsx("div", { className: "results-row-meta", style: { marginTop: 0 }, children: linkedGroupSummary(activeReviewRow.item) })] })) : null, _jsx("div", { className: "results-review-page__actions", children: _jsx("button", { type: "button", className: `bom-toolbar-btn${cart[activeReviewRow.item.line_id] ? " bom-toolbar-btn--added" : " bom-toolbar-btn--accent"}`, onClick: () => {
614
659
  if (cart[activeReviewRow.item.line_id])
615
660
  onRemoveFromCart(activeReviewRow.item.line_id);
616
661
  else {
@@ -627,26 +672,16 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
627
672
  : "Search the catalogue to choose a product." })] }) }), !reviewQuery && reviewDisplayCandidates.length > 0 ? (_jsx("div", { className: "customer-review-option-grid", children: reviewOptionCandidates.map((candidate) => {
628
673
  const selected = candidate.product_id === activeReviewRow.best?.product_id;
629
674
  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));
630
- }) })) : 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) => {
675
+ }) })) : 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) => {
631
676
  const selected = candidate.product_id === activeReviewRow.best?.product_id;
632
677
  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));
633
678
  }) })) : null] }), activeReviewRow.best ? (activeReviewHasPendingUpdate ? (_jsxs("div", { style: { display: "grid", gap: 8 }, children: [_jsx("button", { type: "button", className: "cta-btn customer-review-add", onClick: () => {
634
- if (activeReviewRow.best)
635
- saveSearchCandidate(activeReviewRow.item.line_id, activeReviewRow.best);
636
- onAddToCart(toCartItem(activeReviewRow));
637
- if (reviewFlowActive)
638
- completeReviewAndReturnToGate(activeReviewRow.item.line_id);
639
- else
640
- closeReview(true);
679
+ addReviewedProduct();
641
680
  }, children: _jsx("span", { children: "Update product" }) }), _jsx("button", { type: "button", className: "cta-btn cta-btn-secondary customer-review-add", onClick: () => onRemoveFromCart(activeReviewRow.item.line_id), children: _jsx("span", { children: `Remove from ${customerCollection}` }) })] })) : (_jsx("button", { type: "button", className: `cta-btn customer-review-add${activeReviewCartMatches ? " cta-btn-secondary" : ""}`, onClick: () => {
642
681
  if (activeReviewCartMatches)
643
682
  onRemoveFromCart(activeReviewRow.item.line_id);
644
683
  else {
645
- if (activeReviewRow.best)
646
- saveSearchCandidate(activeReviewRow.item.line_id, activeReviewRow.best);
647
- onAddToCart(toCartItem(activeReviewRow));
648
- if (reviewFlowActive)
649
- completeReviewAndReturnToGate(activeReviewRow.item.line_id);
684
+ addReviewedProduct();
650
685
  }
651
686
  }, children: _jsx("span", { children: activeReviewCartMatches ? `Remove from ${customerCollection}` : `Add selected product to ${customerCollection}` }) }))) : null, reviewFlowActive ? (_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary customer-review-skip", onClick: () => skipReviewAndReturnToGate(activeReviewRow.item.line_id), children: "Skip this item" })) : null] })) })] }) })) : null, _jsxs("div", { className: "results-tile-layout", children: [visibleRows.length === 0 ? _jsx("div", { className: "results-empty-card", children: "No line items match the current search/filter." }) : null, matchedDisplayRows.length > 0 ? (_jsx("section", { className: "results-tile-section", "aria-label": "Matched and review items", children: _jsx("div", { className: "results-tile-grid", children: matchedDisplayRows.map((row) => {
652
687
  const { item, best, qty } = row;
@@ -675,7 +710,12 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
675
710
  }, onMouseLeave: () => {
676
711
  hoveredRowRef.current = null;
677
712
  setHoveredRowBounds(null);
678
- }, children: [_jsxs("td", { children: [_jsx("div", { className: "bom-component-name", children: displayLineLabel(item, lineItems) }), hasVisibleLinkedGroup(item) ? (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 4 }, children: [_jsx(LinkedGroupPill, { meta: linkedGroupMetaById.get(item.linked_group_id) }), _jsx("div", { className: "results-row-meta", style: { marginTop: 0 }, children: linkedGroupSummary(item) })] })) : null] }), _jsx("td", { children: best ? (_jsxs("div", { children: [_jsx("div", { className: "results-match-cell-text", title: best.description, children: best.description }), detailMode === "detailed" && decision?.technical_classification && (_jsxs("div", { className: "results-row-meta", children: [classificationLabel(decision.technical_classification), decision.approval_required ? " - approval required" : ""] }))] })) : (_jsx("span", { className: "results-empty-hint" })) }), _jsx("td", { style: { textAlign: "center" }, children: best?.price != null ? fmtPrice(best.price, best.price_basis) : "-" }), detailMode === "simple" && (_jsx("td", { style: { textAlign: "center" }, children: _jsx("button", { type: "button", className: `bom-toolbar-btn${cart[item.line_id] ? " bom-toolbar-btn--added" : " bom-toolbar-btn--accent"}`, style: { width: 84, justifyContent: "center" }, onClick: (event) => {
713
+ }, children: [_jsxs("td", { children: [_jsx("div", { className: "bom-component-name", children: displayLineLabel(item, lineItems) }), hasVisibleLinkedGroup(item) ? (_jsxs("div", { style: {
714
+ display: "flex",
715
+ alignItems: "center",
716
+ gap: 8,
717
+ marginTop: 4,
718
+ }, children: [_jsx(LinkedGroupPill, { meta: linkedGroupMetaById.get(item.linked_group_id) }), _jsx("div", { className: "results-row-meta", style: { marginTop: 0 }, children: linkedGroupSummary(item) })] })) : null] }), _jsx("td", { children: best ? (_jsxs("div", { children: [_jsx("div", { className: "results-match-cell-text", title: best.description, children: best.description }), detailMode === "detailed" && decision?.technical_classification && (_jsxs("div", { className: "results-row-meta", children: [classificationLabel(decision.technical_classification), decision.approval_required ? " - approval required" : ""] }))] })) : (_jsx("span", { className: "results-empty-hint" })) }), _jsx("td", { style: { textAlign: "center" }, children: best?.price != null ? fmtPrice(best.price, best.price_basis) : "-" }), detailMode === "simple" && (_jsx("td", { style: { textAlign: "center" }, children: _jsx("button", { type: "button", className: `bom-toolbar-btn${cart[item.line_id] ? " bom-toolbar-btn--added" : " bom-toolbar-btn--accent"}`, style: { width: 84, justifyContent: "center" }, onClick: (event) => {
679
719
  event.stopPropagation();
680
720
  if (cart[item.line_id])
681
721
  onRemoveFromCart(item.line_id);
@@ -683,10 +723,17 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
683
723
  onAddToCart(toCartItem(row));
684
724
  }
685
725
  }, "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));
686
- })] })] }) }), 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: { flex: cartCount > 0 ? "0 1 30%" : "0 1 30%", gap: 10, justifyContent: "center" }, onClick: () => setConfirmStartOver(true), "aria-label": "Start over", children: [_jsx(RefreshIcon, {}), _jsx("span", { children: "Start Over" })] }), cartCount > 0 && (_jsxs("button", { className: "cta-btn", style: { flex: "0 1 70%" }, onClick: onGoToBasket, "aria-label": internal ? "View selection" : qzaQuoteRequest ? "View quote" : "View basket", children: [_jsxs("span", { children: [internal ? "View Selection" : qzaQuoteRequest ? "View Quote" : "View Basket", " (", cartCount, ")"] }), _jsx("span", { className: "arrow", children: _jsx(ForwardArrow, {}) })] }))] }))] })] }), reviewGateOpen && (_jsx("div", { className: "modal-backdrop quick-order-modal-backdrop review-gate-modal-backdrop", role: "dialog", "aria-modal": "true", "aria-labelledby": "review-gate-title", children: _jsxs("section", { className: "quick-order-workflow-modal review-gate-modal", children: [_jsxs("div", { className: "review-gate-modal__header", children: [_jsx("h2", { id: "review-gate-title", children: "Choose products for these items" }), _jsx("p", { children: "We couldn\u2019t find a clear match for these items, so we need your help. Select an option from the suggestions, search the catalogue, or leave the item out." })] }), _jsx("div", { className: "review-gate-list", children: pendingReviewRows.map((row) => {
726
+ })] })] }) }), 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: {
727
+ flex: cartCount > 0 ? "0 1 30%" : "0 1 30%",
728
+ gap: 10,
729
+ justifyContent: "center",
730
+ }, onClick: () => setConfirmStartOver(true), "aria-label": "Start over", children: [_jsx(RefreshIcon, {}), _jsx("span", { children: "Start Over" })] }), cartCount > 0 && (_jsxs("button", { className: "cta-btn", style: { flex: "0 1 70%" }, onClick: onGoToBasket, "aria-label": internal ? "View selection" : qzaQuoteRequest ? "View quote" : "View basket", children: [_jsxs("span", { children: [internal ? "View Selection" : qzaQuoteRequest ? "View Quote" : "View Basket", " (", cartCount, ")"] }), _jsx("span", { className: "arrow", children: _jsx(ForwardArrow, {}) })] }))] }))] })] }), reviewGateOpen && (_jsx("div", { className: "modal-backdrop quick-order-modal-backdrop review-gate-modal-backdrop", role: "dialog", "aria-modal": "true", "aria-labelledby": "review-gate-title", children: _jsxs("section", { className: "quick-order-workflow-modal review-gate-modal", children: [_jsxs("div", { className: "review-gate-modal__header", children: [_jsx("h2", { id: "review-gate-title", children: "Choose products for these items" }), _jsx("p", { children: "We couldn\u2019t find a clear match for these items, so we need your help. Select an option from the suggestions, search the catalogue, or leave the item out." })] }), _jsx("div", { className: "review-gate-list", children: pendingReviewRows.map((row) => {
687
731
  const reviewed = reviewedReviewIds.has(row.item.line_id);
688
732
  const partial = hasBestAlternative(row);
689
- return (_jsx("div", { className: `review-gate-item${reviewed ? " is-reviewed" : ""}`, children: _jsxs("button", { type: "button", className: "review-gate-item__review", onClick: () => { setReviewFlowActive(true); openReview(row.item.line_id); }, children: [_jsxs("span", { className: "review-gate-item__copy", children: [_jsx("strong", { children: displayLineLabel(row.item, lineItems) }), _jsx("span", { children: row.decision?.reason || (partial ? "A close alternative is available but needs confirmation." : "No product has been selected for this item yet.") })] }), _jsx("span", { className: "review-gate-item__action", children: reviewed ? "Choice made" : "Choose a product" })] }) }, row.item.line_id));
733
+ return (_jsx("div", { className: `review-gate-item${reviewed ? " is-reviewed" : ""}`, children: _jsxs("button", { type: "button", className: "review-gate-item__review", onClick: () => {
734
+ setReviewFlowActive(true);
735
+ openReview(row.item.line_id);
736
+ }, children: [_jsxs("span", { className: "review-gate-item__copy", children: [_jsx("strong", { children: displayLineLabel(row.item, lineItems) }), _jsx("span", { children: row.decision?.reason || (partial ? "A close alternative is available but needs confirmation." : "No product has been selected for this item yet.") })] }), _jsx("span", { className: "review-gate-item__action", children: reviewed ? "Choice made" : "Choose a product" })] }) }, row.item.line_id));
690
737
  }) }), _jsx("button", { type: "button", className: "review-gate-skip", onClick: skipAllReviews, children: "Skip all" }), skippedReviewRows.length > 0 ? (_jsxs("div", { className: "review-gate-skipped", children: [_jsx("strong", { children: "Skipped" }), skippedReviewRows.map((row) => (_jsxs("button", { type: "button", className: "review-gate-item is-skipped", onClick: () => {
691
738
  setSkippedReviewIds((ids) => {
692
739
  const next = new Set(ids);