@metron-labs/quote-react 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,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
|
|
16
|
+
export declare function displayLineLabel(item: LineItem, allItems: LineItem[]): string;
|
|
17
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,20 +114,10 @@ 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
|
-
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) {
|
|
117
|
+
export function displayLineLabel(item, allItems) {
|
|
128
118
|
const siblings = allItems.filter((candidate) => candidate.source_line_id === item.source_line_id && candidate.description_original === item.description_original);
|
|
129
|
-
const description =
|
|
130
|
-
if (siblings.length <= 1 || !item.procurement_intent) {
|
|
119
|
+
const description = item.customer_display_label?.trim() || item.procurement_intent?.trim() || item.description_original || "";
|
|
120
|
+
if (siblings.length <= 1 || !item.procurement_intent || description === item.procurement_intent) {
|
|
131
121
|
return description || "(no description)";
|
|
132
122
|
}
|
|
133
123
|
return `${description} -> ${item.procurement_intent}`;
|
|
@@ -472,6 +462,16 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
472
462
|
setSkippedReviewIds((ids) => new Set([...ids, ...pendingReviewRows.map((row) => row.item.line_id)]));
|
|
473
463
|
onReviewGateComplete();
|
|
474
464
|
};
|
|
465
|
+
const addReviewedProduct = () => {
|
|
466
|
+
if (!activeReviewRow?.best)
|
|
467
|
+
return;
|
|
468
|
+
saveSearchCandidate(activeReviewRow.item.line_id, activeReviewRow.best);
|
|
469
|
+
onAddToCart(toCartItem(activeReviewRow));
|
|
470
|
+
if (reviewFlowActive)
|
|
471
|
+
completeReviewAndReturnToGate(activeReviewRow.item.line_id);
|
|
472
|
+
else
|
|
473
|
+
closeReview(true);
|
|
474
|
+
};
|
|
475
475
|
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
476
|
const { item, qty } = row;
|
|
477
477
|
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));
|
|
@@ -604,7 +604,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
604
604
|
})();
|
|
605
605
|
const activeReviewCartMatches = !!activeReviewRow?.best && cart[activeReviewRow.item.line_id]?.productId === activeReviewRow.best.product_id;
|
|
606
606
|
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: "
|
|
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: 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: { 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) => {
|
|
608
608
|
const active = sortConfig === opt.dir;
|
|
609
609
|
return (_jsxs("button", { type: "button", className: `sort-menu-item${active ? " is-active" : ""}`, onClick: () => {
|
|
610
610
|
setSortConfig(active ? null : opt.dir);
|
|
@@ -631,22 +631,12 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
631
631
|
const selected = candidate.product_id === activeReviewRow.best?.product_id;
|
|
632
632
|
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
633
|
}) })) : 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);
|
|
634
|
+
addReviewedProduct();
|
|
641
635
|
}, 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
636
|
if (activeReviewCartMatches)
|
|
643
637
|
onRemoveFromCart(activeReviewRow.item.line_id);
|
|
644
638
|
else {
|
|
645
|
-
|
|
646
|
-
saveSearchCandidate(activeReviewRow.item.line_id, activeReviewRow.best);
|
|
647
|
-
onAddToCart(toCartItem(activeReviewRow));
|
|
648
|
-
if (reviewFlowActive)
|
|
649
|
-
completeReviewAndReturnToGate(activeReviewRow.item.line_id);
|
|
639
|
+
addReviewedProduct();
|
|
650
640
|
}
|
|
651
641
|
}, 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
642
|
const { item, best, qty } = row;
|