@metron-labs/quote-react 0.1.67 → 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.
@@ -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
  }
@@ -285,14 +280,13 @@ function AdjudicationDebugBlock({ result }) {
285
280
  const details = [
286
281
  ["Dependent rerank applied", debug.dependent_rerank_applied ? "yes" : "no"],
287
282
  ["Linked parent selected product", debug.linked_parent_selected_candidate_description || debug.linked_parent_selected_candidate_id || "-"],
288
- ["Dimension reason overridden", debug.dimension_reason_overridden ? "yes" : "no"],
289
283
  ];
290
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] }));
291
285
  }
292
- function DetailedDecision({ item, decision, result, showRetrievalDebug, reviewOnly = false, linkedGroupMeta, }) {
286
+ function DetailedDecision({ item, decision, selectedProductId, result, showRetrievalDebug, reviewOnly = false, linkedGroupMeta, }) {
293
287
  if (!decision)
294
288
  return null;
295
- 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:";
296
290
  if (reviewOnly) {
297
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;
298
292
  }
@@ -306,7 +300,7 @@ function CandidateBreakdown({ item, candidates, result, decision, unmatchable, s
306
300
  // Metron Labs' own tooling — only surfaced under the `debug` config's detail mode.
307
301
  const showBusinessDetail = internal || detailMode === "detailed";
308
302
  const showScoreColumns = debugEnabled && detailMode === "detailed";
309
- 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;
310
304
  if (unmatchable) {
311
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." })] }));
312
306
  }
@@ -391,7 +385,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
391
385
  let totalValue = hasPendingReview ? null : getCartSubtotal(Object.values(cart), { wasteFactorEnabled: false, wasteFactorOverride: 0 });
392
386
  let needsPricingCount = 0;
393
387
  for (const row of rows) {
394
- if (row.unmatchable || row.decision?.include_in_quote !== true || !row.best)
388
+ if (row.unmatchable || !row.best)
395
389
  continue;
396
390
  const cartItem = cart[row.item.line_id];
397
391
  if (!cartItem || getPricingBreakdown(cartItem).lineTotal == null)
@@ -400,7 +394,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
400
394
  if (needsPricingCount > 0)
401
395
  totalValue = null;
402
396
  const totalLabel = formatResultsTotal(totalValue, needsPricingCount);
403
- 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;
404
398
  const previewCandidate = (row) => row.best ?? recommendedCandidates(row.result)[0];
405
399
  const reviewRequiredRows = rows.filter((row) => !row.unmatchable && !row.best);
406
400
  const pendingReviewRows = reviewRequiredRows.filter((row) => !skippedReviewIds.has(row.item.line_id));
@@ -418,9 +412,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
418
412
  const currentPage = Math.min(page, totalPages);
419
413
  const pagedRows = visibleRows.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
420
414
  const matchedTileRows = pagedRows.filter((row) => !row.unmatchable && row.best);
421
- const alternativeTileRows = pagedRows.filter((row) => hasBestAlternative(row));
422
- const matchedDisplayRows = pagedRows.filter((row) => !row.unmatchable && (row.best || hasBestAlternative(row)));
423
- 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);
424
417
  const activeReviewRow = selectedReviewId ? (rows.find((row) => row.item.line_id === selectedReviewId) ?? null) : null;
425
418
  const reviewGateOpen = !internal && !reviewGateComplete && !activeReviewRow && pendingReviewRows.length > 0;
426
419
  const sortMenuRef = useRef(null);
@@ -534,7 +527,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
534
527
  else
535
528
  closeReview(true);
536
529
  };
537
- 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) => {
538
531
  const { item, qty } = row;
539
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));
540
533
  }) })] })) : null;
@@ -565,7 +558,9 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
565
558
  })));
566
559
  }
567
560
  function toCartItem(row) {
568
- 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.");
569
564
  return candidateToCartItem(candidate, {
570
565
  lineId: row.item.line_id,
571
566
  description: row.item.description_original,
@@ -590,8 +585,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
590
585
  : undefined,
591
586
  });
592
587
  }
593
- // All matched items, including review-required ones, land in the basket automatically
594
- // 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
595
590
  // to BoQApp) so this fires once per schedule result, not on every remount of this stage.
596
591
  useEffect(() => {
597
592
  if (cartSeeded)
@@ -650,8 +645,8 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
650
645
  const cartCount = Object.keys(cart).length;
651
646
  const reviewQuery = reviewSearch.trim();
652
647
  const llmReviewCandidates = recommendedCandidates(activeReviewRow?.result);
653
- const hasNoSuitableCandidate = activeReviewRow?.decision?.technical_classification === "no_suitable_candidate" || (!!activeReviewRow && !activeReviewRow.best);
654
- 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;
655
650
  const activeReviewReason = activeReviewRow?.decision?.reason ?? (hasNoSuitableCandidate ? "No catalogue candidate found. Refine the request or search the catalogue." : null);
656
651
  const catalogueSearchCandidates = catalogueSearchResults.map((candidate) => ({
657
652
  product_id: candidate.product_id,
@@ -750,22 +745,13 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
750
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) => {
751
746
  const { item, best, qty } = row;
752
747
  const preview = previewCandidate(row);
753
- const isPartial = !best && hasBestAlternative(row);
754
- 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: {
755
- padding: "6px 10px",
756
- borderRadius: 999,
757
- background: "#fef3c7",
758
- color: "#92400e",
759
- fontSize: 12,
760
- fontWeight: 700,
761
- lineHeight: 1,
762
- }, 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: () => {
763
749
  if (cart[item.line_id])
764
750
  onRemoveFromCart(item.line_id);
765
751
  else {
766
752
  onAddToCart(toCartItem(row));
767
753
  }
768
- }, disabled: !preview, "aria-label": cart[item.line_id]
754
+ }, disabled: !best, "aria-label": cart[item.line_id]
769
755
  ? internal
770
756
  ? "Remove from selection"
771
757
  : `Remove from ${customerCollection}`
@@ -785,7 +771,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
785
771
  alignItems: "center",
786
772
  gap: 8,
787
773
  marginTop: 4,
788
- }, 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) => {
789
775
  event.stopPropagation();
790
776
  if (cart[item.line_id])
791
777
  onRemoveFromCart(item.line_id);