@paro.io/expert-shared-components 1.14.48 → 1.14.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/TaxAxis/TaxAxisApi.d.ts +54 -0
- package/lib/components/TaxAxis/TaxAxisApi.js +2 -0
- package/lib/components/TaxAxis/TaxAxisShell.d.ts +3 -0
- package/lib/components/TaxAxis/TaxAxisShell.js +318 -0
- package/lib/components/TaxAxis/index.d.ts +3 -0
- package/lib/components/TaxAxis/index.js +8 -0
- package/lib/components/TaxAxis/types.d.ts +16 -0
- package/lib/components/TaxAxis/types.js +2 -0
- package/lib/components/shared/UploadClient.d.ts +2 -1
- package/lib/components/shared/UploadClient.js +6 -2
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/lib/tax-axis/components/clientReport/TaxAxisClientReport.js +1 -1
- package/lib/tax-axis/components/dashboard/DashboardActions.js +5 -4
- package/lib/tax-axis/components/dashboard/DashboardSummary.js +1 -1
- package/lib/tax-axis/components/dashboard/TaxAxisDashboard.d.ts +33 -1
- package/lib/tax-axis/components/dashboard/TaxAxisDashboard.js +83 -7
- package/lib/tax-axis/components/documents/DocumentCard.d.ts +5 -3
- package/lib/tax-axis/components/documents/DocumentCard.js +53 -12
- package/lib/tax-axis/components/documents/DocumentTier.d.ts +3 -2
- package/lib/tax-axis/components/documents/DocumentTier.js +2 -2
- package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +17 -1
- package/lib/tax-axis/components/documents/TaxAxisDocuments.js +136 -20
- package/lib/tax-axis/components/intake/IntakeCtaCards.js +1 -1
- package/lib/tax-axis/components/intake/TaxAxisIntake.js +1 -1
- package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.js +1 -1
- package/lib/tax-axis/components/prospectReport/ProspectPrintView.js +0 -2
- package/lib/tax-axis/lib/data/documents.js +8 -8
- package/lib/tax-axis/lib/data/strategies.js +9 -9
- package/package.json +1 -1
- package/lib/README.md +0 -2
- package/lib/package.json +0 -68
|
@@ -34,6 +34,64 @@ const DashboardTopBar_1 = require("./DashboardTopBar");
|
|
|
34
34
|
const DashboardActions_1 = require("./DashboardActions");
|
|
35
35
|
const StrategyTile_1 = require("./StrategyTile");
|
|
36
36
|
const StrategyDetailPanel_1 = require("./StrategyDetailPanel");
|
|
37
|
+
const STRATEGY_TYPE_LABELS = {
|
|
38
|
+
ENTITY_RESTRUCTURING: "Entity Restructuring",
|
|
39
|
+
QBI_199A_OPTIMIZATION: "QBI §199A Optimization",
|
|
40
|
+
DOCUMENTATION_GAP_REMEDIATION: "Documentation Gap Remediation",
|
|
41
|
+
RETIREMENT_PLAN: "Retirement Plan Optimization",
|
|
42
|
+
DEPRECIATION_ACCELERATION: "Depreciation Acceleration",
|
|
43
|
+
COST_SEGREGATION: "Cost Segregation",
|
|
44
|
+
RD_CREDIT: "R&D Tax Credit",
|
|
45
|
+
HSA_MAXIMIZATION: "HSA Maximization",
|
|
46
|
+
SALT_PTE: "SALT / PTE Optimization",
|
|
47
|
+
WOTC: "Work Opportunity Tax Credit",
|
|
48
|
+
INCOME_DEFERRAL: "Income Deferral",
|
|
49
|
+
CHARITABLE_GIVING: "Charitable Giving Strategy",
|
|
50
|
+
FAMILY_EMPLOYMENT: "Family Employment",
|
|
51
|
+
MEALS_DEDUCTIONS: "Business Meals Deduction",
|
|
52
|
+
OPPORTUNITY_ZONE: "Opportunity Zone Investment",
|
|
53
|
+
ACCOUNTING_METHOD: "Accounting Method Change",
|
|
54
|
+
BONUS_DEPRECIATION: "Bonus Depreciation §168(k)",
|
|
55
|
+
SECTION_179: "Section 179 Expensing",
|
|
56
|
+
};
|
|
57
|
+
function humanizeStrategyType(raw) {
|
|
58
|
+
if (STRATEGY_TYPE_LABELS[raw])
|
|
59
|
+
return STRATEGY_TYPE_LABELS[raw];
|
|
60
|
+
return raw
|
|
61
|
+
.replace(/_/g, " ")
|
|
62
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
63
|
+
}
|
|
64
|
+
function mapLlmToStrategies(llm) {
|
|
65
|
+
return llm.strategies
|
|
66
|
+
.filter((s) => s.applicable)
|
|
67
|
+
.map((s, idx) => ({
|
|
68
|
+
rank: idx + 1,
|
|
69
|
+
code: s.strategyType,
|
|
70
|
+
name: humanizeStrategyType(s.strategyType),
|
|
71
|
+
cat: "income",
|
|
72
|
+
priority: (s.priority || "MEDIUM").toUpperCase(),
|
|
73
|
+
score: s.priority === "HIGH" ? 85 : s.priority === "MEDIUM" ? 70 : 55,
|
|
74
|
+
entities: [],
|
|
75
|
+
lo: s.estimatedSavings.min,
|
|
76
|
+
hi: s.estimatedSavings.max,
|
|
77
|
+
timeline: s.priority === "HIGH" ? "Act now" : "Within 90 days",
|
|
78
|
+
timelineBucket: s.priority === "HIGH" ? "now" : "90d",
|
|
79
|
+
clientBrief: s.summary,
|
|
80
|
+
action: s.implementationSteps.join(". "),
|
|
81
|
+
forms: s.requiredForms.join(", "),
|
|
82
|
+
abstract: s.summary,
|
|
83
|
+
sources: [],
|
|
84
|
+
trace: [],
|
|
85
|
+
cost: undefined,
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
function buildLlmComputed(strategies) {
|
|
89
|
+
const map = new Map();
|
|
90
|
+
for (const s of strategies) {
|
|
91
|
+
map.set(s.rank, { lo: s.lo, hi: s.hi, sources: s.sources, trace: s.trace });
|
|
92
|
+
}
|
|
93
|
+
return map;
|
|
94
|
+
}
|
|
37
95
|
// ─── Formatting helper ──────────────────────────────────────────
|
|
38
96
|
const fmtK = (n) => `$${(n / 1000).toFixed(n % 1000 ? 1 : 0)}K`;
|
|
39
97
|
// ─── Timeline buckets for Client Summary ────────────────────────
|
|
@@ -42,10 +100,14 @@ const BUCKETS = [
|
|
|
42
100
|
{ key: "30d", label: "Within 30 Days", desc: "Documentation or payroll changes" },
|
|
43
101
|
{ key: "90d", label: "Within 90 Days", desc: "Structural changes — new accounts or plan setup" },
|
|
44
102
|
];
|
|
45
|
-
function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPresent, onSend, onReset, onReviewData, userContext: _userContext = "expert", }) {
|
|
103
|
+
function TaxAxisDashboard({ profile, llmResult, onDownloadClient, onDownloadPreparer, onPresent, onSend, onReset, onReviewData, userContext: _userContext = "expert", }) {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
const hasLlm = !!((_a = llmResult === null || llmResult === void 0 ? void 0 : llmResult.strategies) === null || _a === void 0 ? void 0 : _a.length);
|
|
46
106
|
// ─── Derived data ──────────────────────────────────────────────
|
|
47
|
-
const
|
|
48
|
-
const
|
|
107
|
+
const llmStrategies = (0, react_1.useMemo)(() => (hasLlm ? mapLlmToStrategies(llmResult) : []), [hasLlm, llmResult]);
|
|
108
|
+
const llmComputed = (0, react_1.useMemo)(() => (hasLlm ? buildLlmComputed(llmStrategies) : new Map()), [hasLlm, llmStrategies]);
|
|
109
|
+
const computed = (0, react_1.useMemo)(() => (hasLlm ? llmComputed : (0, compute_1.computeAllStrategies)(profile)), [hasLlm, llmComputed, profile]);
|
|
110
|
+
const dashEligible = (0, react_1.useMemo)(() => (hasLlm ? llmStrategies : (0, compute_1.filterEligibleStrategies)(profile)), [hasLlm, llmStrategies, profile]);
|
|
49
111
|
const maxSavings = Math.max(...dashEligible.map((s) => { var _a, _b; return (_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : s.hi; }), 1);
|
|
50
112
|
// ─── State ─────────────────────────────────────────────────────
|
|
51
113
|
const [topTab, setTopTab] = (0, react_1.useState)("extraction");
|
|
@@ -80,6 +142,21 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
|
|
|
80
142
|
// Sorted strategies for impact distribution
|
|
81
143
|
const sortedByImpact = (0, react_1.useMemo)(() => [...dashEligible].sort((a, b) => { var _a, _b, _c, _d; return ((_b = (_a = computed.get(b.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : b.hi) - ((_d = (_c = computed.get(a.rank)) === null || _c === void 0 ? void 0 : _c.hi) !== null && _d !== void 0 ? _d : a.hi); }), [dashEligible, computed]);
|
|
82
144
|
return (react_1.default.createElement("div", null,
|
|
145
|
+
hasLlm && (llmResult === null || llmResult === void 0 ? void 0 : llmResult.meta) && (react_1.default.createElement("div", { className: "rounded-lg mb-3 px-4 py-2.5 flex items-center justify-between text-[11px] font-tax-axis-mono", style: {
|
|
146
|
+
background: "rgba(36,131,132,0.06)",
|
|
147
|
+
border: "1px solid rgba(36,131,132,0.15)",
|
|
148
|
+
color: "#9498B8",
|
|
149
|
+
} },
|
|
150
|
+
react_1.default.createElement("span", null,
|
|
151
|
+
"Source: ",
|
|
152
|
+
react_1.default.createElement("strong", { className: "text-tax-axis-teal-light" }, llmResult.meta.provider),
|
|
153
|
+
" · ",
|
|
154
|
+
llmResult.meta.parsedDocumentCount,
|
|
155
|
+
" documents parsed",
|
|
156
|
+
" · ",
|
|
157
|
+
((_b = llmResult.meta.detectedDocumentTypes) === null || _b === void 0 ? void 0 : _b.length) || 0,
|
|
158
|
+
" document types detected"),
|
|
159
|
+
react_1.default.createElement("span", { className: "text-tax-axis-text-4" }, llmResult.meta.tokenCount > 0 ? `${llmResult.meta.tokenCount} tokens` : ""))),
|
|
83
160
|
react_1.default.createElement(DashboardSummary_1.DashboardSummary, { profile: profile, dashEligible: dashEligible, computed: computed, dataConfirmed: dataConfirmed, reviewUnreviewed: reviewStatus.unreviewed }),
|
|
84
161
|
react_1.default.createElement(DashboardTopBar_1.DashboardTopBar, { topTab: topTab, setTopTab: setTopTab, dataConfirmed: dataConfirmed, reviewUnreviewed: reviewStatus.unreviewed }),
|
|
85
162
|
topTab === "extraction" && (react_1.default.createElement("div", null,
|
|
@@ -197,7 +274,7 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
|
|
|
197
274
|
const c = computed.get(s.rank);
|
|
198
275
|
return (react_1.default.createElement(StrategyTile_1.StrategyTile, { key: s.rank, s: Object.assign(Object.assign({}, s), { lo: (_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo, hi: (_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi }), delay: 0.2 + i * 0.06, onClick: () => setSelected(s), maxSavings: maxSavings }));
|
|
199
276
|
})),
|
|
200
|
-
react_1.default.createElement("div", { className: "rounded-[14px] overflow-hidden mb-4", style: {
|
|
277
|
+
!hasLlm && (react_1.default.createElement("div", { className: "rounded-[14px] overflow-hidden mb-4", style: {
|
|
201
278
|
background: "#0E1132",
|
|
202
279
|
border: "1px solid rgba(36,131,132,0.12)",
|
|
203
280
|
boxShadow: "0 2px 8px rgba(6,8,33,0.3)",
|
|
@@ -210,7 +287,7 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
|
|
|
210
287
|
react_1.default.createElement("span", { className: "text-[13px] text-tax-axis-text" }, a.name),
|
|
211
288
|
react_1.default.createElement("div", { className: "flex items-center gap-2.5" },
|
|
212
289
|
react_1.default.createElement("span", { className: "text-[11px] font-tax-axis-mono text-tax-axis-text-2" }, a.savings),
|
|
213
|
-
react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: a.priority === "MEDIUM" ? "orange" : "neutral", size: "xs" }, a.priority)))))),
|
|
290
|
+
react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: a.priority === "MEDIUM" ? "orange" : "neutral", size: "xs" }, a.priority))))))),
|
|
214
291
|
react_1.default.createElement("div", { className: "rounded-[10px] mb-4 text-xs text-tax-axis-text leading-[1.7] font-tax-axis-body", style: {
|
|
215
292
|
padding: "14px 18px",
|
|
216
293
|
background: "#0E1132",
|
|
@@ -218,8 +295,7 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
|
|
|
218
295
|
} },
|
|
219
296
|
react_1.default.createElement("strong", null, "\u00A76694 Preparer Compliance \u2014 "),
|
|
220
297
|
"All HIGH priority strategies hold Substantial Authority (established IRS guidance supporting the position). No elevated preparer penalty exposure for positions taken on these strategies. OBBBA strategies default to Reasonable Basis pending IRS guidance \u2014 CPA verification required before filing."),
|
|
221
|
-
react_1.default.createElement(
|
|
222
|
-
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onDownloadPreparer, className: "w-full" }, "Download Full Preparer Report")))) : (
|
|
298
|
+
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: onDownloadPreparer, className: "w-full" }, "Download Full Preparer Report"))) : (
|
|
223
299
|
/* ═══ CLIENT SUMMARY — Timeline View ═══ */
|
|
224
300
|
react_1.default.createElement("div", null,
|
|
225
301
|
react_1.default.createElement("p", { className: "text-sm text-tax-axis-text leading-[1.7] mb-6 font-tax-axis-body" },
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { DocSpec } from "../../lib/types";
|
|
3
|
-
export type DocStatus = "empty" | "validating" | "valid";
|
|
3
|
+
export type DocStatus = "empty" | "validating" | "parsing" | "failed" | "valid";
|
|
4
4
|
export interface DocState extends DocSpec {
|
|
5
5
|
status: DocStatus;
|
|
6
6
|
fileName: string | null;
|
|
7
|
+
parseError?: string | null;
|
|
7
8
|
}
|
|
8
9
|
interface DocumentCardProps {
|
|
9
10
|
doc: DocState;
|
|
@@ -11,8 +12,9 @@ interface DocumentCardProps {
|
|
|
11
12
|
tierBadgeColor: "red" | "orange" | "neutral";
|
|
12
13
|
tierBadgeText: string;
|
|
13
14
|
helpOverride?: string;
|
|
14
|
-
onUpload: () => void;
|
|
15
|
+
onUpload: (file: File) => void;
|
|
15
16
|
onClear: () => void;
|
|
17
|
+
onRemove?: () => void;
|
|
16
18
|
}
|
|
17
|
-
export declare function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, }: DocumentCardProps): React.JSX.Element;
|
|
19
|
+
export declare function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, onRemove, }: DocumentCardProps): React.JSX.Element;
|
|
18
20
|
export {};
|
|
@@ -6,8 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.DocumentCard = DocumentCard;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
8
|
const TaxAxisBadge_1 = require("../shared/TaxAxisBadge");
|
|
9
|
-
// Inline style values per status — mock T hex values for SVG strokes and
|
|
10
|
-
// rgba borders that don't have direct Tailwind token equivalents.
|
|
11
9
|
const STATUS_STYLES = {
|
|
12
10
|
empty: {
|
|
13
11
|
iconBg: "#171B44",
|
|
@@ -19,16 +17,28 @@ const STATUS_STYLES = {
|
|
|
19
17
|
iconBorder: "rgba(251,154,29,0.25)",
|
|
20
18
|
cardBorder: "rgba(251,154,29,0.25)",
|
|
21
19
|
},
|
|
20
|
+
parsing: {
|
|
21
|
+
iconBg: "rgba(36,131,132,0.08)",
|
|
22
|
+
iconBorder: "rgba(36,131,132,0.25)",
|
|
23
|
+
cardBorder: "rgba(36,131,132,0.25)",
|
|
24
|
+
},
|
|
25
|
+
failed: {
|
|
26
|
+
iconBg: "rgba(197,48,48,0.08)",
|
|
27
|
+
iconBorder: "rgba(197,48,48,0.25)",
|
|
28
|
+
cardBorder: "rgba(197,48,48,0.25)",
|
|
29
|
+
},
|
|
22
30
|
valid: {
|
|
23
31
|
iconBg: "rgba(15,110,86,0.08)",
|
|
24
32
|
iconBorder: "rgba(15,110,86,0.25)",
|
|
25
33
|
cardBorder: "rgba(15,110,86,0.25)",
|
|
26
34
|
},
|
|
27
35
|
};
|
|
28
|
-
function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, }) {
|
|
29
|
-
var _a;
|
|
36
|
+
function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, helpOverride, onUpload, onClear, onRemove, }) {
|
|
37
|
+
var _a, _b, _c;
|
|
30
38
|
const ss = STATUS_STYLES[doc.status];
|
|
31
39
|
const leftBorder = doc.status === "valid" ? "rgba(15,110,86,0.6)" : tierBorderColor;
|
|
40
|
+
const fileInputId = `tax-axis-upload-${doc.id}`;
|
|
41
|
+
const reuploadInputId = `tax-axis-reupload-${doc.id}`;
|
|
32
42
|
return (react_1.default.createElement("div", { className: "bg-tax-axis-surface overflow-hidden", style: {
|
|
33
43
|
border: `1px solid ${ss.cardBorder}`,
|
|
34
44
|
borderLeft: `3px solid ${leftBorder}`,
|
|
@@ -42,20 +52,51 @@ function DocumentCard({ doc, tierBorderColor, tierBadgeColor, tierBadgeText, hel
|
|
|
42
52
|
} }, doc.status === "validating" ? (react_1.default.createElement("div", { className: "w-3.5 h-3.5 rounded-full animate-spin", style: {
|
|
43
53
|
border: "2px solid transparent",
|
|
44
54
|
borderTopColor: "#FB9A1D",
|
|
45
|
-
} })) : doc.status === "
|
|
55
|
+
} })) : doc.status === "parsing" ? (react_1.default.createElement("div", { className: "w-3.5 h-3.5 rounded-full animate-spin", style: {
|
|
56
|
+
border: "2px solid transparent",
|
|
57
|
+
borderTopColor: "#248384",
|
|
58
|
+
} })) : doc.status === "failed" ? (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
|
|
59
|
+
react_1.default.createElement("path", { d: "M4 4l6 6M10 4L4 10", stroke: "#C53030", strokeWidth: "2", strokeLinecap: "round" }))) : doc.status === "valid" ? (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
|
|
46
60
|
react_1.default.createElement("path", { d: "M2.5 7l3.5 3.5 5.5-6", stroke: "#0F6E56", strokeWidth: "2", strokeLinecap: "round" }))) : (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
|
|
47
61
|
react_1.default.createElement("circle", { cx: "7", cy: "7", r: "5", stroke: "#9498B8", strokeWidth: "1.5", strokeDasharray: "3 3" })))),
|
|
48
62
|
react_1.default.createElement("div", { className: "flex-1 min-w-0" },
|
|
49
63
|
react_1.default.createElement("div", { className: "flex items-center gap-1.5" },
|
|
50
64
|
react_1.default.createElement("span", { className: "text-[13px] font-medium text-white font-tax-axis-body" }, doc.name),
|
|
51
|
-
doc.status === "empty" && (react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: tierBadgeColor, size: "xs" }, tierBadgeText))
|
|
52
|
-
|
|
65
|
+
doc.status === "empty" && (react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: tierBadgeColor, size: "xs" }, tierBadgeText)),
|
|
66
|
+
doc.status === "failed" && (react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: "red", size: "xs" }, "FAILED"))),
|
|
67
|
+
react_1.default.createElement("div", { className: "text-[11px] text-tax-axis-text-3 font-tax-axis-body mt-0.5" }, doc.status === "failed" && doc.parseError
|
|
68
|
+
? doc.parseError
|
|
69
|
+
: (_a = doc.fileName) !== null && _a !== void 0 ? _a : (helpOverride !== null && helpOverride !== void 0 ? helpOverride : doc.help))),
|
|
53
70
|
react_1.default.createElement("div", { className: "flex items-center gap-2 flex-shrink-0" },
|
|
54
71
|
doc.status === "valid" && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
55
|
-
react_1.default.createElement("span", { className: "text-[10px] font-semibold font-tax-axis-mono", style: { color: "#0F6E56" } }, "
|
|
72
|
+
react_1.default.createElement("span", { className: "text-[10px] font-semibold font-tax-axis-mono", style: { color: "#0F6E56" } }, "Parsed"),
|
|
56
73
|
react_1.default.createElement("button", { onClick: onClear, className: "bg-transparent border-none p-1 text-tax-axis-text-4 text-sm cursor-pointer" }, "\u00D7"))),
|
|
57
|
-
doc.status === "
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
(doc.status === "parsing" || doc.status === "validating") && (react_1.default.createElement("span", { className: "text-[10px] font-semibold font-tax-axis-mono", style: { color: doc.status === "parsing" ? "#248384" : "#FB9A1D" } }, doc.status === "parsing" ? "Parsing…" : "Uploading…")),
|
|
75
|
+
doc.status === "failed" && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
76
|
+
react_1.default.createElement("input", { id: reuploadInputId, type: "file", className: "hidden", accept: (_b = doc.accept) === null || _b === void 0 ? void 0 : _b.join(","), onChange: (event) => {
|
|
77
|
+
var _a;
|
|
78
|
+
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
79
|
+
if (file) {
|
|
80
|
+
onUpload(file);
|
|
81
|
+
}
|
|
82
|
+
event.currentTarget.value = "";
|
|
83
|
+
} }),
|
|
84
|
+
react_1.default.createElement("label", { htmlFor: reuploadInputId, className: "rounded-md px-2.5 py-1 text-[10px] font-semibold text-tax-axis-teal-light font-tax-axis-mono cursor-pointer", style: {
|
|
85
|
+
background: "rgba(36,131,132,0.10)",
|
|
86
|
+
border: "1px solid rgba(36,131,132,0.2)",
|
|
87
|
+
} }, "Re-upload"),
|
|
88
|
+
react_1.default.createElement("button", { onClick: onRemove || onClear, className: "bg-transparent border-none p-1 text-tax-axis-text-4 text-sm cursor-pointer", title: "Remove document" }, "\u00D7"))),
|
|
89
|
+
doc.status === "empty" && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
90
|
+
react_1.default.createElement("input", { id: fileInputId, type: "file", className: "hidden", accept: (_c = doc.accept) === null || _c === void 0 ? void 0 : _c.join(","), onChange: (event) => {
|
|
91
|
+
var _a;
|
|
92
|
+
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
93
|
+
if (file) {
|
|
94
|
+
onUpload(file);
|
|
95
|
+
}
|
|
96
|
+
event.currentTarget.value = "";
|
|
97
|
+
} }),
|
|
98
|
+
react_1.default.createElement("label", { htmlFor: fileInputId, className: "rounded-md px-3.5 py-1.5 text-[11px] font-semibold text-tax-axis-teal-light font-tax-axis-mono cursor-pointer", style: {
|
|
99
|
+
background: "rgba(36,131,132,0.10)",
|
|
100
|
+
border: "1px solid rgba(36,131,132,0.2)",
|
|
101
|
+
} }, "Upload")))))));
|
|
61
102
|
}
|
|
@@ -14,8 +14,9 @@ interface DocumentTierProps {
|
|
|
14
14
|
tier: TierDef;
|
|
15
15
|
docs: DocState[];
|
|
16
16
|
helpOverrides: Record<string, string>;
|
|
17
|
-
onUpload: (idx: number) => void;
|
|
17
|
+
onUpload: (idx: number, file: File) => void;
|
|
18
18
|
onClear: (idx: number) => void;
|
|
19
|
+
onRemove?: (idx: number) => void;
|
|
19
20
|
}
|
|
20
|
-
export declare function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, }: DocumentTierProps): React.JSX.Element | null;
|
|
21
|
+
export declare function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, onRemove, }: DocumentTierProps): React.JSX.Element | null;
|
|
21
22
|
export {};
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.DocumentTier = DocumentTier;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
8
|
const DocumentCard_1 = require("./DocumentCard");
|
|
9
|
-
function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, }) {
|
|
9
|
+
function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, onRemove, }) {
|
|
10
10
|
const tierDocs = tier.ids
|
|
11
11
|
.map((id) => {
|
|
12
12
|
const idx = docs.findIndex((d) => d.id === id);
|
|
@@ -19,5 +19,5 @@ function DocumentTier({ tier, docs, helpOverrides, onUpload, onClear, }) {
|
|
|
19
19
|
react_1.default.createElement("div", { className: "flex items-center gap-2 mb-2" },
|
|
20
20
|
react_1.default.createElement("span", { className: "text-[10px] font-bold uppercase tracking-widest font-tax-axis-mono", style: { color: tier.labelColor } }, tier.label),
|
|
21
21
|
react_1.default.createElement("span", { className: "text-[10px] text-tax-axis-text-4 font-tax-axis-body" }, tier.sublabel)),
|
|
22
|
-
react_1.default.createElement("div", { className: "grid gap-1.5" }, tierDocs.map(({ doc, idx }) => (react_1.default.createElement(DocumentCard_1.DocumentCard, { key: doc.id, doc: doc, tierBorderColor: tier.borderColor, tierBadgeColor: tier.badgeColor, tierBadgeText: tier.badgeText, helpOverride: helpOverrides[doc.id], onUpload: () => onUpload(idx), onClear: () => onClear(idx) }))))));
|
|
22
|
+
react_1.default.createElement("div", { className: "grid gap-1.5" }, tierDocs.map(({ doc, idx }) => (react_1.default.createElement(DocumentCard_1.DocumentCard, { key: doc.id, doc: doc, tierBorderColor: tier.borderColor, tierBadgeColor: tier.badgeColor, tierBadgeText: tier.badgeText, helpOverride: helpOverrides[doc.id], onUpload: (file) => onUpload(idx, file), onClear: () => onClear(idx), onRemove: onRemove ? () => onRemove(idx) : undefined }))))));
|
|
23
23
|
}
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
|
|
3
|
+
import { DocState } from "./DocumentCard";
|
|
3
4
|
export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
|
|
4
5
|
profile: ClientProfile;
|
|
5
6
|
onContinue: () => void;
|
|
6
7
|
onBack: () => void;
|
|
8
|
+
onUploadDocument?: (doc: DocState, file: File) => Promise<void | {
|
|
9
|
+
documentId?: string;
|
|
10
|
+
fileName?: string;
|
|
11
|
+
status?: string;
|
|
12
|
+
documentType?: string;
|
|
13
|
+
parseError?: string | null;
|
|
14
|
+
}>;
|
|
15
|
+
onDeleteDocument?: (documentId: string) => Promise<void>;
|
|
16
|
+
fetchUploadedDocuments?: () => Promise<Array<{
|
|
17
|
+
fileName: string;
|
|
18
|
+
status: string;
|
|
19
|
+
documentType?: string | null;
|
|
20
|
+
parseError?: string | null;
|
|
21
|
+
updatedAt?: string | null;
|
|
22
|
+
}>>;
|
|
7
23
|
}
|
|
8
|
-
export declare function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
|
|
24
|
+
export declare function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
|
|
@@ -22,13 +22,21 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
25
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
35
|
exports.TaxAxisDocuments = TaxAxisDocuments;
|
|
27
36
|
const react_1 = __importStar(require("react"));
|
|
28
37
|
const documents_1 = require("../../lib/data/documents");
|
|
29
38
|
const TaxAxisButton_1 = require("../shared/TaxAxisButton");
|
|
30
39
|
const DocumentTier_1 = require("./DocumentTier");
|
|
31
|
-
// Stub filenames assigned when the user clicks "Upload" (mock App.jsx:1237)
|
|
32
40
|
const STUB_FILENAMES = {
|
|
33
41
|
"1120s": "2025_1120S.pdf",
|
|
34
42
|
"state-return": "2025_State_Return.pdf",
|
|
@@ -39,8 +47,6 @@ const STUB_FILENAMES = {
|
|
|
39
47
|
"fixed-assets": "Fixed_Assets.xlsx",
|
|
40
48
|
"prior-returns": "Prior_Returns.pdf",
|
|
41
49
|
};
|
|
42
|
-
// Short help text shown below each doc name when not yet uploaded
|
|
43
|
-
// (mock App.jsx:1384). Overrides the longer DOC_SPECS_BASE.help.
|
|
44
50
|
const HELP_OVERRIDES = {
|
|
45
51
|
"1120s": "Improves accuracy of every strategy",
|
|
46
52
|
"payroll": "Critical for Entity Structure and QBI calculations",
|
|
@@ -49,7 +55,6 @@ const HELP_OVERRIDES = {
|
|
|
49
55
|
"cashflow": "Helps with income deferral and timing strategies",
|
|
50
56
|
"prior-returns": "Improves confidence intervals (more years = tighter estimates)",
|
|
51
57
|
};
|
|
52
|
-
// Tier groupings from mock (App.jsx:1379-1383)
|
|
53
58
|
const TIER_DEFS = [
|
|
54
59
|
{
|
|
55
60
|
key: "required",
|
|
@@ -82,24 +87,128 @@ const TIER_DEFS = [
|
|
|
82
87
|
ids: ["state-return", "cashflow", "prior-returns"],
|
|
83
88
|
},
|
|
84
89
|
];
|
|
85
|
-
function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userContext = "expert", }) {
|
|
90
|
+
function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext = "expert", }) {
|
|
91
|
+
const normalizeDocumentType = (value) => String(value || "").toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
86
92
|
const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(profile), [profile]);
|
|
87
|
-
const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null }))));
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
93
|
+
const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null, parseError: null }))));
|
|
94
|
+
const [uploadedDocIds, setUploadedDocIds] = (0, react_1.useState)({});
|
|
95
|
+
const [continueError, setContinueError] = (0, react_1.useState)(null);
|
|
96
|
+
const pollForParseStatus = (idx, fileName, expectedDocumentType) => __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
if (!fetchUploadedDocuments)
|
|
98
|
+
return;
|
|
99
|
+
const maxAttempts = 80;
|
|
100
|
+
const pollIntervalMs = 1500;
|
|
101
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
102
|
+
try {
|
|
103
|
+
const uploaded = yield fetchUploadedDocuments();
|
|
104
|
+
const exactTypeMatch = uploaded.filter((doc) => doc.fileName === fileName &&
|
|
105
|
+
normalizeDocumentType(String(doc.documentType || "")) ===
|
|
106
|
+
normalizeDocumentType(expectedDocumentType));
|
|
107
|
+
const filenameMatches = uploaded.filter((doc) => doc.fileName === fileName);
|
|
108
|
+
const candidates = exactTypeMatch.length > 0 ? exactTypeMatch : filenameMatches;
|
|
109
|
+
const match = candidates
|
|
110
|
+
.slice()
|
|
111
|
+
.sort((a, b) => new Date(String(b.updatedAt || "")).getTime() -
|
|
112
|
+
new Date(String(a.updatedAt || "")).getTime())[0];
|
|
113
|
+
if (match) {
|
|
114
|
+
const status = String(match.status || "").toUpperCase();
|
|
115
|
+
if (status === "FAILED") {
|
|
116
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
117
|
+
? Object.assign(Object.assign({}, d), { status: "failed", fileName, parseError: match.parseError || "Parsing failed. Try a different file." }) : d));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (status === "PARSED") {
|
|
121
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
122
|
+
? Object.assign(Object.assign({}, d), { status: "valid", fileName, parseError: null }) : d));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (status === "PARSING" || status === "UPLOADED") {
|
|
126
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
127
|
+
? Object.assign(Object.assign({}, d), { status: "parsing", fileName }) : d));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (pollError) {
|
|
132
|
+
// keep polling
|
|
133
|
+
}
|
|
134
|
+
yield new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
135
|
+
}
|
|
136
|
+
setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "failed", fileName, parseError: "Parsing timed out. Please try again." }) : d));
|
|
137
|
+
});
|
|
138
|
+
const handleUpload = (idx, file) => __awaiter(this, void 0, void 0, function* () {
|
|
139
|
+
const selectedDoc = docs[idx];
|
|
140
|
+
if (!selectedDoc)
|
|
141
|
+
return;
|
|
142
|
+
setContinueError(null);
|
|
91
143
|
setDocs((prev) => prev.map((d, i) => i === idx
|
|
92
|
-
? Object.assign(Object.assign({}, d), { status: "validating", fileName: STUB_FILENAMES[d.id] || "document.pdf" }) : d));
|
|
93
|
-
|
|
94
|
-
|
|
144
|
+
? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name || STUB_FILENAMES[d.id] || "document.pdf", parseError: null }) : d));
|
|
145
|
+
try {
|
|
146
|
+
const uploadResult = onUploadDocument
|
|
147
|
+
? yield onUploadDocument(selectedDoc, file)
|
|
148
|
+
: undefined;
|
|
149
|
+
const nextFileName = file.name || STUB_FILENAMES[selectedDoc.id] || "document.pdf";
|
|
150
|
+
const expectedDocumentType = normalizeDocumentType(selectedDoc.id);
|
|
151
|
+
const immediateStatus = String((uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.status) || "").toUpperCase();
|
|
152
|
+
if (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.documentId) {
|
|
153
|
+
setUploadedDocIds((prev) => (Object.assign(Object.assign({}, prev), { [idx]: uploadResult.documentId })));
|
|
154
|
+
}
|
|
155
|
+
if (immediateStatus === "PARSED") {
|
|
156
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
157
|
+
? Object.assign(Object.assign({}, d), { status: "valid", fileName: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.fileName) || nextFileName, parseError: null }) : d));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (immediateStatus === "FAILED") {
|
|
161
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
162
|
+
? Object.assign(Object.assign({}, d), { status: "failed", fileName: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.fileName) || nextFileName, parseError: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.parseError) || "Parsing failed. Please upload a correct document." }) : d));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
166
|
+
? Object.assign(Object.assign({}, d), { status: "parsing", fileName: nextFileName }) : d));
|
|
167
|
+
yield pollForParseStatus(idx, nextFileName, expectedDocumentType);
|
|
168
|
+
}
|
|
169
|
+
catch (uploadError) {
|
|
170
|
+
const errorMessage = uploadError instanceof Error
|
|
171
|
+
? uploadError.message
|
|
172
|
+
: "Upload failed. Please try again.";
|
|
173
|
+
setDocs((prev) => prev.map((d, i) => i === idx
|
|
174
|
+
? Object.assign(Object.assign({}, d), { status: "failed", parseError: errorMessage }) : d));
|
|
175
|
+
}
|
|
176
|
+
});
|
|
95
177
|
const handleClear = (idx) => {
|
|
96
178
|
setDocs((prev) => prev.map((d, i) => i === idx
|
|
97
|
-
? Object.assign(Object.assign({}, d), { status: "empty", fileName: null }) : d));
|
|
179
|
+
? Object.assign(Object.assign({}, d), { status: "empty", fileName: null, parseError: null }) : d));
|
|
180
|
+
setContinueError(null);
|
|
181
|
+
};
|
|
182
|
+
const handleRemove = (idx) => __awaiter(this, void 0, void 0, function* () {
|
|
183
|
+
const docId = uploadedDocIds[idx];
|
|
184
|
+
if (docId && onDeleteDocument) {
|
|
185
|
+
try {
|
|
186
|
+
yield onDeleteDocument(docId);
|
|
187
|
+
}
|
|
188
|
+
catch (deleteError) {
|
|
189
|
+
// still clear from UI even if backend delete fails
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
setUploadedDocIds((prev) => {
|
|
193
|
+
const next = Object.assign({}, prev);
|
|
194
|
+
delete next[idx];
|
|
195
|
+
return next;
|
|
196
|
+
});
|
|
197
|
+
handleClear(idx);
|
|
198
|
+
});
|
|
199
|
+
const handleContinue = () => {
|
|
200
|
+
const failedDocs = docs.filter((d) => d.status === "failed");
|
|
201
|
+
if (failedDocs.length > 0) {
|
|
202
|
+
const names = failedDocs.map((d) => d.name).join(", ");
|
|
203
|
+
setContinueError(`Cannot continue — ${failedDocs.length === 1 ? "this document has" : "these documents have"} errors: ${names}. Please re-upload correct files or remove them.`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
setContinueError(null);
|
|
207
|
+
onContinue();
|
|
98
208
|
};
|
|
99
209
|
const validCount = docs.filter((d) => d.status === "valid").length;
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
// Coverage heuristic from mock (App.jsx:1361)
|
|
210
|
+
const failedCount = docs.filter((d) => d.status === "failed").length;
|
|
211
|
+
const busyCount = docs.filter((d) => d.status === "validating" || d.status === "parsing").length;
|
|
103
212
|
const coveragePct = validCount === 0
|
|
104
213
|
? 32
|
|
105
214
|
: validCount === 1
|
|
@@ -148,10 +257,17 @@ function TaxAxisDocuments({ profile, onContinue, onBack, userContext: _userConte
|
|
|
148
257
|
coveragePct,
|
|
149
258
|
"% coverage"),
|
|
150
259
|
validCount < docs.length && (react_1.default.createElement("span", { className: "text-[10px] text-tax-axis-text-4 font-tax-axis-body" }, "Upload more documents to increase strategy coverage")))),
|
|
151
|
-
|
|
260
|
+
continueError && (react_1.default.createElement("div", { className: "py-3 px-4 mb-4 text-xs leading-relaxed font-tax-axis-body rounded-lg", style: {
|
|
261
|
+
background: "rgba(197,48,48,0.08)",
|
|
262
|
+
border: "1px solid rgba(197,48,48,0.3)",
|
|
263
|
+
color: "#FEB2B2",
|
|
264
|
+
} }, continueError)),
|
|
265
|
+
TIER_DEFS.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: HELP_OVERRIDES, onUpload: handleUpload, onClear: handleClear, onRemove: handleRemove }))),
|
|
152
266
|
react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
|
|
153
267
|
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
|
|
154
|
-
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick:
|
|
155
|
-
?
|
|
156
|
-
:
|
|
268
|
+
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: handleContinue, className: "flex-1", disabled: busyCount > 0 }, busyCount > 0
|
|
269
|
+
? "Processing..."
|
|
270
|
+
: failedCount > 0
|
|
271
|
+
? `Continue (${failedCount} failed)`
|
|
272
|
+
: "Continue"))));
|
|
157
273
|
}
|
|
@@ -8,7 +8,7 @@ const react_1 = __importDefault(require("react"));
|
|
|
8
8
|
const TaxAxisButton_1 = require("../shared/TaxAxisButton");
|
|
9
9
|
const TaxAxisBadge_1 = require("../shared/TaxAxisBadge");
|
|
10
10
|
function IntakeCtaCards({ onProspect, onFullAnalysis, userContext = "expert", }) {
|
|
11
|
-
return (react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3"
|
|
11
|
+
return (react_1.default.createElement("div", { className: "grid grid-cols-2 gap-3" },
|
|
12
12
|
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
13
|
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
14
|
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"),
|
|
@@ -33,6 +33,6 @@ function TaxAxisIntake({ userContext = "expert", onProspect, onFullAnalysis, ini
|
|
|
33
33
|
react_1.default.createElement(RefineAnalysisSection_1.RefineAnalysisSection, { userContext: userContext }),
|
|
34
34
|
react_1.default.createElement(CpaIntakeQuestionsSection_1.CpaIntakeQuestionsSection, { userContext: userContext }),
|
|
35
35
|
react_1.default.createElement(IntakeCtaCards_1.IntakeCtaCards, { onProspect: handleProspect, onFullAnalysis: handleFull, userContext: userContext })),
|
|
36
|
-
react_1.default.createElement("div", { className: "sticky top-5 self-start"
|
|
36
|
+
react_1.default.createElement("div", { className: "sticky top-5 self-start" },
|
|
37
37
|
react_1.default.createElement(StrategyRadar_1.StrategyRadar, { profile: profile })))));
|
|
38
38
|
}
|
|
@@ -111,7 +111,7 @@ function TaxAxisPreparerWorkpaper({ profile, onBack, onToggleToClient }) {
|
|
|
111
111
|
.flex.flex-col.w-full { margin-top: 0 !important; }
|
|
112
112
|
main.w-full { width: 100% !important; margin-left: 0 !important; padding: 0 !important; }
|
|
113
113
|
* { color-adjust: exact; -webkit-print-color-adjust: exact; }
|
|
114
|
-
|
|
114
|
+
p, span, div, td, th, li { color: #212529 !important; }
|
|
115
115
|
h1, h2, h3, h4, h5, h6, strong, b { color: #060821 !important; }
|
|
116
116
|
}
|
|
117
117
|
`),
|
|
@@ -16,8 +16,6 @@ function ProspectPrintView({ profile, bizName, displayLo, displayHi, currentTax,
|
|
|
16
16
|
body * { visibility: hidden !important; }
|
|
17
17
|
.prospect-print-root, .prospect-print-root * { visibility: visible !important; }
|
|
18
18
|
.prospect-print-root { position: absolute !important; left: 0 !important; top: 0 !important; width: 100% !important; }
|
|
19
|
-
body, p, span, div, td, th, li { color: #212529 !important; }
|
|
20
|
-
h1, h2, h3, h4, h5, h6, strong, b { color: #060821 !important; }
|
|
21
19
|
.no-print { display: none !important; }
|
|
22
20
|
}
|
|
23
21
|
`),
|
|
@@ -8,14 +8,14 @@ exports.DOC_SPECS_BASE = void 0;
|
|
|
8
8
|
exports.getDocSpecs = getDocSpecs;
|
|
9
9
|
const states_1 = require("./states");
|
|
10
10
|
exports.DOC_SPECS_BASE = [
|
|
11
|
-
{ id: "1120s", name: "Federal Tax Return (1120S/1065/1040)", accept: [".pdf"], strategies: ["QBI \u00A7199A", "S-Corp Structure", "\u00A7179 Expensing", "Bonus Depreciation", "Business Interest \u00A7163(j)"], required: "optional", help: "Optional \u2014 startups or new entities may not have prior returns. Improves accuracy if available." },
|
|
12
|
-
{ id: "state-return", name: "State Return(s)", accept: [".pdf"], strategies: ["SALT / PTE Optimization", "State Nexus Analysis"], required: "conditional", help: "Required if client operates in income-tax states. Not needed for WY, NV, TX, FL, etc." },
|
|
13
|
-
{ id: "payroll", name: "Payroll Records (W-3 / 941)", accept: [".pdf", ".xlsx", ".csv"], strategies: ["S-Corp Salary", "HSA Maximization", "WOTC", "Overtime Deduction"], required: "optional", help: "Optional for V1 \u2014 intake questions substitute for most payroll data." },
|
|
14
|
-
{ id: "pnl", name: "Profit & Loss Statement (Current Year)", accept: [".pdf", ".xlsx", ".csv"], strategies: ["QBI \u00A7199A", "R&D Credit", "Business Meals", "Professional Fees", "Home Office"], required: true, help: "Required \u2014 current year P&L is the minimum input for savings calculations." },
|
|
15
|
-
{ id: "balance", name: "Balance Sheet (Current Year)", accept: [".pdf", ".xlsx"], strategies: ["Business Interest \u00A7163(j)", "\u00A7179 Expensing", "Opportunity Zone"], required: true, help: "Required \u2014 needed for asset-based strategies and financial health assessment." },
|
|
16
|
-
{ id: "cashflow", name: "Cash Flow Statement", accept: [".pdf", ".xlsx"], strategies: ["Income Deferral", "Tax Method Elections", "Estimated Tax Timing"], required: "optional", help: "Recommended \u2014 helps with income deferral and timing strategies." },
|
|
17
|
-
{ id: "fixed-assets", name: "Fixed Asset Schedule", accept: [".pdf", ".xlsx"], strategies: ["\u00A7179", "Bonus Depreciation \u00A7168(k)", "Cost Segregation"], required: "conditional", help: "Required for real estate clients (cost segregation). Optional otherwise." },
|
|
18
|
-
{ id: "prior-returns", name: "Prior Year Returns (2022\u20132024)", accept: [".pdf"], strategies: ["R&E Catch-Up \u00A7174A", "Charitable Bunching", "Confidence Intervals"], required: false, help: "More years = tighter estimates. 1 year: \u00B130% range. 2\u20133 years: \u00B115%. 4\u20135 years: \u00B15%." },
|
|
11
|
+
{ id: "1120s", name: "Federal Tax Return (1120S/1065/1040)", accept: [".pdf", ".docx", ".doc"], strategies: ["QBI \u00A7199A", "S-Corp Structure", "\u00A7179 Expensing", "Bonus Depreciation", "Business Interest \u00A7163(j)"], required: "optional", help: "Optional \u2014 startups or new entities may not have prior returns. Improves accuracy if available." },
|
|
12
|
+
{ id: "state-return", name: "State Return(s)", accept: [".pdf", ".docx", ".doc"], strategies: ["SALT / PTE Optimization", "State Nexus Analysis"], required: "conditional", help: "Required if client operates in income-tax states. Not needed for WY, NV, TX, FL, etc." },
|
|
13
|
+
{ id: "payroll", name: "Payroll Records (W-3 / 941)", accept: [".pdf", ".xlsx", ".csv", ".docx", ".doc"], strategies: ["S-Corp Salary", "HSA Maximization", "WOTC", "Overtime Deduction"], required: "optional", help: "Optional for V1 \u2014 intake questions substitute for most payroll data." },
|
|
14
|
+
{ id: "pnl", name: "Profit & Loss Statement (Current Year)", accept: [".pdf", ".xlsx", ".csv", ".docx", ".doc"], strategies: ["QBI \u00A7199A", "R&D Credit", "Business Meals", "Professional Fees", "Home Office"], required: true, help: "Required \u2014 current year P&L is the minimum input for savings calculations." },
|
|
15
|
+
{ id: "balance", name: "Balance Sheet (Current Year)", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["Business Interest \u00A7163(j)", "\u00A7179 Expensing", "Opportunity Zone"], required: true, help: "Required \u2014 needed for asset-based strategies and financial health assessment." },
|
|
16
|
+
{ id: "cashflow", name: "Cash Flow Statement", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["Income Deferral", "Tax Method Elections", "Estimated Tax Timing"], required: "optional", help: "Recommended \u2014 helps with income deferral and timing strategies." },
|
|
17
|
+
{ id: "fixed-assets", name: "Fixed Asset Schedule", accept: [".pdf", ".xlsx", ".docx", ".doc"], strategies: ["\u00A7179", "Bonus Depreciation \u00A7168(k)", "Cost Segregation"], required: "conditional", help: "Required for real estate clients (cost segregation). Optional otherwise." },
|
|
18
|
+
{ id: "prior-returns", name: "Prior Year Returns (2022\u20132024)", accept: [".pdf", ".docx", ".doc"], strategies: ["R&E Catch-Up \u00A7174A", "Charitable Bunching", "Confidence Intervals"], required: false, help: "More years = tighter estimates. 1 year: \u00B130% range. 2\u20133 years: \u00B115%. 4\u20135 years: \u00B15%." },
|
|
19
19
|
];
|
|
20
20
|
function getDocSpecs(profile) {
|
|
21
21
|
const states = (profile === null || profile === void 0 ? void 0 : profile.states) || [];
|