@paro.io/expert-shared-components 1.14.65 → 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.
Files changed (81) hide show
  1. package/lib/components/DocumentCenter/MultiFileUploadSection.js +220 -121
  2. package/lib/components/TaxAxis/TaxAxisApi.d.ts +0 -1
  3. package/lib/components/TaxAxis/TaxAxisShell.js +5 -80
  4. package/lib/components/shared/UploadClient.d.ts +2 -1
  5. package/lib/components/shared/UploadClient.js +6 -2
  6. package/lib/index.d.ts +1 -13
  7. package/lib/index.js +1 -27
  8. package/lib/tax-axis/components/clientReport/ExecutiveSummary.d.ts +4 -1
  9. package/lib/tax-axis/components/clientReport/ExecutiveSummary.js +10 -6
  10. package/lib/tax-axis/components/clientReport/Methodology.js +2 -2
  11. package/lib/tax-axis/components/clientReport/RecommendedStrategies.d.ts +6 -1
  12. package/lib/tax-axis/components/clientReport/RecommendedStrategies.js +26 -24
  13. package/lib/tax-axis/components/clientReport/StrategyCard.d.ts +1 -1
  14. package/lib/tax-axis/components/clientReport/StrategyCard.js +39 -23
  15. package/lib/tax-axis/components/clientReport/TaxAxisClientReport.d.ts +5 -2
  16. package/lib/tax-axis/components/clientReport/TaxAxisClientReport.js +9 -7
  17. package/lib/tax-axis/components/dashboard/DashboardActions.js +6 -5
  18. package/lib/tax-axis/components/dashboard/DashboardSummary.d.ts +6 -1
  19. package/lib/tax-axis/components/dashboard/DashboardSummary.js +19 -10
  20. package/lib/tax-axis/components/dashboard/StrategyDetailPanel.d.ts +1 -1
  21. package/lib/tax-axis/components/dashboard/StrategyDetailPanel.js +122 -95
  22. package/lib/tax-axis/components/dashboard/TaxAxisDashboard.d.ts +58 -4
  23. package/lib/tax-axis/components/dashboard/TaxAxisDashboard.js +375 -56
  24. package/lib/tax-axis/components/documents/DocumentCard.d.ts +6 -3
  25. package/lib/tax-axis/components/documents/DocumentCard.js +72 -15
  26. package/lib/tax-axis/components/documents/DocumentReviewModal.d.ts +3 -1
  27. package/lib/tax-axis/components/documents/DocumentReviewModal.js +113 -263
  28. package/lib/tax-axis/components/documents/DocumentTier.d.ts +5 -4
  29. package/lib/tax-axis/components/documents/DocumentTier.js +2 -2
  30. package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +28 -8
  31. package/lib/tax-axis/components/documents/TaxAxisDocuments.js +340 -156
  32. package/lib/tax-axis/components/documents/qbo/QboAvailableReportsModal.d.ts +13 -0
  33. package/lib/tax-axis/components/documents/qbo/QboAvailableReportsModal.js +180 -0
  34. package/lib/tax-axis/components/documents/qbo/QboClientSelectorModal.d.ts +10 -0
  35. package/lib/tax-axis/components/documents/qbo/QboClientSelectorModal.js +155 -0
  36. package/lib/tax-axis/components/documents/qbo/QboConnectBanner.d.ts +9 -0
  37. package/lib/tax-axis/components/documents/qbo/QboConnectBanner.js +55 -0
  38. package/lib/tax-axis/components/documents/qbo/QboDocumentMappingModal.d.ts +10 -0
  39. package/lib/tax-axis/components/documents/qbo/QboDocumentMappingModal.js +202 -0
  40. package/lib/tax-axis/components/documents/qbo/QboImportingModal.d.ts +8 -0
  41. package/lib/tax-axis/components/documents/qbo/QboImportingModal.js +75 -0
  42. package/lib/tax-axis/components/documents/qbo/QboPermissionsModal.d.ts +8 -0
  43. package/lib/tax-axis/components/documents/qbo/QboPermissionsModal.js +126 -0
  44. package/lib/tax-axis/components/documents/qbo/index.d.ts +8 -0
  45. package/lib/tax-axis/components/documents/qbo/index.js +17 -0
  46. package/lib/tax-axis/components/documents/qbo/qboConstants.d.ts +24 -0
  47. package/lib/tax-axis/components/documents/qbo/qboConstants.js +71 -0
  48. package/lib/tax-axis/components/documents/qbo/types.d.ts +43 -0
  49. package/lib/tax-axis/components/documents/qbo/types.js +3 -0
  50. package/lib/tax-axis/components/documents/qbo/useQboFlow.d.ts +19 -0
  51. package/lib/tax-axis/components/documents/qbo/useQboFlow.js +207 -0
  52. package/lib/tax-axis/components/intake/ClientParametersSection.js +14 -30
  53. package/lib/tax-axis/components/intake/CpaIntakeQuestionsSection.js +3 -3
  54. package/lib/tax-axis/components/intake/IntakeCtaCards.d.ts +1 -2
  55. package/lib/tax-axis/components/intake/IntakeCtaCards.js +6 -13
  56. package/lib/tax-axis/components/intake/RefineAnalysisSection.js +7 -7
  57. package/lib/tax-axis/components/intake/TaxAxisIntake.js +7 -95
  58. package/lib/tax-axis/components/intake/intakeSchema.d.ts +0 -3
  59. package/lib/tax-axis/components/intake/intakeSchema.js +2 -4
  60. package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.d.ts +23 -4
  61. package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.js +15 -4
  62. package/lib/tax-axis/components/processing/TaxAxisProcessing.d.ts +2 -1
  63. package/lib/tax-axis/components/processing/TaxAxisProcessing.js +102 -31
  64. package/lib/tax-axis/components/prospectReport/ProspectPrintView.js +0 -2
  65. package/lib/tax-axis/components/prospectReport/ProspectStrategyCard.d.ts +1 -8
  66. package/lib/tax-axis/components/prospectReport/ProspectStrategyCard.js +5 -5
  67. package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.d.ts +1 -27
  68. package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.js +25 -43
  69. package/lib/tax-axis/index.d.ts +3 -1
  70. package/lib/tax-axis/index.js +4 -3
  71. package/lib/tax-axis/lib/adapters/useEngineOutput.d.ts +138 -13
  72. package/lib/tax-axis/lib/adapters/useEngineOutput.js +156 -7
  73. package/lib/tax-axis/lib/data/documents.d.ts +3 -2
  74. package/lib/tax-axis/lib/data/documents.js +225 -25
  75. package/lib/tax-axis/lib/data/strategies.js +9 -9
  76. package/lib/tax-axis/lib/documentFieldCatalog.d.ts +7 -12
  77. package/lib/tax-axis/lib/documentFieldCatalog.js +805 -8
  78. package/lib/tax-axis/lib/types/index.d.ts +13 -1
  79. package/package.json +1 -1
  80. package/lib/README.md +0 -2
  81. package/lib/package.json +0 -68
@@ -38,39 +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
- // 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 per doc (mirrors STUB_SECTIONS in DocumentReviewModal)
53
- const STUB_FIELD_COUNTS = {
54
- pnl: 13,
55
- balance: 9,
56
- "1120s": 3,
57
- payroll: 3,
58
- "state-return": 2,
59
- cashflow: 1,
60
- "fixed-assets": 1,
61
- "prior-returns": 1,
62
- };
63
- // Short help text shown below each doc name when not yet uploaded
64
- // (mock App.jsx:1384). Overrides the longer DOC_SPECS_BASE.help.
65
- const HELP_OVERRIDES = {
66
- "1120s": "Improves accuracy of every strategy",
67
- "payroll": "Critical for Entity Structure and QBI calculations",
68
- "fixed-assets": "Unlocks Cost Segregation (potential $15K\u201385K)",
69
- "state-return": "If client operates in income-tax states",
70
- "cashflow": "Helps with income deferral and timing strategies",
71
- "prior-returns": "Improves confidence intervals (more years = tighter estimates)",
72
- };
73
- // 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");
74
48
  const TIER_DEFS = [
75
49
  {
76
50
  key: "required",
@@ -80,7 +54,7 @@ const TIER_DEFS = [
80
54
  labelColor: "#C53030",
81
55
  badgeColor: "red",
82
56
  badgeText: "REQUIRED",
83
- ids: ["pnl", "balance"],
57
+ ids: [],
84
58
  },
85
59
  {
86
60
  key: "recommended",
@@ -90,7 +64,7 @@ const TIER_DEFS = [
90
64
  labelColor: "#FB9A1D",
91
65
  badgeColor: "orange",
92
66
  badgeText: "RECOMMENDED",
93
- ids: ["1120s", "payroll", "fixed-assets"],
67
+ ids: [],
94
68
  },
95
69
  {
96
70
  key: "conditional",
@@ -100,151 +74,361 @@ const TIER_DEFS = [
100
74
  labelColor: "#C8CCE5",
101
75
  badgeColor: "neutral",
102
76
  badgeText: "CONDITIONAL",
103
- ids: ["state-return", "cashflow", "prior-returns"],
77
+ ids: [],
104
78
  },
105
79
  ];
106
- function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, jobId, userContext: _userContext = "expert", }) {
107
- var _a;
108
- const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(profile), [profile]);
109
- const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null }))));
110
- // Review modal state
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);
111
92
  const [reviewDocId, setReviewDocId] = (0, react_1.useState)(null);
112
- const reviewDoc = reviewDocId
113
- ? (_a = docs.find((d) => d.id === reviewDocId)) !== null && _a !== void 0 ? _a : null
114
- : null;
115
- // File picker refs
116
- const fileInputRef = (0, react_1.useRef)(null);
117
- const uploadTargetIdx = (0, react_1.useRef)(-1);
118
- const documentIdMap = (0, react_1.useRef)({});
119
- // Load previously uploaded documents on mount
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.
120
146
  (0, react_1.useEffect)(() => {
121
147
  if (!fetchUploadedDocuments)
122
148
  return;
123
- fetchUploadedDocuments()
124
- .then((uploaded) => {
125
- if (!uploaded || uploaded.length === 0)
149
+ fetchUploadedDocuments().then((uploaded) => {
150
+ if (!uploaded.length)
126
151
  return;
127
- setDocs((prev) => prev.map((d) => {
128
- const match = uploaded.find((u) => u.documentType === d.id);
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];
129
159
  if (!match)
130
- return d;
131
- if (match.documentId)
132
- documentIdMap.current[d.id] = match.documentId;
133
- const status = match.status === 'PARSED' || match.status === 'valid'
134
- ? 'valid'
135
- : match.status === 'FAILED'
136
- ? 'empty'
137
- : 'validating';
138
- return Object.assign(Object.assign({}, d), { status, fileName: match.fileName || d.fileName });
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 });
139
171
  }));
140
- })
141
- .catch((err) => console.error('[TaxAxisDocuments] Failed to fetch uploaded documents:', err));
142
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
143
- // Open native file picker for the target document slot
144
- const handleUpload = (idx) => {
145
- var _a;
146
- if (!onUploadDocument) {
147
- console.warn('[TaxAxisDocuments] No onUploadDocument handler provided');
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
+ }
183
+ }
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
- uploadTargetIdx.current = idx;
151
- if (fileInputRef.current) {
152
- const doc = docs[idx];
153
- fileInputRef.current.accept = ((_a = doc.accept) === null || _a === void 0 ? void 0 : _a.join(',')) || '.pdf,.docx,.xlsx,.csv';
154
- fileInputRef.current.value = '';
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
- 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));
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 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;
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
- setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: 'valid', fileName: file.name }) : d));
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;
283
+ }
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);
174
286
  }
175
- catch (err) {
176
- console.error('[TaxAxisDocuments] Upload failed:', err);
177
- setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: 'empty', fileName: null }) : d));
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));
178
293
  }
179
- }), [docs, onUploadDocument]);
294
+ });
180
295
  const handleClear = (idx) => {
181
- const doc = docs[idx];
182
- const backendDocId = documentIdMap.current[doc.id];
183
- if (backendDocId && onDeleteDocument) {
184
- onDeleteDocument(backendDocId).catch((err) => console.error('[TaxAxisDocuments] Delete failed:', err));
185
- delete documentIdMap.current[doc.id];
186
- }
187
296
  setDocs((prev) => prev.map((d, i) => i === idx
188
- ? Object.assign(Object.assign({}, d), { status: "empty", fileName: null }) : d));
297
+ ? Object.assign(Object.assign({}, d), { status: "empty", fileName: null, parseError: null }) : d));
298
+ setContinueError(null);
189
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
+ }
309
+ }
310
+ setUploadedDocIds((prev) => {
311
+ const next = Object.assign({}, prev);
312
+ delete next[idx];
313
+ return next;
314
+ });
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;
323
+ }
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
+ });
190
356
  const validCount = docs.filter((d) => d.status === "valid").length;
191
- const requiredCount = docs.filter((d) => d.required === true).length;
192
- const requiredValid = docs.filter((d) => d.required === true && d.status === "valid").length;
193
- // Coverage heuristic from mock (App.jsx:1361)
194
- const coveragePct = validCount === 0
195
- ? 32
196
- : validCount === 1
197
- ? 50
198
- : validCount === 2
199
- ? 60
200
- : validCount <= 4
201
- ? 75
202
- : validCount <= 6
203
- ? 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
204
364
  : 100;
205
365
  const strategiesCovered = Math.round((25 * coveragePct) / 100);
206
366
  return (react_1.default.createElement("div", { className: "max-w-[580px] mx-auto" },
207
367
  react_1.default.createElement("div", { className: "text-center mb-5 pt-5" },
208
368
  react_1.default.createElement("h1", { className: "text-[28px] font-bold text-white font-tax-axis-head mb-1.5" }, "Source Documents"),
209
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.")),
210
- react_1.default.createElement("div", { className: "py-3.5 px-[18px] mb-4 text-xs leading-relaxed font-tax-axis-body", style: {
211
- background: "#171B44",
212
- borderLeft: "3px solid #248384",
213
- borderRadius: "0 8px 8px 0",
214
- color: "#E6E8F5",
215
- } }, "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."),
216
- react_1.default.createElement("div", { className: "bg-tax-axis-surface border border-tax-axis-border rounded-[10px] px-[18px] py-3.5 mb-4" },
217
- 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"),
218
- react_1.default.createElement("div", { className: "flex justify-between mb-1.5" },
219
- react_1.default.createElement("span", { className: "text-xs text-tax-axis-text-2 font-tax-axis-body" },
220
- "Documents uploaded:",
221
- " ",
222
- react_1.default.createElement("strong", { className: "text-white" },
223
- validCount,
224
- " / ",
225
- docs.length)),
226
- react_1.default.createElement("span", { className: "text-xs text-tax-axis-text-2 font-tax-axis-body" },
227
- "Strategies covered:",
228
- " ",
229
- react_1.default.createElement("strong", { className: "text-tax-axis-teal-light" },
230
- strategiesCovered,
231
- " / 25"))),
232
- react_1.default.createElement("div", { className: "h-1.5 bg-tax-axis-surface-2 rounded-sm overflow-hidden mb-1.5" },
233
- react_1.default.createElement("div", { className: "h-full rounded-sm transition-[width] duration-[400ms]", style: {
234
- width: `${coveragePct}%`,
235
- background: "linear-gradient(90deg, #248384, #A1E5E6)",
236
- } })),
237
- react_1.default.createElement("div", { className: "flex justify-between" },
238
- react_1.default.createElement("span", { className: "text-[10px] text-tax-axis-text-4 font-tax-axis-mono" },
239
- coveragePct,
240
- "% coverage"),
241
- 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")))),
242
- TIER_DEFS.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: HELP_OVERRIDES, fieldCounts: STUB_FIELD_COUNTS, onUpload: handleUpload, onClear: handleClear, onReview: (docId) => setReviewDocId(docId) }))),
243
- react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
244
- react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
245
- react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onContinue, disabled: requiredCount > requiredValid, className: "flex-1" }, requiredCount > requiredValid
246
- ? `Upload ${requiredCount - requiredValid} more required doc${requiredCount - requiredValid > 1 ? "s" : ""}`
247
- : "Run Analysis")),
248
- react_1.default.createElement("input", { ref: fileInputRef, type: "file", style: { display: 'none' }, onChange: handleFileSelected }),
249
- 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) }))));
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) || "" })));
250
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 {};