@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.
Files changed (32) hide show
  1. package/lib/components/TaxAxis/TaxAxisApi.d.ts +54 -0
  2. package/lib/components/TaxAxis/TaxAxisApi.js +2 -0
  3. package/lib/components/TaxAxis/TaxAxisShell.d.ts +3 -0
  4. package/lib/components/TaxAxis/TaxAxisShell.js +318 -0
  5. package/lib/components/TaxAxis/index.d.ts +3 -0
  6. package/lib/components/TaxAxis/index.js +8 -0
  7. package/lib/components/TaxAxis/types.d.ts +16 -0
  8. package/lib/components/TaxAxis/types.js +2 -0
  9. package/lib/components/shared/UploadClient.d.ts +2 -1
  10. package/lib/components/shared/UploadClient.js +6 -2
  11. package/lib/index.d.ts +3 -1
  12. package/lib/index.js +3 -1
  13. package/lib/tax-axis/components/clientReport/TaxAxisClientReport.js +1 -1
  14. package/lib/tax-axis/components/dashboard/DashboardActions.js +5 -4
  15. package/lib/tax-axis/components/dashboard/DashboardSummary.js +1 -1
  16. package/lib/tax-axis/components/dashboard/TaxAxisDashboard.d.ts +33 -1
  17. package/lib/tax-axis/components/dashboard/TaxAxisDashboard.js +83 -7
  18. package/lib/tax-axis/components/documents/DocumentCard.d.ts +5 -3
  19. package/lib/tax-axis/components/documents/DocumentCard.js +53 -12
  20. package/lib/tax-axis/components/documents/DocumentTier.d.ts +3 -2
  21. package/lib/tax-axis/components/documents/DocumentTier.js +2 -2
  22. package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +17 -1
  23. package/lib/tax-axis/components/documents/TaxAxisDocuments.js +136 -20
  24. package/lib/tax-axis/components/intake/IntakeCtaCards.js +1 -1
  25. package/lib/tax-axis/components/intake/TaxAxisIntake.js +1 -1
  26. package/lib/tax-axis/components/preparerWorkpaper/TaxAxisPreparerWorkpaper.js +1 -1
  27. package/lib/tax-axis/components/prospectReport/ProspectPrintView.js +0 -2
  28. package/lib/tax-axis/lib/data/documents.js +8 -8
  29. package/lib/tax-axis/lib/data/strategies.js +9 -9
  30. package/package.json +1 -1
  31. package/lib/README.md +0 -2
  32. 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 computed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(profile), [profile]);
48
- const dashEligible = (0, react_1.useMemo)(() => (0, compute_1.filterEligibleStrategies)(profile), [profile]);
107
+ const llmStrategies = (0, react_1.useMemo)(() => (hasLlm ? mapLlmToStrategies(llmResult) : []), [hasLlm, llmResult]);
108
+ const llmComputed = (0, react_1.useMemo)(() => (hasLlm ? buildLlmComputed(llmStrategies) : new Map()), [hasLlm, llmStrategies]);
109
+ const computed = (0, react_1.useMemo)(() => (hasLlm ? llmComputed : (0, compute_1.computeAllStrategies)(profile)), [hasLlm, llmComputed, profile]);
110
+ const dashEligible = (0, react_1.useMemo)(() => (hasLlm ? llmStrategies : (0, compute_1.filterEligibleStrategies)(profile)), [hasLlm, llmStrategies, profile]);
49
111
  const maxSavings = Math.max(...dashEligible.map((s) => { var _a, _b; return (_b = (_a = computed.get(s.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : s.hi; }), 1);
50
112
  // ─── State ─────────────────────────────────────────────────────
51
113
  const [topTab, setTopTab] = (0, react_1.useState)("extraction");
@@ -80,6 +142,21 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
80
142
  // Sorted strategies for impact distribution
81
143
  const sortedByImpact = (0, react_1.useMemo)(() => [...dashEligible].sort((a, b) => { var _a, _b, _c, _d; return ((_b = (_a = computed.get(b.rank)) === null || _a === void 0 ? void 0 : _a.hi) !== null && _b !== void 0 ? _b : b.hi) - ((_d = (_c = computed.get(a.rank)) === null || _c === void 0 ? void 0 : _c.hi) !== null && _d !== void 0 ? _d : a.hi); }), [dashEligible, computed]);
82
144
  return (react_1.default.createElement("div", null,
145
+ hasLlm && (llmResult === null || llmResult === void 0 ? void 0 : llmResult.meta) && (react_1.default.createElement("div", { className: "rounded-lg mb-3 px-4 py-2.5 flex items-center justify-between text-[11px] font-tax-axis-mono", style: {
146
+ background: "rgba(36,131,132,0.06)",
147
+ border: "1px solid rgba(36,131,132,0.15)",
148
+ color: "#9498B8",
149
+ } },
150
+ react_1.default.createElement("span", null,
151
+ "Source: ",
152
+ react_1.default.createElement("strong", { className: "text-tax-axis-teal-light" }, llmResult.meta.provider),
153
+ " · ",
154
+ llmResult.meta.parsedDocumentCount,
155
+ " documents parsed",
156
+ " · ",
157
+ ((_b = llmResult.meta.detectedDocumentTypes) === null || _b === void 0 ? void 0 : _b.length) || 0,
158
+ " document types detected"),
159
+ react_1.default.createElement("span", { className: "text-tax-axis-text-4" }, llmResult.meta.tokenCount > 0 ? `${llmResult.meta.tokenCount} tokens` : ""))),
83
160
  react_1.default.createElement(DashboardSummary_1.DashboardSummary, { profile: profile, dashEligible: dashEligible, computed: computed, dataConfirmed: dataConfirmed, reviewUnreviewed: reviewStatus.unreviewed }),
84
161
  react_1.default.createElement(DashboardTopBar_1.DashboardTopBar, { topTab: topTab, setTopTab: setTopTab, dataConfirmed: dataConfirmed, reviewUnreviewed: reviewStatus.unreviewed }),
85
162
  topTab === "extraction" && (react_1.default.createElement("div", null,
@@ -197,7 +274,7 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
197
274
  const c = computed.get(s.rank);
198
275
  return (react_1.default.createElement(StrategyTile_1.StrategyTile, { key: s.rank, s: Object.assign(Object.assign({}, s), { lo: (_a = c === null || c === void 0 ? void 0 : c.lo) !== null && _a !== void 0 ? _a : s.lo, hi: (_b = c === null || c === void 0 ? void 0 : c.hi) !== null && _b !== void 0 ? _b : s.hi }), delay: 0.2 + i * 0.06, onClick: () => setSelected(s), maxSavings: maxSavings }));
199
276
  })),
200
- react_1.default.createElement("div", { className: "rounded-[14px] overflow-hidden mb-4", style: {
277
+ !hasLlm && (react_1.default.createElement("div", { className: "rounded-[14px] overflow-hidden mb-4", style: {
201
278
  background: "#0E1132",
202
279
  border: "1px solid rgba(36,131,132,0.12)",
203
280
  boxShadow: "0 2px 8px rgba(6,8,33,0.3)",
@@ -210,7 +287,7 @@ function TaxAxisDashboard({ profile, onDownloadClient, onDownloadPreparer, onPre
210
287
  react_1.default.createElement("span", { className: "text-[13px] text-tax-axis-text" }, a.name),
211
288
  react_1.default.createElement("div", { className: "flex items-center gap-2.5" },
212
289
  react_1.default.createElement("span", { className: "text-[11px] font-tax-axis-mono text-tax-axis-text-2" }, a.savings),
213
- react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: a.priority === "MEDIUM" ? "orange" : "neutral", size: "xs" }, a.priority)))))),
290
+ react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: a.priority === "MEDIUM" ? "orange" : "neutral", size: "xs" }, a.priority))))))),
214
291
  react_1.default.createElement("div", { className: "rounded-[10px] mb-4 text-xs text-tax-axis-text leading-[1.7] font-tax-axis-body", style: {
215
292
  padding: "14px 18px",
216
293
  background: "#0E1132",
@@ -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("div", { style: { position: "sticky", bottom: 24, zIndex: 10, paddingTop: 12, background: "linear-gradient(to bottom, transparent 0%, #060821 24%)" } },
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 === "valid" ? (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
55
+ } })) : doc.status === "parsing" ? (react_1.default.createElement("div", { className: "w-3.5 h-3.5 rounded-full animate-spin", style: {
56
+ border: "2px solid transparent",
57
+ borderTopColor: "#248384",
58
+ } })) : doc.status === "failed" ? (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
59
+ react_1.default.createElement("path", { d: "M4 4l6 6M10 4L4 10", stroke: "#C53030", strokeWidth: "2", strokeLinecap: "round" }))) : doc.status === "valid" ? (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" },
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
- react_1.default.createElement("div", { className: "text-[11px] text-tax-axis-text-3 font-tax-axis-body mt-0.5" }, (_a = doc.fileName) !== null && _a !== void 0 ? _a : (helpOverride !== null && helpOverride !== void 0 ? helpOverride : doc.help))),
65
+ doc.status === "empty" && (react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: tierBadgeColor, size: "xs" }, tierBadgeText)),
66
+ doc.status === "failed" && (react_1.default.createElement(TaxAxisBadge_1.TaxAxisBadge, { color: "red", size: "xs" }, "FAILED"))),
67
+ react_1.default.createElement("div", { className: "text-[11px] text-tax-axis-text-3 font-tax-axis-body mt-0.5" }, doc.status === "failed" && doc.parseError
68
+ ? doc.parseError
69
+ : (_a = doc.fileName) !== null && _a !== void 0 ? _a : (helpOverride !== null && helpOverride !== void 0 ? helpOverride : doc.help))),
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" } }, "Done"),
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 === "empty" && (react_1.default.createElement("button", { onClick: onUpload, 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: {
58
- background: "rgba(36,131,132,0.10)",
59
- border: "1px solid rgba(36,131,132,0.2)",
60
- } }, "Upload"))))));
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
- // Stub upload: empty -> validating -> valid after a short delay
89
- // (mock App.jsx:1238-1241)
90
- const handleUpload = (idx) => {
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
- setTimeout(() => setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "valid" }) : d)), 500 + Math.random() * 400);
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 requiredCount = docs.filter((d) => d.required === true).length;
101
- const requiredValid = docs.filter((d) => d.required === true && d.status === "valid").length;
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
- TIER_DEFS.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: HELP_OVERRIDES, onUpload: handleUpload, onClear: handleClear }))),
260
+ continueError && (react_1.default.createElement("div", { className: "py-3 px-4 mb-4 text-xs leading-relaxed font-tax-axis-body rounded-lg", style: {
261
+ background: "rgba(197,48,48,0.08)",
262
+ border: "1px solid rgba(197,48,48,0.3)",
263
+ color: "#FEB2B2",
264
+ } }, continueError)),
265
+ TIER_DEFS.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: HELP_OVERRIDES, onUpload: handleUpload, onClear: handleClear, onRemove: handleRemove }))),
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: onContinue, disabled: requiredCount > requiredValid, className: "flex-1" }, requiredCount > requiredValid
155
- ? `Upload ${requiredCount - requiredValid} more required doc${requiredCount - requiredValid > 1 ? "s" : ""}`
156
- : "Run Analysis"))));
268
+ react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: handleContinue, className: "flex-1", disabled: busyCount > 0 }, busyCount > 0
269
+ ? "Processing..."
270
+ : failedCount > 0
271
+ ? `Continue (${failedCount} failed)`
272
+ : "Continue"))));
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", style: { position: "sticky", bottom: 0, zIndex: 10, paddingTop: 12, paddingBottom: 4, background: "linear-gradient(to bottom, transparent 0%, #060821 16%)" } },
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", style: { minWidth: 0, overflow: 'hidden' } },
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
- body, p, span, div, td, th, li { color: #212529 !important; }
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) || [];