@metron-labs/quote-react 0.1.53 → 0.1.55
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,11 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { assetUrl } from "../../lib/assets";
|
|
4
|
-
import { apiUrl } from "../../lib/config";
|
|
5
4
|
import { useTrack } from "../../lib/analytics";
|
|
6
5
|
import ForwardArrow from "../../components/ForwardArrow";
|
|
7
6
|
import CloseIcon from "../../components/CloseIcon";
|
|
8
|
-
import { clientHeaders, readJson } from "../../lib/api/boqClient";
|
|
9
7
|
import { FILE_EXTENSIONS, isPhotoFile, shrinkPhoto, validateLines, validateUploadFile } from "../../lib/boq/inputLimits";
|
|
10
8
|
import { ProcessingContent } from "./ProcessingStage";
|
|
11
9
|
import { useEscapeKey } from "../../lib/useEscapeKey";
|
|
@@ -156,42 +154,25 @@ export default function EntryStage({ onPasteSchedule, onUploadSchedule, onEntryM
|
|
|
156
154
|
setMatching(false);
|
|
157
155
|
}
|
|
158
156
|
};
|
|
159
|
-
const matchPhoto = async (file) => {
|
|
160
|
-
const id = matchRun.current;
|
|
161
|
-
try {
|
|
162
|
-
const photo = await shrinkPhoto(file);
|
|
163
|
-
const problem = validateUploadFile(photo);
|
|
164
|
-
if (problem)
|
|
165
|
-
throw new Error(problem);
|
|
166
|
-
const formData = new FormData();
|
|
167
|
-
formData.append("file", photo);
|
|
168
|
-
const response = await fetch(apiUrl("/api/schedule/extract-photo"), { method: "POST", body: formData, headers: clientHeaders() });
|
|
169
|
-
const payload = await readJson(response);
|
|
170
|
-
if (!response.ok)
|
|
171
|
-
throw new Error(payload?.error || payload?.message || "Failed to extract line items from image");
|
|
172
|
-
const text = typeof payload?.text === "string" ? payload.text.trim() : "";
|
|
173
|
-
if (!text)
|
|
174
|
-
throw new Error("No line items were extracted from this image.");
|
|
175
|
-
const lines = Array.isArray(payload?.lines)
|
|
176
|
-
? payload.lines.filter((line) => Boolean(line && typeof line === "object" && typeof line.text === "string"))
|
|
177
|
-
: undefined;
|
|
178
|
-
// Cancelled while the photo was being read - don't start matching it.
|
|
179
|
-
if (id !== matchRun.current)
|
|
180
|
-
return;
|
|
181
|
-
await onPasteSchedule(text, lines, false);
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
if (id !== matchRun.current)
|
|
185
|
-
return;
|
|
186
|
-
track("error", { stage: "photo" });
|
|
187
|
-
setUploadError(error?.message || "Failed to extract line items from image");
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
157
|
const matchUpload = () => {
|
|
191
158
|
if (!uploadFile)
|
|
192
159
|
return;
|
|
193
160
|
track("list_submitted", { method: isPhotoFile(uploadFile) ? "photo" : "upload" });
|
|
194
|
-
return startMatching(() =>
|
|
161
|
+
return startMatching(async () => {
|
|
162
|
+
if (!isPhotoFile(uploadFile))
|
|
163
|
+
return onUploadSchedule(uploadFile);
|
|
164
|
+
try {
|
|
165
|
+
const photo = await shrinkPhoto(uploadFile);
|
|
166
|
+
const problem = validateUploadFile(photo);
|
|
167
|
+
if (problem)
|
|
168
|
+
throw new Error(problem);
|
|
169
|
+
await onUploadSchedule(photo);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
track("error", { stage: "photo" });
|
|
173
|
+
setUploadError(error?.message || "Failed to process image upload");
|
|
174
|
+
}
|
|
175
|
+
});
|
|
195
176
|
};
|
|
196
177
|
const isFileDrag = (event) => event.dataTransfer.types.includes("Files");
|
|
197
178
|
const onPageDragEnter = (event) => {
|
|
@@ -689,7 +689,7 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
689
689
|
})();
|
|
690
690
|
const activeReviewCartMatches = !!activeReviewRow?.best && cart[activeReviewRow.item.line_id]?.productId === activeReviewRow.best.product_id;
|
|
691
691
|
const activeReviewHasPendingUpdate = !!activeReviewRow?.best && !!cart[activeReviewRow.item.line_id] && !activeReviewCartMatches;
|
|
692
|
-
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: totalLabel ?? "Review quantities" }), " 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" }), totalLabel != null && _jsx("strong", { children: totalLabel })] }), _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: {
|
|
692
|
+
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: totalLabel ?? "Review quantities" }), " 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, disabled: cartCount === 0, "aria-label": qzaQuoteRequest ? "View quote" : "View basket", children: [_jsxs("span", { children: [_jsx("small", { children: qzaQuoteRequest ? "View Quote" : "View Basket" }), totalLabel != null && _jsx("strong", { children: totalLabel })] }), _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: {
|
|
693
693
|
display: "flex",
|
|
694
694
|
gap: 8,
|
|
695
695
|
flexWrap: "wrap",
|
|
@@ -778,10 +778,10 @@ export default function ScheduleResultsStage({ scheduleResult, onStartOver, cart
|
|
|
778
778
|
}
|
|
779
779
|
}, "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));
|
|
780
780
|
})] })] }) }), 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: {
|
|
781
|
-
flex:
|
|
781
|
+
flex: "0 1 30%",
|
|
782
782
|
gap: 10,
|
|
783
783
|
justifyContent: "center",
|
|
784
|
-
}, onClick: () => setConfirmStartOver(true), "aria-label": "Start over", children: [_jsx(RefreshIcon, {}), _jsx("span", { children: "Start Over" })] }),
|
|
784
|
+
}, onClick: () => setConfirmStartOver(true), "aria-label": "Start over", children: [_jsx(RefreshIcon, {}), _jsx("span", { children: "Start Over" })] }), _jsxs("button", { className: "cta-btn", style: { flex: "0 1 70%" }, onClick: onGoToBasket, disabled: cartCount === 0, "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) => {
|
|
785
785
|
const reviewed = reviewedReviewIds.has(row.item.line_id);
|
|
786
786
|
const partial = hasBestAlternative(row);
|
|
787
787
|
return (_jsx("div", { className: `review-gate-item${reviewed ? " is-reviewed" : ""}`, children: _jsxs("button", { type: "button", className: "review-gate-item__review", onClick: () => {
|