@paro.io/expert-shared-components 1.14.71 → 1.14.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/DocumentCenter/ClientDocumentsTable.js +20 -23
- package/lib/components/TaxAxis/TaxAxisApi.d.ts +8 -0
- package/lib/components/TaxAxis/TaxAxisShell.js +27 -7
- package/lib/tax-axis/components/clientReport/ExecutiveSummary.d.ts +4 -2
- package/lib/tax-axis/components/clientReport/ExecutiveSummary.js +8 -6
- package/lib/tax-axis/components/clientReport/ImplementationRoadmap.d.ts +4 -3
- package/lib/tax-axis/components/clientReport/ImplementationRoadmap.js +2 -2
- package/lib/tax-axis/components/clientReport/TaxAxisClientReport.js +14 -5
- package/lib/tax-axis/components/intake/IntakeCtaCards.d.ts +8 -2
- package/lib/tax-axis/components/intake/IntakeCtaCards.js +70 -7
- package/lib/tax-axis/components/intake/TaxAxisIntake.js +1 -1
- package/lib/tax-axis/components/processing/TaxAxisProcessing.d.ts +5 -1
- package/lib/tax-axis/components/processing/TaxAxisProcessing.js +27 -11
- package/lib/tax-axis/components/prospectReport/ProspectNextSteps.js +3 -1
- package/lib/tax-axis/components/prospectReport/ProspectReportSkeleton.d.ts +2 -0
- package/lib/tax-axis/components/prospectReport/ProspectReportSkeleton.js +78 -0
- package/lib/tax-axis/components/prospectReport/ProspectStrategyCard.d.ts +7 -1
- package/lib/tax-axis/components/prospectReport/ProspectStrategyCard.js +8 -6
- package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.d.ts +33 -1
- package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.js +57 -56
- package/lib/tax-axis/lib/adapters/useEngineOutput.js +39 -3
- package/lib/tax-axis/lib/documentFieldCatalog.d.ts +5 -10
- package/lib/tax-axis/lib/documentFieldCatalog.js +115 -329
- package/package.json +1 -1
|
@@ -70,7 +70,7 @@ exports.useStyles = (0, core_1.makeStyles)({
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
function descendingComparator(a, b, orderBy) {
|
|
73
|
-
var _a, _b, _c, _d
|
|
73
|
+
var _a, _b, _c, _d;
|
|
74
74
|
if (orderBy === 'docSize') {
|
|
75
75
|
//@ts-ignore
|
|
76
76
|
const sizeA = (_a = a === null || a === void 0 ? void 0 : a.docSize) !== null && _a !== void 0 ? _a : 0;
|
|
@@ -89,34 +89,31 @@ function descendingComparator(a, b, orderBy) {
|
|
|
89
89
|
const dateA = a === null || a === void 0 ? void 0 : a[orderBy];
|
|
90
90
|
//@ts-ignore
|
|
91
91
|
const dateB = b === null || b === void 0 ? void 0 : b[orderBy];
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
return
|
|
95
|
-
|
|
96
|
-
//@ts-ignore
|
|
97
|
-
if (dateB > dateA) {
|
|
92
|
+
// Handle empty or missing dates - push them to the end
|
|
93
|
+
if (!dateA && !dateB)
|
|
94
|
+
return 0;
|
|
95
|
+
if (!dateA)
|
|
98
96
|
return 1;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
else if (orderBy === 'clientName') {
|
|
97
|
+
if (!dateB)
|
|
98
|
+
return -1;
|
|
99
|
+
// Convert to Date objects for proper comparison
|
|
103
100
|
//@ts-ignore
|
|
104
|
-
const
|
|
101
|
+
const timeA = new Date(dateA).getTime();
|
|
105
102
|
//@ts-ignore
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
103
|
+
const timeB = new Date(dateB).getTime();
|
|
104
|
+
if (timeB < timeA) {
|
|
108
105
|
return -1;
|
|
109
106
|
}
|
|
110
|
-
if (
|
|
107
|
+
if (timeB > timeA) {
|
|
111
108
|
return 1;
|
|
112
109
|
}
|
|
113
110
|
return 0;
|
|
114
111
|
}
|
|
115
|
-
else if (orderBy === 'fileName' || orderBy === 'docType' || orderBy === 'projectName' || orderBy === 'uploadedBy') {
|
|
112
|
+
else if (orderBy === 'fileName' || orderBy === 'docType' || orderBy === 'clientName' || orderBy === 'expertName' || orderBy === 'projectName' || orderBy === 'projectStatus' || orderBy === 'uploadedBy' || orderBy === 'lastViewedBy') {
|
|
116
113
|
//@ts-ignore
|
|
117
|
-
const valueA = (
|
|
114
|
+
const valueA = (_c = a === null || a === void 0 ? void 0 : a[orderBy]) !== null && _c !== void 0 ? _c : "";
|
|
118
115
|
//@ts-ignore
|
|
119
|
-
const valueB = (
|
|
116
|
+
const valueB = (_d = b === null || b === void 0 ? void 0 : b[orderBy]) !== null && _d !== void 0 ? _d : "";
|
|
120
117
|
if (valueB < valueA) {
|
|
121
118
|
return -1;
|
|
122
119
|
}
|
|
@@ -126,7 +123,7 @@ function descendingComparator(a, b, orderBy) {
|
|
|
126
123
|
return 0;
|
|
127
124
|
}
|
|
128
125
|
else {
|
|
129
|
-
(0, utils_1.compareItems)(a, b, orderBy);
|
|
126
|
+
return (0, utils_1.compareItems)(a, b, orderBy);
|
|
130
127
|
}
|
|
131
128
|
}
|
|
132
129
|
const getKeyByValue = (object, value) => {
|
|
@@ -171,9 +168,9 @@ const ClientDocumentsTable = ({ legacyFreelancerId, expertFiles, setExpertClient
|
|
|
171
168
|
expertName: (file === null || file === void 0 ? void 0 : file.freelancerName) || "-",
|
|
172
169
|
projectName: (file === null || file === void 0 ? void 0 : file.projectName) || "-",
|
|
173
170
|
projectStatus: file === null || file === void 0 ? void 0 : file.projectStatus,
|
|
174
|
-
uploadedDate: (
|
|
171
|
+
uploadedDate: (file === null || file === void 0 ? void 0 : file.uploadedDate) || '',
|
|
175
172
|
uploadedBy: (file === null || file === void 0 ? void 0 : file.uploadedBy) || "-",
|
|
176
|
-
lastViewed: (file === null || file === void 0 ? void 0 : file.lastViewed)
|
|
173
|
+
lastViewed: (file === null || file === void 0 ? void 0 : file.lastViewed) || '',
|
|
177
174
|
lastViewedBy: file === null || file === void 0 ? void 0 : file.lastViewedBy,
|
|
178
175
|
projectId: file === null || file === void 0 ? void 0 : file.projectId,
|
|
179
176
|
};
|
|
@@ -363,8 +360,8 @@ const ClientDocumentsTable = ({ legacyFreelancerId, expertFiles, setExpertClient
|
|
|
363
360
|
react_1.default.createElement(core_1.TableCell, { className: classes.tableCell, align: "center" }, isClientPortal ? expertName : clientName),
|
|
364
361
|
react_1.default.createElement(core_1.TableCell, { className: classes.tableCell, align: "center" }, projectName),
|
|
365
362
|
react_1.default.createElement(core_1.TableCell, { className: classes.tableCell, align: "center" }, projectStatus),
|
|
366
|
-
react_1.default.createElement(core_1.TableCell, { className: classes.tableCell, align: "center" }, uploadedDate),
|
|
367
|
-
react_1.default.createElement(core_1.TableCell, { className: classes.tableCell, align: "center" }, `${lastViewedBy ? `Viewed by ${lastViewedBy === 'CLIENT' ? 'Client on ' : 'Expert on '}` : ''}${lastViewed ? lastViewed : 'Not opened'}`),
|
|
363
|
+
react_1.default.createElement(core_1.TableCell, { className: classes.tableCell, align: "center" }, uploadedDate ? (0, utils_1.formatDate)(uploadedDate) : '-'),
|
|
364
|
+
react_1.default.createElement(core_1.TableCell, { className: classes.tableCell, align: "center" }, `${lastViewedBy ? `Viewed by ${lastViewedBy === 'CLIENT' ? 'Client on ' : 'Expert on '}` : ''}${lastViewed ? (0, utils_1.formatDate)(lastViewed) : 'Not opened'}`),
|
|
368
365
|
react_1.default.createElement(core_1.TableCell, { className: classes.tableCell },
|
|
369
366
|
react_1.default.createElement(core_1.Grid, { item: true, container: true, direction: "row", justify: "space-evenly", alignItems: "center" },
|
|
370
367
|
react_1.default.createElement(core_1.Tooltip, { arrow: true, placement: "top", interactive: true, className: "whitespace-nowrap", title: "Download Document" },
|
|
@@ -55,4 +55,12 @@ export type TaxAxisApi = {
|
|
|
55
55
|
generatePdf: (sessionId: string) => Promise<any>;
|
|
56
56
|
getArtifacts: (sessionId: string) => Promise<any[]>;
|
|
57
57
|
importQboReport?: (sessionId: string, expertId: string, year: number, reportType: string) => Promise<any>;
|
|
58
|
+
generateProspectReport?: (input: {
|
|
59
|
+
freelancerId?: number | null;
|
|
60
|
+
clientProfile: Record<string, unknown>;
|
|
61
|
+
}) => Promise<{
|
|
62
|
+
reportId: string;
|
|
63
|
+
computePayload: Record<string, unknown>;
|
|
64
|
+
outputPayload: Record<string, unknown>;
|
|
65
|
+
}>;
|
|
58
66
|
};
|
|
@@ -147,6 +147,8 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
|
|
|
147
147
|
const [busyMessage, setBusyMessage] = (0, react_1.useState)('Syncing Tax Axis session...');
|
|
148
148
|
const [reportReady, setReportReady] = (0, react_1.useState)(false);
|
|
149
149
|
const isPollingRef = react_1.default.useRef(false);
|
|
150
|
+
const [prospectLoading, setProspectLoading] = (0, react_1.useState)(false);
|
|
151
|
+
const [prospectData, setProspectData] = (0, react_1.useState)(null);
|
|
150
152
|
// QBO state driven by EPS props — not localStorage
|
|
151
153
|
const [qboConnectedState, setQboConnectedState] = (0, react_1.useState)(!!qboConnected);
|
|
152
154
|
const [qboCompanyNameState, setQboCompanyNameState] = (0, react_1.useState)(qboCompanyName !== null && qboCompanyName !== void 0 ? qboCompanyName : null);
|
|
@@ -242,11 +244,30 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
|
|
|
242
244
|
setReportReady(false);
|
|
243
245
|
updateSessionId(null);
|
|
244
246
|
}, [updateSessionId]);
|
|
245
|
-
const handleProspect = (0,
|
|
247
|
+
const handleProspect = (nextProfile) => __awaiter(void 0, void 0, void 0, function* () {
|
|
246
248
|
setProfile(nextProfile);
|
|
247
249
|
setIsProspectFlow(true);
|
|
250
|
+
setProspectData(null);
|
|
251
|
+
setProspectLoading(true);
|
|
248
252
|
setStep('PROSPECT_REPORT');
|
|
249
|
-
|
|
253
|
+
try {
|
|
254
|
+
if (taxAxisApi.generateProspectReport) {
|
|
255
|
+
const result = yield taxAxisApi.generateProspectReport({
|
|
256
|
+
clientProfile: nextProfile,
|
|
257
|
+
});
|
|
258
|
+
setProspectData({
|
|
259
|
+
computePayload: result.computePayload,
|
|
260
|
+
outputPayload: result.outputPayload,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
console.error('[TaxAxisShell] generateProspectReport failed', err);
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
setProspectLoading(false);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
250
271
|
const handleFullAnalysis = (0, react_1.useCallback)((nextProfile) => __awaiter(void 0, void 0, void 0, function* () {
|
|
251
272
|
setProfile(nextProfile);
|
|
252
273
|
setIsProspectFlow(false);
|
|
@@ -415,7 +436,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
|
|
|
415
436
|
}
|
|
416
437
|
}), [sessionId, taxAxisApi]);
|
|
417
438
|
const currentView = (0, react_1.useMemo)(() => {
|
|
418
|
-
var _a, _b, _c, _d;
|
|
439
|
+
var _a, _b, _c, _d, _e, _f;
|
|
419
440
|
if (step === 'SESSION_SETUP') {
|
|
420
441
|
return (react_1.default.createElement(ShellContainer, null,
|
|
421
442
|
react_1.default.createElement(TaxAxisIntake_1.TaxAxisIntake, { userContext: userContext, initialProfile: initialProfile, onProspect: handleProspect, onFullAnalysis: handleFullAnalysis })));
|
|
@@ -426,8 +447,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
|
|
|
426
447
|
}
|
|
427
448
|
switch (step) {
|
|
428
449
|
case 'PROSPECT_REPORT':
|
|
429
|
-
return (react_1.default.createElement(ShellContainer, null,
|
|
430
|
-
react_1.default.createElement(TaxAxisProspectReport_1.TaxAxisProspectReport, { profile: profile, userContext: userContext, onUpgrade: () => setStep('DOCUMENT_UPLOAD'), onPresent: () => setStep('PRESENTATION'), onReset: handleReset })));
|
|
450
|
+
return (react_1.default.createElement(ShellContainer, null, prospectLoading ? (react_1.default.createElement(TaxAxisProcessing_1.TaxAxisProcessing, { profile: profile, reportReady: false, userContext: userContext, crawlStep: 1.2, mode: "prospect", onComplete: () => { } })) : (react_1.default.createElement(TaxAxisProspectReport_1.TaxAxisProspectReport, { profile: profile, userContext: userContext, backendComputePayload: (_a = prospectData === null || prospectData === void 0 ? void 0 : prospectData.computePayload) !== null && _a !== void 0 ? _a : null, outputPayload: (_b = prospectData === null || prospectData === void 0 ? void 0 : prospectData.outputPayload) !== null && _b !== void 0 ? _b : null, onUpgrade: () => window.open('/tax-axis/documents', '_blank', 'noopener'), onPresent: () => setStep('PRESENTATION'), onReset: handleReset }))));
|
|
431
451
|
case 'DOCUMENT_UPLOAD':
|
|
432
452
|
return (react_1.default.createElement(ShellContainer, null,
|
|
433
453
|
react_1.default.createElement("div", { className: "flex items-center justify-between rounded-lg px-4 py-3 mb-5", style: {
|
|
@@ -491,10 +511,10 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
|
|
|
491
511
|
react_1.default.createElement(TaxAxisDashboard_1.TaxAxisDashboard, { profile: profile, userContext: userContext, llmResult: llmResult, parsedDocuments: parsedDocuments, onDownloadClient: () => setStep('CLIENT_REPORT'), onDownloadPreparer: () => setStep('PREPARER_WORKPAPER'), onPresent: () => setStep('PRESENTATION'), onSend: handleSendReport, onReviewData: () => setStep('PARSED_REVIEW'), onReset: handleReset })));
|
|
492
512
|
case 'CLIENT_REPORT':
|
|
493
513
|
return (react_1.default.createElement(ShellContainer, { fullWidth: true },
|
|
494
|
-
react_1.default.createElement(TaxAxisClientReport_1.TaxAxisClientReport, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onNavigatePreparer: () => setStep('PREPARER_WORKPAPER'), liveStrategies: adapted === null || adapted === void 0 ? void 0 : adapted.strategies, liveComputedMap: adapted === null || adapted === void 0 ? void 0 : adapted.computedMap, engineRawOutput: (
|
|
514
|
+
react_1.default.createElement(TaxAxisClientReport_1.TaxAxisClientReport, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onNavigatePreparer: () => setStep('PREPARER_WORKPAPER'), liveStrategies: adapted === null || adapted === void 0 ? void 0 : adapted.strategies, liveComputedMap: adapted === null || adapted === void 0 ? void 0 : adapted.computedMap, engineRawOutput: (_c = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) !== null && _c !== void 0 ? _c : llmResult === null || llmResult === void 0 ? void 0 : llmResult.engineOutput })));
|
|
495
515
|
case 'PREPARER_WORKPAPER':
|
|
496
516
|
return (react_1.default.createElement(ShellContainer, { fullWidth: true },
|
|
497
|
-
react_1.default.createElement(TaxAxisPreparerWorkpaper_1.TaxAxisPreparerWorkpaper, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onToggleToClient: () => setStep('CLIENT_REPORT'), liveStrategies: adapted === null || adapted === void 0 ? void 0 : adapted.strategies, liveComputedMap: adapted === null || adapted === void 0 ? void 0 : adapted.computedMap, cpaWorkflow: (
|
|
517
|
+
react_1.default.createElement(TaxAxisPreparerWorkpaper_1.TaxAxisPreparerWorkpaper, { profile: profile, userContext: userContext, onBack: () => setStep('DASHBOARD'), onToggleToClient: () => setStep('CLIENT_REPORT'), liveStrategies: adapted === null || adapted === void 0 ? void 0 : adapted.strategies, liveComputedMap: adapted === null || adapted === void 0 ? void 0 : adapted.computedMap, cpaWorkflow: (_d = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _d === void 0 ? void 0 : _d.cpa_workflow, riskDisclosures: (_e = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _e === void 0 ? void 0 : _e.risk_disclosures, businessProfile: (_f = llmResult === null || llmResult === void 0 ? void 0 : llmResult.rawOutput) === null || _f === void 0 ? void 0 : _f.business_profile })));
|
|
498
518
|
case 'PRESENTATION':
|
|
499
519
|
return (react_1.default.createElement(ShellContainer, { fullWidth: true },
|
|
500
520
|
react_1.default.createElement(TaxAxisPresentationMode_1.TaxAxisPresentationMode, { profile: profile, userContext: userContext, onBack: () => setStep(isProspectFlow ? 'PROSPECT_REPORT' : 'DASHBOARD') })));
|
|
@@ -7,12 +7,14 @@ interface ExecutiveSummaryProps {
|
|
|
7
7
|
computed: ComputedMap;
|
|
8
8
|
totalLo: number;
|
|
9
9
|
totalHi: number;
|
|
10
|
-
|
|
10
|
+
leadCount: number;
|
|
11
|
+
leadMidK: number;
|
|
12
|
+
leadLabel: string;
|
|
11
13
|
top3: Strategy[];
|
|
12
14
|
palette: Palette;
|
|
13
15
|
effectiveTaxRate?: number;
|
|
14
16
|
confidenceTier?: string;
|
|
15
17
|
dataYears?: number;
|
|
16
18
|
}
|
|
17
|
-
export declare function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi,
|
|
19
|
+
export declare function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi, leadCount, leadMidK, leadLabel, top3, palette, effectiveTaxRate, confidenceTier, dataYears }: ExecutiveSummaryProps): React.JSX.Element;
|
|
18
20
|
export {};
|
|
@@ -10,16 +10,18 @@ const SectionOpener_1 = require("./SectionOpener");
|
|
|
10
10
|
const ETRChart_1 = require("./ETRChart");
|
|
11
11
|
// Format K values: >= 1000K -> "$X.XM", otherwise "$XK"
|
|
12
12
|
const fmtKRange = (k) => k >= 1000 ? "$" + (k / 1000).toFixed(1) + "M" : "$" + k + "K";
|
|
13
|
-
function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi,
|
|
13
|
+
function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi, leadCount, leadMidK, leadLabel, top3, palette, effectiveTaxRate, confidenceTier, dataYears }) {
|
|
14
|
+
const actionLabel = leadLabel === "this week" ? "Immediate Actions"
|
|
15
|
+
: leadLabel === "within 30 days" ? "30-Day Actions"
|
|
16
|
+
: leadLabel === "within 90 days" ? "90-Day Actions"
|
|
17
|
+
: "Planned Actions";
|
|
14
18
|
const bizName = profile.bizName || "Client";
|
|
15
19
|
const rev = parseInt((profile.revenue || "0").replace(/,/g, "")) || 500000;
|
|
16
20
|
const dataYearsLabel = dataYears ? `${dataYears} year${dataYears > 1 ? "s" : ""}` : (profile.taxDataYears || "1 year");
|
|
17
21
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
18
22
|
react_1.default.createElement(SectionOpener_1.SectionOpener, { number: "01", eyebrow: "EXECUTIVE SUMMARY", headline: "Where " + bizName + "'s tax dollars are going \u2014 and where they don't have to.", bullets: [
|
|
19
23
|
eligible.length + " strategies apply to your current profile, with combined estimated savings of " + fmtKRange(totalLo) + "\u2013" + fmtKRange(totalHi) + " annually.",
|
|
20
|
-
|
|
21
|
-
? nowCount + " can be initiated this week without any structural changes to your business."
|
|
22
|
-
: "Implementation timeline spans the current tax year with no structural changes required.",
|
|
24
|
+
leadCount + " " + (leadCount === 1 ? "strategy" : "strategies") + " can be initiated " + leadLabel + ", representing roughly $" + leadMidK + "K of the midpoint savings estimate.",
|
|
23
25
|
"Analysis is based on " + dataYearsLabel + " of financial data and current law as of April 2026, including OBBBA provisions.",
|
|
24
26
|
], palette: palette }),
|
|
25
27
|
react_1.default.createElement("div", { style: { marginBottom: 28 } },
|
|
@@ -28,7 +30,7 @@ function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi, nowCo
|
|
|
28
30
|
react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [
|
|
29
31
|
{ v: fmtKRange(totalLo) + "\u2013" + fmtKRange(totalHi), l: "Est. Annual Savings" },
|
|
30
32
|
{ v: String(eligible.length), l: "Strategies Identified" },
|
|
31
|
-
{ v: String(
|
|
33
|
+
{ v: String(leadCount), l: actionLabel },
|
|
32
34
|
{ v: String(top3.length), l: "Priority Recommendations" },
|
|
33
35
|
].map(({ v, l }) => (react_1.default.createElement("div", { key: l },
|
|
34
36
|
react_1.default.createElement("div", { style: { fontSize: 22, fontWeight: 800, color: palette.teal, fontFamily: palette.head, lineHeight: 1.2, marginBottom: 4 } }, v),
|
|
@@ -46,7 +48,7 @@ function ExecutiveSummary({ profile, eligible, computed, totalLo, totalHi, nowCo
|
|
|
46
48
|
react_1.default.createElement("div", { style: { fontSize: 14, color: palette.gray700, lineHeight: 1.8, fontFamily: palette.body, marginBottom: 16 } },
|
|
47
49
|
top3.length,
|
|
48
50
|
" strategies represent the highest-impact opportunities and are detailed below. ",
|
|
49
|
-
|
|
51
|
+
"Of these and the broader eligible set, " + leadCount + " can be initiated " + leadLabel + ".",
|
|
50
52
|
" Savings estimates reflect your current revenue of ",
|
|
51
53
|
rev >= 1000000 ? "$" + (rev / 1000000).toFixed(1) + "M" : "$" + Math.round(rev / 1000) + "K",
|
|
52
54
|
", an effective tax rate of ",
|
|
@@ -15,13 +15,14 @@ interface ImplementationRoadmapProps {
|
|
|
15
15
|
eligible: Strategy[];
|
|
16
16
|
computed: ComputedMap;
|
|
17
17
|
top3: Strategy[];
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
leadCount: number;
|
|
19
|
+
leadMidK: number;
|
|
20
|
+
leadLabel: string;
|
|
20
21
|
bucketDefs: BucketDef[];
|
|
21
22
|
bucketTotals: Record<string, BucketTotal>;
|
|
22
23
|
activeBuckets: BucketDef[];
|
|
23
24
|
palette: Palette;
|
|
24
25
|
taxYear?: string;
|
|
25
26
|
}
|
|
26
|
-
export declare function ImplementationRoadmap({ eligible, computed, top3,
|
|
27
|
+
export declare function ImplementationRoadmap({ eligible, computed, top3, leadCount, leadMidK, leadLabel, bucketDefs, bucketTotals, activeBuckets, palette, taxYear, }: ImplementationRoadmapProps): React.JSX.Element;
|
|
27
28
|
export {};
|
|
@@ -9,7 +9,7 @@ const data_1 = require("../../lib/data");
|
|
|
9
9
|
const SectionOpener_1 = require("./SectionOpener");
|
|
10
10
|
const QuarterlyCashChart_1 = require("./QuarterlyCashChart");
|
|
11
11
|
const ImplementationTimelineChart_1 = require("./ImplementationTimelineChart");
|
|
12
|
-
function ImplementationRoadmap({ eligible, computed, top3,
|
|
12
|
+
function ImplementationRoadmap({ eligible, computed, top3, leadCount, leadMidK, leadLabel, bucketDefs, bucketTotals, activeBuckets, palette, taxYear, }) {
|
|
13
13
|
const card = {
|
|
14
14
|
background: palette.white,
|
|
15
15
|
border: "1px solid " + palette.gray200,
|
|
@@ -27,7 +27,7 @@ function ImplementationRoadmap({ eligible, computed, top3, nowCount, nowMidK, bu
|
|
|
27
27
|
};
|
|
28
28
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
29
29
|
react_1.default.createElement(SectionOpener_1.SectionOpener, { number: "03", eyebrow: "IMPLEMENTATION ROADMAP", headline: "Most savings can be locked in within the current quarter.", bullets: [
|
|
30
|
-
|
|
30
|
+
leadCount + " eligible " + (leadCount === 1 ? "strategy" : "strategies") + " can be initiated " + leadLabel + ", representing roughly $" + leadMidK + "K of the midpoint savings estimate.",
|
|
31
31
|
"Time-sensitive elections and filings have firm deadlines noted in each strategy detail above.",
|
|
32
32
|
"Year-end planning items will be reviewed in a coordinated session before December 31.",
|
|
33
33
|
], palette: palette }),
|
|
@@ -35,7 +35,7 @@ const RecommendedStrategies_1 = require("./RecommendedStrategies");
|
|
|
35
35
|
const ImplementationRoadmap_1 = require("./ImplementationRoadmap");
|
|
36
36
|
const Methodology_1 = require("./Methodology");
|
|
37
37
|
function TaxAxisClientReport({ profile, onBack, onNavigatePreparer, liveStrategies, liveComputedMap, engineRawOutput }) {
|
|
38
|
-
var _a, _b, _c
|
|
38
|
+
var _a, _b, _c;
|
|
39
39
|
const [view, setView] = (0, react_1.useState)("client");
|
|
40
40
|
const staticEligible = (0, react_1.useMemo)(() => (0, compute_1.filterEligibleStrategies)(profile), [profile]);
|
|
41
41
|
const staticComputed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(profile), [profile]);
|
|
@@ -68,8 +68,17 @@ function TaxAxisClientReport({ profile, onBack, onNavigatePreparer, liveStrategi
|
|
|
68
68
|
bucketTotals[b.key] = { strategies, lo, hi };
|
|
69
69
|
});
|
|
70
70
|
const activeBuckets = bucketDefs.filter(b => bucketTotals[b.key].strategies.length > 0);
|
|
71
|
-
|
|
72
|
-
const
|
|
71
|
+
// Lead bucket: first non-empty bucket for AT A GLANCE summaries
|
|
72
|
+
const leadBucket = activeBuckets[0];
|
|
73
|
+
const leadBt = leadBucket ? bucketTotals[leadBucket.key] : undefined;
|
|
74
|
+
const leadCount = (leadBt === null || leadBt === void 0 ? void 0 : leadBt.strategies.length) || 0;
|
|
75
|
+
const leadMidK = Math.round((((leadBt === null || leadBt === void 0 ? void 0 : leadBt.lo) || 0) + ((leadBt === null || leadBt === void 0 ? void 0 : leadBt.hi) || 0)) / 2 / 1000);
|
|
76
|
+
const leadLabel = leadBucket
|
|
77
|
+
? leadBucket.key === "now" ? "this week"
|
|
78
|
+
: leadBucket.key === "30d" ? "within 30 days"
|
|
79
|
+
: leadBucket.key === "90d" ? "within 90 days"
|
|
80
|
+
: "before year-end"
|
|
81
|
+
: "this quarter";
|
|
73
82
|
const handleToggle = (v) => {
|
|
74
83
|
if (v === "preparer" && onNavigatePreparer) {
|
|
75
84
|
onNavigatePreparer();
|
|
@@ -117,12 +126,12 @@ function TaxAxisClientReport({ profile, onBack, onNavigatePreparer, liveStrategi
|
|
|
117
126
|
react_1.default.createElement("div", { style: { maxWidth: 680, margin: "0 auto", padding: "32px 20px 60px" } },
|
|
118
127
|
react_1.default.createElement(ClientReportCover_1.ClientReportCover, { profile: profile, palette: palette_1.P }),
|
|
119
128
|
react_1.default.createElement(ClientReportTOC_1.ClientReportTOC, { bizName: bizName, palette: palette_1.P }),
|
|
120
|
-
react_1.default.createElement(ExecutiveSummary_1.ExecutiveSummary, { profile: profile, eligible: eligible, computed: computed, totalLo: totalLo, totalHi: totalHi,
|
|
129
|
+
react_1.default.createElement(ExecutiveSummary_1.ExecutiveSummary, { profile: profile, eligible: eligible, computed: computed, totalLo: totalLo, totalHi: totalHi, leadCount: leadCount, leadMidK: leadMidK, leadLabel: leadLabel, top3: top3, palette: palette_1.P, effectiveTaxRate: (_a = engineRawOutput === null || engineRawOutput === void 0 ? void 0 : engineRawOutput.business_profile) === null || _a === void 0 ? void 0 : _a.effective_tax_rate, confidenceTier: (_b = engineRawOutput === null || engineRawOutput === void 0 ? void 0 : engineRawOutput.business_profile) === null || _b === void 0 ? void 0 : _b.confidence_tier, dataYears: (_c = engineRawOutput === null || engineRawOutput === void 0 ? void 0 : engineRawOutput.business_profile) === null || _c === void 0 ? void 0 : _c.data_years }),
|
|
121
130
|
react_1.default.createElement("div", { style: { background: palette_1.P.gray50, borderLeft: "3px solid " + palette_1.P.gray300, borderRadius: "0 8px 8px 0", padding: "14px 20px", marginBottom: 20, fontSize: 11, color: palette_1.P.gray500, lineHeight: 1.7, fontFamily: palette_1.P.body } },
|
|
122
131
|
"This summary was prepared using TaxAxis, Paro's AI-powered tax analysis engine. Savings estimates are based on financial data provided for tax year " + profile.year + " (" + (profile.taxDataYears || "1 year") + " of data). All figures are estimates. Your tax preparer has reviewed these recommendations.",
|
|
123
132
|
hasOBBBA && " Some strategies reference provisions from the One Big Beautiful Bill Act (OBBBA), signed July 4, 2025, with limited IRS guidance."),
|
|
124
133
|
react_1.default.createElement(RecommendedStrategies_1.RecommendedStrategies, { profile: profile, eligible: eligible, computed: computed, top3: top3, hasOBBBA: hasOBBBA, palette: palette_1.P, interactionWarnings: engineRawOutput === null || engineRawOutput === void 0 ? void 0 : engineRawOutput.strategy_interaction_warnings }),
|
|
125
|
-
react_1.default.createElement(ImplementationRoadmap_1.ImplementationRoadmap, { eligible: eligible, computed: computed, top3: top3,
|
|
134
|
+
react_1.default.createElement(ImplementationRoadmap_1.ImplementationRoadmap, { eligible: eligible, computed: computed, top3: top3, leadCount: leadCount, leadMidK: leadMidK, leadLabel: leadLabel, bucketDefs: bucketDefs, bucketTotals: bucketTotals, activeBuckets: activeBuckets, palette: palette_1.P, taxYear: profile.year }),
|
|
126
135
|
react_1.default.createElement(Methodology_1.Methodology, { profile: profile, eligible: eligible, palette: palette_1.P }),
|
|
127
136
|
react_1.default.createElement("div", { style: { padding: "16px 20px", marginBottom: 16, fontSize: 10, color: palette_1.P.gray400, lineHeight: 1.7, fontFamily: palette_1.P.body } }, "This summary was generated by TaxAxis, an AI-powered analysis tool by Paro. Recommendations are based on financial data provided and current tax law as of April 2026, including OBBBA provisions. These are estimates, not guarantees. Consult your tax preparer before implementing any strategy. This is not legal or financial advice."),
|
|
128
137
|
react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "12px 0", borderTop: "1px solid " + palette_1.P.gray200, fontSize: 10, color: palette_1.P.gray400, fontFamily: palette_1.P.mono } },
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { UserContext } from "../../lib/types";
|
|
2
|
+
import type { ClientProfile, UserContext } from "../../lib/types";
|
|
3
|
+
interface ProspectReadiness {
|
|
4
|
+
ready: boolean;
|
|
5
|
+
missingFields: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function isProspectReady(profile: Partial<ClientProfile>): ProspectReadiness;
|
|
3
8
|
interface IntakeCtaCardsProps {
|
|
9
|
+
profile: Partial<ClientProfile>;
|
|
4
10
|
onProspect: () => void;
|
|
5
11
|
onFullAnalysis: () => void;
|
|
6
12
|
userContext?: UserContext;
|
|
7
13
|
}
|
|
8
|
-
export declare function IntakeCtaCards({ onProspect, onFullAnalysis, userContext, }: IntakeCtaCardsProps): React.JSX.Element;
|
|
14
|
+
export declare function IntakeCtaCards({ profile, onProspect, onFullAnalysis, userContext, }: IntakeCtaCardsProps): React.JSX.Element;
|
|
9
15
|
export {};
|
|
@@ -1,21 +1,84 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
4
24
|
};
|
|
5
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.isProspectReady = isProspectReady;
|
|
6
27
|
exports.IntakeCtaCards = IntakeCtaCards;
|
|
7
|
-
const react_1 =
|
|
28
|
+
const react_1 = __importStar(require("react"));
|
|
8
29
|
const TaxAxisButton_1 = require("../shared/TaxAxisButton");
|
|
9
30
|
const TaxAxisBadge_1 = require("../shared/TaxAxisBadge");
|
|
10
|
-
function
|
|
31
|
+
function isProspectReady(profile) {
|
|
32
|
+
const missing = [];
|
|
33
|
+
if (!profile.entity)
|
|
34
|
+
missing.push("Entity Type");
|
|
35
|
+
if (!profile.states || profile.states.length === 0)
|
|
36
|
+
missing.push("States of Operation");
|
|
37
|
+
if (!profile.federalRate)
|
|
38
|
+
missing.push("Federal Marginal Rate");
|
|
39
|
+
if (!profile.stateRate)
|
|
40
|
+
missing.push("State Marginal Rate");
|
|
41
|
+
if (!profile.taxDataYears)
|
|
42
|
+
missing.push("Tax Data Years Available");
|
|
43
|
+
if (!profile.riskTolerance)
|
|
44
|
+
missing.push("Client Risk Tolerance");
|
|
45
|
+
const hasFinancial = parseFloat(profile.netIncome || "0") > 0 ||
|
|
46
|
+
parseFloat(profile.equipmentPurchased || "0") > 0 ||
|
|
47
|
+
parseFloat(profile.capitalGains || "0") > 0 ||
|
|
48
|
+
parseFloat(profile.revenue || "0") > 0;
|
|
49
|
+
if (!hasFinancial)
|
|
50
|
+
missing.push("Annual Revenue or Net Income (at least one > 0)");
|
|
51
|
+
return { ready: missing.length === 0, missingFields: missing };
|
|
52
|
+
}
|
|
53
|
+
function IntakeCtaCards({ profile, onProspect, onFullAnalysis, userContext = "expert", }) {
|
|
54
|
+
const [showTooltip, setShowTooltip] = (0, react_1.useState)(false);
|
|
55
|
+
const { ready, missingFields } = isProspectReady(profile);
|
|
11
56
|
return (react_1.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3" },
|
|
12
57
|
react_1.default.createElement("div", { className: "bg-tax-axis-surface border border-tax-axis-border rounded-xl p-4 min-h-[140px] flex flex-col transition-all" },
|
|
13
58
|
react_1.default.createElement("div", { className: "text-[11px] font-bold text-tax-axis-orange uppercase tracking-widest mb-2.5 font-tax-axis-body" }, "Prospect Report"),
|
|
14
59
|
react_1.default.createElement("div", { className: "text-xs text-tax-axis-text-3 font-tax-axis-body mb-3" }, "Top 3 strategies \u00B7 Savings ranges \u00B7 ~2 min \u00B7 No docs"),
|
|
15
60
|
react_1.default.createElement("div", { className: "flex-1" }),
|
|
16
|
-
react_1.default.createElement(
|
|
17
|
-
? "
|
|
18
|
-
|
|
61
|
+
react_1.default.createElement("div", { className: "relative", onMouseEnter: () => !ready && setShowTooltip(true), onMouseLeave: () => setShowTooltip(false) },
|
|
62
|
+
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "orange", onClick: ready ? onProspect : undefined, className: `w-full text-xs ${!ready ? "opacity-40 cursor-not-allowed" : ""}`, disabled: !ready }, userContext === "cpa-firm-client"
|
|
63
|
+
? "Generate My Savings Report"
|
|
64
|
+
: "Generate Prospect Report"),
|
|
65
|
+
!ready && showTooltip && (react_1.default.createElement("div", { style: {
|
|
66
|
+
position: "absolute",
|
|
67
|
+
bottom: "calc(100% + 8px)",
|
|
68
|
+
left: 0,
|
|
69
|
+
right: 0,
|
|
70
|
+
background: "#1a1f3d",
|
|
71
|
+
border: "1px solid #2a2f5d",
|
|
72
|
+
borderRadius: 8,
|
|
73
|
+
padding: "10px 12px",
|
|
74
|
+
zIndex: 50,
|
|
75
|
+
boxShadow: "0 4px 24px rgba(0,0,0,0.5)",
|
|
76
|
+
} },
|
|
77
|
+
react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, color: "#f97316", marginBottom: 6, textTransform: "uppercase", letterSpacing: "0.1em" } }, "Required to generate:"),
|
|
78
|
+
missingFields.map((f) => (react_1.default.createElement("div", { key: f, style: { fontSize: 11, color: "#94a3b8", marginBottom: 2, display: "flex", alignItems: "center", gap: 6 } },
|
|
79
|
+
react_1.default.createElement("span", { style: { color: "#f97316", fontSize: 9 } }, "\u25CF"),
|
|
80
|
+
" ",
|
|
81
|
+
f))))))),
|
|
19
82
|
react_1.default.createElement("div", { className: "bg-tax-axis-surface border border-tax-axis-teal rounded-xl p-4 min-h-[140px] flex flex-col transition-all", style: {
|
|
20
83
|
background: "radial-gradient(ellipse at 50% 0%,rgba(36,131,132,0.06) 0%,transparent 70%) #0F1330",
|
|
21
84
|
boxShadow: "0 0 30px rgba(36,131,132,0.15)",
|
|
@@ -32,7 +32,7 @@ function TaxAxisIntake({ userContext = "expert", onProspect, onFullAnalysis, ini
|
|
|
32
32
|
react_1.default.createElement(ClientParametersSection_1.ClientParametersSection, { userContext: userContext }),
|
|
33
33
|
react_1.default.createElement(RefineAnalysisSection_1.RefineAnalysisSection, { userContext: userContext }),
|
|
34
34
|
react_1.default.createElement(CpaIntakeQuestionsSection_1.CpaIntakeQuestionsSection, { userContext: userContext }),
|
|
35
|
-
react_1.default.createElement(IntakeCtaCards_1.IntakeCtaCards, { onProspect: handleProspect, onFullAnalysis: handleFull, userContext: userContext })),
|
|
35
|
+
react_1.default.createElement(IntakeCtaCards_1.IntakeCtaCards, { profile: profile, onProspect: handleProspect, onFullAnalysis: handleFull, userContext: userContext })),
|
|
36
36
|
react_1.default.createElement("div", { className: "hidden sm:block sm:sticky sm:top-5 sm:self-start" },
|
|
37
37
|
react_1.default.createElement(StrategyRadar_1.StrategyRadar, { profile: profile })))));
|
|
38
38
|
}
|
|
@@ -5,5 +5,9 @@ export interface TaxAxisProcessingProps extends TaxAxisScreenProps {
|
|
|
5
5
|
profile?: ClientProfile;
|
|
6
6
|
/** Set to true by the shell poller when stage===REPORT_READY is detected. */
|
|
7
7
|
reportReady?: boolean;
|
|
8
|
+
/** Override crawl speed. Default: 0.27%/tick (full analysis ~110s). Prospect report: use ~6 for ~15s. */
|
|
9
|
+
crawlStep?: number;
|
|
10
|
+
/** 'prospect' shows lightweight stages without document parsing steps */
|
|
11
|
+
mode?: 'full' | 'prospect';
|
|
8
12
|
}
|
|
9
|
-
export declare function TaxAxisProcessing({ onComplete, profile, reportReady, userContext: _userContext, }: TaxAxisProcessingProps): React.JSX.Element;
|
|
13
|
+
export declare function TaxAxisProcessing({ onComplete, profile, reportReady, crawlStep, mode, userContext: _userContext, }: TaxAxisProcessingProps): React.JSX.Element;
|
|
@@ -31,8 +31,21 @@ const CRAWL_TICK_MS = 300; // interval between crawl ticks
|
|
|
31
31
|
const CRAWL_STEP = 0.27; // % added each tick → 0.27 / 0.3s = 0.9%/s
|
|
32
32
|
const FINISH_MS = 400; // ms to animate from 99% → 100% once ready
|
|
33
33
|
const STAGE_ADVANCE_MS = 3200; // how often the displayed stage advances
|
|
34
|
-
function buildStages(profile) {
|
|
34
|
+
function buildStages(profile, mode) {
|
|
35
35
|
var _a;
|
|
36
|
+
const entity = (profile === null || profile === void 0 ? void 0 : profile.entity) || "your business";
|
|
37
|
+
const stateCount = ((_a = profile === null || profile === void 0 ? void 0 : profile.states) === null || _a === void 0 ? void 0 : _a.length) || 1;
|
|
38
|
+
const bizName = (profile === null || profile === void 0 ? void 0 : profile.bizName) || "your business";
|
|
39
|
+
if (mode === 'prospect') {
|
|
40
|
+
return [
|
|
41
|
+
`Classifying ${entity} profile`,
|
|
42
|
+
"Injecting IRS 2026 + OBBBA parameters",
|
|
43
|
+
`Screening 25 strategies across ${stateCount} state${stateCount > 1 ? "s" : ""}`,
|
|
44
|
+
"Computing deterministic tax math",
|
|
45
|
+
"Generating strategy narratives",
|
|
46
|
+
`Assembling prospect report for ${bizName}`,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
36
49
|
if (!profile) {
|
|
37
50
|
return [
|
|
38
51
|
"Parsing source documents",
|
|
@@ -42,9 +55,6 @@ function buildStages(profile) {
|
|
|
42
55
|
"Generating IRS citations",
|
|
43
56
|
];
|
|
44
57
|
}
|
|
45
|
-
const entity = profile.entity || "your business";
|
|
46
|
-
const stateCount = ((_a = profile.states) === null || _a === void 0 ? void 0 : _a.length) || 1;
|
|
47
|
-
const bizName = profile.bizName || "your business";
|
|
48
58
|
const base = [
|
|
49
59
|
"Parsing source documents",
|
|
50
60
|
"Injecting IRS 2026 parameters",
|
|
@@ -60,10 +70,16 @@ function buildStages(profile) {
|
|
|
60
70
|
base.push("Screening 30 eligible strategies", "Computing deterministic tax math", "Cross-validating source documents", "Generating IRS citations", `Assembling TaxAxis report for ${bizName}`);
|
|
61
71
|
return base;
|
|
62
72
|
}
|
|
63
|
-
function TaxAxisProcessing({ onComplete, profile, reportReady = false, userContext: _userContext = "expert", }) {
|
|
64
|
-
const stages = (0, react_1.useMemo)(() => buildStages(profile), [profile]);
|
|
73
|
+
function TaxAxisProcessing({ onComplete, profile, reportReady = false, crawlStep, mode, userContext: _userContext = "expert", }) {
|
|
74
|
+
const stages = (0, react_1.useMemo)(() => buildStages(profile, mode), [profile, mode]);
|
|
65
75
|
const [progress, setProgress] = (0, react_1.useState)(0);
|
|
66
76
|
const [stageIdx, setStageIdx] = (0, react_1.useState)(0);
|
|
77
|
+
const effectiveCrawlStep = crawlStep !== null && crawlStep !== void 0 ? crawlStep : CRAWL_STEP;
|
|
78
|
+
// Stable refs so effects don't restart when props change after mount
|
|
79
|
+
const crawlStepRef = (0, react_1.useRef)(effectiveCrawlStep);
|
|
80
|
+
const onCompleteRef = (0, react_1.useRef)(onComplete);
|
|
81
|
+
(0, react_1.useEffect)(() => { crawlStepRef.current = effectiveCrawlStep; }, [effectiveCrawlStep]);
|
|
82
|
+
(0, react_1.useEffect)(() => { onCompleteRef.current = onComplete; }, [onComplete]);
|
|
67
83
|
const finishingRef = (0, react_1.useRef)(false);
|
|
68
84
|
const completedRef = (0, react_1.useRef)(false);
|
|
69
85
|
// Track current progress in a ref so the finish animation can read it synchronously
|
|
@@ -97,7 +113,7 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
|
|
|
97
113
|
progressRef.current = v;
|
|
98
114
|
setProgress(v);
|
|
99
115
|
};
|
|
100
|
-
// ── Crawl: 0 →
|
|
116
|
+
// ── Crawl: 0 → 99% — runs once on mount, reads crawlStepRef so prop changes don't restart it ──
|
|
101
117
|
(0, react_1.useEffect)(() => {
|
|
102
118
|
if (reportReady || finishingRef.current)
|
|
103
119
|
return;
|
|
@@ -106,14 +122,14 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
|
|
|
106
122
|
clearInterval(iv);
|
|
107
123
|
return;
|
|
108
124
|
}
|
|
109
|
-
const next = Math.min(progressRef.current +
|
|
125
|
+
const next = Math.min(progressRef.current + crawlStepRef.current, CRAWL_CEILING);
|
|
110
126
|
setProgressSync(next);
|
|
111
127
|
if (next >= CRAWL_CEILING)
|
|
112
128
|
clearInterval(iv);
|
|
113
129
|
}, CRAWL_TICK_MS);
|
|
114
130
|
return () => clearInterval(iv);
|
|
115
|
-
//
|
|
116
|
-
}, [
|
|
131
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
132
|
+
}, []);
|
|
117
133
|
// ── Stage labels: advance every STAGE_ADVANCE_MS ─────────────────
|
|
118
134
|
(0, react_1.useEffect)(() => {
|
|
119
135
|
const iv = setInterval(() => {
|
|
@@ -142,7 +158,7 @@ function TaxAxisProcessing({ onComplete, profile, reportReady = false, userConte
|
|
|
142
158
|
setProgressSync(100);
|
|
143
159
|
if (!completedRef.current) {
|
|
144
160
|
completedRef.current = true;
|
|
145
|
-
setTimeout(
|
|
161
|
+
setTimeout(() => onCompleteRef.current(), 150);
|
|
146
162
|
}
|
|
147
163
|
}
|
|
148
164
|
};
|
|
@@ -25,7 +25,9 @@ function ProspectNextSteps({ sectionNum, bizName, onUpgrade, onPresent, onPrint,
|
|
|
25
25
|
bizName,
|
|
26
26
|
"'s documents to run the full TaxAxis analysis \u2014 computed savings with calculation traces, IRS citations, and a client-ready report."),
|
|
27
27
|
react_1.default.createElement("div", { style: { display: "flex", gap: 10, flexWrap: "wrap" } },
|
|
28
|
-
react_1.default.createElement(GlowBtn, { onClick:
|
|
28
|
+
react_1.default.createElement(GlowBtn, { onClick: () => { if (onUpgrade) {
|
|
29
|
+
onUpgrade();
|
|
30
|
+
} } }, "Upload Documents"),
|
|
29
31
|
react_1.default.createElement(GlowBtn, { onClick: onPresent, style: { fontSize: 13 } }, "Present to Client"),
|
|
30
32
|
react_1.default.createElement(GlowBtn, { secondary: true, onClick: onPrint, style: { fontSize: 13 } }, "Download PDF")),
|
|
31
33
|
react_1.default.createElement("div", { style: { fontSize: 11, color: theme_1.T.text4, marginTop: 12, fontFamily: theme_1.T.body } }, "Typical turnaround: same day. No obligation until you review the results.")))),
|