@paro.io/expert-shared-components 1.14.66 → 1.14.67
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/components/DocumentCenter/MultiFileUploadSection.js +220 -121
- package/lib/components/TaxAxis/TaxAxisApi.d.ts +0 -1
- package/lib/components/TaxAxis/TaxAxisShell.js +5 -80
- package/lib/components/shared/UploadClient.d.ts +2 -1
- package/lib/components/shared/UploadClient.js +6 -2
- package/lib/index.d.ts +1 -13
- package/lib/index.js +1 -27
- package/lib/tax-axis/components/clientReport/ExecutiveSummary.d.ts +4 -1
- package/lib/tax-axis/components/clientReport/ExecutiveSummary.js +10 -6
- package/lib/tax-axis/components/clientReport/Methodology.js +2 -2
- package/lib/tax-axis/components/clientReport/RecommendedStrategies.d.ts +6 -1
- package/lib/tax-axis/components/clientReport/RecommendedStrategies.js +26 -24
- package/lib/tax-axis/components/clientReport/StrategyCard.d.ts +1 -1
- package/lib/tax-axis/components/clientReport/StrategyCard.js +39 -23
- package/lib/tax-axis/components/clientReport/TaxAxisClientReport.d.ts +5 -2
- package/lib/tax-axis/components/clientReport/TaxAxisClientReport.js +9 -7
- package/lib/tax-axis/components/dashboard/DashboardActions.js +6 -5
- package/lib/tax-axis/components/dashboard/DashboardSummary.d.ts +6 -1
- package/lib/tax-axis/components/dashboard/DashboardSummary.js +19 -10
- package/lib/tax-axis/components/dashboard/StrategyDetailPanel.d.ts +1 -1
- package/lib/tax-axis/components/dashboard/StrategyDetailPanel.js +122 -95
- package/lib/tax-axis/components/dashboard/TaxAxisDashboard.d.ts +58 -4
- package/lib/tax-axis/components/dashboard/TaxAxisDashboard.js +375 -56
- package/lib/tax-axis/components/documents/DocumentCard.d.ts +6 -3
- package/lib/tax-axis/components/documents/DocumentCard.js +72 -15
- package/lib/tax-axis/components/documents/DocumentReviewModal.d.ts +3 -2
- package/lib/tax-axis/components/documents/DocumentReviewModal.js +109 -295
- package/lib/tax-axis/components/documents/DocumentTier.d.ts +5 -4
- package/lib/tax-axis/components/documents/DocumentTier.js +2 -2
- package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +28 -8
- package/lib/tax-axis/components/documents/TaxAxisDocuments.js +335 -172
- package/lib/tax-axis/components/documents/qbo/QboAvailableReportsModal.d.ts +13 -0
- package/lib/tax-axis/components/documents/qbo/QboAvailableReportsModal.js +180 -0
- package/lib/tax-axis/components/documents/qbo/QboClientSelectorModal.d.ts +10 -0
- package/lib/tax-axis/components/documents/qbo/QboClientSelectorModal.js +155 -0
- package/lib/tax-axis/components/documents/qbo/QboConnectBanner.d.ts +9 -0
- package/lib/tax-axis/components/documents/qbo/QboConnectBanner.js +55 -0
- package/lib/tax-axis/components/documents/qbo/QboDocumentMappingModal.d.ts +10 -0
- package/lib/tax-axis/components/documents/qbo/QboDocumentMappingModal.js +202 -0
- package/lib/tax-axis/components/documents/qbo/QboImportingModal.d.ts +8 -0
- package/lib/tax-axis/components/documents/qbo/QboImportingModal.js +75 -0
- package/lib/tax-axis/components/documents/qbo/QboPermissionsModal.d.ts +8 -0
- package/lib/tax-axis/components/documents/qbo/QboPermissionsModal.js +126 -0
- package/lib/tax-axis/components/documents/qbo/index.d.ts +8 -0
- package/lib/tax-axis/components/documents/qbo/index.js +17 -0
- package/lib/tax-axis/components/documents/qbo/qboConstants.d.ts +24 -0
- package/lib/tax-axis/components/documents/qbo/qboConstants.js +71 -0
- package/lib/tax-axis/components/documents/qbo/types.d.ts +43 -0
- package/lib/tax-axis/components/documents/qbo/types.js +3 -0
- package/lib/tax-axis/components/documents/qbo/useQboFlow.d.ts +19 -0
- package/lib/tax-axis/components/documents/qbo/useQboFlow.js +207 -0
- package/lib/tax-axis/components/intake/ClientParametersSection.js +14 -30
- package/lib/tax-axis/components/intake/CpaIntakeQuestionsSection.js +3 -3
- package/lib/tax-axis/components/intake/IntakeCtaCards.d.ts +1 -2
- package/lib/tax-axis/components/intake/IntakeCtaCards.js +6 -13
- package/lib/tax-axis/components/intake/RefineAnalysisSection.js +7 -7
- package/lib/tax-axis/components/intake/TaxAxisIntake.js +7 -95
- package/lib/tax-axis/components/intake/intakeSchema.d.ts +0 -3
- package/lib/tax-axis/components/intake/intakeSchema.js +2 -4
- package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.d.ts +23 -4
- package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.js +15 -4
- package/lib/tax-axis/components/processing/TaxAxisProcessing.d.ts +2 -1
- package/lib/tax-axis/components/processing/TaxAxisProcessing.js +102 -31
- package/lib/tax-axis/components/prospectReport/ProspectPrintView.js +0 -2
- package/lib/tax-axis/components/prospectReport/ProspectStrategyCard.d.ts +1 -8
- package/lib/tax-axis/components/prospectReport/ProspectStrategyCard.js +5 -5
- package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.d.ts +1 -27
- package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.js +25 -43
- package/lib/tax-axis/index.d.ts +3 -1
- package/lib/tax-axis/index.js +4 -3
- package/lib/tax-axis/lib/adapters/useEngineOutput.d.ts +138 -13
- package/lib/tax-axis/lib/adapters/useEngineOutput.js +156 -7
- package/lib/tax-axis/lib/data/documents.d.ts +3 -2
- package/lib/tax-axis/lib/data/documents.js +225 -25
- package/lib/tax-axis/lib/data/strategies.js +9 -9
- package/lib/tax-axis/lib/documentFieldCatalog.d.ts +7 -12
- package/lib/tax-axis/lib/documentFieldCatalog.js +805 -8
- package/lib/tax-axis/lib/types/index.d.ts +13 -1
- package/package.json +1 -1
- package/lib/README.md +0 -2
- package/lib/package.json +0 -68
|
@@ -38,33 +38,13 @@ const documents_1 = require("../../lib/data/documents");
|
|
|
38
38
|
const TaxAxisButton_1 = require("../shared/TaxAxisButton");
|
|
39
39
|
const DocumentTier_1 = require("./DocumentTier");
|
|
40
40
|
const DocumentReviewModal_1 = require("./DocumentReviewModal");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
// };
|
|
57
|
-
// Short help text shown below each doc name when not yet uploaded
|
|
58
|
-
// (mock App.jsx:1384). Overrides the longer DOC_SPECS_BASE.help.
|
|
59
|
-
const HELP_OVERRIDES = {
|
|
60
|
-
"1120s": "Improves accuracy of every strategy",
|
|
61
|
-
"payroll": "Critical for Entity Structure and QBI calculations",
|
|
62
|
-
"fixed-assets": "Unlocks Cost Segregation (potential $15K\u201385K)",
|
|
63
|
-
"state-return": "If client operates in income-tax states",
|
|
64
|
-
"cashflow": "Helps with income deferral and timing strategies",
|
|
65
|
-
"prior-returns": "Improves confidence intervals (more years = tighter estimates)",
|
|
66
|
-
};
|
|
67
|
-
// Tier groupings from mock (App.jsx:1379-1383)
|
|
41
|
+
const QboConnectBanner_1 = require("./qbo/QboConnectBanner");
|
|
42
|
+
const QboPermissionsModal_1 = require("./qbo/QboPermissionsModal");
|
|
43
|
+
const QboClientSelectorModal_1 = require("./qbo/QboClientSelectorModal");
|
|
44
|
+
const QboAvailableReportsModal_1 = require("./qbo/QboAvailableReportsModal");
|
|
45
|
+
const QboDocumentMappingModal_1 = require("./qbo/QboDocumentMappingModal");
|
|
46
|
+
const QboImportingModal_1 = require("./qbo/QboImportingModal");
|
|
47
|
+
const useQboFlow_1 = require("./qbo/useQboFlow");
|
|
68
48
|
const TIER_DEFS = [
|
|
69
49
|
{
|
|
70
50
|
key: "required",
|
|
@@ -74,7 +54,7 @@ const TIER_DEFS = [
|
|
|
74
54
|
labelColor: "#C53030",
|
|
75
55
|
badgeColor: "red",
|
|
76
56
|
badgeText: "REQUIRED",
|
|
77
|
-
ids: [
|
|
57
|
+
ids: [],
|
|
78
58
|
},
|
|
79
59
|
{
|
|
80
60
|
key: "recommended",
|
|
@@ -84,7 +64,7 @@ const TIER_DEFS = [
|
|
|
84
64
|
labelColor: "#FB9A1D",
|
|
85
65
|
badgeColor: "orange",
|
|
86
66
|
badgeText: "RECOMMENDED",
|
|
87
|
-
ids: [
|
|
67
|
+
ids: [],
|
|
88
68
|
},
|
|
89
69
|
{
|
|
90
70
|
key: "conditional",
|
|
@@ -94,178 +74,361 @@ const TIER_DEFS = [
|
|
|
94
74
|
labelColor: "#C8CCE5",
|
|
95
75
|
badgeColor: "neutral",
|
|
96
76
|
badgeText: "CONDITIONAL",
|
|
97
|
-
ids: [
|
|
77
|
+
ids: [],
|
|
98
78
|
},
|
|
99
79
|
];
|
|
100
|
-
function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, jobId, userContext: _userContext = "expert", }) {
|
|
101
|
-
var _a;
|
|
102
|
-
const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(
|
|
103
|
-
|
|
104
|
-
|
|
80
|
+
function TaxAxisDocuments({ profile, entityType, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, parsedFieldCounts, jobId = "stub-job-id", onSaveReviewedField, userContext: _userContext = "expert", qboConnected = false, qboCompanyName, qboAuthorizeUrl, onImportQboReport, onQboConnected, onQboDisconnected, onQboImport, qboClientConfirmed, }) {
|
|
81
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
82
|
+
const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(entityType !== null && entityType !== void 0 ? entityType : undefined), [entityType]);
|
|
83
|
+
// Build tier defs dynamically from the doc spec list so they're always in sync.
|
|
84
|
+
const tierDefs = (0, react_1.useMemo)(() => {
|
|
85
|
+
return TIER_DEFS.map((def) => (Object.assign(Object.assign({}, def), { ids: docSpecs
|
|
86
|
+
.filter((s) => s.tier === def.key)
|
|
87
|
+
.map((s) => s.id) })));
|
|
88
|
+
}, [docSpecs]);
|
|
89
|
+
const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null, parseError: null }))));
|
|
90
|
+
const [uploadedDocIds, setUploadedDocIds] = (0, react_1.useState)({});
|
|
91
|
+
const [continueError, setContinueError] = (0, react_1.useState)(null);
|
|
105
92
|
const [reviewDocId, setReviewDocId] = (0, react_1.useState)(null);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
// parsedData keyed by documentType (e.g. "profit_loss") — populated from poll results
|
|
94
|
+
const [parsedDataByDocType, setParsedDataByDocType] = (0, react_1.useState)({});
|
|
95
|
+
// reviewedData keyed by documentType — populated after CPA saves corrections; takes priority over parsedData in modal
|
|
96
|
+
const [reviewedDataByDocType, setReviewedDataByDocType] = (0, react_1.useState)({});
|
|
97
|
+
// FIX A: handler for QBO import completion — updates doc card state and refreshes parsedData
|
|
98
|
+
const handleQboImportComplete = (0, react_1.useCallback)((mappedDocs) => {
|
|
99
|
+
setDocs((prev) => prev.map((d) => {
|
|
100
|
+
const match = mappedDocs.find((m) => m.slot === (d.documentType || d.id));
|
|
101
|
+
if (!match)
|
|
102
|
+
return d;
|
|
103
|
+
return Object.assign(Object.assign({}, d), { status: "valid", qboSource: true, fileName: match.fileName, parseError: null });
|
|
104
|
+
}));
|
|
105
|
+
// Refresh parsedData so the review modal has data immediately after import
|
|
106
|
+
if (fetchUploadedDocuments) {
|
|
107
|
+
fetchUploadedDocuments().then((uploaded) => {
|
|
108
|
+
const parsedByType = {};
|
|
109
|
+
for (const doc of uploaded) {
|
|
110
|
+
if (doc.documentType && doc.parsedData) {
|
|
111
|
+
parsedByType[doc.documentType] = doc.parsedData;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (Object.keys(parsedByType).length > 0) {
|
|
115
|
+
setParsedDataByDocType((prev) => (Object.assign(Object.assign({}, prev), parsedByType)));
|
|
116
|
+
}
|
|
117
|
+
}).catch(() => { });
|
|
118
|
+
}
|
|
119
|
+
if (onQboImport)
|
|
120
|
+
onQboImport(mappedDocs);
|
|
121
|
+
}, [onQboImport, fetchUploadedDocuments]);
|
|
122
|
+
// QBO connect flow
|
|
123
|
+
const qboFlow = (0, useQboFlow_1.useQboFlow)({
|
|
124
|
+
qboConnected,
|
|
125
|
+
qboCompanyName,
|
|
126
|
+
qboAuthorizeUrl,
|
|
127
|
+
onImportQboReport,
|
|
128
|
+
fetchUploadedDocuments,
|
|
129
|
+
sessionId: jobId,
|
|
130
|
+
profileYear: (profile === null || profile === void 0 ? void 0 : profile.year) ? Number(profile.year) : undefined,
|
|
131
|
+
onQboConnected,
|
|
132
|
+
onQboDisconnected,
|
|
133
|
+
onQboImportComplete: handleQboImportComplete,
|
|
134
|
+
});
|
|
135
|
+
const showQboBanner = !!qboAuthorizeUrl || qboConnected;
|
|
136
|
+
// FIX B: gate document list behind QBO client confirmation or manual skip
|
|
137
|
+
const [clientConfirmed, setClientConfirmed] = (0, react_1.useState)(!!(qboClientConfirmed || qboConnected || !showQboBanner));
|
|
138
|
+
// Sync when qboConnected prop updates (e.g. localStorage restore in parent)
|
|
139
|
+
(0, react_1.useEffect)(() => {
|
|
140
|
+
if (qboConnected)
|
|
141
|
+
setClientConfirmed(true);
|
|
142
|
+
}, [qboConnected]);
|
|
143
|
+
// On mount, restore card state from any already-uploaded documents in this session.
|
|
144
|
+
// This runs whenever the user re-enters the DOCUMENT_UPLOAD step (e.g. after an
|
|
145
|
+
// eval failure) so previously uploaded files appear as valid/failed instead of empty.
|
|
115
146
|
(0, react_1.useEffect)(() => {
|
|
116
147
|
if (!fetchUploadedDocuments)
|
|
117
148
|
return;
|
|
118
|
-
fetchUploadedDocuments()
|
|
119
|
-
|
|
120
|
-
if (!uploaded || uploaded.length === 0)
|
|
149
|
+
fetchUploadedDocuments().then((uploaded) => {
|
|
150
|
+
if (!uploaded.length)
|
|
121
151
|
return;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
152
|
+
const parsedByType = {};
|
|
153
|
+
const reviewedByType = {};
|
|
154
|
+
const nextDocIds = {};
|
|
155
|
+
setDocs((prev) => prev.map((docState, idx) => {
|
|
156
|
+
// Match by documentType (canonical key) — find the most recently updated upload
|
|
157
|
+
const candidates = uploaded.filter((u) => String(u.documentType || '').toLowerCase() === (docState.documentType || docState.id).toLowerCase());
|
|
158
|
+
const match = candidates.sort((a, b) => new Date(String(b.updatedAt || 0)).getTime() - new Date(String(a.updatedAt || 0)).getTime())[0];
|
|
125
159
|
if (!match)
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
? '
|
|
135
|
-
|
|
136
|
-
return Object.assign(Object.assign({},
|
|
160
|
+
return docState;
|
|
161
|
+
// Populate uploadedDocIds for delete/review actions
|
|
162
|
+
if (match.documentId) {
|
|
163
|
+
nextDocIds[idx] = match.documentId;
|
|
164
|
+
}
|
|
165
|
+
const apiStatus = String(match.status || '').toUpperCase();
|
|
166
|
+
const nextStatus = apiStatus === 'PARSED' ? 'valid' :
|
|
167
|
+
apiStatus === 'FAILED' ? 'failed' :
|
|
168
|
+
apiStatus === 'PARSING' || apiStatus === 'UPLOADED' ? 'parsing' :
|
|
169
|
+
docState.status;
|
|
170
|
+
return Object.assign(Object.assign({}, docState), { status: nextStatus, fileName: match.fileName || docState.fileName, parseError: match.parseError || null });
|
|
137
171
|
}));
|
|
138
|
-
|
|
139
|
-
|
|
172
|
+
// Populate document ID map in one batch
|
|
173
|
+
setUploadedDocIds((prev) => (Object.assign(Object.assign({}, prev), nextDocIds)));
|
|
174
|
+
// Populate review modal data
|
|
175
|
+
for (const doc of uploaded) {
|
|
176
|
+
if (!doc.documentType)
|
|
177
|
+
continue;
|
|
178
|
+
if (doc.parsedData)
|
|
179
|
+
parsedByType[doc.documentType] = doc.parsedData;
|
|
180
|
+
if (doc.reviewedData && Object.keys(doc.reviewedData).length > 0) {
|
|
181
|
+
reviewedByType[doc.documentType] = doc.reviewedData;
|
|
182
|
+
}
|
|
140
183
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
184
|
+
if (Object.keys(parsedByType).length > 0)
|
|
185
|
+
setParsedDataByDocType(parsedByType);
|
|
186
|
+
if (Object.keys(reviewedByType).length > 0)
|
|
187
|
+
setReviewedDataByDocType(reviewedByType);
|
|
188
|
+
}).catch(() => { });
|
|
189
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
190
|
+
}, []);
|
|
191
|
+
const reviewDoc = reviewDocId
|
|
192
|
+
? (_a = docs.find((d) => d.id === reviewDocId)) !== null && _a !== void 0 ? _a : null
|
|
193
|
+
: null;
|
|
194
|
+
// Prefer reviewedData (CPA-corrected) over parsedData (raw LLM extraction)
|
|
195
|
+
const reviewParsedData = reviewDoc
|
|
196
|
+
? ((_e = (_d = (_c = (_b = reviewedDataByDocType[reviewDoc.documentType || reviewDoc.id]) !== null && _b !== void 0 ? _b : reviewedDataByDocType[reviewDoc.id]) !== null && _c !== void 0 ? _c : parsedDataByDocType[reviewDoc.documentType || reviewDoc.id]) !== null && _d !== void 0 ? _d : parsedDataByDocType[reviewDoc.id]) !== null && _e !== void 0 ? _e : null)
|
|
197
|
+
: null;
|
|
198
|
+
const pollForParseStatus = (idx, fileName, documentType) => __awaiter(this, void 0, void 0, function* () {
|
|
199
|
+
if (!fetchUploadedDocuments)
|
|
148
200
|
return;
|
|
201
|
+
const maxAttempts = 80;
|
|
202
|
+
const pollIntervalMs = 1500;
|
|
203
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
204
|
+
try {
|
|
205
|
+
const uploaded = yield fetchUploadedDocuments();
|
|
206
|
+
// Match by documentType first (exact lowercase key), fall back to fileName.
|
|
207
|
+
const typeMatch = uploaded.filter((doc) => doc.fileName === fileName &&
|
|
208
|
+
String(doc.documentType || "").toLowerCase() === documentType.toLowerCase());
|
|
209
|
+
const nameMatch = uploaded.filter((doc) => doc.fileName === fileName);
|
|
210
|
+
const candidates = typeMatch.length > 0 ? typeMatch : nameMatch;
|
|
211
|
+
const match = candidates
|
|
212
|
+
.slice()
|
|
213
|
+
.sort((a, b) => new Date(String(b.updatedAt || "")).getTime() -
|
|
214
|
+
new Date(String(a.updatedAt || "")).getTime())[0];
|
|
215
|
+
if (match) {
|
|
216
|
+
const status = String(match.status || "").toUpperCase();
|
|
217
|
+
if (status === "FAILED") {
|
|
218
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
219
|
+
? Object.assign(Object.assign({}, d), { status: "failed", fileName, parseError: match.parseError || "Parsing failed. Try a different file." }) : d));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (status === "PARSED") {
|
|
223
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
224
|
+
? Object.assign(Object.assign({}, d), { status: "valid", fileName, parseError: null }) : d));
|
|
225
|
+
if (match.parsedData && documentType) {
|
|
226
|
+
setParsedDataByDocType((prev) => (Object.assign(Object.assign({}, prev), { [documentType]: match.parsedData })));
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (status === "PARSING" || status === "UPLOADED") {
|
|
231
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
232
|
+
? Object.assign(Object.assign({}, d), { status: "parsing", fileName }) : d));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (_pollError) {
|
|
237
|
+
// keep polling
|
|
238
|
+
}
|
|
239
|
+
yield new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
149
240
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
fileInputRef.current.click();
|
|
156
|
-
}
|
|
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)
|
|
241
|
+
setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "failed", fileName, parseError: "Parsing timed out. Please try again." }) : d));
|
|
242
|
+
});
|
|
243
|
+
const handleUpload = (idx, file) => __awaiter(this, void 0, void 0, function* () {
|
|
244
|
+
const selectedDoc = docs[idx];
|
|
245
|
+
if (!selectedDoc)
|
|
164
246
|
return;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
247
|
+
setContinueError(null);
|
|
248
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
249
|
+
? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name, parseError: null }) : d));
|
|
168
250
|
try {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
251
|
+
const uploadResult = onUploadDocument
|
|
252
|
+
? yield onUploadDocument(selectedDoc, file)
|
|
253
|
+
: undefined;
|
|
254
|
+
const nextFileName = file.name;
|
|
255
|
+
// Use the canonical documentType from the catalog entry.
|
|
256
|
+
const documentType = selectedDoc.documentType;
|
|
257
|
+
const immediateStatus = String((uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.status) || "").toUpperCase();
|
|
258
|
+
if (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.documentId) {
|
|
259
|
+
setUploadedDocIds((prev) => (Object.assign(Object.assign({}, prev), { [idx]: uploadResult.documentId })));
|
|
260
|
+
}
|
|
261
|
+
if (immediateStatus === "PARSED") {
|
|
262
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
263
|
+
? Object.assign(Object.assign({}, d), { status: "valid", fileName: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.fileName) || nextFileName, parseError: null }) : d));
|
|
264
|
+
// parsedData is not on the upload mutation response — fetch it now via one poll call.
|
|
265
|
+
if (fetchUploadedDocuments && documentType) {
|
|
266
|
+
try {
|
|
267
|
+
const uploaded = yield fetchUploadedDocuments();
|
|
268
|
+
const match = uploaded.find((d) => String(d.documentType || "").toLowerCase() === documentType.toLowerCase());
|
|
269
|
+
if (match === null || match === void 0 ? void 0 : match.parsedData) {
|
|
270
|
+
setParsedDataByDocType((prev) => (Object.assign(Object.assign({}, prev), { [documentType]: match.parsedData })));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (_a) {
|
|
274
|
+
// non-fatal — user can still open modal, will show empty state
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
172
278
|
}
|
|
173
|
-
if (
|
|
174
|
-
|
|
279
|
+
if (immediateStatus === "FAILED") {
|
|
280
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
281
|
+
? 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));
|
|
282
|
+
return;
|
|
175
283
|
}
|
|
176
|
-
setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status:
|
|
284
|
+
setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "parsing", fileName: nextFileName }) : d));
|
|
285
|
+
yield pollForParseStatus(idx, nextFileName, documentType);
|
|
177
286
|
}
|
|
178
|
-
catch (
|
|
179
|
-
|
|
180
|
-
|
|
287
|
+
catch (uploadError) {
|
|
288
|
+
const errorMessage = uploadError instanceof Error
|
|
289
|
+
? uploadError.message
|
|
290
|
+
: "Upload failed. Please try again.";
|
|
291
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
292
|
+
? Object.assign(Object.assign({}, d), { status: "failed", parseError: errorMessage }) : d));
|
|
181
293
|
}
|
|
182
|
-
})
|
|
294
|
+
});
|
|
183
295
|
const handleClear = (idx) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
296
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
297
|
+
? Object.assign(Object.assign({}, d), { status: "empty", fileName: null, parseError: null }) : d));
|
|
298
|
+
setContinueError(null);
|
|
299
|
+
};
|
|
300
|
+
const handleRemove = (idx) => __awaiter(this, void 0, void 0, function* () {
|
|
301
|
+
const docId = uploadedDocIds[idx];
|
|
302
|
+
if (docId && onDeleteDocument) {
|
|
303
|
+
try {
|
|
304
|
+
yield onDeleteDocument(docId);
|
|
305
|
+
}
|
|
306
|
+
catch (_deleteError) {
|
|
307
|
+
// clear from UI even if backend delete fails
|
|
308
|
+
}
|
|
189
309
|
}
|
|
190
|
-
|
|
310
|
+
setUploadedDocIds((prev) => {
|
|
191
311
|
const next = Object.assign({}, prev);
|
|
192
|
-
delete next[
|
|
312
|
+
delete next[idx];
|
|
193
313
|
return next;
|
|
194
314
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
}
|
|
315
|
+
handleClear(idx);
|
|
316
|
+
});
|
|
317
|
+
const handleContinue = () => __awaiter(this, void 0, void 0, function* () {
|
|
318
|
+
const failedDocs = docs.filter((d) => d.status === "failed");
|
|
319
|
+
if (failedDocs.length > 0) {
|
|
320
|
+
const names = failedDocs.map((d) => d.name).join(", ");
|
|
321
|
+
setContinueError(`Cannot continue — ${failedDocs.length === 1 ? "this document has" : "these documents have"} errors: ${names}. Please re-upload correct files or remove them.`);
|
|
322
|
+
return;
|
|
208
323
|
}
|
|
209
|
-
|
|
210
|
-
|
|
324
|
+
setContinueError(null);
|
|
325
|
+
// For every parsed doc that was not manually reviewed, persist parsedData.fields
|
|
326
|
+
// as reviewedData so the engine always has a reviewedData record to read from.
|
|
327
|
+
if (onSaveReviewedField) {
|
|
328
|
+
const validDocs = docs.filter((d) => d.status === "valid");
|
|
329
|
+
yield Promise.all(validDocs.map((doc) => __awaiter(this, void 0, void 0, function* () {
|
|
330
|
+
var _a;
|
|
331
|
+
const idx = docs.indexOf(doc);
|
|
332
|
+
const documentId = uploadedDocIds[idx];
|
|
333
|
+
if (!documentId)
|
|
334
|
+
return;
|
|
335
|
+
const parsed = parsedDataByDocType[doc.documentType || doc.id];
|
|
336
|
+
if (!parsed)
|
|
337
|
+
return;
|
|
338
|
+
const fields = ((_a = parsed.fields) !== null && _a !== void 0 ? _a : parsed);
|
|
339
|
+
// Convert all scalar values to strings for reviewedData
|
|
340
|
+
const reviewedFields = {};
|
|
341
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
342
|
+
if (v !== null && v !== undefined && typeof v !== "object") {
|
|
343
|
+
reviewedFields[k] = v;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (Object.keys(reviewedFields).length > 0) {
|
|
347
|
+
try {
|
|
348
|
+
yield onSaveReviewedField(documentId, reviewedFields);
|
|
349
|
+
}
|
|
350
|
+
catch ( /* non-fatal */_b) { /* non-fatal */ }
|
|
351
|
+
}
|
|
352
|
+
})));
|
|
353
|
+
}
|
|
354
|
+
onContinue();
|
|
355
|
+
});
|
|
211
356
|
const validCount = docs.filter((d) => d.status === "valid").length;
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
: validCount === 2
|
|
220
|
-
? 60
|
|
221
|
-
: validCount <= 4
|
|
222
|
-
? 75
|
|
223
|
-
: validCount <= 6
|
|
224
|
-
? 90
|
|
357
|
+
const failedCount = docs.filter((d) => d.status === "failed").length;
|
|
358
|
+
const busyCount = docs.filter((d) => d.status === "validating" || d.status === "parsing").length;
|
|
359
|
+
const coveragePct = validCount === 0 ? 32
|
|
360
|
+
: validCount === 1 ? 50
|
|
361
|
+
: validCount === 2 ? 60
|
|
362
|
+
: validCount <= 4 ? 75
|
|
363
|
+
: validCount <= 6 ? 90
|
|
225
364
|
: 100;
|
|
226
365
|
const strategiesCovered = Math.round((25 * coveragePct) / 100);
|
|
227
366
|
return (react_1.default.createElement("div", { className: "max-w-[580px] mx-auto" },
|
|
228
367
|
react_1.default.createElement("div", { className: "text-center mb-5 pt-5" },
|
|
229
368
|
react_1.default.createElement("h1", { className: "text-[28px] font-bold text-white font-tax-axis-head mb-1.5" }, "Source Documents"),
|
|
230
369
|
react_1.default.createElement("p", { className: "text-sm text-tax-axis-text-2 font-tax-axis-body" }, "Upload documents to run the full analysis.")),
|
|
231
|
-
react_1.default.createElement(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
370
|
+
showQboBanner && (react_1.default.createElement(QboConnectBanner_1.QboConnectBanner, { connected: qboConnected, companyName: qboCompanyName, onConnect: qboConnected ? qboFlow.openImport : qboFlow.openPermissions, onDisconnect: qboFlow.handleDisconnect })),
|
|
371
|
+
showQboBanner && !clientConfirmed && (react_1.default.createElement("div", { className: "text-center mt-3 mb-4" },
|
|
372
|
+
react_1.default.createElement("button", { onClick: () => setClientConfirmed(true), className: "text-[12px] text-tax-axis-text-3 font-tax-axis-body bg-transparent border-none cursor-pointer", style: { textDecoration: "underline", textUnderlineOffset: "2px" } }, "Skip and upload manually"))),
|
|
373
|
+
clientConfirmed && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
374
|
+
react_1.default.createElement("div", { className: "py-3.5 px-[18px] mb-4 text-xs leading-relaxed font-tax-axis-body", style: {
|
|
375
|
+
background: "#171B44",
|
|
376
|
+
borderLeft: "3px solid #248384",
|
|
377
|
+
borderRadius: "0 8px 8px 0",
|
|
378
|
+
color: "#E6E8F5",
|
|
379
|
+
} }, "The minimum to run an analysis is the current-year P&L and Balance Sheet. However, accuracy and strategy coverage improve significantly with additional documents \u2014 particularly the federal tax return, fixed asset schedule, and W-2 forms."),
|
|
380
|
+
react_1.default.createElement("div", { className: "bg-tax-axis-surface border border-tax-axis-border rounded-[10px] px-[18px] py-3.5 mb-4" },
|
|
381
|
+
react_1.default.createElement("div", { className: "text-[10px] font-bold text-tax-axis-text-3 uppercase tracking-widest mb-2.5 font-tax-axis-mono" }, "Analysis Coverage"),
|
|
382
|
+
react_1.default.createElement("div", { className: "flex flex-col gap-1 sm:flex-row sm:justify-between mb-1.5" },
|
|
383
|
+
react_1.default.createElement("span", { className: "text-xs text-tax-axis-text-2 font-tax-axis-body" },
|
|
384
|
+
"Documents uploaded:",
|
|
385
|
+
" ",
|
|
386
|
+
react_1.default.createElement("strong", { className: "text-white" },
|
|
387
|
+
validCount,
|
|
388
|
+
" / ",
|
|
389
|
+
docs.length)),
|
|
390
|
+
react_1.default.createElement("span", { className: "text-xs text-tax-axis-text-2 font-tax-axis-body" },
|
|
391
|
+
"Strategies covered:",
|
|
392
|
+
" ",
|
|
393
|
+
react_1.default.createElement("strong", { className: "text-tax-axis-teal-light" },
|
|
394
|
+
strategiesCovered,
|
|
395
|
+
" / 25"))),
|
|
396
|
+
react_1.default.createElement("div", { className: "h-1.5 bg-tax-axis-surface-2 rounded-sm overflow-hidden mb-1.5" },
|
|
397
|
+
react_1.default.createElement("div", { className: "h-full rounded-sm transition-[width] duration-[400ms]", style: {
|
|
398
|
+
width: `${coveragePct}%`,
|
|
399
|
+
background: "linear-gradient(90deg, #248384, #A1E5E6)",
|
|
400
|
+
} })),
|
|
401
|
+
react_1.default.createElement("div", { className: "flex flex-col gap-1 sm:flex-row sm:justify-between" },
|
|
402
|
+
react_1.default.createElement("span", { className: "text-[10px] text-tax-axis-text-4 font-tax-axis-mono" },
|
|
403
|
+
coveragePct,
|
|
404
|
+
"% coverage"),
|
|
405
|
+
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")))),
|
|
406
|
+
continueError && (react_1.default.createElement("div", { className: "py-3 px-4 mb-4 text-xs leading-relaxed font-tax-axis-body rounded-lg", style: {
|
|
407
|
+
background: "rgba(197,48,48,0.08)",
|
|
408
|
+
border: "1px solid rgba(197,48,48,0.3)",
|
|
409
|
+
color: "#FEB2B2",
|
|
410
|
+
} }, continueError)),
|
|
411
|
+
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) }))),
|
|
412
|
+
react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
|
|
413
|
+
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
|
|
414
|
+
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: handleContinue, className: "flex-1", disabled: busyCount > 0 }, busyCount > 0
|
|
415
|
+
? "Processing..."
|
|
416
|
+
: failedCount > 0
|
|
417
|
+
? `Continue (${failedCount} failed)`
|
|
418
|
+
: "Continue")))),
|
|
419
|
+
reviewDoc && (react_1.default.createElement(DocumentReviewModal_1.DocumentReviewModal, { documentId: reviewDoc.id, documentName: reviewDoc.name, fileName: reviewDoc.fileName || "", jobId: jobId, parsedData: reviewParsedData, onSaveReviewedData: onSaveReviewedField
|
|
420
|
+
? (fields) => __awaiter(this, void 0, void 0, function* () {
|
|
421
|
+
const docId = uploadedDocIds[docs.indexOf(reviewDoc)];
|
|
422
|
+
if (docId)
|
|
423
|
+
yield onSaveReviewedField(docId, fields);
|
|
424
|
+
// Store locally so reopening the modal shows corrected values
|
|
425
|
+
const docTypeKey = reviewDoc.documentType || reviewDoc.id;
|
|
426
|
+
setReviewedDataByDocType((prev) => (Object.assign(Object.assign({}, prev), { [docTypeKey]: fields })));
|
|
427
|
+
})
|
|
428
|
+
: undefined, onClose: () => setReviewDocId(null) })),
|
|
429
|
+
react_1.default.createElement(QboPermissionsModal_1.QboPermissionsModal, { open: qboFlow.modalStep === "permissions", onCancel: qboFlow.closeModal, onContinue: qboFlow.handlePermissionsContinue }),
|
|
430
|
+
react_1.default.createElement(QboClientSelectorModal_1.QboClientSelectorModal, { open: qboFlow.modalStep === "client_selector", companies: qboFlow.companies, onCancel: qboFlow.closeModal, onSelect: qboFlow.handleCompanySelected }),
|
|
431
|
+
react_1.default.createElement(QboAvailableReportsModal_1.QboAvailableReportsModal, { open: qboFlow.modalStep === "available_reports", companyName: ((_f = qboFlow.selectedCompany) === null || _f === void 0 ? void 0 : _f.name) || "", selectedYear: qboFlow.selectedYear, onYearChange: qboFlow.setSelectedYear, onCancel: qboFlow.closeModal, onConfirm: qboFlow.handleReportsConfirmed }),
|
|
432
|
+
react_1.default.createElement(QboDocumentMappingModal_1.QboDocumentMappingModal, { open: qboFlow.modalStep === "mapping", companyName: ((_g = qboFlow.selectedCompany) === null || _g === void 0 ? void 0 : _g.name) || "", selectedReports: qboFlow.selectedReports, onCancel: qboFlow.closeModal, onConfirm: qboFlow.handleMappingConfirm }),
|
|
433
|
+
react_1.default.createElement(QboImportingModal_1.QboImportingModal, { open: qboFlow.modalStep === "importing", currentStepIndex: qboFlow.importStepIndex, companyName: ((_h = qboFlow.selectedCompany) === null || _h === void 0 ? void 0 : _h.name) || "" })));
|
|
271
434
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { QboReportOption } from "./types";
|
|
3
|
+
interface QboAvailableReportsModalProps {
|
|
4
|
+
open: boolean;
|
|
5
|
+
companyName: string;
|
|
6
|
+
reports?: QboReportOption[];
|
|
7
|
+
selectedYear?: number;
|
|
8
|
+
onYearChange?: (year: number) => void;
|
|
9
|
+
onCancel: () => void;
|
|
10
|
+
onConfirm: (selectedReportIds: string[]) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function QboAvailableReportsModal({ open, companyName, reports, selectedYear, onYearChange, onCancel, onConfirm, }: QboAvailableReportsModalProps): React.JSX.Element | null;
|
|
13
|
+
export {};
|