@paro.io/expert-shared-components 1.14.51 → 1.14.53

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.
Files changed (32) hide show
  1. package/lib/components/DocumentCenter/MultiFileUploadSection.js +220 -121
  2. package/lib/components/TaxAxis/TaxAxisApi.d.ts +1 -0
  3. package/lib/components/TaxAxis/TaxAxisShell.js +55 -12
  4. package/lib/index.d.ts +1 -14
  5. package/lib/index.js +1 -27
  6. package/lib/tax-axis/components/clientReport/TaxAxisClientReport.d.ts +6 -2
  7. package/lib/tax-axis/components/clientReport/TaxAxisClientReport.js +5 -3
  8. package/lib/tax-axis/components/dashboard/TaxAxisDashboard.d.ts +2 -1
  9. package/lib/tax-axis/components/dashboard/TaxAxisDashboard.js +163 -34
  10. package/lib/tax-axis/components/documents/DocumentCard.d.ts +3 -1
  11. package/lib/tax-axis/components/documents/DocumentCard.js +17 -5
  12. package/lib/tax-axis/components/documents/DocumentReviewModal.d.ts +13 -0
  13. package/lib/tax-axis/components/documents/DocumentReviewModal.js +248 -0
  14. package/lib/tax-axis/components/documents/DocumentTier.d.ts +3 -1
  15. package/lib/tax-axis/components/documents/DocumentTier.js +2 -2
  16. package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +7 -1
  17. package/lib/tax-axis/components/documents/TaxAxisDocuments.js +116 -53
  18. package/lib/tax-axis/components/extractionReview/TaxAxisExtractionReview.js +17 -17
  19. package/lib/tax-axis/components/intake/ClientParametersSection.js +1 -1
  20. package/lib/tax-axis/components/intake/intakeSchema.js +1 -1
  21. package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.d.ts +6 -2
  22. package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.js +5 -3
  23. package/lib/tax-axis/index.d.ts +4 -0
  24. package/lib/tax-axis/index.js +6 -1
  25. package/lib/tax-axis/lib/adapters/useEngineOutput.d.ts +144 -0
  26. package/lib/tax-axis/lib/adapters/useEngineOutput.js +155 -0
  27. package/lib/tax-axis/lib/data/documents.d.ts +3 -2
  28. package/lib/tax-axis/lib/data/documents.js +225 -25
  29. package/lib/tax-axis/lib/documentFieldCatalog.d.ts +13 -0
  30. package/lib/tax-axis/lib/documentFieldCatalog.js +808 -0
  31. package/lib/tax-axis/lib/types/index.d.ts +3 -0
  32. package/package.json +1 -1
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.SAVE_FIELD_EDIT_MUTATION = exports.EXTRACTED_FIELDS_QUERY = void 0;
36
+ exports.DocumentReviewModal = DocumentReviewModal;
37
+ const react_1 = __importStar(require("react"));
38
+ const extractedData_1 = require("../../lib/data/extractedData");
39
+ const documentFieldCatalog_1 = require("../../lib/documentFieldCatalog");
40
+ // ── GraphQL contracts (for consuming apps to use once resolvers land) ──
41
+ exports.EXTRACTED_FIELDS_QUERY = `
42
+ query ExtractedDocumentFields($jobId: ID!, $documentId: ID!) {
43
+ extractedDocumentFields(jobId: $jobId, documentId: $documentId) {
44
+ documentId
45
+ documentName
46
+ fileName
47
+ sections {
48
+ head
49
+ fields {
50
+ key
51
+ label
52
+ value
53
+ sourceRef
54
+ confidence
55
+ }
56
+ }
57
+ }
58
+ }
59
+ `;
60
+ exports.SAVE_FIELD_EDIT_MUTATION = `
61
+ mutation SaveExtractedFieldEdit($jobId: ID!, $documentId: ID!, $fieldKey: String!, $value: String!) {
62
+ saveExtractedFieldEdit(jobId: $jobId, documentId: $documentId, fieldKey: $fieldKey, value: $value) {
63
+ success
64
+ fieldKey
65
+ savedValue
66
+ }
67
+ }
68
+ `;
69
+ // ── Format a raw value for display ──
70
+ function formatValue(v) {
71
+ if (v === null || v === undefined)
72
+ return "";
73
+ if (typeof v === "number")
74
+ return `$${v.toLocaleString()}`;
75
+ if (typeof v === "boolean")
76
+ return v ? "Yes" : "No";
77
+ return String(v);
78
+ }
79
+ // ── Build ReviewSection[] from real parsedData using the catalog ──
80
+ function buildSectionsFromCatalog(documentType, fields) {
81
+ const catalog = documentFieldCatalog_1.DOCUMENT_FIELD_CATALOG[documentType];
82
+ if (!catalog) {
83
+ // Unknown doc type — render all non-null scalar fields in one flat section
84
+ const flatFields = Object.entries(fields)
85
+ .filter(([, v]) => v !== null && v !== undefined && typeof v !== "object")
86
+ .map(([key, val]) => ({
87
+ key,
88
+ label: key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
89
+ value: formatValue(val),
90
+ sourceRef: "",
91
+ confidence: 0.9,
92
+ }));
93
+ return flatFields.length > 0 ? [{ head: "Extracted Fields", fields: flatFields }] : [];
94
+ }
95
+ return catalog.sections
96
+ .map((section) => {
97
+ const sectionFields = section.fields
98
+ .filter((key) => {
99
+ const v = fields[key];
100
+ // Include all scalar fields from the catalog — show empty string for null/undefined.
101
+ // Skip only object/array values (dicts, arrays) which can't be inline-edited.
102
+ return typeof v !== "object" || v === null || v === undefined;
103
+ })
104
+ .map((key) => {
105
+ var _a, _b;
106
+ const def = catalog.fields[key];
107
+ return {
108
+ key,
109
+ label: (_a = def === null || def === void 0 ? void 0 : def.label) !== null && _a !== void 0 ? _a : key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
110
+ value: formatValue(fields[key]),
111
+ sourceRef: (_b = def === null || def === void 0 ? void 0 : def.sourceRef) !== null && _b !== void 0 ? _b : "",
112
+ confidence: 0.9,
113
+ };
114
+ });
115
+ return { head: section.head, fields: sectionFields };
116
+ })
117
+ .filter((s) => s.fields.length > 0);
118
+ }
119
+ // ── Stub fallback when no real parsedData is available ──
120
+ function getStubSections(documentId) {
121
+ const doc = extractedData_1.EXTRACTED_DATA.find((d) => d.docId === documentId);
122
+ if (!doc)
123
+ return [];
124
+ return [
125
+ {
126
+ head: doc.docName,
127
+ fields: doc.fields.map((f, i) => ({
128
+ key: `${documentId}-${i}`,
129
+ label: f.field,
130
+ value: f.value,
131
+ sourceRef: f.line,
132
+ confidence: f.status === "pass" ? 0.92 : 0.65,
133
+ })),
134
+ },
135
+ ];
136
+ }
137
+ function getTotalFieldCount(sections) {
138
+ return sections.reduce((sum, s) => sum + s.fields.length, 0);
139
+ }
140
+ // ── Component ──
141
+ function DocumentReviewModal({ documentId, documentName, fileName, jobId: _jobId, parsedData, onSaveReviewedData, onClose, }) {
142
+ const [loading, setLoading] = (0, react_1.useState)(true);
143
+ const [sections, setSections] = (0, react_1.useState)([]);
144
+ const [saveStatus, setSaveStatus] = (0, react_1.useState)("idle");
145
+ const debounceTimers = (0, react_1.useRef)({});
146
+ (0, react_1.useEffect)(() => {
147
+ var _a;
148
+ if (parsedData && typeof parsedData === "object") {
149
+ const fields = ((_a = parsedData.fields) !== null && _a !== void 0 ? _a : parsedData);
150
+ const documentType = parsedData.documentType || documentId;
151
+ setSections(buildSectionsFromCatalog(documentType, fields));
152
+ setLoading(false);
153
+ return;
154
+ }
155
+ // Fallback: stub loading delay
156
+ const t = setTimeout(() => {
157
+ setSections(getStubSections(documentId));
158
+ setLoading(false);
159
+ }, 400);
160
+ return () => clearTimeout(t);
161
+ }, [documentId, parsedData]);
162
+ const handleFieldChange = (0, react_1.useCallback)((sectionIdx, fieldIdx, newValue) => {
163
+ setSections((prev) => prev.map((sec, si) => si === sectionIdx
164
+ ? Object.assign(Object.assign({}, sec), { fields: sec.fields.map((f, fi) => fi === fieldIdx ? Object.assign(Object.assign({}, f), { value: newValue }) : f) }) : sec));
165
+ const timerKey = `${sectionIdx}-${fieldIdx}`;
166
+ clearTimeout(debounceTimers.current[timerKey]);
167
+ setSaveStatus("saving");
168
+ debounceTimers.current[timerKey] = setTimeout(() => {
169
+ setSaveStatus("saved");
170
+ setTimeout(() => setSaveStatus("idle"), 1500);
171
+ }, 900);
172
+ }, [sections]);
173
+ const handleDone = (0, react_1.useCallback)(() => __awaiter(this, void 0, void 0, function* () {
174
+ if (onSaveReviewedData && sections.length > 0) {
175
+ const allFields = {};
176
+ sections.forEach((sec) => sec.fields.forEach((f) => { allFields[f.key] = f.value; }));
177
+ try {
178
+ yield onSaveReviewedData(allFields);
179
+ }
180
+ catch ( /* non-fatal */_a) { /* non-fatal */ }
181
+ }
182
+ onClose();
183
+ }), [onSaveReviewedData, sections, onClose]);
184
+ (0, react_1.useEffect)(() => {
185
+ const timers = debounceTimers.current;
186
+ return () => { Object.values(timers).forEach(clearTimeout); };
187
+ }, []);
188
+ const totalFields = getTotalFieldCount(sections);
189
+ const isFlagged = (f) => f.confidence < 0.8;
190
+ return (react_1.default.createElement("div", { onClick: onClose, style: { position: "fixed", inset: 0, zIndex: 9999, background: "rgba(0,0,0,0.65)", display: "flex", alignItems: "center", justifyContent: "center" } },
191
+ react_1.default.createElement("div", { onClick: (e) => e.stopPropagation(), style: { width: 520, maxHeight: "85vh", background: "#111827", border: "1px solid #1f2937", borderRadius: 16, display: "flex", flexDirection: "column", boxShadow: "0 25px 60px rgba(0,0,0,0.5)" } },
192
+ react_1.default.createElement("div", { style: { padding: "20px 24px 16px", borderBottom: "1px solid #1f2937", display: "flex", alignItems: "flex-start", gap: 14 } },
193
+ react_1.default.createElement("div", { style: { width: 40, height: 40, borderRadius: 10, background: "rgba(99,102,241,0.12)", border: "1px solid rgba(99,102,241,0.25)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 } },
194
+ react_1.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none" },
195
+ react_1.default.createElement("rect", { x: "3", y: "2", width: "12", height: "14", rx: "2", stroke: "#818cf8", strokeWidth: "1.5" }),
196
+ react_1.default.createElement("path", { d: "M6 6h6M6 9h6M6 12h4", stroke: "#818cf8", strokeWidth: "1.2", strokeLinecap: "round" }))),
197
+ react_1.default.createElement("div", { style: { flex: 1, minWidth: 0 } },
198
+ react_1.default.createElement("div", { className: "font-tax-axis-body", style: { fontSize: 15, fontWeight: 600, color: "#f3f4f6", marginBottom: 3 } },
199
+ documentName,
200
+ " \u2014 Extracted Fields"),
201
+ react_1.default.createElement("div", { className: "font-tax-axis-body", style: { fontSize: 12, color: "#9ca3af" } },
202
+ fileName,
203
+ !loading && react_1.default.createElement(react_1.default.Fragment, null,
204
+ " ",
205
+ "\u00B7 ",
206
+ totalFields,
207
+ " fields \u00B7 Review and correct parsed values"))),
208
+ react_1.default.createElement("button", { onClick: onClose, style: { background: "transparent", border: "none", color: "#6b7280", fontSize: 20, cursor: "pointer", padding: "2px 6px", lineHeight: 1 } }, "\u00D7")),
209
+ react_1.default.createElement("div", { style: { flex: 1, overflowY: "auto", padding: "16px 24px 8px" } }, loading ? (react_1.default.createElement(LoadingSkeleton, null)) : sections.length === 0 ? (react_1.default.createElement("div", { style: { color: "#6b7280", fontSize: 13, textAlign: "center", padding: "32px 0" } }, "No extracted fields available for this document.")) : (sections.map((section, si) => (react_1.default.createElement("div", { key: section.head, style: { marginBottom: 20 } },
210
+ react_1.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 10 } },
211
+ react_1.default.createElement("span", { className: "font-tax-axis-mono", style: { fontSize: 10, fontWeight: 700, letterSpacing: "0.08em", textTransform: "uppercase", color: "#6b7280" } }, section.head),
212
+ react_1.default.createElement("div", { style: { flex: 1, height: 1, background: "#1f2937" } })),
213
+ react_1.default.createElement("div", { style: { background: "#0d1117", borderRadius: 10, border: "1px solid #1f2937", overflow: "hidden" } }, section.fields.map((field, fi) => {
214
+ const flagged = isFlagged(field);
215
+ return (react_1.default.createElement("div", { key: field.key, style: { display: "grid", gridTemplateColumns: "160px 1fr auto", alignItems: "center", padding: "10px 14px", borderBottom: fi < section.fields.length - 1 ? "1px solid #1f2937" : "none", gap: 12 } },
216
+ react_1.default.createElement("span", { className: "font-tax-axis-body", style: { fontSize: 12, color: flagged ? "#fbbf24" : "#9ca3af", display: "flex", alignItems: "center", gap: 5 } },
217
+ flagged && (react_1.default.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none" },
218
+ react_1.default.createElement("path", { d: "M6 1L11 10H1L6 1Z", stroke: "#fbbf24", strokeWidth: "1.2", strokeLinejoin: "round" }),
219
+ react_1.default.createElement("path", { d: "M6 4.5V6.5M6 8V8.01", stroke: "#fbbf24", strokeWidth: "1.1", strokeLinecap: "round" }))),
220
+ field.label),
221
+ react_1.default.createElement("input", { type: "text", value: field.value, onChange: (e) => handleFieldChange(si, fi, e.target.value), style: { background: "#0d1117", border: `1px solid ${flagged ? "rgba(251,191,36,0.4)" : "#1f2937"}`, borderRadius: 6, padding: "7px 10px", color: "#e5e7eb", fontSize: 13, fontFamily: "monospace", outline: "none", width: "100%", boxSizing: "border-box" } }),
222
+ react_1.default.createElement("span", { className: "font-tax-axis-mono", style: { fontSize: 11, color: flagged ? "#fbbf24" : "#4ade80", whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: 4 } },
223
+ field.sourceRef || "",
224
+ " ",
225
+ flagged ? react_1.default.createElement("span", { style: { fontSize: 10 } }, "\u25B2") : react_1.default.createElement("span", { style: { fontSize: 12 } }, "\u2713"))));
226
+ }))))))),
227
+ react_1.default.createElement("div", { style: { padding: "14px 24px", borderTop: "1px solid #1f2937", display: "flex", alignItems: "center", justifyContent: "space-between" } },
228
+ react_1.default.createElement("span", { className: "font-tax-axis-body", style: { fontSize: 11, color: "#6b7280", display: "flex", alignItems: "center", gap: 6 } },
229
+ saveStatus === "idle" && (react_1.default.createElement(react_1.default.Fragment, null,
230
+ react_1.default.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none" },
231
+ react_1.default.createElement("circle", { cx: "6", cy: "6", r: "5", stroke: "#6b7280", strokeWidth: "1.2" }),
232
+ react_1.default.createElement("path", { d: "M6 3.5V6.5L8 7.5", stroke: "#6b7280", strokeWidth: "1.1", strokeLinecap: "round" })),
233
+ "Changes save automatically")),
234
+ saveStatus === "saving" && (react_1.default.createElement(react_1.default.Fragment, null,
235
+ react_1.default.createElement("div", { style: { width: 10, height: 10, borderRadius: "50%", border: "2px solid transparent", borderTopColor: "#818cf8", animation: "spin 0.6s linear infinite" } }),
236
+ "Saving\u2026")),
237
+ saveStatus === "saved" && react_1.default.createElement("span", { style: { color: "#4ade80" } }, "Saved \u2713")),
238
+ react_1.default.createElement("button", { onClick: handleDone, className: "font-tax-axis-body", style: { background: "rgba(99,102,241,0.9)", border: "none", borderRadius: 8, padding: "9px 28px", color: "#fff", fontSize: 13, fontWeight: 600, cursor: "pointer" } }, "Done"))),
239
+ react_1.default.createElement("style", null, `@keyframes spin { to { transform: rotate(360deg); } }`)));
240
+ }
241
+ function LoadingSkeleton() {
242
+ return (react_1.default.createElement("div", null, [1, 2].map((i) => (react_1.default.createElement("div", { key: i, style: { marginBottom: 20 } },
243
+ react_1.default.createElement("div", { style: { width: 100, height: 10, background: "#1f2937", borderRadius: 4, marginBottom: 10 } }),
244
+ react_1.default.createElement("div", { style: { background: "#0d1117", borderRadius: 10, border: "1px solid #1f2937", padding: 14 } }, [1, 2, 3].map((j) => (react_1.default.createElement("div", { key: j, style: { display: "grid", gridTemplateColumns: "160px 1fr 60px", gap: 12, marginBottom: j < 3 ? 12 : 0 } },
245
+ react_1.default.createElement("div", { style: { height: 14, background: "#1f2937", borderRadius: 4 } }),
246
+ react_1.default.createElement("div", { style: { height: 32, background: "#1f2937", borderRadius: 6 } }),
247
+ react_1.default.createElement("div", { style: { height: 14, background: "#1f2937", borderRadius: 4 } }))))))))));
248
+ }
@@ -14,9 +14,11 @@ interface DocumentTierProps {
14
14
  tier: TierDef;
15
15
  docs: DocState[];
16
16
  helpOverrides: Record<string, string>;
17
+ fieldCounts?: Record<string, number>;
17
18
  onUpload: (idx: number, file: File) => void;
18
19
  onClear: (idx: number) => void;
19
20
  onRemove?: (idx: number) => void;
21
+ onReview?: (docId: string) => void;
20
22
  }
21
- export declare function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, onRemove, }: DocumentTierProps): React.JSX.Element | null;
23
+ export declare function DocumentTier({ tier, docs, helpOverrides, fieldCounts, onUpload, onClear, onRemove, onReview, }: DocumentTierProps): React.JSX.Element | null;
22
24
  export {};
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DocumentTier = DocumentTier;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const DocumentCard_1 = require("./DocumentCard");
9
- function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, onRemove, }) {
9
+ function DocumentTier({ tier, docs, helpOverrides, fieldCounts, onUpload, onClear, onRemove, onReview, }) {
10
10
  const tierDocs = tier.ids
11
11
  .map((id) => {
12
12
  const idx = docs.findIndex((d) => d.id === id);
@@ -19,5 +19,5 @@ function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, onRemove,
19
19
  react_1.default.createElement("div", { className: "flex items-center gap-2 mb-2" },
20
20
  react_1.default.createElement("span", { className: "text-[10px] font-bold uppercase tracking-widest font-tax-axis-mono", style: { color: tier.labelColor } }, tier.label),
21
21
  react_1.default.createElement("span", { className: "text-[10px] text-tax-axis-text-4 font-tax-axis-body" }, tier.sublabel)),
22
- react_1.default.createElement("div", { className: "grid gap-1.5" }, tierDocs.map(({ doc, idx }) => (react_1.default.createElement(DocumentCard_1.DocumentCard, { key: doc.id, doc: doc, tierBorderColor: tier.borderColor, tierBadgeColor: tier.badgeColor, tierBadgeText: tier.badgeText, helpOverride: helpOverrides[doc.id], onUpload: (file) => onUpload(idx, file), onClear: () => onClear(idx), onRemove: onRemove ? () => onRemove(idx) : undefined }))))));
22
+ react_1.default.createElement("div", { className: "grid gap-1.5" }, tierDocs.map(({ doc, idx }) => (react_1.default.createElement(DocumentCard_1.DocumentCard, { key: doc.id, doc: doc, tierBorderColor: tier.borderColor, tierBadgeColor: tier.badgeColor, tierBadgeText: tier.badgeText, helpOverride: helpOverrides[doc.id], fieldCount: fieldCounts === null || fieldCounts === void 0 ? void 0 : fieldCounts[doc.id], onUpload: (file) => onUpload(idx, file), onClear: () => onClear(idx), onRemove: onRemove ? () => onRemove(idx) : undefined, onReview: onReview ? () => onReview(doc.id) : undefined }))))));
23
23
  }
@@ -1,8 +1,10 @@
1
1
  import React from "react";
2
2
  import { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
3
+ import { TaxAxisEntityTypeKey } from "../../lib/data/documents";
3
4
  import { DocState } from "./DocumentCard";
4
5
  export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
5
6
  profile: ClientProfile;
7
+ entityType?: TaxAxisEntityTypeKey | string | null;
6
8
  onContinue: () => void;
7
9
  onBack: () => void;
8
10
  onUploadDocument?: (doc: DocState, file: File) => Promise<void | {
@@ -19,6 +21,10 @@ export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
19
21
  documentType?: string | null;
20
22
  parseError?: string | null;
21
23
  updatedAt?: string | null;
24
+ parsedData?: Record<string, unknown> | null;
22
25
  }>>;
26
+ parsedFieldCounts?: Record<string, number>;
27
+ jobId?: string;
28
+ onSaveReviewedField?: (documentId: string, reviewedData: Record<string, unknown>) => Promise<void>;
23
29
  }
24
- export declare function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
30
+ export declare function TaxAxisDocuments({ profile, entityType, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, parsedFieldCounts, jobId, onSaveReviewedField, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
@@ -37,24 +37,7 @@ const react_1 = __importStar(require("react"));
37
37
  const documents_1 = require("../../lib/data/documents");
38
38
  const TaxAxisButton_1 = require("../shared/TaxAxisButton");
39
39
  const DocumentTier_1 = require("./DocumentTier");
40
- const STUB_FILENAMES = {
41
- "1120s": "2025_1120S.pdf",
42
- "state-return": "2025_State_Return.pdf",
43
- "payroll": "2025_Payroll.xlsx",
44
- "pnl": "QBO_PnL_2025.pdf",
45
- "balance": "QBO_BS_2025.pdf",
46
- "cashflow": "QBO_CashFlow_2025.pdf",
47
- "fixed-assets": "Fixed_Assets.xlsx",
48
- "prior-returns": "Prior_Returns.pdf",
49
- };
50
- const HELP_OVERRIDES = {
51
- "1120s": "Improves accuracy of every strategy",
52
- "payroll": "Critical for Entity Structure and QBI calculations",
53
- "fixed-assets": "Unlocks Cost Segregation (potential $15K\u201385K)",
54
- "state-return": "If client operates in income-tax states",
55
- "cashflow": "Helps with income deferral and timing strategies",
56
- "prior-returns": "Improves confidence intervals (more years = tighter estimates)",
57
- };
40
+ const DocumentReviewModal_1 = require("./DocumentReviewModal");
58
41
  const TIER_DEFS = [
59
42
  {
60
43
  key: "required",
@@ -64,7 +47,7 @@ const TIER_DEFS = [
64
47
  labelColor: "#C53030",
65
48
  badgeColor: "red",
66
49
  badgeText: "REQUIRED",
67
- ids: ["pnl", "balance"],
50
+ ids: [],
68
51
  },
69
52
  {
70
53
  key: "recommended",
@@ -74,7 +57,7 @@ const TIER_DEFS = [
74
57
  labelColor: "#FB9A1D",
75
58
  badgeColor: "orange",
76
59
  badgeText: "RECOMMENDED",
77
- ids: ["1120s", "payroll", "fixed-assets"],
60
+ ids: [],
78
61
  },
79
62
  {
80
63
  key: "conditional",
@@ -84,16 +67,49 @@ const TIER_DEFS = [
84
67
  labelColor: "#C8CCE5",
85
68
  badgeColor: "neutral",
86
69
  badgeText: "CONDITIONAL",
87
- ids: ["state-return", "cashflow", "prior-returns"],
70
+ ids: [],
88
71
  },
89
72
  ];
90
- function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext = "expert", }) {
91
- const normalizeDocumentType = (value) => String(value || "").toUpperCase().replace(/[^A-Z0-9]+/g, "_");
92
- const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(profile), [profile]);
73
+ function TaxAxisDocuments({ profile, entityType, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, parsedFieldCounts, jobId = "stub-job-id", onSaveReviewedField, userContext: _userContext = "expert", }) {
74
+ var _a, _b, _c;
75
+ const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(entityType !== null && entityType !== void 0 ? entityType : undefined), [entityType]);
76
+ // Build tier defs dynamically from the doc spec list so they're always in sync.
77
+ const tierDefs = (0, react_1.useMemo)(() => {
78
+ return TIER_DEFS.map((def) => (Object.assign(Object.assign({}, def), { ids: docSpecs
79
+ .filter((s) => s.tier === def.key)
80
+ .map((s) => s.id) })));
81
+ }, [docSpecs]);
93
82
  const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null, parseError: null }))));
94
83
  const [uploadedDocIds, setUploadedDocIds] = (0, react_1.useState)({});
95
84
  const [continueError, setContinueError] = (0, react_1.useState)(null);
96
- const pollForParseStatus = (idx, fileName, expectedDocumentType) => __awaiter(this, void 0, void 0, function* () {
85
+ const [reviewDocId, setReviewDocId] = (0, react_1.useState)(null);
86
+ // parsedData keyed by documentType (e.g. "profit_loss") — populated from poll results
87
+ const [parsedDataByDocType, setParsedDataByDocType] = (0, react_1.useState)({});
88
+ // On mount, fetch any already-parsed docs so the review modal has real data
89
+ (0, react_1.useEffect)(() => {
90
+ if (!fetchUploadedDocuments)
91
+ return;
92
+ fetchUploadedDocuments().then((uploaded) => {
93
+ const byType = {};
94
+ for (const doc of uploaded) {
95
+ if (doc.parsedData && doc.documentType) {
96
+ byType[doc.documentType] = doc.parsedData;
97
+ }
98
+ }
99
+ if (Object.keys(byType).length > 0) {
100
+ setParsedDataByDocType(byType);
101
+ }
102
+ }).catch(() => { });
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps
104
+ }, []);
105
+ const reviewDoc = reviewDocId
106
+ ? (_a = docs.find((d) => d.id === reviewDocId)) !== null && _a !== void 0 ? _a : null
107
+ : null;
108
+ // The real parsedData for the document currently open in the review modal
109
+ const reviewParsedData = reviewDoc
110
+ ? ((_c = (_b = parsedDataByDocType[reviewDoc.documentType || reviewDoc.id]) !== null && _b !== void 0 ? _b : parsedDataByDocType[reviewDoc.id]) !== null && _c !== void 0 ? _c : null)
111
+ : null;
112
+ const pollForParseStatus = (idx, fileName, documentType) => __awaiter(this, void 0, void 0, function* () {
97
113
  if (!fetchUploadedDocuments)
98
114
  return;
99
115
  const maxAttempts = 80;
@@ -101,11 +117,11 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
101
117
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
102
118
  try {
103
119
  const uploaded = yield fetchUploadedDocuments();
104
- const exactTypeMatch = uploaded.filter((doc) => doc.fileName === fileName &&
105
- normalizeDocumentType(String(doc.documentType || "")) ===
106
- normalizeDocumentType(expectedDocumentType));
107
- const filenameMatches = uploaded.filter((doc) => doc.fileName === fileName);
108
- const candidates = exactTypeMatch.length > 0 ? exactTypeMatch : filenameMatches;
120
+ // Match by documentType first (exact lowercase key), fall back to fileName.
121
+ const typeMatch = uploaded.filter((doc) => doc.fileName === fileName &&
122
+ String(doc.documentType || "").toLowerCase() === documentType.toLowerCase());
123
+ const nameMatch = uploaded.filter((doc) => doc.fileName === fileName);
124
+ const candidates = typeMatch.length > 0 ? typeMatch : nameMatch;
109
125
  const match = candidates
110
126
  .slice()
111
127
  .sort((a, b) => new Date(String(b.updatedAt || "")).getTime() -
@@ -120,6 +136,9 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
120
136
  if (status === "PARSED") {
121
137
  setDocs((prev) => prev.map((d, i) => i === idx
122
138
  ? Object.assign(Object.assign({}, d), { status: "valid", fileName, parseError: null }) : d));
139
+ if (match.parsedData && documentType) {
140
+ setParsedDataByDocType((prev) => (Object.assign(Object.assign({}, prev), { [documentType]: match.parsedData })));
141
+ }
123
142
  return;
124
143
  }
125
144
  if (status === "PARSING" || status === "UPLOADED") {
@@ -128,7 +147,7 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
128
147
  }
129
148
  }
130
149
  }
131
- catch (pollError) {
150
+ catch (_pollError) {
132
151
  // keep polling
133
152
  }
134
153
  yield new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
@@ -141,13 +160,14 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
141
160
  return;
142
161
  setContinueError(null);
143
162
  setDocs((prev) => prev.map((d, i) => i === idx
144
- ? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name || STUB_FILENAMES[d.id] || "document.pdf", parseError: null }) : d));
163
+ ? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name, parseError: null }) : d));
145
164
  try {
146
165
  const uploadResult = onUploadDocument
147
166
  ? yield onUploadDocument(selectedDoc, file)
148
167
  : undefined;
149
- const nextFileName = file.name || STUB_FILENAMES[selectedDoc.id] || "document.pdf";
150
- const expectedDocumentType = normalizeDocumentType(selectedDoc.id);
168
+ const nextFileName = file.name;
169
+ // Use the canonical documentType from the catalog entry.
170
+ const documentType = selectedDoc.documentType;
151
171
  const immediateStatus = String((uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.status) || "").toUpperCase();
152
172
  if (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.documentId) {
153
173
  setUploadedDocIds((prev) => (Object.assign(Object.assign({}, prev), { [idx]: uploadResult.documentId })));
@@ -155,6 +175,19 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
155
175
  if (immediateStatus === "PARSED") {
156
176
  setDocs((prev) => prev.map((d, i) => i === idx
157
177
  ? Object.assign(Object.assign({}, d), { status: "valid", fileName: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.fileName) || nextFileName, parseError: null }) : d));
178
+ // parsedData is not on the upload mutation response — fetch it now via one poll call.
179
+ if (fetchUploadedDocuments && documentType) {
180
+ try {
181
+ const uploaded = yield fetchUploadedDocuments();
182
+ const match = uploaded.find((d) => String(d.documentType || "").toLowerCase() === documentType.toLowerCase());
183
+ if (match === null || match === void 0 ? void 0 : match.parsedData) {
184
+ setParsedDataByDocType((prev) => (Object.assign(Object.assign({}, prev), { [documentType]: match.parsedData })));
185
+ }
186
+ }
187
+ catch (_a) {
188
+ // non-fatal — user can still open modal, will show empty state
189
+ }
190
+ }
158
191
  return;
159
192
  }
160
193
  if (immediateStatus === "FAILED") {
@@ -162,9 +195,8 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
162
195
  ? Object.assign(Object.assign({}, d), { status: "failed", fileName: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.fileName) || nextFileName, parseError: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.parseError) || "Parsing failed. Please upload a correct document." }) : d));
163
196
  return;
164
197
  }
165
- setDocs((prev) => prev.map((d, i) => i === idx
166
- ? Object.assign(Object.assign({}, d), { status: "parsing", fileName: nextFileName }) : d));
167
- yield pollForParseStatus(idx, nextFileName, expectedDocumentType);
198
+ setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "parsing", fileName: nextFileName }) : d));
199
+ yield pollForParseStatus(idx, nextFileName, documentType);
168
200
  }
169
201
  catch (uploadError) {
170
202
  const errorMessage = uploadError instanceof Error
@@ -185,8 +217,8 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
185
217
  try {
186
218
  yield onDeleteDocument(docId);
187
219
  }
188
- catch (deleteError) {
189
- // still clear from UI even if backend delete fails
220
+ catch (_deleteError) {
221
+ // clear from UI even if backend delete fails
190
222
  }
191
223
  }
192
224
  setUploadedDocIds((prev) => {
@@ -196,7 +228,7 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
196
228
  });
197
229
  handleClear(idx);
198
230
  });
199
- const handleContinue = () => {
231
+ const handleContinue = () => __awaiter(this, void 0, void 0, function* () {
200
232
  const failedDocs = docs.filter((d) => d.status === "failed");
201
233
  if (failedDocs.length > 0) {
202
234
  const names = failedDocs.map((d) => d.name).join(", ");
@@ -204,21 +236,45 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
204
236
  return;
205
237
  }
206
238
  setContinueError(null);
239
+ // For every parsed doc that was not manually reviewed, persist parsedData.fields
240
+ // as reviewedData so the engine always has a reviewedData record to read from.
241
+ if (onSaveReviewedField) {
242
+ const validDocs = docs.filter((d) => d.status === "valid");
243
+ yield Promise.all(validDocs.map((doc) => __awaiter(this, void 0, void 0, function* () {
244
+ var _a;
245
+ const idx = docs.indexOf(doc);
246
+ const documentId = uploadedDocIds[idx];
247
+ if (!documentId)
248
+ return;
249
+ const parsed = parsedDataByDocType[doc.documentType || doc.id];
250
+ if (!parsed)
251
+ return;
252
+ const fields = ((_a = parsed.fields) !== null && _a !== void 0 ? _a : parsed);
253
+ // Convert all scalar values to strings for reviewedData
254
+ const reviewedFields = {};
255
+ for (const [k, v] of Object.entries(fields)) {
256
+ if (v !== null && v !== undefined && typeof v !== "object") {
257
+ reviewedFields[k] = v;
258
+ }
259
+ }
260
+ if (Object.keys(reviewedFields).length > 0) {
261
+ try {
262
+ yield onSaveReviewedField(documentId, reviewedFields);
263
+ }
264
+ catch ( /* non-fatal */_b) { /* non-fatal */ }
265
+ }
266
+ })));
267
+ }
207
268
  onContinue();
208
- };
269
+ });
209
270
  const validCount = docs.filter((d) => d.status === "valid").length;
210
271
  const failedCount = docs.filter((d) => d.status === "failed").length;
211
272
  const busyCount = docs.filter((d) => d.status === "validating" || d.status === "parsing").length;
212
- const coveragePct = validCount === 0
213
- ? 32
214
- : validCount === 1
215
- ? 50
216
- : validCount === 2
217
- ? 60
218
- : validCount <= 4
219
- ? 75
220
- : validCount <= 6
221
- ? 90
273
+ const coveragePct = validCount === 0 ? 32
274
+ : validCount === 1 ? 50
275
+ : validCount === 2 ? 60
276
+ : validCount <= 4 ? 75
277
+ : validCount <= 6 ? 90
222
278
  : 100;
223
279
  const strategiesCovered = Math.round((25 * coveragePct) / 100);
224
280
  return (react_1.default.createElement("div", { className: "max-w-[580px] mx-auto" },
@@ -262,12 +318,19 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
262
318
  border: "1px solid rgba(197,48,48,0.3)",
263
319
  color: "#FEB2B2",
264
320
  } }, continueError)),
265
- TIER_DEFS.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: HELP_OVERRIDES, onUpload: handleUpload, onClear: handleClear, onRemove: handleRemove }))),
321
+ tierDefs.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: {}, fieldCounts: parsedFieldCounts, onUpload: handleUpload, onClear: handleClear, onRemove: handleRemove, onReview: (docId) => setReviewDocId(docId) }))),
266
322
  react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
267
323
  react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
268
324
  react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: handleContinue, className: "flex-1", disabled: busyCount > 0 }, busyCount > 0
269
325
  ? "Processing..."
270
326
  : failedCount > 0
271
327
  ? `Continue (${failedCount} failed)`
272
- : "Continue"))));
328
+ : "Continue")),
329
+ reviewDoc && (react_1.default.createElement(DocumentReviewModal_1.DocumentReviewModal, { documentId: reviewDoc.id, documentName: reviewDoc.name, fileName: reviewDoc.fileName || "", jobId: jobId, parsedData: reviewParsedData, onSaveReviewedData: onSaveReviewedField
330
+ ? (fields) => __awaiter(this, void 0, void 0, function* () {
331
+ const docId = uploadedDocIds[docs.indexOf(reviewDoc)];
332
+ if (docId)
333
+ yield onSaveReviewedField(docId, fields);
334
+ })
335
+ : undefined, onClose: () => setReviewDocId(null) }))));
273
336
  }
@@ -89,6 +89,22 @@ function TaxAxisExtractionReview({ onStatusChange, onConfirmAndUnlock, onBack, }
89
89
  return (react_1.default.createElement("div", null,
90
90
  react_1.default.createElement("style", null, `@keyframes spin{to{transform:rotate(360deg)}}`),
91
91
  react_1.default.createElement(PurposeBlock_1.PurposeBlock, { totalFields: totalFields, totalDocs: data_1.EXTRACTED_DATA.length }),
92
+ onConfirmAndUnlock && (react_1.default.createElement("div", { style: {
93
+ marginBottom: 16,
94
+ background: "linear-gradient(135deg, rgba(36,131,132,0.12), rgba(36,131,132,0.06))",
95
+ border: "1px solid rgba(36,131,132,0.3)",
96
+ borderRadius: 12,
97
+ padding: "18px 20px",
98
+ } },
99
+ react_1.default.createElement("div", { className: "flex items-center gap-4" },
100
+ react_1.default.createElement("div", { className: "flex-1" },
101
+ react_1.default.createElement("div", { className: "text-[15px] font-bold text-white font-tax-axis-head mb-1" }, "Ready to view your report?"),
102
+ react_1.default.createElement("div", { className: "text-[13px] text-tax-axis-text leading-relaxed font-tax-axis-body" }, totalFlags - totalEdits > 0
103
+ ? (totalFlags - totalEdits) + " value" + (totalFlags - totalEdits > 1 ? "s" : "") + " still flagged. You can confirm now or correct values first."
104
+ : "All extracted values look good. Confirm to unlock the Intelligence Report.")),
105
+ react_1.default.createElement("div", { className: "flex gap-2.5" },
106
+ onBack && (react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back to Dashboard")),
107
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onConfirmAndUnlock }, totalFlags - totalEdits > 0 ? "Confirm & Unlock Report" : "Confirm & View Report"))))),
92
108
  react_1.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 12 } },
93
109
  react_1.default.createElement("div", { style: {
94
110
  width: 8,
@@ -166,21 +182,5 @@ function TaxAxisExtractionReview({ onStatusChange, onConfirmAndUnlock, onBack, }
166
182
  borderRadius: 4,
167
183
  border: "1px solid #2E3160",
168
184
  } }, d.code))))))),
169
- react_1.default.createElement(RerunFooter_1.RerunFooter, { totalEdits: totalEdits, rerunning: rerunning, onRerun: handleRerun }),
170
- onConfirmAndUnlock && (react_1.default.createElement("div", { style: {
171
- marginTop: 16,
172
- background: "linear-gradient(135deg, rgba(36,131,132,0.12), rgba(36,131,132,0.06))",
173
- border: "1px solid rgba(36,131,132,0.3)",
174
- borderRadius: 12,
175
- padding: "18px 20px",
176
- } },
177
- react_1.default.createElement("div", { className: "flex items-center gap-4" },
178
- react_1.default.createElement("div", { className: "flex-1" },
179
- react_1.default.createElement("div", { className: "text-[15px] font-bold text-white font-tax-axis-head mb-1" }, "Ready to view your report?"),
180
- react_1.default.createElement("div", { className: "text-[13px] text-tax-axis-text leading-relaxed font-tax-axis-body" }, totalFlags - totalEdits > 0
181
- ? (totalFlags - totalEdits) + " value" + (totalFlags - totalEdits > 1 ? "s" : "") + " still flagged. You can confirm now or correct values first."
182
- : "All extracted values look good. Confirm to unlock the Intelligence Report.")),
183
- react_1.default.createElement("div", { className: "flex gap-2.5" },
184
- onBack && (react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back to Dashboard")),
185
- react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onConfirmAndUnlock }, totalFlags - totalEdits > 0 ? "Confirm & Unlock Report" : "Confirm & View Report")))))));
185
+ react_1.default.createElement(RerunFooter_1.RerunFooter, { totalEdits: totalEdits, rerunning: rerunning, onRerun: handleRerun })));
186
186
  }