@paro.io/expert-shared-components 1.14.72 → 1.14.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,47 +35,48 @@ 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;
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 (dynamic computation) ═══
50
+ // ═══ SAVINGS RANGE backend-authoritative when available ═══
51
51
  const computed = (0, react_1.useMemo)(() => (0, compute_1.computeAllStrategies)(profile), [profile]);
52
- const displayLo = 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);
53
- const displayHi = 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);
54
- // Savings bar chart data top 3 strategies with midpoint + percentage
55
- 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);
56
- const barData = 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 }; });
52
+ const displayLo = 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 displayHi = 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 : (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
+ 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));
64
+ const optimizedTax = (_e = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.gapBlock.optimizedTax) !== null && _e !== void 0 ? _e : (currentTax - realizedSavings);
65
+ 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);
66
+ // ═══ BAR CHART DATA ═══
67
+ const barData = (_g = backendComputePayload === null || backendComputePayload === void 0 ? void 0 : backendComputePayload.barData) !== null && _g !== void 0 ? _g : (() => {
68
+ 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);
69
+ 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 }; });
70
+ })();
57
71
  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
72
  // ═══ DOCUMENT GROUPING ═══
74
73
  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
74
  const requiredDocs = ["Profit & Loss Statement (current year)", "Balance Sheet (current year)"];
76
75
  const recommendedDocs = ["Federal Tax Return (most recent)", "W-2 / Officer Compensation records"];
77
76
  const conditionalDocs = allDocsNeeded.filter(d => !requiredDocs.includes(d) && !recommendedDocs.includes(d)).slice(0, 5);
78
- // ═══ PRINT HANDLER ═══
77
+ const sectionNums = remaining > 0
78
+ ? { recommended: "02", additional: "03", sample: "04", documents: "05", nextSteps: "06" }
79
+ : { recommended: "02", additional: null, sample: "03", documents: "04", nextSteps: "05" };
79
80
  const handlePrint = () => {
80
81
  setPrintMode(true);
81
82
  requestAnimationFrame(() => {
@@ -86,18 +87,16 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
86
87
  });
87
88
  });
88
89
  };
89
- // ═══ PRINT MODE — Light theme layout ═══
90
90
  if (printMode) {
91
91
  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
92
  }
93
- // ═══ INTERACTIVE MODE — Dark theme layout ═══
94
93
  return (react_1.default.createElement("div", null,
95
94
  react_1.default.createElement(ProspectCover_1.ProspectCover, { profile: profile, bizName: bizName, displayLo: displayLo, displayHi: displayHi }),
96
95
  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
96
  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
97
  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
98
  react_1.default.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 } }, [
100
- { v: `$${displayLo}K\u2013$${displayHi}K`, l: "Est. Savings" },
99
+ { v: `$${displayLo}K–$${displayHi}K`, l: "Est. Savings" },
101
100
  { v: String(eligible.length), l: "Strategies Found" },
102
101
  { v: String(high.length), l: "High Impact" },
103
102
  { v: String(quick.length), l: "Quick Wins" },
@@ -109,29 +108,30 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
109
108
  react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "01"),
110
109
  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
110
  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
- "We evaluated ",
114
- bizName,
115
- "'s tax position against 25 federal strategies and identified ",
116
- react_1.default.createElement("strong", { style: { color: theme_1.T.white } },
117
- eligible.length,
118
- " applicable opportunities"),
119
- " with combined estimated savings of ",
120
- react_1.default.createElement("strong", { style: { color: theme_1.T.accentLt } },
121
- "$",
122
- displayLo,
123
- "K\u2013$",
124
- displayHi,
125
- "K annually"),
126
- ". ",
127
- high.length,
128
- " strategies could have significant impact."),
129
- react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8 } },
130
- "The top ",
131
- top3.length,
132
- " strategies are detailed below.",
133
- quick.length > 0 ? ` ${quick.length} can be implemented this week with no structural changes.` : "",
134
- " These are profile-based estimates \u2014 uploading financial documents will refine savings calculations and unlock all 25 strategies.")),
111
+ (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,
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
+ "We evaluated ",
114
+ bizName,
115
+ "'s tax position against 25 federal strategies and identified ",
116
+ react_1.default.createElement("strong", { style: { color: theme_1.T.white } },
117
+ eligible.length,
118
+ " applicable opportunities"),
119
+ " with combined estimated savings of ",
120
+ react_1.default.createElement("strong", { style: { color: theme_1.T.accentLt } },
121
+ "$",
122
+ displayLo,
123
+ "K\u2013$",
124
+ displayHi,
125
+ "K annually"),
126
+ ". ",
127
+ high.length,
128
+ " strategies could have significant impact."),
129
+ react_1.default.createElement("div", { style: { fontSize: 14, color: theme_1.T.text2, fontFamily: theme_1.T.body, lineHeight: 1.8 } },
130
+ "The top ",
131
+ top3.length,
132
+ " strategies are detailed below.",
133
+ quick.length > 0 ? ` ${quick.length} can be implemented this week with no structural changes.` : "",
134
+ " These are profile-based estimates \u2014 uploading financial documents will refine savings calculations and unlock all 25 strategies.")))),
135
135
  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
136
  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
137
  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 +151,10 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
151
151
  react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "02"),
152
152
  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
153
  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) => (react_1.default.createElement(ProspectStrategyCard_1.ProspectStrategyCard, { key: s.rank, strategy: s, index: i, profile: profile, computed: computed }))))),
154
+ react_1.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 14 } }, top3.map((s, i) => {
155
+ var _a;
156
+ 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}`] }));
157
+ }))),
155
158
  remaining > 0 && (react_1.default.createElement("div", { style: { marginBottom: 24 } },
156
159
  react_1.default.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6 } },
157
160
  react_1.default.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: theme_1.T.accent, fontFamily: theme_1.T.mono } }, "03"),
@@ -162,10 +165,8 @@ function TaxAxisProspectReport({ profile, onUpgrade, onPresent, onReset, }) {
162
165
  "+ ",
163
166
  remaining,
164
167
  " 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
- "Including: ",
167
- remainingNames.join(", "),
168
- remaining > 4 ? " and more" : ""),
168
+ 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)
169
+ || `Including: ${remainingNames.join(", ")}${remaining > 4 ? " and more" : ""}`),
169
170
  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
171
  react_1.default.createElement(SampleAnalysisPreview_1.SampleAnalysisPreview, { sectionNum: sectionNums.sample, eligibleCount: eligible.length }),
171
172
  react_1.default.createElement(ProspectDocuments_1.ProspectDocuments, { sectionNum: sectionNums.documents, requiredDocs: requiredDocs, recommendedDocs: recommendedDocs, conditionalDocs: conditionalDocs }),
@@ -1,18 +1,13 @@
1
- export interface CatalogFieldDef {
2
- label?: string;
1
+ export interface FieldDef {
2
+ label: string;
3
3
  sourceRef?: string;
4
4
  }
5
5
  export interface CatalogSection {
6
6
  head: string;
7
7
  fields: string[];
8
8
  }
9
- export interface CatalogEntry {
9
+ export interface DocumentCatalogEntry {
10
10
  sections: CatalogSection[];
11
- fields: Record<string, CatalogFieldDef>;
11
+ fields: Record<string, FieldDef>;
12
12
  }
13
- /**
14
- * Keyed by documentType string returned by the backend in parsedData.documentType.
15
- * When DocumentReviewModal receives parsedData, it looks up the documentType here
16
- * to get section groupings and field labels. Falls back to flat rendering if no entry.
17
- */
18
- export declare const DOCUMENT_FIELD_CATALOG: Record<string, CatalogEntry>;
13
+ export declare const DOCUMENT_FIELD_CATALOG: Record<string, DocumentCatalogEntry>;