@paro.io/expert-shared-components 1.14.72 → 1.14.74
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/intake/ClientParametersSection.js +7 -0
- 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/RefineAnalysisSection.js +1 -1
- package/lib/tax-axis/components/intake/StrategyRadar.js +1 -1
- package/lib/tax-axis/components/intake/TaxAxisIntake.js +1 -1
- package/lib/tax-axis/components/intake/intakeSchema.js +15 -15
- 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/SampleAnalysisPreview.d.ts +4 -1
- package/lib/tax-axis/components/prospectReport/SampleAnalysisPreview.js +17 -8
- package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.d.ts +33 -1
- package/lib/tax-axis/components/prospectReport/TaxAxisProspectReport.js +66 -57
- package/lib/tax-axis/lib/compute/index.js +10 -2
- 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') })));
|
|
@@ -80,6 +80,13 @@ function ClientParametersSection({ userContext = "expert", }) {
|
|
|
80
80
|
setValue("period", "YTD");
|
|
81
81
|
}
|
|
82
82
|
}, [isCurrentOrFuture, setValue]);
|
|
83
|
+
// Auto-select 21% federal rate for C-Corporations (flat TCJA rate)
|
|
84
|
+
const selectedEntity = watch("entity");
|
|
85
|
+
react_1.default.useEffect(() => {
|
|
86
|
+
if (selectedEntity === "C-Corporation") {
|
|
87
|
+
setValue("federalRate", "21%");
|
|
88
|
+
}
|
|
89
|
+
}, [selectedEntity, setValue]);
|
|
83
90
|
const [expanded, setExpanded] = (0, react_1.useState)(true);
|
|
84
91
|
return (react_1.default.createElement(TaxAxisCard_1.TaxAxisCard, null,
|
|
85
92
|
react_1.default.createElement("div", { onClick: () => setExpanded((prev) => !prev), className: "flex items-center justify-between cursor-pointer", style: { marginBottom: expanded ? 14 : 0 } },
|
|
@@ -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)",
|
|
@@ -42,7 +42,7 @@ const SSTB_OPTIONS = ["Yes", "No", "Unsure"];
|
|
|
42
42
|
const REAL_ESTATE_OPTIONS = ["No", "Yes — Commercial", "Yes — Residential", "Yes — Both"];
|
|
43
43
|
const ITEMIZES_OPTIONS = ["Yes", "No", "Unsure"];
|
|
44
44
|
const SINGLE_OWNER_OPTIONS = ["Yes", "No"];
|
|
45
|
-
const FEDERAL_RATE_OPTIONS = ["10%", "12%", "22%", "24%", "32%", "35%", "37%"];
|
|
45
|
+
const FEDERAL_RATE_OPTIONS = ["10%", "12%", "21%", "22%", "24%", "32%", "35%", "37%"];
|
|
46
46
|
const TAX_DATA_YEARS_OPTIONS = ["1 year", "2 years", "3 years", "4 years", "5 years"];
|
|
47
47
|
// Risk Tolerance: mock stores first char ("1"-"5") as value.
|
|
48
48
|
// NOTE: intakeDefaultValues sets riskTolerance to "M" (spec) but the mock
|
|
@@ -110,7 +110,7 @@ function StrategyRadar({ profile }) {
|
|
|
110
110
|
// so [profile] as a dep would never re-fire the memo on field changes
|
|
111
111
|
const profileKey = JSON.stringify(profile);
|
|
112
112
|
const computed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(safeProfile), [profileKey]);
|
|
113
|
-
const fedRate = parseFloat((profile.federalRate || "24").replace("%", "")) / 100 || 0.24;
|
|
113
|
+
const fedRate = entity === "C-Corporation" ? 0.21 : parseFloat((profile.federalRate || "24").replace("%", "")) / 100 || 0.24;
|
|
114
114
|
const stateRate = parseFloat(profile.stateRate || "4.95") / 100;
|
|
115
115
|
const netIncome = parseInt((profile.netIncome || "0").replace(/,/g, "")) || Math.round(rev * 0.3);
|
|
116
116
|
let loSum = eligibleStrategies.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.lo) !== null && _b !== void 0 ? _b : s.lo); }, 0);
|
|
@@ -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
|
}
|
|
@@ -59,33 +59,33 @@ exports.intakeSchema = Yup.object().shape({
|
|
|
59
59
|
period: Yup.string().ensure(),
|
|
60
60
|
});
|
|
61
61
|
exports.intakeDefaultValues = {
|
|
62
|
-
bizName: "",
|
|
63
|
-
cpaName: "",
|
|
62
|
+
bizName: "Ironclad Systems, Inc.",
|
|
63
|
+
cpaName: "Amar",
|
|
64
64
|
entity: "C-Corporation",
|
|
65
|
-
industry: "
|
|
66
|
-
revenue: "",
|
|
67
|
-
netIncome: "",
|
|
68
|
-
ownerComp: "",
|
|
69
|
-
employees: "",
|
|
70
|
-
year: "
|
|
71
|
-
states: [],
|
|
65
|
+
industry: "Technology",
|
|
66
|
+
revenue: "2150000",
|
|
67
|
+
netIncome: "410000",
|
|
68
|
+
ownerComp: "405000",
|
|
69
|
+
employees: "11",
|
|
70
|
+
year: "2023",
|
|
71
|
+
states: ["GA"],
|
|
72
72
|
filingStatus: "MFJ",
|
|
73
|
-
age: "",
|
|
73
|
+
age: "45",
|
|
74
74
|
sstb: "No",
|
|
75
75
|
ownsRealEstate: "No",
|
|
76
76
|
itemizesDeductions: "No",
|
|
77
|
-
singleOwner: "
|
|
78
|
-
equipmentPurchased: "
|
|
77
|
+
singleOwner: "No",
|
|
78
|
+
equipmentPurchased: "87000",
|
|
79
79
|
realEstateValue: "0",
|
|
80
80
|
capitalGains: "0",
|
|
81
|
-
federalRate: "
|
|
82
|
-
stateRate: "",
|
|
81
|
+
federalRate: "22%",
|
|
82
|
+
stateRate: "5.39",
|
|
83
83
|
taxDataYears: "1 year",
|
|
84
84
|
riskTolerance: "3",
|
|
85
85
|
overtimePremium: "0",
|
|
86
86
|
tipIncomePct: "0",
|
|
87
87
|
retirementContributions: "0",
|
|
88
|
-
hdhpEnrolled: "
|
|
88
|
+
hdhpEnrolled: "Yes",
|
|
89
89
|
hsaContributions: "0",
|
|
90
90
|
wotcHires: "No",
|
|
91
91
|
familyEmployed: "No",
|
|
@@ -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.")))),
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ProspectReportSkeleton = ProspectReportSkeleton;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const theme_1 = require("./theme");
|
|
9
|
+
// Dark-themed skeleton shown while generateProspectReport mutation is in-flight
|
|
10
|
+
function Bone({ w = "100%", h = 16, radius = 6, mb = 0 }) {
|
|
11
|
+
return (react_1.default.createElement("div", { style: {
|
|
12
|
+
width: w,
|
|
13
|
+
height: h,
|
|
14
|
+
borderRadius: radius,
|
|
15
|
+
background: `linear-gradient(90deg, ${theme_1.T.surface2} 25%, rgba(255,255,255,0.04) 50%, ${theme_1.T.surface2} 75%)`,
|
|
16
|
+
backgroundSize: "400% 100%",
|
|
17
|
+
animation: "skeletonShimmer 1.6s infinite linear",
|
|
18
|
+
marginBottom: mb,
|
|
19
|
+
flexShrink: 0,
|
|
20
|
+
} }));
|
|
21
|
+
}
|
|
22
|
+
function ProspectReportSkeleton() {
|
|
23
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
24
|
+
react_1.default.createElement("style", null, `
|
|
25
|
+
@keyframes skeletonShimmer {
|
|
26
|
+
0% { background-position: 100% 0 }
|
|
27
|
+
100% { background-position: -100% 0 }
|
|
28
|
+
}
|
|
29
|
+
`),
|
|
30
|
+
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 16, padding: "36px 32px 28px", marginBottom: 24, boxShadow: theme_1.T.shadowLg, overflow: "hidden" } },
|
|
31
|
+
react_1.default.createElement("div", { style: { height: 4, background: `linear-gradient(90deg,${theme_1.T.accent},${theme_1.T.orange})`, marginBottom: 24, borderRadius: 2 } }),
|
|
32
|
+
react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 20 } },
|
|
33
|
+
react_1.default.createElement(Bone, { w: 120, h: 12 }),
|
|
34
|
+
react_1.default.createElement(Bone, { w: 80, h: 12 })),
|
|
35
|
+
react_1.default.createElement(Bone, { w: "45%", h: 10, mb: 12 }),
|
|
36
|
+
react_1.default.createElement(Bone, { w: "70%", h: 36, mb: 12 }),
|
|
37
|
+
react_1.default.createElement(Bone, { w: "50%", h: 14, mb: 24 }),
|
|
38
|
+
react_1.default.createElement("div", { style: { height: 1, background: theme_1.T.border, marginBottom: 20 } }),
|
|
39
|
+
react_1.default.createElement(Bone, { w: "55%", h: 44, mb: 12 }),
|
|
40
|
+
react_1.default.createElement(Bone, { w: "30%", h: 12 })),
|
|
41
|
+
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
|
|
42
|
+
react_1.default.createElement(Bone, { w: "40%", h: 10, mb: 16 }),
|
|
43
|
+
react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 16 } }, [1, 2, 3].map(i => (react_1.default.createElement("div", { key: i },
|
|
44
|
+
react_1.default.createElement(Bone, { w: "60%", h: 28, mb: 8 }),
|
|
45
|
+
react_1.default.createElement(Bone, { w: "80%", h: 10 })))))),
|
|
46
|
+
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
|
|
47
|
+
react_1.default.createElement(Bone, { w: "25%", h: 10, mb: 16 }),
|
|
48
|
+
react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [1, 2, 3, 4].map(i => (react_1.default.createElement("div", { key: i },
|
|
49
|
+
react_1.default.createElement(Bone, { w: "70%", h: 22, mb: 8 }),
|
|
50
|
+
react_1.default.createElement(Bone, { w: "80%", h: 10 })))))),
|
|
51
|
+
react_1.default.createElement("div", { style: { marginBottom: 24 } },
|
|
52
|
+
react_1.default.createElement(Bone, { w: "30%", h: 10, mb: 12 }),
|
|
53
|
+
react_1.default.createElement(Bone, { w: "100%", h: 2, mb: 16 }),
|
|
54
|
+
react_1.default.createElement(Bone, { w: "100%", h: 14, mb: 8 }),
|
|
55
|
+
react_1.default.createElement(Bone, { w: "95%", h: 14, mb: 8 }),
|
|
56
|
+
react_1.default.createElement(Bone, { w: "80%", h: 14 })),
|
|
57
|
+
[1, 2, 3].map(i => (react_1.default.createElement("div", { key: i, style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 14, boxShadow: theme_1.T.shadow } },
|
|
58
|
+
react_1.default.createElement("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 20 } },
|
|
59
|
+
react_1.default.createElement("div", { style: { display: "flex", gap: 14, alignItems: "center" } },
|
|
60
|
+
react_1.default.createElement("div", { style: { width: 34, height: 34, borderRadius: "50%", background: theme_1.T.surface2 } }),
|
|
61
|
+
react_1.default.createElement("div", null,
|
|
62
|
+
react_1.default.createElement(Bone, { w: 160, h: 16, mb: 8 }),
|
|
63
|
+
react_1.default.createElement(Bone, { w: 100, h: 10 }))),
|
|
64
|
+
react_1.default.createElement("div", { style: { textAlign: "right" } },
|
|
65
|
+
react_1.default.createElement(Bone, { w: 90, h: 22, mb: 6 }),
|
|
66
|
+
react_1.default.createElement(Bone, { w: 80, h: 10 }))),
|
|
67
|
+
react_1.default.createElement(Bone, { w: "100%", h: 12, mb: 6 }),
|
|
68
|
+
react_1.default.createElement(Bone, { w: "90%", h: 12, mb: 6 }),
|
|
69
|
+
react_1.default.createElement(Bone, { w: "75%", h: 12, mb: 16 }),
|
|
70
|
+
react_1.default.createElement(Bone, { w: "100%", h: 12, mb: 6 }),
|
|
71
|
+
react_1.default.createElement(Bone, { w: "85%", h: 12 })))),
|
|
72
|
+
react_1.default.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 10, padding: "20px 0", color: theme_1.T.text4, fontFamily: theme_1.T.body, fontSize: 12 } },
|
|
73
|
+
react_1.default.createElement("div", { style: { width: 6, height: 6, borderRadius: "50%", background: theme_1.T.accent, animation: "pulse 1s infinite" } }),
|
|
74
|
+
"Generating your prospect report\u2026"),
|
|
75
|
+
react_1.default.createElement("style", null, `
|
|
76
|
+
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
|
|
77
|
+
`)));
|
|
78
|
+
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { Strategy, ClientProfile, ComputedMap } from "../../lib/types";
|
|
3
|
+
export interface LlmStrategyNarrative {
|
|
4
|
+
whyItMatters: string;
|
|
5
|
+
whatWeDo: string;
|
|
6
|
+
}
|
|
3
7
|
interface ProspectStrategyCardProps {
|
|
4
8
|
strategy: Strategy;
|
|
5
9
|
index: number;
|
|
6
10
|
profile: ClientProfile;
|
|
7
11
|
computed: ComputedMap;
|
|
12
|
+
/** LLM-generated narrative — when present, overrides all static/computed text */
|
|
13
|
+
llmNarrative?: LlmStrategyNarrative;
|
|
8
14
|
}
|
|
9
|
-
export declare function ProspectStrategyCard({ strategy: s, index: i, profile, computed }: ProspectStrategyCardProps): React.JSX.Element;
|
|
15
|
+
export declare function ProspectStrategyCard({ strategy: s, index: i, profile, computed, llmNarrative }: ProspectStrategyCardProps): React.JSX.Element;
|
|
10
16
|
export {};
|
|
@@ -8,17 +8,19 @@ const react_1 = __importDefault(require("react"));
|
|
|
8
8
|
const strategyNarrative_1 = require("../../lib/data/strategyNarrative");
|
|
9
9
|
const strategyProspect_1 = require("../../lib/data/strategyProspect");
|
|
10
10
|
const theme_1 = require("./theme");
|
|
11
|
-
function ProspectStrategyCard({ strategy: s, index: i, profile, computed }) {
|
|
12
|
-
var _a, _b;
|
|
11
|
+
function ProspectStrategyCard({ strategy: s, index: i, profile, computed, llmNarrative }) {
|
|
12
|
+
var _a, _b, _c, _d;
|
|
13
13
|
const p = strategyProspect_1.STRATEGY_PROSPECT[s.rank];
|
|
14
14
|
const narr = strategyNarrative_1.STRATEGY_NARRATIVE[s.rank];
|
|
15
15
|
const c = computed.get(s.rank);
|
|
16
|
-
const whyText = narr ? narr.whyMatters(profile) : strategyNarrative_1.NARRATIVE_FALLBACK.whyMatters(s);
|
|
17
|
-
const breakdownText = narr ? narr.breakdown(profile, c) : strategyNarrative_1.NARRATIVE_FALLBACK.breakdown(s, c);
|
|
18
|
-
const specialistText = (narr === null || narr === void 0 ? void 0 : narr.whySpecialist) || strategyNarrative_1.NARRATIVE_FALLBACK.whySpecialist(s);
|
|
19
|
-
const nextText = narr ? narr.nextSteps : strategyNarrative_1.NARRATIVE_FALLBACK.nextSteps(s);
|
|
20
16
|
const scaledLo = Math.round(((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) / 1000);
|
|
21
17
|
const scaledHi = Math.round(((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi) / 1000);
|
|
18
|
+
// When LLM narrative is provided, use it for whyItMatters and whatWeDo.
|
|
19
|
+
// HOW SAVINGS BREAK DOWN and WHY YOU NEED A SPECIALIST remain deterministic.
|
|
20
|
+
const whyText = (_c = llmNarrative === null || llmNarrative === void 0 ? void 0 : llmNarrative.whyItMatters) !== null && _c !== void 0 ? _c : (narr ? narr.whyMatters(profile) : strategyNarrative_1.NARRATIVE_FALLBACK.whyMatters(s));
|
|
21
|
+
const breakdownText = narr ? narr.breakdown(profile, c) : strategyNarrative_1.NARRATIVE_FALLBACK.breakdown(s, c);
|
|
22
|
+
const specialistText = (narr === null || narr === void 0 ? void 0 : narr.whySpecialist) || strategyNarrative_1.NARRATIVE_FALLBACK.whySpecialist(s);
|
|
23
|
+
const nextText = (_d = llmNarrative === null || llmNarrative === void 0 ? void 0 : llmNarrative.whatWeDo) !== null && _d !== void 0 ? _d : (narr ? narr.nextSteps : strategyNarrative_1.NARRATIVE_FALLBACK.nextSteps(s));
|
|
22
24
|
return (react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", boxShadow: theme_1.T.shadow } },
|
|
23
25
|
react_1.default.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 16 } },
|
|
24
26
|
react_1.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14 } },
|
|
@@ -2,6 +2,9 @@ import React from "react";
|
|
|
2
2
|
interface SampleAnalysisPreviewProps {
|
|
3
3
|
sectionNum: string;
|
|
4
4
|
eligibleCount: number;
|
|
5
|
+
fedRatePct?: number;
|
|
6
|
+
stateRatePct?: number;
|
|
7
|
+
stateCode?: string;
|
|
5
8
|
}
|
|
6
|
-
export declare function SampleAnalysisPreview({ sectionNum, eligibleCount }: SampleAnalysisPreviewProps): React.JSX.Element;
|
|
9
|
+
export declare function SampleAnalysisPreview({ sectionNum, eligibleCount, fedRatePct, stateRatePct, stateCode }: SampleAnalysisPreviewProps): React.JSX.Element;
|
|
7
10
|
export {};
|
|
@@ -26,7 +26,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.SampleAnalysisPreview = SampleAnalysisPreview;
|
|
27
27
|
const react_1 = __importStar(require("react"));
|
|
28
28
|
const theme_1 = require("./theme");
|
|
29
|
-
function SampleAnalysisPreview({ sectionNum, eligibleCount }) {
|
|
29
|
+
function SampleAnalysisPreview({ sectionNum, eligibleCount, fedRatePct = 24, stateRatePct = 4.95, stateCode = "IL" }) {
|
|
30
|
+
const fedRateDecimal = (fedRatePct / 100).toFixed(2);
|
|
31
|
+
const stateRateDecimal = (stateRatePct / 100).toFixed(4);
|
|
30
32
|
return (react_1.default.createElement("div", { style: { marginBottom: 24 } },
|
|
31
33
|
react_1.default.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6 } },
|
|
32
34
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.body } }, sectionNum),
|
|
@@ -62,12 +64,12 @@ function SampleAnalysisPreview({ sectionNum, eligibleCount }) {
|
|
|
62
64
|
react_1.default.createElement("span", { style: { color: theme_1.T.orange, fontWeight: 700 } }, "\u00B7 illustrative")),
|
|
63
65
|
react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr auto", gap: "6px 16px", fontSize: 11, fontFamily: theme_1.T.mono, marginBottom: 6 } }, [
|
|
64
66
|
["state_income_attributable_to_owner", "$1,750,000"],
|
|
65
|
-
["state_rate",
|
|
67
|
+
["state_rate", `${stateRateDecimal} (${stateCode})`],
|
|
66
68
|
["filing_status", "mfj"],
|
|
67
69
|
["MAGI_PROXY: net_income", "$1,750,000"],
|
|
68
70
|
["EFFECTIVE_SALT_CAP", "$0 (fully phased out by MFJ income)"],
|
|
69
|
-
["federal_marginal_rate",
|
|
70
|
-
["state_credit_rate",
|
|
71
|
+
["federal_marginal_rate", fedRateDecimal],
|
|
72
|
+
["state_credit_rate", `1.00 (${stateCode} full credit)`],
|
|
71
73
|
["pte_tax_paid", "$86,625"],
|
|
72
74
|
].map(([k, v]) => (react_1.default.createElement(react_1.Fragment, { key: k },
|
|
73
75
|
react_1.default.createElement("span", { style: { color: theme_1.T.text3 } }, k),
|
|
@@ -79,8 +81,8 @@ function SampleAnalysisPreview({ sectionNum, eligibleCount }) {
|
|
|
79
81
|
{ doc: "Form 1120-S", line: "Line 12 \u00B7 Taxes and licenses", field: "pte_tax_paid" },
|
|
80
82
|
{ doc: "Schedule K-1", line: "Box 16, Code A \u00B7 State tax credit info", field: "state_credit_pass_through" },
|
|
81
83
|
{ doc: "Form 1040 Sch A", line: "Line 5a \u00B7 State and local income tax", field: "salt_paid" },
|
|
82
|
-
{ doc:
|
|
83
|
-
{ doc: "Parser-injected", line:
|
|
84
|
+
{ doc: `State Return (${stateCode}-1120-ST)`, line: "PTE election line items", field: "pte_election_status" },
|
|
85
|
+
{ doc: "Parser-injected", line: `state_pte_lookup[${stateCode}]`, field: "state_credit_rate, pte_deadline" },
|
|
84
86
|
].map((s, idx) => (react_1.default.createElement("div", { key: idx, style: { display: "grid", gridTemplateColumns: "1.1fr 1.4fr 1fr", gap: 12, fontSize: 11, fontFamily: theme_1.T.body, padding: "7px 12px", background: theme_1.T.surface2, border: `1px solid ${theme_1.T.border}`, borderRadius: 6 } },
|
|
85
87
|
react_1.default.createElement("span", { style: { color: theme_1.T.text, fontWeight: 600 } }, s.doc),
|
|
86
88
|
react_1.default.createElement("span", { style: { color: theme_1.T.text2, fontFamily: theme_1.T.mono, fontSize: 10 } }, s.line),
|
|
@@ -102,13 +104,20 @@ function SampleAnalysisPreview({ sectionNum, eligibleCount }) {
|
|
|
102
104
|
react_1.default.createElement("div", { style: { fontSize: 11, color: theme_1.T.text2, fontFamily: theme_1.T.mono, lineHeight: 1.6 } },
|
|
103
105
|
"State PTE election form",
|
|
104
106
|
react_1.default.createElement("br", null),
|
|
105
|
-
"(
|
|
107
|
+
"(",
|
|
108
|
+
stateCode,
|
|
109
|
+
": Schedule B, Form ",
|
|
110
|
+
stateCode,
|
|
111
|
+
"-1120-ST)",
|
|
106
112
|
react_1.default.createElement("br", null),
|
|
107
113
|
"Updated Schedule K-1 (Box 13)"))),
|
|
108
114
|
react_1.default.createElement("div", { style: { marginTop: 14, paddingTop: 14, borderTop: `1px solid ${theme_1.T.border}`, display: "flex", justifyContent: "space-between", alignItems: "center", gap: 14 } },
|
|
109
115
|
react_1.default.createElement("div", null,
|
|
110
116
|
react_1.default.createElement("div", { style: { fontSize: 9, fontWeight: 700, color: theme_1.T.orange, textTransform: "uppercase", letterSpacing: "0.12em", fontFamily: theme_1.T.body, marginBottom: 3 } }, "deadline_flag"),
|
|
111
|
-
react_1.default.createElement("div", { style: { fontSize: 11, color: theme_1.T.text2, fontFamily: theme_1.T.body } },
|
|
117
|
+
react_1.default.createElement("div", { style: { fontSize: 11, color: theme_1.T.text2, fontFamily: theme_1.T.body } },
|
|
118
|
+
"Per state_pte_lookup \u2014 ",
|
|
119
|
+
stateCode,
|
|
120
|
+
": file with original return")),
|
|
112
121
|
react_1.default.createElement("span", { style: { fontSize: 9, fontWeight: 700, padding: "4px 9px", background: theme_1.T.surface2, border: `1px solid ${theme_1.T.border}`, borderRadius: 4, color: theme_1.T.text3, fontFamily: theme_1.T.mono, letterSpacing: 0.6, textTransform: "uppercase" } }, "algorithm_version V9.0")))),
|
|
113
122
|
react_1.default.createElement("div", { style: { marginTop: 14, padding: "14px 18px", background: `linear-gradient(135deg,${theme_1.T.accentBg} 0%,rgba(36,131,132,0.04) 100%)`, border: `1px solid ${theme_1.T.accent}`, borderRadius: 10, display: "flex", alignItems: "center", gap: 14 } },
|
|
114
123
|
react_1.default.createElement("div", { style: { flex: 1, fontSize: 13, color: theme_1.T.text, fontFamily: theme_1.T.body, lineHeight: 1.55 } },
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
|
|
3
|
+
import type { LlmStrategyNarrative } from "./ProspectStrategyCard";
|
|
4
|
+
/** LLM output payload shape returned by generateProspectReport mutation */
|
|
5
|
+
export interface LlmOutputPayload {
|
|
6
|
+
execSummary: string;
|
|
7
|
+
strategyNarratives: Record<string, LlmStrategyNarrative>;
|
|
8
|
+
additionalStrategiesBlurb: string;
|
|
9
|
+
}
|
|
10
|
+
/** Backend-computed math payload */
|
|
11
|
+
export interface BackendComputePayload {
|
|
12
|
+
savingsLo: number;
|
|
13
|
+
savingsHi: number;
|
|
14
|
+
eligibleCount: number;
|
|
15
|
+
highPriorityCount: number;
|
|
16
|
+
quickWinCount: number;
|
|
17
|
+
gapBlock: {
|
|
18
|
+
currentTax: number;
|
|
19
|
+
optimizedTax: number;
|
|
20
|
+
realizedSavings: number;
|
|
21
|
+
realizedPctOfCurrent: number;
|
|
22
|
+
fedRatePct: number;
|
|
23
|
+
stateRatePct: number;
|
|
24
|
+
};
|
|
25
|
+
barData: Array<{
|
|
26
|
+
name: string;
|
|
27
|
+
mid: number;
|
|
28
|
+
pct: number;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
3
31
|
export interface TaxAxisProspectReportProps extends TaxAxisScreenProps {
|
|
4
32
|
profile: ClientProfile;
|
|
33
|
+
/** When provided (from generateProspectReport mutation), all math comes from backend */
|
|
34
|
+
backendComputePayload?: BackendComputePayload | null;
|
|
35
|
+
/** LLM-generated narrative text from the mutation */
|
|
36
|
+
outputPayload?: LlmOutputPayload | null;
|
|
5
37
|
onUpgrade?: () => void;
|
|
6
38
|
onPresent?: () => void;
|
|
7
39
|
onReset?: () => void;
|
|
8
40
|
}
|
|
9
|
-
export declare function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }: TaxAxisProspectReportProps): React.JSX.Element;
|
|
41
|
+
export declare function TaxAxisProspectReport({ profile, backendComputePayload, outputPayload, onUpgrade, onPresent, onReset, }: TaxAxisProspectReportProps): React.JSX.Element;
|
|
@@ -35,47 +35,56 @@ const SampleAnalysisPreview_1 = require("./SampleAnalysisPreview");
|
|
|
35
35
|
const ProspectDocuments_1 = require("./ProspectDocuments");
|
|
36
36
|
const ProspectNextSteps_1 = require("./ProspectNextSteps");
|
|
37
37
|
const ProspectPrintView_1 = require("./ProspectPrintView");
|
|
38
|
-
function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
38
|
+
function TaxAxisProspectReport({ profile, backendComputePayload, outputPayload, onUpgrade, onPresent, onReset, }) {
|
|
39
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
39
40
|
const bizName = profile.bizName || "Client";
|
|
40
41
|
const [confirmReset, setConfirmReset] = (0, react_1.useState)(false);
|
|
41
42
|
const [printMode, setPrintMode] = (0, react_1.useState)(false);
|
|
42
|
-
// ═══ ELIGIBLE STRATEGIES ═══
|
|
43
|
+
// ═══ ELIGIBLE STRATEGIES (client-side, for rendering strategy cards) ═══
|
|
43
44
|
const eligible = (0, react_1.useMemo)(() => (0, compute_1.filterEligibleStrategies)(profile), [profile]);
|
|
44
45
|
const high = eligible.filter(s => s.priority === "HIGH");
|
|
45
46
|
const quick = eligible.filter(s => s.priority === "QUICK WIN");
|
|
46
|
-
// Top 3 by score for the sales narrative
|
|
47
47
|
const top3 = (0, react_1.useMemo)(() => [...eligible].sort((a, b) => b.score - a.score).slice(0, 3), [eligible]);
|
|
48
48
|
const remaining = eligible.length - top3.length;
|
|
49
49
|
const remainingNames = eligible.filter(s => !top3.includes(s)).slice(0, 4).map(s => s.name);
|
|
50
|
-
// ═══ SAVINGS RANGE
|
|
50
|
+
// ═══ RAW SAVINGS RANGE — backend-authoritative when available ═══
|
|
51
51
|
const computed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(profile), [profile]);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
52
|
+
const rawLo = backendComputePayload
|
|
53
|
+
? Math.round(backendComputePayload.savingsLo / 1000)
|
|
54
|
+
: Math.round(eligible.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.lo) !== null && _b !== void 0 ? _b : s.lo); }, 0) / 1000);
|
|
55
|
+
const rawHi = backendComputePayload
|
|
56
|
+
? Math.round(backendComputePayload.savingsHi / 1000)
|
|
57
|
+
: Math.round(eligible.reduce((a, s) => { var _a, _b; return a + ((_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : s.hi); }, 0) / 1000);
|
|
58
|
+
// ═══ GAP BLOCK — backend-authoritative when available ═══
|
|
59
|
+
const fedRatePct = (_a = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.fedRatePct) !== null && _a !== void 0 ? _a : (profile.entity === "C-Corporation" ? 21 : (parseFloat((profile.federalRate || "24").replace("%", "")) || 24));
|
|
60
|
+
const stateRatePct = (_b = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.stateRatePct) !== null && _b !== void 0 ? _b : (parseFloat(profile.stateRate || "4.95") || 4.95);
|
|
61
|
+
const currentTax = (_c = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.currentTax) !== null && _c !== void 0 ? _c : Math.round(((0, compute_1.parseNum)(profile.netIncome) || Math.round((0, compute_1.parseNum)(profile.revenue) * 0.20))
|
|
62
|
+
* (fedRatePct + stateRatePct) / 100);
|
|
63
|
+
// ═══ SAVINGS CAP — headline savings cannot exceed total tax liability ═══
|
|
64
|
+
const currentTaxK = Math.round(currentTax / 1000);
|
|
65
|
+
let displayHi = Math.min(rawHi, currentTaxK);
|
|
66
|
+
let displayLo = Math.min(rawLo, Math.round(currentTax * 0.6 / 1000));
|
|
67
|
+
// If cap squashed lo close to hi, re-spread to maintain a meaningful range
|
|
68
|
+
if (displayHi > 0 && displayLo >= displayHi * 0.9) {
|
|
69
|
+
displayLo = Math.round(displayHi / 3);
|
|
70
|
+
}
|
|
71
|
+
const realizedSavings = (_d = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.realizedSavings) !== null && _d !== void 0 ? _d : Math.min(Math.round((displayLo + displayHi) / 2 * 1000 * 0.325), Math.round(currentTax * 0.6));
|
|
72
|
+
const optimizedTax = (_e = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.optimizedTax) !== null && _e !== void 0 ? _e : (currentTax - realizedSavings);
|
|
73
|
+
const realizedPctOfCurrent = (_f = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.realizedPctOfCurrent) !== null && _f !== void 0 ? _f : (currentTax > 0 ? Math.round((realizedSavings / currentTax) * 100) : 0);
|
|
74
|
+
// ═══ BAR CHART DATA ═══
|
|
75
|
+
const barData = (_g = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.barData) !== null && _g !== void 0 ? _g : (() => {
|
|
76
|
+
const totalMid = eligible.reduce((a, s) => { var _a, _b; const c = computed.get(s.rank); return a + (((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) + ((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi)) / 2; }, 0);
|
|
77
|
+
return top3.map(s => { var _a, _b; const c = computed.get(s.rank); const mid = Math.round((((_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo) + ((_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi)) / 2); return { name: s.name, mid, pct: totalMid > 0 ? Math.round(mid / totalMid * 100) : 0 }; });
|
|
78
|
+
})();
|
|
57
79
|
const maxMid = Math.max(...barData.map(b => b.mid), 1);
|
|
58
|
-
// ═══ GAP BLOCK MATH — Current vs Optimized tax position ═══
|
|
59
|
-
const rev = (0, compute_1.parseNum)(profile.revenue);
|
|
60
|
-
const netIncome = (0, compute_1.parseNum)(profile.netIncome) || Math.round(rev * 0.20);
|
|
61
|
-
const fedRatePct = parseFloat((profile.federalRate || "24").replace("%", "")) || 24;
|
|
62
|
-
const stateRatePct = parseFloat(profile.stateRate || "4.95") || 4.95;
|
|
63
|
-
const combinedRate = (fedRatePct + stateRatePct) / 100;
|
|
64
|
-
const currentTax = Math.round(netIncome * combinedRate);
|
|
65
|
-
const theoreticalMidK = (displayLo + displayHi) / 2;
|
|
66
|
-
const realizedSavings = Math.min(Math.round(theoreticalMidK * 1000 * 0.325), Math.round(currentTax * 0.6));
|
|
67
|
-
const optimizedTax = currentTax - realizedSavings;
|
|
68
|
-
const realizedPctOfCurrent = currentTax > 0 ? Math.round((realizedSavings / currentTax) * 100) : 0;
|
|
69
|
-
// Section numbering — shifts when Additional Strategies section is present
|
|
70
|
-
const sectionNums = remaining > 0
|
|
71
|
-
? { recommended: "02", additional: "03", sample: "04", documents: "05", nextSteps: "06" }
|
|
72
|
-
: { recommended: "02", additional: null, sample: "03", documents: "04", nextSteps: "05" };
|
|
73
80
|
// ═══ DOCUMENT GROUPING ═══
|
|
74
81
|
const allDocsNeeded = [...new Set(eligible.flatMap(s => { var _a; return ((_a = strategyProspect_1.STRATEGY_PROSPECT[s.rank]) === null || _a === void 0 ? void 0 : _a.docsNeeded) || []; }))];
|
|
75
82
|
const requiredDocs = ["Profit & Loss Statement (current year)", "Balance Sheet (current year)"];
|
|
76
83
|
const recommendedDocs = ["Federal Tax Return (most recent)", "W-2 / Officer Compensation records"];
|
|
77
84
|
const conditionalDocs = allDocsNeeded.filter(d => !requiredDocs.includes(d) && !recommendedDocs.includes(d)).slice(0, 5);
|
|
78
|
-
|
|
85
|
+
const sectionNums = remaining > 0
|
|
86
|
+
? { recommended: "02", additional: "03", sample: "04", documents: "05", nextSteps: "06" }
|
|
87
|
+
: { recommended: "02", additional: null, sample: "03", documents: "04", nextSteps: "05" };
|
|
79
88
|
const handlePrint = () => {
|
|
80
89
|
setPrintMode(true);
|
|
81
90
|
requestAnimationFrame(() => {
|
|
@@ -86,18 +95,16 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
|
86
95
|
});
|
|
87
96
|
});
|
|
88
97
|
};
|
|
89
|
-
// ═══ PRINT MODE — Light theme layout ═══
|
|
90
98
|
if (printMode) {
|
|
91
99
|
return (react_1.default.createElement(ProspectPrintView_1.ProspectPrintView, { profile: profile, bizName: bizName, displayLo: displayLo, displayHi: displayHi, currentTax: currentTax, optimizedTax: optimizedTax, realizedSavings: realizedSavings, realizedPctOfCurrent: realizedPctOfCurrent, fedRatePct: fedRatePct, stateRatePct: stateRatePct, eligible: eligible, high: high, quick: quick, top3: top3, computed: computed, fmtTax: compute_1.fmtTax, fmtSavings: compute_1.fmtSavings, requiredDocs: requiredDocs, recommendedDocs: recommendedDocs, conditionalDocs: conditionalDocs }));
|
|
92
100
|
}
|
|
93
|
-
// ═══ INTERACTIVE MODE — Dark theme layout ═══
|
|
94
101
|
return (react_1.default.createElement("div", null,
|
|
95
102
|
react_1.default.createElement(ProspectCover_1.ProspectCover, { profile: profile, bizName: bizName, displayLo: displayLo, displayHi: displayHi }),
|
|
96
103
|
react_1.default.createElement(TaxPositionGap_1.TaxPositionGap, { bizName: bizName, currentTax: currentTax, optimizedTax: optimizedTax, realizedSavings: realizedSavings, realizedPctOfCurrent: realizedPctOfCurrent, fedRatePct: fedRatePct, stateRatePct: stateRatePct, eligibleCount: eligible.length, fmtTax: compute_1.fmtTax, fmtSavings: compute_1.fmtSavings }),
|
|
97
104
|
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "24px 28px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
|
|
98
105
|
react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.15em", color: theme_1.T.text4, marginBottom: 16, fontFamily: theme_1.T.mono } }, "AT A GLANCE"),
|
|
99
106
|
react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [
|
|
100
|
-
{ v: `$${displayLo}K
|
|
107
|
+
{ v: `$${displayLo}K–$${displayHi}K`, l: "Est. Savings" },
|
|
101
108
|
{ v: String(eligible.length), l: "Strategies Found" },
|
|
102
109
|
{ v: String(high.length), l: "High Impact" },
|
|
103
110
|
{ v: String(quick.length), l: "Quick Wins" },
|
|
@@ -109,29 +116,30 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
|
109
116
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "01"),
|
|
110
117
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: theme_1.T.text4, fontFamily: theme_1.T.body } }, "EXECUTIVE SUMMARY")),
|
|
111
118
|
react_1.default.createElement("div", { style: { height: 2, background: theme_1.T.accent, width: 48, marginBottom: 16 } }),
|
|
112
|
-
react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } },
|
|
113
|
-
"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
119
|
+
(outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.execSummary) ? (react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } }, outputPayload.execSummary)) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
120
|
+
react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8, marginBottom: 12 } },
|
|
121
|
+
"We evaluated ",
|
|
122
|
+
bizName,
|
|
123
|
+
"'s tax position against 25 federal strategies and identified ",
|
|
124
|
+
react_1.default.createElement("strong", { style: { color: theme_1.T.white } },
|
|
125
|
+
eligible.length,
|
|
126
|
+
" applicable opportunities"),
|
|
127
|
+
" with combined estimated savings of ",
|
|
128
|
+
react_1.default.createElement("strong", { style: { color: theme_1.T.accentLt } },
|
|
129
|
+
"$",
|
|
130
|
+
displayLo,
|
|
131
|
+
"K\u2013$",
|
|
132
|
+
displayHi,
|
|
133
|
+
"K annually"),
|
|
134
|
+
". ",
|
|
135
|
+
high.length,
|
|
136
|
+
" strategies could have significant impact."),
|
|
137
|
+
react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8 } },
|
|
138
|
+
"The top ",
|
|
139
|
+
top3.length,
|
|
140
|
+
" strategies are detailed below.",
|
|
141
|
+
quick.length > 0 ? ` ${quick.length} can be implemented this week with no structural changes.` : "",
|
|
142
|
+
" These are profile-based estimates \u2014 uploading financial documents will refine savings calculations and unlock all 25 strategies.")))),
|
|
135
143
|
react_1.default.createElement("div", { style: { background: theme_1.T.surface, border: `1px solid ${theme_1.T.border}`, borderRadius: 14, padding: "20px 24px", marginBottom: 24, boxShadow: theme_1.T.shadow } },
|
|
136
144
|
react_1.default.createElement("div", { style: { fontSize: 10, fontWeight: 700, color: theme_1.T.text4, textTransform: "uppercase", letterSpacing: "0.12em", marginBottom: 12, fontFamily: theme_1.T.mono } }, "SAVINGS DISTRIBUTION"),
|
|
137
145
|
barData.map((b, i) => (react_1.default.createElement("div", { key: i, style: { display: "flex", alignItems: "center", gap: 10, marginBottom: i < barData.length - 1 ? 8 : 0 } },
|
|
@@ -151,7 +159,10 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
|
151
159
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "02"),
|
|
152
160
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: theme_1.T.text4, fontFamily: theme_1.T.body } }, "RECOMMENDED STRATEGIES")),
|
|
153
161
|
react_1.default.createElement("div", { style: { height: 2, background: theme_1.T.accent, width: 48, marginBottom: 16 } }),
|
|
154
|
-
react_1.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 14 } }, top3.map((s, i) =>
|
|
162
|
+
react_1.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 14 } }, top3.map((s, i) => {
|
|
163
|
+
var _a;
|
|
164
|
+
return (react_1.default.createElement(ProspectStrategyCard_1.ProspectStrategyCard, { key: s.rank, strategy: s, index: i, profile: profile, computed: computed, llmNarrative: (_a = outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.strategyNarratives) === null || _a === void 0 ? void 0 : _a[`S${s.rank}`] }));
|
|
165
|
+
}))),
|
|
155
166
|
remaining > 0 && (react_1.default.createElement("div", { style: { marginBottom: 24 } },
|
|
156
167
|
react_1.default.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6 } },
|
|
157
168
|
react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "03"),
|
|
@@ -162,12 +173,10 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
|
|
|
162
173
|
"+ ",
|
|
163
174
|
remaining,
|
|
164
175
|
" more strategies identified"),
|
|
165
|
-
react_1.default.createElement("div", { style: { fontSize: 13, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.7, marginBottom: 10 } },
|
|
166
|
-
|
|
167
|
-
remainingNames.join(", "),
|
|
168
|
-
remaining > 4 ? " and more" : ""),
|
|
176
|
+
react_1.default.createElement("div", { style: { fontSize: 13, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.7, marginBottom: 10 } }, (outputPayload === null || outputPayload === void 0 ? void 0 : outputPayload.additionalStrategiesBlurb)
|
|
177
|
+
|| `Including: ${remainingNames.join(", ")}${remaining > 4 ? " and more" : ""}`),
|
|
169
178
|
react_1.default.createElement("div", { style: { fontSize: 12, color: theme_1.T.text3, fontFamily: theme_1.T.body, lineHeight: 1.6, borderLeft: `3px solid ${theme_1.T.accent}`, paddingLeft: 12 } }, "Full analysis with documents unlocks specific savings estimates for every eligible strategy.")))),
|
|
170
|
-
react_1.default.createElement(SampleAnalysisPreview_1.SampleAnalysisPreview, { sectionNum: sectionNums.sample, eligibleCount: eligible.length }),
|
|
179
|
+
react_1.default.createElement(SampleAnalysisPreview_1.SampleAnalysisPreview, { sectionNum: sectionNums.sample, eligibleCount: eligible.length, fedRatePct: fedRatePct, stateRatePct: stateRatePct, stateCode: ((_h = profile.states) === null || _h === void 0 ? void 0 : _h[0]) || "IL" }),
|
|
171
180
|
react_1.default.createElement(ProspectDocuments_1.ProspectDocuments, { sectionNum: sectionNums.documents, requiredDocs: requiredDocs, recommendedDocs: recommendedDocs, conditionalDocs: conditionalDocs }),
|
|
172
181
|
react_1.default.createElement(ProspectNextSteps_1.ProspectNextSteps, { sectionNum: sectionNums.nextSteps, bizName: bizName, onUpgrade: onUpgrade, onPresent: onPresent, onPrint: handlePrint, onReset: onReset, confirmReset: confirmReset, onConfirmResetChange: setConfirmReset })));
|
|
173
182
|
}
|
|
@@ -48,6 +48,8 @@ function filterEligibleStrategies(profile) {
|
|
|
48
48
|
return false;
|
|
49
49
|
if (s.needsOwnerComp && ownerComp <= 0)
|
|
50
50
|
return false;
|
|
51
|
+
if (s.rank === 8 && (profile.ownsRealEstate || "No") === "No")
|
|
52
|
+
return false;
|
|
51
53
|
return true;
|
|
52
54
|
});
|
|
53
55
|
}
|
|
@@ -62,7 +64,7 @@ function computeAllStrategies(profile) {
|
|
|
62
64
|
const age = parseInt(profile.age || "45") || 45;
|
|
63
65
|
const hsaExisting = (0, exports.parseNum)(profile.hsaContributions);
|
|
64
66
|
const retirementExisting = (0, exports.parseNum)(profile.retirementContributions);
|
|
65
|
-
const fedRate = parseFloat((profile.federalRate || "24").replace("%", "")) / 100 || 0.24;
|
|
67
|
+
const fedRate = profile.entity === "C-Corporation" ? 0.21 : parseFloat((profile.federalRate || "24").replace("%", "")) / 100 || 0.24;
|
|
66
68
|
const stateRate = parseFloat(profile.stateRate || "4.95") / 100;
|
|
67
69
|
const combinedRate = fedRate + stateRate;
|
|
68
70
|
const seTaxRate = 0.153;
|
|
@@ -133,7 +135,7 @@ function computeAllStrategies(profile) {
|
|
|
133
135
|
const hsaLimit = 8550;
|
|
134
136
|
const hsaAdditional = Math.max(hsaLimit - hsaExisting, 0);
|
|
135
137
|
const hsaSavings = Math.round(hsaAdditional * (combinedRate + 0.0765));
|
|
136
|
-
results.set(9, { lo: Math.max(Math.round(hsaSavings * 0.
|
|
138
|
+
results.set(9, { lo: Math.max(Math.round(hsaSavings * 0.80), 0), hi: Math.max(Math.round(hsaSavings * 1.15), 0),
|
|
137
139
|
sources: [{ doc: "Intake", line: "HSA", value: `$${hsaExisting.toLocaleString()}`, field: "YTD Contributions" }, { doc: "IRS 2026", line: "Rev. Proc.", value: "$8,550", field: "Family Limit" }],
|
|
138
140
|
trace: [{ n: 1, step: "2026 family limit", formula: "Rev. Proc. 2025-32", result: "$8,550", src: "IRS" }, { n: 2, step: "Already contributed", formula: "W-2 Box 12 Code W", result: `$${hsaExisting.toLocaleString()}`, src: "Intake" }, { n: 3, step: "Remaining room", formula: `8,550 \u2212 ${hsaExisting.toLocaleString()}`, result: `$${hsaAdditional.toLocaleString()}`, src: "Computed" }, { n: 4, step: "Tax savings", formula: `${hsaAdditional.toLocaleString()} \u00D7 ${((combinedRate + 0.0765) * 100).toFixed(1)}%`, result: `$${hsaSavings.toLocaleString()}`, src: "Triple tax" }] });
|
|
139
141
|
}
|
|
@@ -165,5 +167,11 @@ function computeAllStrategies(profile) {
|
|
|
165
167
|
results.set(rank, Object.assign(Object.assign({}, v), { lo: Math.round(v.hi / 3.0) }));
|
|
166
168
|
}
|
|
167
169
|
}
|
|
170
|
+
// Safety net: ensure lo and hi don't round to same K (prevents "$XK–$XK" display)
|
|
171
|
+
for (const [rank, v] of results) {
|
|
172
|
+
if (v.hi > 0 && v.lo > 0 && Math.round(v.lo / 1000) >= Math.round(v.hi / 1000)) {
|
|
173
|
+
results.set(rank, Object.assign(Object.assign({}, v), { lo: Math.round(v.hi * 0.85) }));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
168
176
|
return results;
|
|
169
177
|
}
|