@paro.io/expert-shared-components 1.14.49 → 1.14.50

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.
@@ -21,11 +21,27 @@ export type TaxAxisUploadInput = {
21
21
  documentType?: string;
22
22
  s3Key: string;
23
23
  };
24
+ export type TaxAxisDocumentRecord = {
25
+ documentId: string;
26
+ sessionId: string;
27
+ fileName: string;
28
+ fileType: string;
29
+ fileSize?: number;
30
+ documentType?: string | null;
31
+ status: string;
32
+ s3Key: string;
33
+ parseError?: string | null;
34
+ parseStageResults?: unknown[] | null;
35
+ createdAt?: string;
36
+ updatedAt?: string;
37
+ };
24
38
  export type TaxAxisApi = {
25
39
  createSession: (input: TaxAxisSessionInput) => Promise<any>;
26
40
  getSession: (sessionId: string) => Promise<any>;
27
41
  listSessions: (input?: Record<string, unknown>) => Promise<any[]>;
28
42
  uploadFile: (input: TaxAxisUploadInput) => Promise<any>;
43
+ deleteDocument: (documentId: string) => Promise<boolean>;
44
+ getDocuments: (sessionId: string) => Promise<TaxAxisDocumentRecord[]>;
29
45
  analyzeDocuments: (sessionId: string) => Promise<any>;
30
46
  saveReviewedData: (documentId: string, reviewedData: Record<string, unknown>) => Promise<any>;
31
47
  runLlm: (sessionId: string) => Promise<any>;
@@ -47,12 +47,16 @@ const TaxAxisClientReport_1 = require("../../tax-axis/components/clientReport/Ta
47
47
  const TaxAxisPreparerWorkpaper_1 = require("../../tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper");
48
48
  const TaxAxisPresentationMode_1 = require("../../tax-axis/components/presentationMode/TaxAxisPresentationMode");
49
49
  const UploadClient_1 = __importDefault(require("../shared/UploadClient"));
50
+ function sleep(ms) {
51
+ return new Promise((resolve) => {
52
+ setTimeout(resolve, ms);
53
+ });
54
+ }
50
55
  function toInt(value) {
51
56
  const parsed = Number(String(value || '0').replace(/[^\d.-]/g, ''));
52
57
  return Number.isFinite(parsed) ? parsed : 0;
53
58
  }
54
59
  function buildSessionInput(profile) {
55
- // Placeholder defaults are overridden by sessionDefaults from the host app.
56
60
  const taxYear = Number(profile.year) || new Date().getFullYear();
57
61
  return {
58
62
  clientId: null,
@@ -79,8 +83,10 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
79
83
  const [profile, setProfile] = (0, react_1.useState)(initialProfile ? Object.assign({}, initialProfile) : null);
80
84
  const [sessionId, setSessionId] = (0, react_1.useState)(initialSessionId || null);
81
85
  const [isProspectFlow, setIsProspectFlow] = (0, react_1.useState)(false);
86
+ const [llmResult, setLlmResult] = (0, react_1.useState)(null);
82
87
  const [isBusy, setIsBusy] = (0, react_1.useState)(false);
83
88
  const [error, setError] = (0, react_1.useState)(null);
89
+ const [busyMessage, setBusyMessage] = (0, react_1.useState)('Syncing Tax Axis session...');
84
90
  const updateSessionId = (0, react_1.useCallback)((nextSessionId) => {
85
91
  setSessionId(nextSessionId);
86
92
  if (onSessionChange) {
@@ -101,7 +107,8 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
101
107
  }
102
108
  }
103
109
  catch (requestError) {
104
- setError('Unable to create Tax Axis session right now.');
110
+ const msg = requestError instanceof Error ? requestError.message : 'Unable to create Tax Axis session right now.';
111
+ setError(msg);
105
112
  }
106
113
  return null;
107
114
  }), [sessionId, sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.clientId, sessionDefaults === null || sessionDefaults === void 0 ? void 0 : sessionDefaults.freelancerId, taxAxisApi, updateSessionId]);
@@ -121,6 +128,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
121
128
  setProfile(nextProfile);
122
129
  setIsProspectFlow(false);
123
130
  setError(null);
131
+ setBusyMessage('Creating Tax Axis session...');
124
132
  setIsBusy(true);
125
133
  const createdSessionId = yield createSessionIfNeeded(nextProfile);
126
134
  setIsBusy(false);
@@ -163,7 +171,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
163
171
  documentType: doc.id.toUpperCase().replace(/[^A-Z0-9]+/g, '_'),
164
172
  s3Key: uploadClient.state.fileName,
165
173
  };
166
- yield taxAxisApi.uploadFile(uploadInput);
174
+ return taxAxisApi.uploadFile(uploadInput);
167
175
  }), [
168
176
  createSessionIfNeeded,
169
177
  documentUploadUrl,
@@ -174,22 +182,40 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
174
182
  taxAxisApi,
175
183
  uploadBucketName,
176
184
  ]);
185
+ const handleDeleteDocument = (0, react_1.useCallback)((documentId) => __awaiter(void 0, void 0, void 0, function* () {
186
+ yield taxAxisApi.deleteDocument(documentId);
187
+ }), [taxAxisApi]);
177
188
  const handleAnalyzeDocuments = (0, react_1.useCallback)(() => __awaiter(void 0, void 0, void 0, function* () {
178
189
  if (!profile)
179
190
  return;
180
191
  setError(null);
192
+ setBusyMessage('Preparing documents for analysis...');
181
193
  setIsBusy(true);
182
194
  const ensuredSessionId = yield createSessionIfNeeded(profile);
183
- if (ensuredSessionId) {
184
- try {
185
- yield taxAxisApi.analyzeDocuments(ensuredSessionId);
186
- }
187
- catch (requestError) {
188
- setError('Analysis trigger failed. Continuing with mock processing flow.');
195
+ if (!ensuredSessionId) {
196
+ setIsBusy(false);
197
+ return;
198
+ }
199
+ try {
200
+ setBusyMessage('Verifying all documents are parsed...');
201
+ yield taxAxisApi.analyzeDocuments(ensuredSessionId);
202
+ setBusyMessage('Generating strategy results...');
203
+ setStep('PROCESSING');
204
+ const llmResponse = yield taxAxisApi.runLlm(ensuredSessionId);
205
+ if (llmResponse === null || llmResponse === void 0 ? void 0 : llmResponse.outputPayload) {
206
+ setLlmResult(llmResponse.outputPayload);
189
207
  }
190
208
  }
209
+ catch (requestError) {
210
+ const msg = requestError instanceof Error
211
+ ? requestError.message
212
+ : 'Analysis did not complete successfully. Please review uploaded files.';
213
+ setError(msg);
214
+ setStep('DOCUMENT_UPLOAD');
215
+ setIsBusy(false);
216
+ return;
217
+ }
191
218
  setIsBusy(false);
192
- setStep('PROCESSING');
193
219
  }), [createSessionIfNeeded, profile, taxAxisApi]);
194
220
  const handleSendReport = (0, react_1.useCallback)(() => __awaiter(void 0, void 0, void 0, function* () {
195
221
  if (!sessionId) {
@@ -202,7 +228,10 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
202
228
  yield taxAxisApi.generatePdf(sessionId);
203
229
  }
204
230
  catch (requestError) {
205
- setError('Failed to request report delivery. Please try again.');
231
+ const msg = requestError instanceof Error
232
+ ? requestError.message
233
+ : 'Failed to request report delivery. Please try again.';
234
+ setError(msg);
206
235
  }
207
236
  finally {
208
237
  setIsBusy(false);
@@ -223,7 +252,20 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
223
252
  react_1.default.createElement(TaxAxisProspectReport_1.TaxAxisProspectReport, { profile: profile, userContext: userContext, onUpgrade: () => setStep('DOCUMENT_UPLOAD'), onPresent: () => setStep('PRESENTATION'), onReset: handleReset })));
224
253
  case 'DOCUMENT_UPLOAD':
225
254
  return (react_1.default.createElement(ShellContainer, null,
226
- react_1.default.createElement(TaxAxisDocuments_1.TaxAxisDocuments, { profile: profile, userContext: userContext, onUploadDocument: handleUploadDocument, onContinue: handleAnalyzeDocuments, onBack: () => isProspectFlow ? setStep('PROSPECT_REPORT') : setStep('SESSION_SETUP') })));
255
+ react_1.default.createElement(TaxAxisDocuments_1.TaxAxisDocuments, { profile: profile, userContext: userContext, onUploadDocument: handleUploadDocument, onDeleteDocument: handleDeleteDocument, fetchUploadedDocuments: () => __awaiter(void 0, void 0, void 0, function* () {
256
+ const ensuredSessionId = sessionId || (profile ? yield createSessionIfNeeded(profile) : null);
257
+ if (!ensuredSessionId) {
258
+ return [];
259
+ }
260
+ const docs = yield taxAxisApi.getDocuments(ensuredSessionId);
261
+ return docs.map((doc) => ({
262
+ fileName: doc.fileName,
263
+ status: doc.status,
264
+ documentType: doc.documentType,
265
+ parseError: doc.parseError,
266
+ updatedAt: doc.updatedAt,
267
+ }));
268
+ }), onContinue: handleAnalyzeDocuments, onBack: () => isProspectFlow ? setStep('PROSPECT_REPORT') : setStep('SESSION_SETUP') })));
227
269
  case 'PROCESSING':
228
270
  return (react_1.default.createElement(ShellContainer, null,
229
271
  react_1.default.createElement(TaxAxisProcessing_1.TaxAxisProcessing, { profile: profile, userContext: userContext, onComplete: () => setStep('DASHBOARD') })));
@@ -232,7 +274,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
232
274
  react_1.default.createElement(TaxAxisExtractionReview_1.TaxAxisExtractionReview, { userContext: userContext, onBack: () => setStep('DASHBOARD'), onConfirmAndUnlock: () => setStep('DASHBOARD') })));
233
275
  case 'DASHBOARD':
234
276
  return (react_1.default.createElement(ShellContainer, null,
235
- react_1.default.createElement(TaxAxisDashboard_1.TaxAxisDashboard, { profile: profile, userContext: userContext, onDownloadClient: () => setStep('CLIENT_REPORT'), onDownloadPreparer: () => setStep('PREPARER_WORKPAPER'), onPresent: () => setStep('PRESENTATION'), onSend: handleSendReport, onReviewData: () => setStep('PARSED_REVIEW'), onReset: handleReset })));
277
+ react_1.default.createElement(TaxAxisDashboard_1.TaxAxisDashboard, { profile: profile, userContext: userContext, llmResult: llmResult, onDownloadClient: () => setStep('CLIENT_REPORT'), onDownloadPreparer: () => setStep('PREPARER_WORKPAPER'), onPresent: () => setStep('PRESENTATION'), onSend: handleSendReport, onReviewData: () => setStep('PARSED_REVIEW'), onReset: handleReset })));
236
278
  case 'CLIENT_REPORT':
237
279
  return (react_1.default.createElement(ShellContainer, { fullWidth: true },
238
280
  react_1.default.createElement(TaxAxisClientReport_1.TaxAxisClientReport, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onNavigatePreparer: () => setStep('PREPARER_WORKPAPER') })));
@@ -255,13 +297,21 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
255
297
  isProspectFlow,
256
298
  handleAnalyzeDocuments,
257
299
  handleUploadDocument,
300
+ handleDeleteDocument,
258
301
  handleReset,
259
302
  handleSendReport,
303
+ llmResult,
260
304
  ]);
261
305
  return (react_1.default.createElement(react_1.default.Fragment, null,
262
- error && (react_1.default.createElement("div", { className: 'fixed right-4 top-4 z-[200] rounded-lg border border-tax-axis-border bg-tax-axis-surface px-3 py-2 text-xs text-tax-axis-text' }, error)),
306
+ error && (react_1.default.createElement("div", { className: 'fixed right-4 top-4 z-[200] max-w-sm rounded-lg border border-red-500/30 bg-tax-axis-surface px-4 py-3 text-xs text-red-200 shadow-lg' },
307
+ react_1.default.createElement("div", { className: 'flex items-start gap-2' },
308
+ react_1.default.createElement("span", { className: 'flex-1' }, error),
309
+ react_1.default.createElement("button", { onClick: () => setError(null), className: 'bg-transparent border-none text-red-300 text-sm cursor-pointer p-0 leading-none' }, "\u00D7")))),
263
310
  isBusy && (react_1.default.createElement("div", { className: 'fixed inset-0 z-[180] bg-black/25' },
264
- react_1.default.createElement("div", { className: 'absolute left-1/2 top-6 -translate-x-1/2 rounded-md bg-tax-axis-surface px-3 py-2 text-xs text-tax-axis-text' }, "Syncing Tax Axis session..."))),
311
+ react_1.default.createElement("div", { className: 'absolute left-1/2 top-6 -translate-x-1/2 rounded-md bg-tax-axis-surface px-4 py-2.5 text-xs text-tax-axis-text shadow-lg border border-tax-axis-border' },
312
+ react_1.default.createElement("div", { className: 'flex items-center gap-2' },
313
+ react_1.default.createElement("div", { className: 'w-3 h-3 rounded-full animate-spin flex-shrink-0', style: { border: '2px solid transparent', borderTopColor: '#248384' } }),
314
+ busyMessage)))),
265
315
  currentView));
266
316
  };
267
317
  exports.TaxAxisShell = TaxAxisShell;
@@ -25,7 +25,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.DashboardActions = DashboardActions;
27
27
  const react_1 = __importStar(require("react"));
28
- const data_1 = require("../../lib/data");
29
28
  const compute_1 = require("../../lib/compute");
30
29
  const TaxAxisButton_1 = require("../shared/TaxAxisButton");
31
30
  function DashboardActions({ profile, dashEligible, computed, onDownloadPreparer, onPresent, onSend, onReset, }) {
@@ -69,7 +68,7 @@ function DashboardActions({ profile, dashEligible, computed, onDownloadPreparer,
69
68
  "\u2013",
70
69
  (0, compute_1.fmtK)(totalHi),
71
70
  "across ",
72
- data_1.STRATEGIES.length,
71
+ dashEligible.length,
73
72
  " strategies \u2014 use this to close the engagement or expand scope."),
74
73
  react_1.default.createElement("div", { className: "flex gap-2.5 flex-wrap" },
75
74
  react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "orange", onClick: onSend }, "Send Report to Client"),
@@ -77,7 +76,7 @@ function DashboardActions({ profile, dashEligible, computed, onDownloadPreparer,
77
76
  react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary" }, "Generate Engagement Letter")))),
78
77
  react_1.default.createElement("div", { className: "flex gap-4 mt-4 pt-3.5", style: { borderTop: "1px solid rgba(36,131,132,0.15)" } }, [
79
78
  { v: "3x", l: "Faster than Manual" },
80
- { v: "25", l: "Strategies Scanned" },
79
+ { v: String(dashEligible.length), l: "Strategies Found" },
81
80
  { v: "100%", l: "IRS-Cited" },
82
81
  { v: String(stateCount), l: `State${stateCount > 1 ? "s" : ""}` },
83
82
  ].map(({ v, l }) => (react_1.default.createElement("div", { key: l },
@@ -78,7 +78,7 @@ function DashboardSummary({ profile, dashEligible, computed, dataConfirmed, revi
78
78
  profile.cpaName)),
79
79
  react_1.default.createElement("div", { className: "flex gap-6 mt-[22px] pt-[18px]", style: { borderTop: "1px solid rgba(36,131,132,0.12)" } }, [
80
80
  { v: String(scoreUp), u: "/100", l: "Avg. Confidence", warn: false, ok: false },
81
- { v: "25", u: "", l: "Strategies Scanned", warn: false, ok: false },
81
+ { v: String(dashEligible.length), u: "", l: "Strategies Identified", warn: false, ok: false },
82
82
  {
83
83
  v: dataConfirmed ? "Done" : String(reviewUnreviewed),
84
84
  u: "",
@@ -1,7 +1,39 @@
1
1
  import React from "react";
2
2
  import type { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
3
+ export interface LlmStrategy {
4
+ strategyType: string;
5
+ applicable: boolean;
6
+ priority: string;
7
+ estimatedSavings: {
8
+ min: number;
9
+ max: number;
10
+ };
11
+ summary: string;
12
+ implementationSteps: string[];
13
+ requiredForms: string[];
14
+ }
15
+ export interface LlmResult {
16
+ strategies: LlmStrategy[];
17
+ summary: {
18
+ applicableCount: number;
19
+ totalStrategies: number;
20
+ highPriorityCount: number;
21
+ quickWinCount: number;
22
+ notEligibleCount: number;
23
+ estimatedSavingsMin: number;
24
+ estimatedSavingsMax: number;
25
+ };
26
+ meta: {
27
+ provider: string;
28
+ tokenCount: number;
29
+ sourceDocumentCount: number;
30
+ parsedDocumentCount: number;
31
+ detectedDocumentTypes: string[];
32
+ };
33
+ }
3
34
  export interface TaxAxisDashboardProps extends TaxAxisScreenProps {
4
35
  profile: ClientProfile;
36
+ llmResult?: LlmResult | null;
5
37
  onDownloadClient: () => void;
6
38
  onDownloadPreparer: () => void;
7
39
  onPresent: () => void;
@@ -9,4 +41,4 @@ export interface TaxAxisDashboardProps extends TaxAxisScreenProps {
9
41
  onReset: () => void;
10
42
  onReviewData?: () => void;
11
43
  }
12
- export declare function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPresent, onSend, onReset, onReviewData, userContext: _userContext, }: TaxAxisDashboardProps): React.JSX.Element;
44
+ export declare function TaxAxisDashboard({ profile, llmResult, onDownloadClient, onDownloadPreparer, onPresent, onSend, onReset, onReviewData, userContext: _userContext, }: TaxAxisDashboardProps): React.JSX.Element;
@@ -34,6 +34,64 @@ const DashboardTopBar_1 = require("./DashboardTopBar");
34
34
  const DashboardActions_1 = require("./DashboardActions");
35
35
  const StrategyTile_1 = require("./StrategyTile");
36
36
  const StrategyDetailPanel_1 = require("./StrategyDetailPanel");
37
+ const STRATEGY_TYPE_LABELS = {
38
+ ENTITY_RESTRUCTURING: "Entity Restructuring",
39
+ QBI_199A_OPTIMIZATION: "QBI §199A Optimization",
40
+ DOCUMENTATION_GAP_REMEDIATION: "Documentation Gap Remediation",
41
+ RETIREMENT_PLAN: "Retirement Plan Optimization",
42
+ DEPRECIATION_ACCELERATION: "Depreciation Acceleration",
43
+ COST_SEGREGATION: "Cost Segregation",
44
+ RD_CREDIT: "R&D Tax Credit",
45
+ HSA_MAXIMIZATION: "HSA Maximization",
46
+ SALT_PTE: "SALT / PTE Optimization",
47
+ WOTC: "Work Opportunity Tax Credit",
48
+ INCOME_DEFERRAL: "Income Deferral",
49
+ CHARITABLE_GIVING: "Charitable Giving Strategy",
50
+ FAMILY_EMPLOYMENT: "Family Employment",
51
+ MEALS_DEDUCTIONS: "Business Meals Deduction",
52
+ OPPORTUNITY_ZONE: "Opportunity Zone Investment",
53
+ ACCOUNTING_METHOD: "Accounting Method Change",
54
+ BONUS_DEPRECIATION: "Bonus Depreciation §168(k)",
55
+ SECTION_179: "Section 179 Expensing",
56
+ };
57
+ function humanizeStrategyType(raw) {
58
+ if (STRATEGY_TYPE_LABELS[raw])
59
+ return STRATEGY_TYPE_LABELS[raw];
60
+ return raw
61
+ .replace(/_/g, " ")
62
+ .replace(/\b\w/g, (c) => c.toUpperCase());
63
+ }
64
+ function mapLlmToStrategies(llm) {
65
+ return llm.strategies
66
+ .filter((s) => s.applicable)
67
+ .map((s, idx) => ({
68
+ rank: idx + 1,
69
+ code: s.strategyType,
70
+ name: humanizeStrategyType(s.strategyType),
71
+ cat: "income",
72
+ priority: (s.priority || "MEDIUM").toUpperCase(),
73
+ score: s.priority === "HIGH" ? 85 : s.priority === "MEDIUM" ? 70 : 55,
74
+ entities: [],
75
+ lo: s.estimatedSavings.min,
76
+ hi: s.estimatedSavings.max,
77
+ timeline: s.priority === "HIGH" ? "Act now" : "Within 90 days",
78
+ timelineBucket: s.priority === "HIGH" ? "now" : "90d",
79
+ clientBrief: s.summary,
80
+ action: s.implementationSteps.join(". "),
81
+ forms: s.requiredForms.join(", "),
82
+ abstract: s.summary,
83
+ sources: [],
84
+ trace: [],
85
+ cost: undefined,
86
+ }));
87
+ }
88
+ function buildLlmComputed(strategies) {
89
+ const map = new Map();
90
+ for (const s of strategies) {
91
+ map.set(s.rank, { lo: s.lo, hi: s.hi, sources: s.sources, trace: s.trace });
92
+ }
93
+ return map;
94
+ }
37
95
  // ─── Formatting helper ──────────────────────────────────────────
38
96
  const fmtK = (n) => `$${(n / 1000).toFixed(n % 1000 ? 1 : 0)}K`;
39
97
  // ─── Timeline buckets for Client Summary ────────────────────────
@@ -42,10 +100,14 @@ const BUCKETS = [
42
100
  { key: "30d", label: "Within 30 Days", desc: "Documentation or payroll changes" },
43
101
  { key: "90d", label: "Within 90 Days", desc: "Structural changes — new accounts or plan setup" },
44
102
  ];
45
- function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPresent, onSend, onReset, onReviewData, userContext: _userContext = "expert", }) {
103
+ function TaxAxisDashboard({ profile, llmResult, onDownloadClient, onDownloadPreparer, onPresent, onSend, onReset, onReviewData, userContext: _userContext = "expert", }) {
104
+ var _a, _b;
105
+ const hasLlm = !!((_a = llmResult === null || llmResult === void 0 ? void 0 : llmResult.strategies) === null || _a === void 0 ? void 0 : _a.length);
46
106
  // ─── Derived data ──────────────────────────────────────────────
47
- const computed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(profile), [profile]);
48
- const dashEligible = (0, react_1.useMemo)(() => (0, compute_1.filterEligibleStrategies)(profile), [profile]);
107
+ const llmStrategies = (0, react_1.useMemo)(() => (hasLlm ? mapLlmToStrategies(llmResult) : []), [hasLlm, llmResult]);
108
+ const llmComputed = (0, react_1.useMemo)(() => (hasLlm ? buildLlmComputed(llmStrategies) : new Map()), [hasLlm, llmStrategies]);
109
+ const computed = (0, react_1.useMemo)(() => (hasLlm ? llmComputed : (0, compute_1.computeAllStrategies)(profile)), [hasLlm, llmComputed, profile]);
110
+ const dashEligible = (0, react_1.useMemo)(() => (hasLlm ? llmStrategies : (0, compute_1.filterEligibleStrategies)(profile)), [hasLlm, llmStrategies, profile]);
49
111
  const maxSavings = Math.max(...dashEligible.map((s) => { var _a, _b; return (_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : s.hi; }), 1);
50
112
  // ─── State ─────────────────────────────────────────────────────
51
113
  const [topTab, setTopTab] = (0, react_1.useState)("extraction");
@@ -80,6 +142,21 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
80
142
  // Sorted strategies for impact distribution
81
143
  const sortedByImpact = (0, react_1.useMemo)(() => [...dashEligible].sort((a, b) => { var _a, _b, _c, _d; return ((_b = (_a = computed.get(b.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : b.hi) - ((_d = (_c = computed.get(a.rank)) === null || _c === void 0 ? void 0 : _c.hi) !== null && _d !== void 0 ? _d : a.hi); }), [dashEligible, computed]);
82
144
  return (react_1.default.createElement("div", null,
145
+ hasLlm && (llmResult === null || llmResult === void 0 ? void 0 : llmResult.meta) && (react_1.default.createElement("div", { className: "rounded-lg mb-3 px-4 py-2.5 flex items-center justify-between text-[11px] font-tax-axis-mono", style: {
146
+ background: "rgba(36,131,132,0.06)",
147
+ border: "1px solid rgba(36,131,132,0.15)",
148
+ color: "#9498B8",
149
+ } },
150
+ react_1.default.createElement("span", null,
151
+ "Source: ",
152
+ react_1.default.createElement("strong", { className: "text-tax-axis-teal-light" }, llmResult.meta.provider),
153
+ " · ",
154
+ llmResult.meta.parsedDocumentCount,
155
+ " documents parsed",
156
+ " · ",
157
+ ((_b = llmResult.meta.detectedDocumentTypes) === null || _b === void 0 ? void 0 : _b.length) || 0,
158
+ " document types detected"),
159
+ react_1.default.createElement("span", { className: "text-tax-axis-text-4" }, llmResult.meta.tokenCount > 0 ? `${llmResult.meta.tokenCount} tokens` : ""))),
83
160
  react_1.default.createElement(DashboardSummary_1.DashboardSummary, { profile: profile, dashEligible: dashEligible, computed: computed, dataConfirmed: dataConfirmed, reviewUnreviewed: reviewStatus.unreviewed }),
84
161
  react_1.default.createElement(DashboardTopBar_1.DashboardTopBar, { topTab: topTab, setTopTab: setTopTab, dataConfirmed: dataConfirmed, reviewUnreviewed: reviewStatus.unreviewed }),
85
162
  topTab === "extraction" && (react_1.default.createElement("div", null,
@@ -197,7 +274,7 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
197
274
  const c = computed.get(s.rank);
198
275
  return (react_1.default.createElement(StrategyTile_1.StrategyTile, { key: s.rank, s: Object.assign(Object.assign({}, s), { lo: (_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo, hi: (_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi }), delay: 0.2 + i * 0.06, onClick: () => setSelected(s), maxSavings: maxSavings }));
199
276
  })),
200
- react_1.default.createElement("div", { className: "rounded-[14px] overflow-hidden mb-4", style: {
277
+ !hasLlm && (react_1.default.createElement("div", { className: "rounded-[14px] overflow-hidden mb-4", style: {
201
278
  background: "#0E1132",
202
279
  border: "1px solid rgba(36,131,132,0.12)",
203
280
  boxShadow: "0 2px 8px rgba(6,8,33,0.3)",
@@ -210,7 +287,7 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
210
287
  react_1.default.createElement("span", { className: "text-[13px] text-tax-axis-text" }, a.name),
211
288
  react_1.default.createElement("div", { className: "flex items-center gap-2.5" },
212
289
  react_1.default.createElement("span", { className: "text-[11px] font-tax-axis-mono text-tax-axis-text-2" }, a.savings),
213
- react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: a.priority === "MEDIUM" ? "orange" : "neutral", size: "xs" }, a.priority)))))),
290
+ react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: a.priority === "MEDIUM" ? "orange" : "neutral", size: "xs" }, a.priority))))))),
214
291
  react_1.default.createElement("div", { className: "rounded-[10px] mb-4 text-xs text-tax-axis-text leading-[1.7] font-tax-axis-body", style: {
215
292
  padding: "14px 18px",
216
293
  background: "#0E1132",
@@ -1,9 +1,10 @@
1
1
  import React from "react";
2
2
  import { DocSpec } from "../../lib/types";
3
- export type DocStatus = "empty" | "validating" | "valid";
3
+ export type DocStatus = "empty" | "validating" | "parsing" | "failed" | "valid";
4
4
  export interface DocState extends DocSpec {
5
5
  status: DocStatus;
6
6
  fileName: string | null;
7
+ parseError?: string | null;
7
8
  }
8
9
  interface DocumentCardProps {
9
10
  doc: DocState;
@@ -13,6 +14,7 @@ interface DocumentCardProps {
13
14
  helpOverride?: string;
14
15
  onUpload: (file: File) => void;
15
16
  onClear: () => void;
17
+ onRemove?: () => void;
16
18
  }
17
- export declare function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, }: DocumentCardProps): React.JSX.Element;
19
+ export declare function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, onRemove, }: DocumentCardProps): React.JSX.Element;
18
20
  export {};
@@ -6,8 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DocumentCard = DocumentCard;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const TaxAxisBadge_1 = require("../shared/TaxAxisBadge");
9
- // Inline style values per status — mock T hex values for SVG strokes and
10
- // rgba borders that don't have direct Tailwind token equivalents.
11
9
  const STATUS_STYLES = {
12
10
  empty: {
13
11
  iconBg: "#171B44",
@@ -19,17 +17,28 @@ const STATUS_STYLES = {
19
17
  iconBorder: "rgba(251,154,29,0.25)",
20
18
  cardBorder: "rgba(251,154,29,0.25)",
21
19
  },
20
+ parsing: {
21
+ iconBg: "rgba(36,131,132,0.08)",
22
+ iconBorder: "rgba(36,131,132,0.25)",
23
+ cardBorder: "rgba(36,131,132,0.25)",
24
+ },
25
+ failed: {
26
+ iconBg: "rgba(197,48,48,0.08)",
27
+ iconBorder: "rgba(197,48,48,0.25)",
28
+ cardBorder: "rgba(197,48,48,0.25)",
29
+ },
22
30
  valid: {
23
31
  iconBg: "rgba(15,110,86,0.08)",
24
32
  iconBorder: "rgba(15,110,86,0.25)",
25
33
  cardBorder: "rgba(15,110,86,0.25)",
26
34
  },
27
35
  };
28
- function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, }) {
29
- var _a;
36
+ function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, onRemove, }) {
37
+ var _a, _b, _c;
30
38
  const ss = STATUS_STYLES[doc.status];
31
39
  const leftBorder = doc.status === "valid" ? "rgba(15,110,86,0.6)" : tierBorderColor;
32
40
  const fileInputId = `tax-axis-upload-${doc.id}`;
41
+ const reuploadInputId = `tax-axis-reupload-${doc.id}`;
33
42
  return (react_1.default.createElement("div", { className: "bg-tax-axis-surface overflow-hidden", style: {
34
43
  border: `1px solid ${ss.cardBorder}`,
35
44
  borderLeft: `3px solid ${leftBorder}`,
@@ -43,20 +52,42 @@ function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, hel
43
52
  } }, doc.status === "validating" ? (react_1.default.createElement("div", { className: "w-3.5 h-3.5 rounded-full animate-spin", style: {
44
53
  border: "2px solid transparent",
45
54
  borderTopColor: "#FB9A1D",
46
- } })) : doc.status === "valid" ? (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
55
+ } })) : doc.status === "parsing" ? (react_1.default.createElement("div", { className: "w-3.5 h-3.5 rounded-full animate-spin", style: {
56
+ border: "2px solid transparent",
57
+ borderTopColor: "#248384",
58
+ } })) : doc.status === "failed" ? (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
59
+ react_1.default.createElement("path", { d: "M4 4l6 6M10 4L4 10", stroke: "#C53030", strokeWidth: "2", strokeLinecap: "round" }))) : doc.status === "valid" ? (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
47
60
  react_1.default.createElement("path", { d: "M2.5 7l3.5 3.5 5.5-6", stroke: "#0F6E56", strokeWidth: "2", strokeLinecap: "round" }))) : (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
48
61
  react_1.default.createElement("circle", { cx: "7", cy: "7", r: "5", stroke: "#9498B8", strokeWidth: "1.5", strokeDasharray: "3 3" })))),
49
62
  react_1.default.createElement("div", { className: "flex-1 min-w-0" },
50
63
  react_1.default.createElement("div", { className: "flex items-center gap-1.5" },
51
64
  react_1.default.createElement("span", { className: "text-[13px] font-medium text-white font-tax-axis-body" }, doc.name),
52
- doc.status === "empty" && (react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: tierBadgeColor, size: "xs" }, tierBadgeText))),
53
- react_1.default.createElement("div", { className: "text-[11px] text-tax-axis-text-3 font-tax-axis-body mt-0.5" }, (_a = doc.fileName) !== null && _a !== void 0 ? _a : (helpOverride !== null && helpOverride !== void 0 ? helpOverride : doc.help))),
65
+ doc.status === "empty" && (react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: tierBadgeColor, size: "xs" }, tierBadgeText)),
66
+ doc.status === "failed" && (react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: "red", size: "xs" }, "FAILED"))),
67
+ react_1.default.createElement("div", { className: "text-[11px] text-tax-axis-text-3 font-tax-axis-body mt-0.5" }, doc.status === "failed" && doc.parseError
68
+ ? doc.parseError
69
+ : (_a = doc.fileName) !== null && _a !== void 0 ? _a : (helpOverride !== null && helpOverride !== void 0 ? helpOverride : doc.help))),
54
70
  react_1.default.createElement("div", { className: "flex items-center gap-2 flex-shrink-0" },
55
71
  doc.status === "valid" && (react_1.default.createElement(react_1.default.Fragment, null,
56
- react_1.default.createElement("span", { className: "text-[10px] font-semibold font-tax-axis-mono", style: { color: "#0F6E56" } }, "Done"),
72
+ react_1.default.createElement("span", { className: "text-[10px] font-semibold font-tax-axis-mono", style: { color: "#0F6E56" } }, "Parsed"),
57
73
  react_1.default.createElement("button", { onClick: onClear, className: "bg-transparent border-none p-1 text-tax-axis-text-4 text-sm cursor-pointer" }, "\u00D7"))),
74
+ (doc.status === "parsing" || doc.status === "validating") && (react_1.default.createElement("span", { className: "text-[10px] font-semibold font-tax-axis-mono", style: { color: doc.status === "parsing" ? "#248384" : "#FB9A1D" } }, doc.status === "parsing" ? "Parsing…" : "Uploading…")),
75
+ doc.status === "failed" && (react_1.default.createElement(react_1.default.Fragment, null,
76
+ react_1.default.createElement("input", { id: reuploadInputId, type: "file", className: "hidden", accept: (_b = doc.accept) === null || _b === void 0 ? void 0 : _b.join(","), onChange: (event) => {
77
+ var _a;
78
+ const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
79
+ if (file) {
80
+ onUpload(file);
81
+ }
82
+ event.currentTarget.value = "";
83
+ } }),
84
+ react_1.default.createElement("label", { htmlFor: reuploadInputId, className: "rounded-md px-2.5 py-1 text-[10px] font-semibold text-tax-axis-teal-light font-tax-axis-mono cursor-pointer", style: {
85
+ background: "rgba(36,131,132,0.10)",
86
+ border: "1px solid rgba(36,131,132,0.2)",
87
+ } }, "Re-upload"),
88
+ react_1.default.createElement("button", { onClick: onRemove || onClear, className: "bg-transparent border-none p-1 text-tax-axis-text-4 text-sm cursor-pointer", title: "Remove document" }, "\u00D7"))),
58
89
  doc.status === "empty" && (react_1.default.createElement(react_1.default.Fragment, null,
59
- react_1.default.createElement("input", { id: fileInputId, type: "file", className: "hidden", onChange: (event) => {
90
+ react_1.default.createElement("input", { id: fileInputId, type: "file", className: "hidden", accept: (_c = doc.accept) === null || _c === void 0 ? void 0 : _c.join(","), onChange: (event) => {
60
91
  var _a;
61
92
  const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
62
93
  if (file) {
@@ -16,6 +16,7 @@ interface DocumentTierProps {
16
16
  helpOverrides: Record<string, string>;
17
17
  onUpload: (idx: number, file: File) => void;
18
18
  onClear: (idx: number) => void;
19
+ onRemove?: (idx: number) => void;
19
20
  }
20
- export declare function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, }: DocumentTierProps): React.JSX.Element | null;
21
+ export declare function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, onRemove, }: DocumentTierProps): React.JSX.Element | null;
21
22
  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, }) {
9
+ function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, onRemove, }) {
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, }) {
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) }))))));
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 }))))));
23
23
  }
@@ -5,6 +5,20 @@ export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
5
5
  profile: ClientProfile;
6
6
  onContinue: () => void;
7
7
  onBack: () => void;
8
- onUploadDocument?: (doc: DocState, file: File) => Promise<void>;
8
+ onUploadDocument?: (doc: DocState, file: File) => Promise<void | {
9
+ documentId?: string;
10
+ fileName?: string;
11
+ status?: string;
12
+ documentType?: string;
13
+ parseError?: string | null;
14
+ }>;
15
+ onDeleteDocument?: (documentId: string) => Promise<void>;
16
+ fetchUploadedDocuments?: () => Promise<Array<{
17
+ fileName: string;
18
+ status: string;
19
+ documentType?: string | null;
20
+ parseError?: string | null;
21
+ updatedAt?: string | null;
22
+ }>>;
9
23
  }
10
- export declare function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
24
+ export declare function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
@@ -37,7 +37,6 @@ 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
- // Stub filenames assigned when the user clicks "Upload" (mock App.jsx:1237)
41
40
  const STUB_FILENAMES = {
42
41
  "1120s": "2025_1120S.pdf",
43
42
  "state-return": "2025_State_Return.pdf",
@@ -48,8 +47,6 @@ const STUB_FILENAMES = {
48
47
  "fixed-assets": "Fixed_Assets.xlsx",
49
48
  "prior-returns": "Prior_Returns.pdf",
50
49
  };
51
- // Short help text shown below each doc name when not yet uploaded
52
- // (mock App.jsx:1384). Overrides the longer DOC_SPECS_BASE.help.
53
50
  const HELP_OVERRIDES = {
54
51
  "1120s": "Improves accuracy of every strategy",
55
52
  "payroll": "Critical for Entity Structure and QBI calculations",
@@ -58,7 +55,6 @@ const HELP_OVERRIDES = {
58
55
  "cashflow": "Helps with income deferral and timing strategies",
59
56
  "prior-returns": "Improves confidence intervals (more years = tighter estimates)",
60
57
  };
61
- // Tier groupings from mock (App.jsx:1379-1383)
62
58
  const TIER_DEFS = [
63
59
  {
64
60
  key: "required",
@@ -91,33 +87,128 @@ const TIER_DEFS = [
91
87
  ids: ["state-return", "cashflow", "prior-returns"],
92
88
  },
93
89
  ];
94
- function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, userContext: _userContext = "expert", }) {
90
+ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext = "expert", }) {
91
+ const normalizeDocumentType = (value) => String(value || "").toUpperCase().replace(/[^A-Z0-9]+/g, "_");
95
92
  const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(profile), [profile]);
96
- const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null }))));
93
+ const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null, parseError: null }))));
94
+ const [uploadedDocIds, setUploadedDocIds] = (0, react_1.useState)({});
95
+ const [continueError, setContinueError] = (0, react_1.useState)(null);
96
+ const pollForParseStatus = (idx, fileName, expectedDocumentType) => __awaiter(this, void 0, void 0, function* () {
97
+ if (!fetchUploadedDocuments)
98
+ return;
99
+ const maxAttempts = 80;
100
+ const pollIntervalMs = 1500;
101
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
102
+ try {
103
+ 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;
109
+ const match = candidates
110
+ .slice()
111
+ .sort((a, b) => new Date(String(b.updatedAt || "")).getTime() -
112
+ new Date(String(a.updatedAt || "")).getTime())[0];
113
+ if (match) {
114
+ const status = String(match.status || "").toUpperCase();
115
+ if (status === "FAILED") {
116
+ setDocs((prev) => prev.map((d, i) => i === idx
117
+ ? Object.assign(Object.assign({}, d), { status: "failed", fileName, parseError: match.parseError || "Parsing failed. Try a different file." }) : d));
118
+ return;
119
+ }
120
+ if (status === "PARSED") {
121
+ setDocs((prev) => prev.map((d, i) => i === idx
122
+ ? Object.assign(Object.assign({}, d), { status: "valid", fileName, parseError: null }) : d));
123
+ return;
124
+ }
125
+ if (status === "PARSING" || status === "UPLOADED") {
126
+ setDocs((prev) => prev.map((d, i) => i === idx
127
+ ? Object.assign(Object.assign({}, d), { status: "parsing", fileName }) : d));
128
+ }
129
+ }
130
+ }
131
+ catch (pollError) {
132
+ // keep polling
133
+ }
134
+ yield new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
135
+ }
136
+ setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "failed", fileName, parseError: "Parsing timed out. Please try again." }) : d));
137
+ });
97
138
  const handleUpload = (idx, file) => __awaiter(this, void 0, void 0, function* () {
98
139
  const selectedDoc = docs[idx];
99
140
  if (!selectedDoc)
100
141
  return;
142
+ setContinueError(null);
101
143
  setDocs((prev) => prev.map((d, i) => i === idx
102
- ? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name || STUB_FILENAMES[d.id] || "document.pdf" }) : d));
144
+ ? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name || STUB_FILENAMES[d.id] || "document.pdf", parseError: null }) : d));
103
145
  try {
104
- if (onUploadDocument) {
105
- yield onUploadDocument(selectedDoc, file);
146
+ const uploadResult = onUploadDocument
147
+ ? yield onUploadDocument(selectedDoc, file)
148
+ : undefined;
149
+ const nextFileName = file.name || STUB_FILENAMES[selectedDoc.id] || "document.pdf";
150
+ const expectedDocumentType = normalizeDocumentType(selectedDoc.id);
151
+ const immediateStatus = String((uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.status) || "").toUpperCase();
152
+ if (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.documentId) {
153
+ setUploadedDocIds((prev) => (Object.assign(Object.assign({}, prev), { [idx]: uploadResult.documentId })));
154
+ }
155
+ if (immediateStatus === "PARSED") {
156
+ setDocs((prev) => prev.map((d, i) => i === idx
157
+ ? Object.assign(Object.assign({}, d), { status: "valid", fileName: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.fileName) || nextFileName, parseError: null }) : d));
158
+ return;
159
+ }
160
+ if (immediateStatus === "FAILED") {
161
+ setDocs((prev) => prev.map((d, i) => i === idx
162
+ ? 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
+ return;
106
164
  }
107
165
  setDocs((prev) => prev.map((d, i) => i === idx
108
- ? Object.assign(Object.assign({}, d), { status: "valid", fileName: file.name || STUB_FILENAMES[d.id] || "document.pdf" }) : d));
166
+ ? Object.assign(Object.assign({}, d), { status: "parsing", fileName: nextFileName }) : d));
167
+ yield pollForParseStatus(idx, nextFileName, expectedDocumentType);
109
168
  }
110
169
  catch (uploadError) {
170
+ const errorMessage = uploadError instanceof Error
171
+ ? uploadError.message
172
+ : "Upload failed. Please try again.";
111
173
  setDocs((prev) => prev.map((d, i) => i === idx
112
- ? Object.assign(Object.assign({}, d), { status: "empty", fileName: null }) : d));
174
+ ? Object.assign(Object.assign({}, d), { status: "failed", parseError: errorMessage }) : d));
113
175
  }
114
176
  });
115
177
  const handleClear = (idx) => {
116
178
  setDocs((prev) => prev.map((d, i) => i === idx
117
- ? Object.assign(Object.assign({}, d), { status: "empty", fileName: null }) : d));
179
+ ? Object.assign(Object.assign({}, d), { status: "empty", fileName: null, parseError: null }) : d));
180
+ setContinueError(null);
181
+ };
182
+ const handleRemove = (idx) => __awaiter(this, void 0, void 0, function* () {
183
+ const docId = uploadedDocIds[idx];
184
+ if (docId && onDeleteDocument) {
185
+ try {
186
+ yield onDeleteDocument(docId);
187
+ }
188
+ catch (deleteError) {
189
+ // still clear from UI even if backend delete fails
190
+ }
191
+ }
192
+ setUploadedDocIds((prev) => {
193
+ const next = Object.assign({}, prev);
194
+ delete next[idx];
195
+ return next;
196
+ });
197
+ handleClear(idx);
198
+ });
199
+ const handleContinue = () => {
200
+ const failedDocs = docs.filter((d) => d.status === "failed");
201
+ if (failedDocs.length > 0) {
202
+ const names = failedDocs.map((d) => d.name).join(", ");
203
+ setContinueError(`Cannot continue — ${failedDocs.length === 1 ? "this document has" : "these documents have"} errors: ${names}. Please re-upload correct files or remove them.`);
204
+ return;
205
+ }
206
+ setContinueError(null);
207
+ onContinue();
118
208
  };
119
209
  const validCount = docs.filter((d) => d.status === "valid").length;
120
- // Coverage heuristic from mock (App.jsx:1361)
210
+ const failedCount = docs.filter((d) => d.status === "failed").length;
211
+ const busyCount = docs.filter((d) => d.status === "validating" || d.status === "parsing").length;
121
212
  const coveragePct = validCount === 0
122
213
  ? 32
123
214
  : validCount === 1
@@ -166,8 +257,17 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, userC
166
257
  coveragePct,
167
258
  "% coverage"),
168
259
  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, onUpload: handleUpload, onClear: handleClear }))),
260
+ continueError && (react_1.default.createElement("div", { className: "py-3 px-4 mb-4 text-xs leading-relaxed font-tax-axis-body rounded-lg", style: {
261
+ background: "rgba(197,48,48,0.08)",
262
+ border: "1px solid rgba(197,48,48,0.3)",
263
+ color: "#FEB2B2",
264
+ } }, 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 }))),
170
266
  react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
171
267
  react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
172
- react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onContinue, className: "flex-1" }, "Run Analysis"))));
268
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: handleContinue, className: "flex-1", disabled: busyCount > 0 }, busyCount > 0
269
+ ? "Processing..."
270
+ : failedCount > 0
271
+ ? `Continue (${failedCount} failed)`
272
+ : "Continue"))));
173
273
  }
@@ -8,14 +8,14 @@ exports.DOC_SPECS_BASE = void 0;
8
8
  exports.getDocSpecs = getDocSpecs;
9
9
  const states_1 = require("./states");
10
10
  exports.DOC_SPECS_BASE = [
11
- { id: "1120s", name: "Federal Tax Return (1120S/1065/1040)", accept: [".pdf"], strategies: ["QBI \u00A7199A", "S-Corp Structure", "\u00A7179 Expensing", "Bonus Depreciation", "Business Interest \u00A7163(j)"], required: "optional", help: "Optional \u2014 startups or new entities may not have prior returns. Improves accuracy if available." },
12
- { id: "state-return", name: "State Return(s)", accept: [".pdf"], strategies: ["SALT / PTE Optimization", "State Nexus Analysis"], required: "conditional", help: "Required if client operates in income-tax states. Not needed for WY, NV, TX, FL, etc." },
13
- { id: "payroll", name: "Payroll Records (W-3 / 941)", accept: [".pdf", ".xlsx", ".csv"], strategies: ["S-Corp Salary", "HSA Maximization", "WOTC", "Overtime Deduction"], required: "optional", help: "Optional for V1 \u2014 intake questions substitute for most payroll data." },
14
- { id: "pnl", name: "Profit & Loss Statement (Current Year)", accept: [".pdf", ".xlsx", ".csv"], strategies: ["QBI \u00A7199A", "R&D Credit", "Business Meals", "Professional Fees", "Home Office"], required: true, help: "Required \u2014 current year P&L is the minimum input for savings calculations." },
15
- { id: "balance", name: "Balance Sheet (Current Year)", accept: [".pdf", ".xlsx"], strategies: ["Business Interest \u00A7163(j)", "\u00A7179 Expensing", "Opportunity Zone"], required: true, help: "Required \u2014 needed for asset-based strategies and financial health assessment." },
16
- { id: "cashflow", name: "Cash Flow Statement", accept: [".pdf", ".xlsx"], strategies: ["Income Deferral", "Tax Method Elections", "Estimated Tax Timing"], required: "optional", help: "Recommended \u2014 helps with income deferral and timing strategies." },
17
- { id: "fixed-assets", name: "Fixed Asset Schedule", accept: [".pdf", ".xlsx"], strategies: ["\u00A7179", "Bonus Depreciation \u00A7168(k)", "Cost Segregation"], required: "conditional", help: "Required for real estate clients (cost segregation). Optional otherwise." },
18
- { id: "prior-returns", name: "Prior Year Returns (2022\u20132024)", accept: [".pdf"], strategies: ["R&E Catch-Up \u00A7174A", "Charitable Bunching", "Confidence Intervals"], required: false, help: "More years = tighter estimates. 1 year: \u00B130% range. 2\u20133 years: \u00B115%. 4\u20135 years: \u00B15%." },
11
+ { id: "1120s", name: "Federal Tax Return (1120S/1065/1040)", accept: [".pdf", ".docx", ".doc"], strategies: ["QBI \u00A7199A", "S-Corp Structure", "\u00A7179 Expensing", "Bonus Depreciation", "Business Interest \u00A7163(j)"], required: "optional", help: "Optional \u2014 startups or new entities may not have prior returns. Improves accuracy if available." },
12
+ { id: "state-return", name: "State Return(s)", accept: [".pdf", ".docx", ".doc"], strategies: ["SALT / PTE Optimization", "State Nexus Analysis"], required: "conditional", help: "Required if client operates in income-tax states. Not needed for WY, NV, TX, FL, etc." },
13
+ { id: "payroll", name: "Payroll Records (W-3 / 941)", accept: [".pdf", ".xlsx", ".csv", ".docx", ".doc"], strategies: ["S-Corp Salary", "HSA Maximization", "WOTC", "Overtime Deduction"], required: "optional", help: "Optional for V1 \u2014 intake questions substitute for most payroll data." },
14
+ { id: "pnl", name: "Profit & Loss Statement (Current Year)", accept: [".pdf", ".xlsx", ".csv", ".docx", ".doc"], strategies: ["QBI \u00A7199A", "R&D Credit", "Business Meals", "Professional Fees", "Home Office"], required: true, help: "Required \u2014 current year P&L is the minimum input for savings calculations." },
15
+ { id: "balance", name: "Balance Sheet (Current Year)", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["Business Interest \u00A7163(j)", "\u00A7179 Expensing", "Opportunity Zone"], required: true, help: "Required \u2014 needed for asset-based strategies and financial health assessment." },
16
+ { id: "cashflow", name: "Cash Flow Statement", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["Income Deferral", "Tax Method Elections", "Estimated Tax Timing"], required: "optional", help: "Recommended \u2014 helps with income deferral and timing strategies." },
17
+ { id: "fixed-assets", name: "Fixed Asset Schedule", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["\u00A7179", "Bonus Depreciation \u00A7168(k)", "Cost Segregation"], required: "conditional", help: "Required for real estate clients (cost segregation). Optional otherwise." },
18
+ { id: "prior-returns", name: "Prior Year Returns (2022\u20132024)", accept: [".pdf", ".docx", ".doc"], strategies: ["R&E Catch-Up \u00A7174A", "Charitable Bunching", "Confidence Intervals"], required: false, help: "More years = tighter estimates. 1 year: \u00B130% range. 2\u20133 years: \u00B115%. 4\u20135 years: \u00B15%." },
19
19
  ];
20
20
  function getDocSpecs(profile) {
21
21
  const states = (profile === null || profile === void 0 ? void 0 : profile.states) || [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paro.io/expert-shared-components",
3
- "version": "1.14.49",
3
+ "version": "1.14.50",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {