@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.
- package/dist/app/stages/BasketStage.js +78 -9
- package/dist/app/stages/ScheduleResultsStage.d.ts +3 -3
- package/dist/app/stages/ScheduleResultsStage.js +100 -53
- package/dist/embed/quote.js +11 -11
- package/dist/lib/boq/toCartItem.js +6 -1
- package/dist/lib/cart/CartContext.js +10 -1
- package/dist/util/exportScheduleExcel.js +1 -4
- package/dist/util/pricing.js +5 -17
- package/dist/util/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -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({
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
|
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) => ({
|
|
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) => ({
|
|
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 ? "
|
|
156
|
-
|
|
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
|
|
17
|
-
export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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: {
|
|
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 {
|
|
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({
|
|
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) => ({
|
|
427
|
-
|
|
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 }) => ({
|
|
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({
|
|
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: {
|
|
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: "
|
|
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: {
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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: {
|
|
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: () => {
|
|
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);
|