@metron-labs/quote-react 0.1.68 → 0.1.70
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/QuickQuote.js
CHANGED
|
@@ -62,7 +62,9 @@ function QuickQuoteInner({ fullPage = false, hideHeader = false, internal = fals
|
|
|
62
62
|
catch { }
|
|
63
63
|
// Older than the TTL (or saved before there was a stamp): start fresh. The save effect
|
|
64
64
|
// below then removes it, as scheduleResult stays null.
|
|
65
|
-
if (
|
|
65
|
+
if (process.env.NODE_ENV === "development")
|
|
66
|
+
saved = null;
|
|
67
|
+
else if (saved && !(Date.now() - (saved.savedAt ?? 0) < SAVED_QUOTE_TTL_MS))
|
|
66
68
|
saved = null;
|
|
67
69
|
if (saved?.scheduleResult) {
|
|
68
70
|
setEntryMode("schedule");
|
|
@@ -17,5 +17,6 @@ export declare function candidateFromCart(item: CartItem): MatchCandidate;
|
|
|
17
17
|
export declare function refreshedCartItem(refreshed: CartItem, existing: CartItem): CartItem;
|
|
18
18
|
export declare function displayLineLabel(item: LineItem, allItems: LineItem[]): string;
|
|
19
19
|
export declare function formatResultsTotal(totalValue: number | null, needsPricingCount: number): string | null;
|
|
20
|
+
export declare function curatedCandidates(candidates: MatchCandidate[], result: MatchResult | undefined, selectedProductId: string | null): MatchCandidate[];
|
|
20
21
|
export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart, onAddToCart, onRemoveFromCart, onGoToBasket, cartSeeded, onCartSeeded, reviewGateComplete, onReviewGateComplete, internal, }: ResultsStageProps): import("react").JSX.Element;
|
|
21
22
|
export {};
|
|
@@ -121,11 +121,6 @@ export function refreshedCartItem(refreshed, existing) {
|
|
|
121
121
|
conversionNotes: existing.conversionNotes,
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
-
function classificationLabel(value) {
|
|
125
|
-
if (!value)
|
|
126
|
-
return "not stated";
|
|
127
|
-
return value.replace(/_/g, " ");
|
|
128
|
-
}
|
|
129
124
|
function ConfChip({ band }) {
|
|
130
125
|
return (_jsx("span", { className: `status-dot status-dot--${band}`, title: band, children: _jsx("span", { className: "status-dot__mark" }) }));
|
|
131
126
|
}
|
|
@@ -194,23 +189,14 @@ function LinkedGroupPill({ meta }) {
|
|
|
194
189
|
}
|
|
195
190
|
return (_jsx("span", { className: "results-linked-pill", style: meta.bubbleStyle, children: `Link ${meta.token}` }));
|
|
196
191
|
}
|
|
197
|
-
function curatedCandidates(candidates, result, selectedProductId) {
|
|
192
|
+
export function curatedCandidates(candidates, result, selectedProductId) {
|
|
198
193
|
const recommendedIds = result?.rerank?.recommendedCandidateIds ?? [];
|
|
199
|
-
if (recommendedIds.length === 0) {
|
|
200
|
-
const topCandidates = candidates.slice(0, 6);
|
|
201
|
-
const pinnedSelected = !topCandidates.some((candidate) => candidate.product_id === selectedProductId)
|
|
202
|
-
? candidates.find((candidate) => candidate.product_id === selectedProductId)
|
|
203
|
-
: undefined;
|
|
204
|
-
return pinnedSelected ? [...topCandidates, pinnedSelected] : topCandidates;
|
|
205
|
-
}
|
|
206
194
|
const byId = new Map(candidates.map((candidate) => [candidate.product_id, candidate]));
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
}
|
|
195
|
+
const ordered = recommendedIds.length > 0
|
|
196
|
+
? recommendedIds.map((candidateId) => byId.get(candidateId)).filter((candidate) => candidate != null)
|
|
197
|
+
: candidates.slice(0, 6);
|
|
198
|
+
const selected = selectedProductId ? byId.get(selectedProductId) : undefined;
|
|
199
|
+
const shown = selected ? [selected, ...ordered.filter(({ product_id }) => product_id !== selected.product_id)] : ordered;
|
|
214
200
|
return [...new Map(shown.map((candidate) => [candidate.product_id, candidate])).values()].slice(0, 6);
|
|
215
201
|
}
|
|
216
202
|
function recommendedCandidates(result) {
|
|
@@ -288,10 +274,10 @@ function AdjudicationDebugBlock({ result }) {
|
|
|
288
274
|
];
|
|
289
275
|
return (_jsxs("div", { className: "results-detail-block", children: [_jsx("h4", { style: { margin: "0 0 12px" }, children: "Adjudication context" }), _jsx("dl", { className: "results-detail-grid", children: details.map(([label, value]) => (_jsxs(Fragment, { children: [_jsx("dt", { children: label }), _jsx("dd", { children: value })] }, label))) }), debug.rerank_line_item_input ? (_jsxs(_Fragment, { children: [_jsx("h5", { style: { margin: "12px 0 8px" }, children: "Rerank input" }), _jsx("pre", { className: "results-pre", children: JSON.stringify(debug.rerank_line_item_input, null, 2) })] })) : null] }));
|
|
290
276
|
}
|
|
291
|
-
function DetailedDecision({ item, decision, result, showRetrievalDebug, reviewOnly = false, linkedGroupMeta, }) {
|
|
277
|
+
function DetailedDecision({ item, decision, selectedProductId, result, showRetrievalDebug, reviewOnly = false, linkedGroupMeta, }) {
|
|
292
278
|
if (!decision)
|
|
293
279
|
return null;
|
|
294
|
-
const reviewLabel =
|
|
280
|
+
const reviewLabel = selectedProductId == null ? "Needs review:" : "Selected:";
|
|
295
281
|
if (reviewOnly) {
|
|
296
282
|
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;
|
|
297
283
|
}
|
|
@@ -305,7 +291,7 @@ function CandidateBreakdown({ item, candidates, result, decision, unmatchable, s
|
|
|
305
291
|
// Metron Labs' own tooling — only surfaced under the `debug` config's detail mode.
|
|
306
292
|
const showBusinessDetail = internal || detailMode === "detailed";
|
|
307
293
|
const showScoreColumns = debugEnabled && detailMode === "detailed";
|
|
308
|
-
const detailBlock = showBusinessDetail || forceReviewNote ? (_jsx(DetailedDecision, { item: item, decision: decision, result: result, showRetrievalDebug: showScoreColumns, reviewOnly: forceReviewNote && !showBusinessDetail, linkedGroupMeta: linkedGroupMeta })) : null;
|
|
294
|
+
const detailBlock = showBusinessDetail || forceReviewNote ? (_jsx(DetailedDecision, { item: item, decision: decision, selectedProductId: selectedProductId, result: result, showRetrievalDebug: showScoreColumns, reviewOnly: forceReviewNote && !showBusinessDetail, linkedGroupMeta: linkedGroupMeta })) : null;
|
|
309
295
|
if (unmatchable) {
|
|
310
296
|
return (_jsxs(_Fragment, { children: [detailBlock, _jsx("p", { className: "results-empty-hint", children: "Classified as labour/plant or referencing another line item - skipped by design." })] }));
|
|
311
297
|
}
|
|
@@ -390,7 +376,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
390
376
|
let totalValue = hasPendingReview ? null : getCartSubtotal(Object.values(cart), { wasteFactorEnabled: false, wasteFactorOverride: 0 });
|
|
391
377
|
let needsPricingCount = 0;
|
|
392
378
|
for (const row of rows) {
|
|
393
|
-
if (row.unmatchable ||
|
|
379
|
+
if (row.unmatchable || !row.best)
|
|
394
380
|
continue;
|
|
395
381
|
const cartItem = cart[row.item.line_id];
|
|
396
382
|
if (!cartItem || getPricingBreakdown(cartItem).lineTotal == null)
|
|
@@ -399,7 +385,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
399
385
|
if (needsPricingCount > 0)
|
|
400
386
|
totalValue = null;
|
|
401
387
|
const totalLabel = formatResultsTotal(totalValue, needsPricingCount);
|
|
402
|
-
const hasBestAlternative = (row) => !row.unmatchable && !row.best &&
|
|
388
|
+
const hasBestAlternative = (row) => !row.unmatchable && !row.best && recommendedCandidates(row.result).length > 0;
|
|
403
389
|
const previewCandidate = (row) => row.best ?? recommendedCandidates(row.result)[0];
|
|
404
390
|
const reviewRequiredRows = rows.filter((row) => !row.unmatchable && !row.best);
|
|
405
391
|
const pendingReviewRows = reviewRequiredRows.filter((row) => !skippedReviewIds.has(row.item.line_id));
|
|
@@ -417,9 +403,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
417
403
|
const currentPage = Math.min(page, totalPages);
|
|
418
404
|
const pagedRows = visibleRows.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
|
|
419
405
|
const matchedTileRows = pagedRows.filter((row) => !row.unmatchable && row.best);
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
const unmatchedTileRows = pagedRows.filter((row) => row.unmatchable || (!row.best && !hasBestAlternative(row)));
|
|
406
|
+
const matchedDisplayRows = pagedRows.filter((row) => !row.unmatchable && row.best);
|
|
407
|
+
const unmatchedTileRows = pagedRows.filter((row) => row.unmatchable || !row.best);
|
|
423
408
|
const activeReviewRow = selectedReviewId ? (rows.find((row) => row.item.line_id === selectedReviewId) ?? null) : null;
|
|
424
409
|
const reviewGateOpen = !internal && !reviewGateComplete && !activeReviewRow && pendingReviewRows.length > 0;
|
|
425
410
|
const sortMenuRef = useRef(null);
|
|
@@ -533,7 +518,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
533
518
|
else
|
|
534
519
|
closeReview(true);
|
|
535
520
|
};
|
|
536
|
-
const unmatchedSection = unmatchedTileRows.length > 0 ? (_jsxs("section", { className: "results-tile-section results-tile-section--unmatched", "aria-label": "Items
|
|
521
|
+
const unmatchedSection = unmatchedTileRows.length > 0 ? (_jsxs("section", { className: "results-tile-section results-tile-section--unmatched", "aria-label": "Items needing review", children: [_jsxs("div", { className: "results-tile-section__head", children: [_jsx("h3", { children: "Needs review" }), _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) => {
|
|
537
522
|
const { item, qty } = row;
|
|
538
523
|
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));
|
|
539
524
|
}) })] })) : null;
|
|
@@ -564,7 +549,9 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
564
549
|
})));
|
|
565
550
|
}
|
|
566
551
|
function toCartItem(row) {
|
|
567
|
-
const candidate = row.best
|
|
552
|
+
const candidate = row.best;
|
|
553
|
+
if (!candidate)
|
|
554
|
+
throw new Error("Cannot add a line item without a selected product.");
|
|
568
555
|
return candidateToCartItem(candidate, {
|
|
569
556
|
lineId: row.item.line_id,
|
|
570
557
|
description: row.item.description_original,
|
|
@@ -589,8 +576,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
589
576
|
: undefined,
|
|
590
577
|
});
|
|
591
578
|
}
|
|
592
|
-
// All
|
|
593
|
-
//
|
|
579
|
+
// All selected items land in the basket automatically so the common case starts from
|
|
580
|
+
// a complete draft basket. Gated by cartSeeded (lifted
|
|
594
581
|
// to BoQApp) so this fires once per schedule result, not on every remount of this stage.
|
|
595
582
|
useEffect(() => {
|
|
596
583
|
if (cartSeeded)
|
|
@@ -649,8 +636,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
649
636
|
const cartCount = Object.keys(cart).length;
|
|
650
637
|
const reviewQuery = reviewSearch.trim();
|
|
651
638
|
const llmReviewCandidates = recommendedCandidates(activeReviewRow?.result);
|
|
652
|
-
const hasNoSuitableCandidate = activeReviewRow?.
|
|
653
|
-
const hasAiVerdict = activeReviewRow?.
|
|
639
|
+
const hasNoSuitableCandidate = !!activeReviewRow && !activeReviewRow.best && (activeReviewRow.result?.candidates.length === 0 || activeReviewRow.result?.rerank?.selectedCandidateId === null);
|
|
640
|
+
const hasAiVerdict = activeReviewRow?.result?.rerank != null;
|
|
654
641
|
const activeReviewReason = activeReviewRow?.decision?.reason ?? (hasNoSuitableCandidate ? "No catalogue candidate found. Refine the request or search the catalogue." : null);
|
|
655
642
|
const catalogueSearchCandidates = catalogueSearchResults.map((candidate) => ({
|
|
656
643
|
product_id: candidate.product_id,
|
|
@@ -749,22 +736,13 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
749
736
|
}, 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) => {
|
|
750
737
|
const { item, best, qty } = row;
|
|
751
738
|
const preview = previewCandidate(row);
|
|
752
|
-
|
|
753
|
-
return (_jsxs("article", { className: "results-tile-card", children: [_jsxs("button", { type: "button", className: "results-tile-card__surface", onClick: () => openReview(item.line_id), "aria-label": `Review match for ${displayLineLabel(item, lineItems) || "item"}`, children: [_jsxs("div", { className: "results-tile-card__media", children: [isPartial || hasVisibleLinkedGroup(item) ? (_jsxs("div", { className: "results-tile-card__badges", children: [hasVisibleLinkedGroup(item) ? _jsx(LinkedGroupPill, { meta: linkedGroupMetaById.get(item.linked_group_id) }) : _jsx("span", {}), isPartial ? (_jsx("span", { style: {
|
|
754
|
-
padding: "6px 10px",
|
|
755
|
-
borderRadius: 999,
|
|
756
|
-
background: "#fef3c7",
|
|
757
|
-
color: "#92400e",
|
|
758
|
-
fontSize: 12,
|
|
759
|
-
fontWeight: 700,
|
|
760
|
-
lineHeight: 1,
|
|
761
|
-
}, children: "Partially matched" })) : null] })) : null, _jsx(ProductImage, { className: "fill-img results-tile-card__image", description: preview?.description || item.description_original, photoUrl: preview?.pdp_photo_url })] }), _jsxs("div", { className: "results-tile-card__body", children: [preview ? (_jsx(_Fragment, { children: _jsx("div", { className: "results-tile-card__heading", children: _jsx("div", { className: "results-tile-card__title", children: preview.description }) }) })) : (_jsx("div", { className: "results-empty-hint", children: "No product currently selected for this line item." })), _jsx("div", { className: "results-tile-card__match", children: preview ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "results-tile-card__match-label", children: isPartial ? "↳ partially matched from" : "↳ matched from" }), _jsx("div", { className: "results-tile-card__match-name", children: displayLineLabel(item, lineItems) }), hasVisibleLinkedGroup(item) ? _jsx("div", { className: "results-row-meta", children: linkedGroupSummary(item) }) : null] })) : (_jsx("span", { className: "results-empty-hint", children: "No matched product selected." })) })] }), _jsx("span", { className: "results-tile-card__hover-prompt", "aria-hidden": "true", children: "Click to view options" })] }), _jsxs("div", { className: "results-tile-card__actions", children: [internal && (_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => openReview(item.line_id), children: "Review" })), _jsx("button", { type: "button", className: `bom-toolbar-btn${cart[item.line_id] ? " bom-toolbar-btn--added" : " bom-toolbar-btn--accent"}`, onClick: () => {
|
|
739
|
+
return (_jsxs("article", { className: "results-tile-card", children: [_jsxs("button", { type: "button", className: "results-tile-card__surface", onClick: () => openReview(item.line_id), "aria-label": `Review match for ${displayLineLabel(item, lineItems) || "item"}`, children: [_jsxs("div", { className: "results-tile-card__media", children: [hasVisibleLinkedGroup(item) ? (_jsx("div", { className: "results-tile-card__badges", children: _jsx(LinkedGroupPill, { meta: linkedGroupMetaById.get(item.linked_group_id) }) })) : null, _jsx(ProductImage, { className: "fill-img results-tile-card__image", description: preview?.description || item.description_original, photoUrl: preview?.pdp_photo_url })] }), _jsxs("div", { className: "results-tile-card__body", children: [preview ? (_jsx(_Fragment, { children: _jsx("div", { className: "results-tile-card__heading", children: _jsx("div", { className: "results-tile-card__title", children: preview.description }) }) })) : (_jsx("div", { className: "results-empty-hint", children: "No product currently selected for this line item." })), _jsx("div", { className: "results-tile-card__match", children: preview ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "results-tile-card__match-label", children: "\u21B3 matched from" }), _jsx("div", { className: "results-tile-card__match-name", children: displayLineLabel(item, lineItems) }), hasVisibleLinkedGroup(item) ? _jsx("div", { className: "results-row-meta", children: linkedGroupSummary(item) }) : null] })) : (_jsx("span", { className: "results-empty-hint", children: "No matched product selected." })) })] }), _jsx("span", { className: "results-tile-card__hover-prompt", "aria-hidden": "true", children: "Click to view options" })] }), _jsxs("div", { className: "results-tile-card__actions", children: [internal && (_jsx("button", { type: "button", className: "bom-toolbar-btn", onClick: () => openReview(item.line_id), children: "Review" })), _jsx("button", { type: "button", className: `bom-toolbar-btn${cart[item.line_id] ? " bom-toolbar-btn--added" : " bom-toolbar-btn--accent"}`, onClick: () => {
|
|
762
740
|
if (cart[item.line_id])
|
|
763
741
|
onRemoveFromCart(item.line_id);
|
|
764
742
|
else {
|
|
765
743
|
onAddToCart(toCartItem(row));
|
|
766
744
|
}
|
|
767
|
-
}, disabled: !
|
|
745
|
+
}, disabled: !best, "aria-label": cart[item.line_id]
|
|
768
746
|
? internal
|
|
769
747
|
? "Remove from selection"
|
|
770
748
|
: `Remove from ${customerCollection}`
|
|
@@ -784,7 +762,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
784
762
|
alignItems: "center",
|
|
785
763
|
gap: 8,
|
|
786
764
|
marginTop: 4,
|
|
787
|
-
}, 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
|
|
765
|
+
}, 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 && (_jsx("div", { className: "results-row-meta", children: decision.selected_candidate_id === best.product_id ? "Automatically selected" : "Selected" }))] })) : (_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) => {
|
|
788
766
|
event.stopPropagation();
|
|
789
767
|
if (cart[item.line_id])
|
|
790
768
|
onRemoveFromCart(item.line_id);
|