@paro.io/expert-shared-components 1.14.64 → 1.14.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/tax-axis/components/documents/DocumentReviewModal.d.ts +2 -1
- package/lib/tax-axis/components/documents/DocumentReviewModal.js +39 -3
- package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +1 -1
- package/lib/tax-axis/components/documents/TaxAxisDocuments.js +125 -30
- package/package.json +1 -1
|
@@ -7,5 +7,6 @@ export interface DocumentReviewModalProps {
|
|
|
7
7
|
fileName: string;
|
|
8
8
|
jobId: string;
|
|
9
9
|
onClose: () => void;
|
|
10
|
+
parsedData?: Record<string, unknown> | null;
|
|
10
11
|
}
|
|
11
|
-
export declare function DocumentReviewModal({ documentId, documentName, fileName, jobId: _jobId, onClose, }: DocumentReviewModalProps): React.JSX.Element;
|
|
12
|
+
export declare function DocumentReviewModal({ documentId, documentName, fileName, jobId: _jobId, onClose, parsedData, }: DocumentReviewModalProps): React.JSX.Element;
|
|
@@ -143,23 +143,59 @@ function getStubSections(documentId) {
|
|
|
143
143
|
},
|
|
144
144
|
];
|
|
145
145
|
}
|
|
146
|
+
function humanizeFieldKey(key) {
|
|
147
|
+
return key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
148
|
+
}
|
|
149
|
+
function formatFieldValue(value) {
|
|
150
|
+
if (typeof value === 'number') {
|
|
151
|
+
return Math.abs(value) >= 1
|
|
152
|
+
? `$${value.toLocaleString('en-US', { maximumFractionDigits: 0 })}`
|
|
153
|
+
: String(value);
|
|
154
|
+
}
|
|
155
|
+
return String(value !== null && value !== void 0 ? value : '');
|
|
156
|
+
}
|
|
157
|
+
function parsedDataToSections(data) {
|
|
158
|
+
const fields = data.fields;
|
|
159
|
+
if (!fields || typeof fields !== 'object')
|
|
160
|
+
return [];
|
|
161
|
+
const entries = Object.entries(fields)
|
|
162
|
+
.filter(([key]) => !key.startsWith('_'));
|
|
163
|
+
if (entries.length === 0)
|
|
164
|
+
return [];
|
|
165
|
+
return [{
|
|
166
|
+
head: humanizeFieldKey(String(data.documentType || 'Extracted Fields')),
|
|
167
|
+
fields: entries.map(([key, value]) => ({
|
|
168
|
+
key,
|
|
169
|
+
label: humanizeFieldKey(key),
|
|
170
|
+
value: formatFieldValue(value),
|
|
171
|
+
sourceRef: 'Extracted',
|
|
172
|
+
confidence: 0.92,
|
|
173
|
+
})),
|
|
174
|
+
}];
|
|
175
|
+
}
|
|
146
176
|
function getTotalFieldCount(sections) {
|
|
147
177
|
return sections.reduce((sum, s) => sum + s.fields.length, 0);
|
|
148
178
|
}
|
|
149
179
|
// ── Component ──
|
|
150
|
-
function DocumentReviewModal({ documentId, documentName, fileName, jobId: _jobId, onClose, }) {
|
|
180
|
+
function DocumentReviewModal({ documentId, documentName, fileName, jobId: _jobId, onClose, parsedData, }) {
|
|
151
181
|
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
152
182
|
const [sections, setSections] = (0, react_1.useState)([]);
|
|
153
183
|
const [saveStatus, setSaveStatus] = (0, react_1.useState)("idle");
|
|
154
184
|
const debounceTimers = (0, react_1.useRef)({});
|
|
155
|
-
//
|
|
185
|
+
// Load sections from real parsedData when available, fall back to stubs
|
|
156
186
|
(0, react_1.useEffect)(() => {
|
|
187
|
+
if (parsedData && typeof parsedData === 'object' && parsedData.fields) {
|
|
188
|
+
setSections(parsedDataToSections(parsedData));
|
|
189
|
+
setLoading(false);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// Fallback: stub data with simulated delay
|
|
157
193
|
const t = setTimeout(() => {
|
|
158
194
|
setSections(getStubSections(documentId));
|
|
159
195
|
setLoading(false);
|
|
160
196
|
}, 400);
|
|
161
197
|
return () => clearTimeout(t);
|
|
162
|
-
}, [documentId]);
|
|
198
|
+
}, [documentId, parsedData]);
|
|
163
199
|
const handleFieldChange = (0, react_1.useCallback)((sectionIdx, fieldIdx, newValue) => {
|
|
164
200
|
setSections((prev) => prev.map((sec, si) => si === sectionIdx
|
|
165
201
|
? Object.assign(Object.assign({}, sec), { fields: sec.fields.map((f, fi) => fi === fieldIdx ? Object.assign(Object.assign({}, f), { value: newValue }) : f) }) : sec));
|
|
@@ -20,4 +20,4 @@ export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
|
|
|
20
20
|
onQboConnected?: (companyName: string) => void;
|
|
21
21
|
onQboDisconnected?: () => void;
|
|
22
22
|
}
|
|
23
|
-
export declare function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
|
|
23
|
+
export declare function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, jobId, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
|
|
@@ -22,6 +22,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
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
|
+
};
|
|
25
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
35
|
exports.TaxAxisDocuments = TaxAxisDocuments;
|
|
27
36
|
const react_1 = __importStar(require("react"));
|
|
@@ -29,28 +38,22 @@ const documents_1 = require("../../lib/data/documents");
|
|
|
29
38
|
const TaxAxisButton_1 = require("../shared/TaxAxisButton");
|
|
30
39
|
const DocumentTier_1 = require("./DocumentTier");
|
|
31
40
|
const DocumentReviewModal_1 = require("./DocumentReviewModal");
|
|
32
|
-
// Stub filenames
|
|
33
|
-
const STUB_FILENAMES = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
};
|
|
43
|
-
// Stub field counts
|
|
44
|
-
const STUB_FIELD_COUNTS = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
payroll: 3,
|
|
49
|
-
"state-return": 2,
|
|
50
|
-
cashflow: 1,
|
|
51
|
-
"fixed-assets": 1,
|
|
52
|
-
"prior-returns": 1,
|
|
53
|
-
};
|
|
41
|
+
// Stub filenames from original mock (App.jsx:1237) — retained for reference.
|
|
42
|
+
// const STUB_FILENAMES: Record<string, string> = {
|
|
43
|
+
// "1120s": "2025_1120S.pdf",
|
|
44
|
+
// "state-return": "2025_State_Return.pdf",
|
|
45
|
+
// "payroll": "2025_Payroll.xlsx",
|
|
46
|
+
// "pnl": "QBO_PnL_2025.pdf",
|
|
47
|
+
// "balance": "QBO_BS_2025.pdf",
|
|
48
|
+
// "cashflow": "QBO_CashFlow_2025.pdf",
|
|
49
|
+
// "fixed-assets": "Fixed_Assets.xlsx",
|
|
50
|
+
// "prior-returns": "Prior_Returns.pdf",
|
|
51
|
+
// };
|
|
52
|
+
// Stub field counts from original mock — retained for reference.
|
|
53
|
+
// const STUB_FIELD_COUNTS: Record<string, number> = {
|
|
54
|
+
// pnl: 13, balance: 9, "1120s": 3, payroll: 3,
|
|
55
|
+
// "state-return": 2, cashflow: 1, "fixed-assets": 1, "prior-returns": 1,
|
|
56
|
+
// };
|
|
54
57
|
// Short help text shown below each doc name when not yet uploaded
|
|
55
58
|
// (mock App.jsx:1384). Overrides the longer DOC_SPECS_BASE.help.
|
|
56
59
|
const HELP_OVERRIDES = {
|
|
@@ -94,7 +97,7 @@ const TIER_DEFS = [
|
|
|
94
97
|
ids: ["state-return", "cashflow", "prior-returns"],
|
|
95
98
|
},
|
|
96
99
|
];
|
|
97
|
-
function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userContext = "expert", }) {
|
|
100
|
+
function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, jobId, userContext: _userContext = "expert", }) {
|
|
98
101
|
var _a;
|
|
99
102
|
const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(profile), [profile]);
|
|
100
103
|
const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null }))));
|
|
@@ -103,17 +106,108 @@ function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userConte
|
|
|
103
106
|
const reviewDoc = reviewDocId
|
|
104
107
|
? (_a = docs.find((d) => d.id === reviewDocId)) !== null && _a !== void 0 ? _a : null
|
|
105
108
|
: null;
|
|
106
|
-
//
|
|
107
|
-
|
|
109
|
+
// File picker refs
|
|
110
|
+
const fileInputRef = (0, react_1.useRef)(null);
|
|
111
|
+
const uploadTargetIdx = (0, react_1.useRef)(-1);
|
|
112
|
+
const documentIdMap = (0, react_1.useRef)({});
|
|
113
|
+
const [parsedDataMap, setParsedDataMap] = (0, react_1.useState)({});
|
|
114
|
+
// Load previously uploaded documents on mount
|
|
115
|
+
(0, react_1.useEffect)(() => {
|
|
116
|
+
if (!fetchUploadedDocuments)
|
|
117
|
+
return;
|
|
118
|
+
fetchUploadedDocuments()
|
|
119
|
+
.then((uploaded) => {
|
|
120
|
+
if (!uploaded || uploaded.length === 0)
|
|
121
|
+
return;
|
|
122
|
+
const newParsed = {};
|
|
123
|
+
setDocs((prev) => prev.map((d) => {
|
|
124
|
+
const match = uploaded.find((u) => u.documentType === d.id);
|
|
125
|
+
if (!match)
|
|
126
|
+
return d;
|
|
127
|
+
if (match.documentId)
|
|
128
|
+
documentIdMap.current[d.id] = match.documentId;
|
|
129
|
+
if (match.parsedData)
|
|
130
|
+
newParsed[d.id] = match.parsedData;
|
|
131
|
+
const status = match.status === 'PARSED' || match.status === 'valid'
|
|
132
|
+
? 'valid'
|
|
133
|
+
: match.status === 'FAILED'
|
|
134
|
+
? 'empty'
|
|
135
|
+
: 'validating';
|
|
136
|
+
return Object.assign(Object.assign({}, d), { status, fileName: match.fileName || d.fileName });
|
|
137
|
+
}));
|
|
138
|
+
if (Object.keys(newParsed).length > 0) {
|
|
139
|
+
setParsedDataMap((prev) => (Object.assign(Object.assign({}, prev), newParsed)));
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
.catch((err) => console.error('[TaxAxisDocuments] Failed to fetch uploaded documents:', err));
|
|
143
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
144
|
+
// Open native file picker for the target document slot
|
|
108
145
|
const handleUpload = (idx) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
146
|
+
if (!onUploadDocument) {
|
|
147
|
+
console.warn('[TaxAxisDocuments] No onUploadDocument handler provided');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
uploadTargetIdx.current = idx;
|
|
151
|
+
if (fileInputRef.current) {
|
|
152
|
+
const doc = docs[idx];
|
|
153
|
+
fileInputRef.current.accept = '.pdf,.doc,.docx,.xlsx,.xls,.csv';
|
|
154
|
+
fileInputRef.current.value = '';
|
|
155
|
+
fileInputRef.current.click();
|
|
156
|
+
}
|
|
112
157
|
};
|
|
158
|
+
// Handle file selection from the native picker
|
|
159
|
+
const handleFileSelected = (0, react_1.useCallback)((e) => __awaiter(this, void 0, void 0, function* () {
|
|
160
|
+
var _a;
|
|
161
|
+
const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
162
|
+
const idx = uploadTargetIdx.current;
|
|
163
|
+
if (!file || idx < 0 || !onUploadDocument)
|
|
164
|
+
return;
|
|
165
|
+
const doc = docs[idx];
|
|
166
|
+
// Mark as validating immediately
|
|
167
|
+
setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: 'validating', fileName: file.name }) : d));
|
|
168
|
+
try {
|
|
169
|
+
const result = yield onUploadDocument({ id: doc.id }, file);
|
|
170
|
+
if (result === null || result === void 0 ? void 0 : result.documentId) {
|
|
171
|
+
documentIdMap.current[doc.id] = result.documentId;
|
|
172
|
+
}
|
|
173
|
+
if (result === null || result === void 0 ? void 0 : result.parsedData) {
|
|
174
|
+
setParsedDataMap((prev) => (Object.assign(Object.assign({}, prev), { [doc.id]: result.parsedData })));
|
|
175
|
+
}
|
|
176
|
+
setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: 'valid', fileName: file.name }) : d));
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
console.error('[TaxAxisDocuments] Upload failed:', err);
|
|
180
|
+
setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: 'empty', fileName: null }) : d));
|
|
181
|
+
}
|
|
182
|
+
}), [docs, onUploadDocument]);
|
|
113
183
|
const handleClear = (idx) => {
|
|
184
|
+
const doc = docs[idx];
|
|
185
|
+
const backendDocId = documentIdMap.current[doc.id];
|
|
186
|
+
if (backendDocId && onDeleteDocument) {
|
|
187
|
+
onDeleteDocument(backendDocId).catch((err) => console.error('[TaxAxisDocuments] Delete failed:', err));
|
|
188
|
+
delete documentIdMap.current[doc.id];
|
|
189
|
+
}
|
|
190
|
+
setParsedDataMap((prev) => {
|
|
191
|
+
const next = Object.assign({}, prev);
|
|
192
|
+
delete next[doc.id];
|
|
193
|
+
return next;
|
|
194
|
+
});
|
|
114
195
|
setDocs((prev) => prev.map((d, i) => i === idx
|
|
115
196
|
? Object.assign(Object.assign({}, d), { status: "empty", fileName: null }) : d));
|
|
116
197
|
};
|
|
198
|
+
// Compute real field counts from parsed data
|
|
199
|
+
const fieldCounts = (0, react_1.useMemo)(() => {
|
|
200
|
+
const counts = {};
|
|
201
|
+
for (const doc of docs) {
|
|
202
|
+
const pd = parsedDataMap[doc.id];
|
|
203
|
+
const fields = pd === null || pd === void 0 ? void 0 : pd.fields;
|
|
204
|
+
if (fields && typeof fields === 'object') {
|
|
205
|
+
counts[doc.id] = Object.keys(fields)
|
|
206
|
+
.filter((k) => !k.startsWith('_')).length;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return counts;
|
|
210
|
+
}, [docs, parsedDataMap]);
|
|
117
211
|
const validCount = docs.filter((d) => d.status === "valid").length;
|
|
118
212
|
const requiredCount = docs.filter((d) => d.required === true).length;
|
|
119
213
|
const requiredValid = docs.filter((d) => d.required === true && d.status === "valid").length;
|
|
@@ -166,11 +260,12 @@ function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userConte
|
|
|
166
260
|
coveragePct,
|
|
167
261
|
"% coverage"),
|
|
168
262
|
validCount < docs.length && (react_1.default.createElement("span", { className: "text-[10px] text-tax-axis-text-4 font-tax-axis-body" }, "Upload more documents to increase strategy coverage")))),
|
|
169
|
-
TIER_DEFS.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: HELP_OVERRIDES, fieldCounts:
|
|
263
|
+
TIER_DEFS.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: HELP_OVERRIDES, fieldCounts: fieldCounts, onUpload: handleUpload, onClear: handleClear, onReview: (docId) => setReviewDocId(docId) }))),
|
|
170
264
|
react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
|
|
171
265
|
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
|
|
172
266
|
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onContinue, disabled: requiredCount > requiredValid, className: "flex-1" }, requiredCount > requiredValid
|
|
173
267
|
? `Upload ${requiredCount - requiredValid} more required doc${requiredCount - requiredValid > 1 ? "s" : ""}`
|
|
174
268
|
: "Run Analysis")),
|
|
175
|
-
|
|
269
|
+
react_1.default.createElement("input", { ref: fileInputRef, type: "file", style: { display: 'none' }, onChange: handleFileSelected }),
|
|
270
|
+
reviewDoc && (react_1.default.createElement(DocumentReviewModal_1.DocumentReviewModal, { documentId: documentIdMap.current[reviewDoc.id] || reviewDoc.id, documentName: reviewDoc.name, fileName: reviewDoc.fileName || "", jobId: jobId || "", onClose: () => setReviewDocId(null), parsedData: parsedDataMap[reviewDoc.id] || null }))));
|
|
176
271
|
}
|