@metron-labs/quote-react 0.1.68 → 0.1.69

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.
@@ -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 (saved && !(Date.now() - (saved.savedAt ?? 0) < SAVED_QUOTE_TTL_MS))
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");
@@ -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
  }
@@ -288,10 +283,10 @@ function AdjudicationDebugBlock({ result }) {
288
283
  ];
289
284
  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
285
  }
291
- function DetailedDecision({ item, decision, result, showRetrievalDebug, reviewOnly = false, linkedGroupMeta, }) {
286
+ function DetailedDecision({ item, decision, selectedProductId, result, showRetrievalDebug, reviewOnly = false, linkedGroupMeta, }) {
292
287
  if (!decision)
293
288
  return null;
294
- const reviewLabel = decision.technical_classification === "no_suitable_candidate" ? "No automatic match:" : `AI review (${decision.confidence ?? "confidence not stated"}):`;
289
+ const reviewLabel = selectedProductId == null ? "Needs review:" : "Selected:";
295
290
  if (reviewOnly) {
296
291
  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
292
  }
@@ -305,7 +300,7 @@ function CandidateBreakdown({ item, candidates, result, decision, unmatchable, s
305
300
  // Metron Labs' own tooling — only surfaced under the `debug` config's detail mode.
306
301
  const showBusinessDetail = internal || detailMode === "detailed";
307
302
  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;
303
+ const detailBlock = showBusinessDetail || forceReviewNote ? (_jsx(DetailedDecision, { item: item, decision: decision, selectedProductId: selectedProductId, result: result, showRetrievalDebug: showScoreColumns, reviewOnly: forceReviewNote && !showBusinessDetail, linkedGroupMeta: linkedGroupMeta })) : null;
309
304
  if (unmatchable) {
310
305
  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
306
  }
@@ -390,7 +385,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
390
385
  let totalValue = hasPendingReview ? null : getCartSubtotal(Object.values(cart), { wasteFactorEnabled: false, wasteFactorOverride: 0 });
391
386
  let needsPricingCount = 0;
392
387
  for (const row of rows) {
393
- if (row.unmatchable || row.decision?.include_in_quote !== true || !row.best)
388
+ if (row.unmatchable || !row.best)
394
389
  continue;
395
390
  const cartItem = cart[row.item.line_id];
396
391
  if (!cartItem || getPricingBreakdown(cartItem).lineTotal == null)
@@ -399,7 +394,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
399
394
  if (needsPricingCount > 0)
400
395
  totalValue = null;
401
396
  const totalLabel = formatResultsTotal(totalValue, needsPricingCount);
402
- const hasBestAlternative = (row) => !row.unmatchable && !row.best && row.decision?.technical_classification !== "no_suitable_candidate" && recommendedCandidates(row.result).length > 0;
397
+ const hasBestAlternative = (row) => !row.unmatchable && !row.best && recommendedCandidates(row.result).length > 0;
403
398
  const previewCandidate = (row) => row.best ?? recommendedCandidates(row.result)[0];
404
399
  const reviewRequiredRows = rows.filter((row) => !row.unmatchable && !row.best);
405
400
  const pendingReviewRows = reviewRequiredRows.filter((row) => !skippedReviewIds.has(row.item.line_id));
@@ -417,9 +412,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
417
412
  const currentPage = Math.min(page, totalPages);
418
413
  const pagedRows = visibleRows.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
419
414
  const matchedTileRows = pagedRows.filter((row) => !row.unmatchable && row.best);
420
- const alternativeTileRows = pagedRows.filter((row) => hasBestAlternative(row));
421
- const matchedDisplayRows = pagedRows.filter((row) => !row.unmatchable && (row.best || hasBestAlternative(row)));
422
- const unmatchedTileRows = pagedRows.filter((row) => row.unmatchable || (!row.best && !hasBestAlternative(row)));
415
+ const matchedDisplayRows = pagedRows.filter((row) => !row.unmatchable && row.best);
416
+ const unmatchedTileRows = pagedRows.filter((row) => row.unmatchable || !row.best);
423
417
  const activeReviewRow = selectedReviewId ? (rows.find((row) => row.item.line_id === selectedReviewId) ?? null) : null;
424
418
  const reviewGateOpen = !internal && !reviewGateComplete && !activeReviewRow && pendingReviewRows.length > 0;
425
419
  const sortMenuRef = useRef(null);
@@ -533,7 +527,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
533
527
  else
534
528
  closeReview(true);
535
529
  };
536
- 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) => {
530
+ 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
531
  const { item, qty } = row;
538
532
  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
533
  }) })] })) : null;
@@ -564,7 +558,9 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
564
558
  })));
565
559
  }
566
560
  function toCartItem(row) {
567
- const candidate = row.best ?? recommendedCandidates(row.result)[0];
561
+ const candidate = row.best;
562
+ if (!candidate)
563
+ throw new Error("Cannot add a line item without a selected product.");
568
564
  return candidateToCartItem(candidate, {
569
565
  lineId: row.item.line_id,
570
566
  description: row.item.description_original,
@@ -589,8 +585,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
589
585
  : undefined,
590
586
  });
591
587
  }
592
- // All matched items, including review-required ones, land in the basket automatically
593
- // so the common case starts from a complete draft basket. Gated by cartSeeded (lifted
588
+ // All selected items land in the basket automatically so the common case starts from
589
+ // a complete draft basket. Gated by cartSeeded (lifted
594
590
  // to BoQApp) so this fires once per schedule result, not on every remount of this stage.
595
591
  useEffect(() => {
596
592
  if (cartSeeded)
@@ -649,8 +645,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
649
645
  const cartCount = Object.keys(cart).length;
650
646
  const reviewQuery = reviewSearch.trim();
651
647
  const llmReviewCandidates = recommendedCandidates(activeReviewRow?.result);
652
- const hasNoSuitableCandidate = activeReviewRow?.decision?.technical_classification === "no_suitable_candidate" || (!!activeReviewRow && !activeReviewRow.best);
653
- const hasAiVerdict = activeReviewRow?.decision?.technical_classification != null;
648
+ const hasNoSuitableCandidate = !!activeReviewRow && !activeReviewRow.best && (activeReviewRow.result?.candidates.length === 0 || activeReviewRow.result?.rerank?.selectedCandidateId === null);
649
+ const hasAiVerdict = activeReviewRow?.result?.rerank != null;
654
650
  const activeReviewReason = activeReviewRow?.decision?.reason ?? (hasNoSuitableCandidate ? "No catalogue candidate found. Refine the request or search the catalogue." : null);
655
651
  const catalogueSearchCandidates = catalogueSearchResults.map((candidate) => ({
656
652
  product_id: candidate.product_id,
@@ -749,22 +745,13 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
749
745
  }, 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
746
  const { item, best, qty } = row;
751
747
  const preview = previewCandidate(row);
752
- const isPartial = !best && hasBestAlternative(row);
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: () => {
748
+ 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
749
  if (cart[item.line_id])
763
750
  onRemoveFromCart(item.line_id);
764
751
  else {
765
752
  onAddToCart(toCartItem(row));
766
753
  }
767
- }, disabled: !preview, "aria-label": cart[item.line_id]
754
+ }, disabled: !best, "aria-label": cart[item.line_id]
768
755
  ? internal
769
756
  ? "Remove from selection"
770
757
  : `Remove from ${customerCollection}`
@@ -784,7 +771,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
784
771
  alignItems: "center",
785
772
  gap: 8,
786
773
  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?.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) => {
774
+ }, 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
775
  event.stopPropagation();
789
776
  if (cart[item.line_id])
790
777
  onRemoveFromCart(item.line_id);