UNPKG

financial-calculators-react

Version:
575 lines (564 loc) 76.6 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; /** * Note: This is a placeholder for the actual utilities module. * * When implementing this package, you need to provide implementations for: * - UI Components: CalculatorContainer, InputSection, ResultsSection, etc. * - UI Elements: InputField, SelectField, Button, ResultCard, etc. * - Chart Components: LineChart, BarChart, ChartToggle, ChartIcons * - Utility Components: ReportDialog, ReportOptions, etc. * - Utility Functions: formatCurrency, numberToWords, etc. * - Validation Functions: validateRequiredFields, validateNumericFields, etc. * - Report Generation: generateReport, downloadReport, sendReportViaWhatsApp */ // Placeholder implementations // UI Components var CalculatorContainer = function (props) { return null; }; var InputSection = function (props) { return null; }; var ResultsSection = function (props) { return null; }; // UI Elements var InputField = function (props) { return null; }; var SelectField = function (props) { return null; }; var Button = function (props) { return null; }; var ButtonPrimary = function (props) { return null; }; var ResultCard = function (props) { return null; }; var NumberField = function (props) { return null; }; var DropdownField = function (props) { return null; }; // Chart Components var LineChart = function (props) { return null; }; var BarChart = function (props) { return null; }; var ChartToggle = function (props) { return null; }; var ChartIcons = { Line: function () { return null; }, Bar: function () { return null; } }; // Table Components var ResultsTable = function (props) { return null; }; var ResultsSummary = function (props) { return null; }; // Utility Components var Disclaimer = function (props) { return null; }; var LoadingIndicator = function (props) { return null; }; var LoadingSpinner = function (props) { return null; }; var TipBox = function (props) { return null; }; var ReportDialog = function (props) { return null; }; var ReportOptions = function (props) { return null; }; // Utility Functions var formatCurrency = function (value, options) { return new Intl.NumberFormat('en-IN', __assign({ style: 'currency', currency: 'INR', maximumFractionDigits: 0 }, options)).format(value); }; var numberToWords = function (num) { return "".concat(num.toLocaleString(), " rupees"); }; // Validation Functions var validateRequiredFields = function (inputs, requiredFields) { var errors = {}; requiredFields.forEach(function (field) { if (!inputs[field]) { errors[field] = 'This field is required'; } }); return errors; }; var validateNumericFields = function (inputs, validations) { var errors = {}; Object.keys(validations).forEach(function (field) { var value = parseFloat(inputs[field]); var _a = validations[field], min = _a.min, max = _a.max, errorMessage = _a.errorMessage; if (inputs[field] && (isNaN(value) || (min !== undefined && value < min) || (max !== undefined && value > max))) { errors[field] = errorMessage; } }); return errors; }; var mergeErrors = function () { var errorObjects = []; for (var _i = 0; _i < arguments.length; _i++) { errorObjects[_i] = arguments[_i]; } return errorObjects.reduce(function (acc, curr) { return (__assign(__assign({}, acc), curr)); }, {}); }; var hasErrors = function (errors) { return Object.keys(errors).length > 0; }; // Report Generation var generateReport = function (options, data) { return "Report for ".concat(options.clientName); }; var downloadReport = function (content, clientName, options, reportData, yearlyData) { console.log('Downloading report...'); }; var sendReportViaWhatsApp = function (content, mobileNumber, options, reportData, yearlyData) { console.log('Sending report via WhatsApp...'); }; /* Future Value of Lumpsum + SIP Calculator Component ---------------------------------------------------------------- */ /* ────────────── Component ────────────── */ var LumpsumSIPCalculator = function (_a) { var _b; var _c = _a.theme, theme = _c === void 0 ? {} : _c; var defaultTheme = { primary: "#0C875E", secondary: "#108E66", accent: "#D6F5E8", background: "#FAFAFA", textPrimary: "#333333", textSecondary: "#666666" }; var calculatorTheme = __assign(__assign({}, defaultTheme), theme); var _d = react.useState({ lumpsumPrincipal: "", lumpsumRate: "", sipAmount: "", sipRate: "", years: "", }), inputs = _d[0], setInputs = _d[1]; var _e = react.useState("Annually"), lumpsumFrequency = _e[0], setLumpsumFrequency = _e[1]; var _f = react.useState({}), errors = _f[0], setErrors = _f[1]; var _g = react.useState(null), results = _g[0], setResults = _g[1]; var _h = react.useState(false), isCalculating = _h[0], setIsCalculating = _h[1]; var _j = react.useState("line"), chartType = _j[0], setChartType = _j[1]; var _k = react.useState(null), calculationTimer = _k[0], setCalculationTimer = _k[1]; // Report related state var _l = react.useState(false), showReportModal = _l[0], setShowReportModal = _l[1]; var _m = react.useState(false), isWhatsAppReport = _m[0], setIsWhatsAppReport = _m[1]; var _o = react.useState(null), reportSuccess = _o[0], setReportSuccess = _o[1]; react.useEffect(function () { return function () { if (calculationTimer) clearTimeout(calculationTimer); }; }, [calculationTimer]); var handleInputChange = function (e) { var _a = e.target, name = _a.name, value = _a.value; setInputs(function (prev) { var _a; return (__assign(__assign({}, prev), (_a = {}, _a[name] = value, _a))); }); }; var handleFrequencyChange = function (e) { setLumpsumFrequency(e.target.value); }; var validateInputs = function () { var requiredErrors = validateRequiredFields(inputs, [ 'lumpsumPrincipal', 'lumpsumRate', 'sipAmount', 'sipRate', 'years' ]); var numericErrors = validateNumericFields(inputs, { lumpsumPrincipal: { min: 1, errorMessage: 'Lumpsum amount must be > 0' }, lumpsumRate: { min: 0, max: 100, errorMessage: 'Return rate must be between 0-100%' }, sipAmount: { min: 1, errorMessage: 'SIP amount must be > 0' }, sipRate: { min: 0, max: 100, errorMessage: 'Return rate must be between 0-100%' }, years: { min: 1, max: 50, errorMessage: 'Duration must be between 1-50 years' } }); var allErrors = mergeErrors(requiredErrors, numericErrors); setErrors(allErrors); return !hasErrors(allErrors); }; var handleCalculate = function () { if (!validateInputs()) return; setIsCalculating(true); setResults(null); var P_lump = parseFloat(inputs.lumpsumPrincipal); var r_lump_annual = parseFloat(inputs.lumpsumRate) / 100; var nMap = { Annually: 1, "Semi-Annually": 2, Quarterly: 4, Monthly: 12 }; var n_lump_compound = nMap[lumpsumFrequency]; var t = parseFloat(inputs.years); var P_sip = parseFloat(inputs.sipAmount); var r_sip_annual = parseFloat(inputs.sipRate) / 100; var r_sip_monthly = r_sip_annual / 12; var n_sip_months = t * 12; var timer = setTimeout(function () { // Lumpsum Calculation var lumpsumFutureValue = P_lump * Math.pow(1 + r_lump_annual / n_lump_compound, n_lump_compound * t); // SIP Calculation var sipFutureValue = P_sip * ((Math.pow(1 + r_sip_monthly, n_sip_months) - 1) / r_sip_monthly) * (1 + r_sip_monthly); var lumpsumInvested = P_lump; var sipInvested = P_sip * n_sip_months; var totalInvested = lumpsumInvested + sipInvested; var combinedFutureValue = lumpsumFutureValue + sipFutureValue; var totalWealthGained = combinedFutureValue - totalInvested; // Year-wise breakdown var rows = Array.from({ length: t }, function (_, i) { var year = i + 1; // Balances at the end of the current year var lumpsumBalance = P_lump * Math.pow(1 + r_lump_annual / n_lump_compound, n_lump_compound * year); var sipMonthsThisYear = year * 12; var sipBalance = P_sip * ((Math.pow(1 + r_sip_monthly, sipMonthsThisYear) - 1) / r_sip_monthly) * (1 + r_sip_monthly); var closingBalance = lumpsumBalance + sipBalance; // Balances at the end of the previous year var prevYear = year - 1; var prevLumpsumBalance = prevYear > 0 ? P_lump * Math.pow(1 + r_lump_annual / n_lump_compound, n_lump_compound * prevYear) : 0; var prevSipMonths = prevYear * 12; var prevSipBalance = prevYear > 0 ? P_sip * ((Math.pow(1 + r_sip_monthly, prevSipMonths) - 1) / r_sip_monthly) * (1 + r_sip_monthly) : 0; var openingBalance = prevLumpsumBalance + prevSipBalance; var sipInvestedThisYear = P_sip * 12; // Interest earned is the change in balance minus new investments var interestEarned = closingBalance - openingBalance - sipInvestedThisYear - (year === 1 ? P_lump : 0); return { year: year, openingBalance: Math.round(openingBalance), interestEarned: Math.round(interestEarned), closingBalance: Math.round(closingBalance), }; }); setResults({ lumpsumFutureValue: Math.round(lumpsumFutureValue), lumpsumInvested: lumpsumInvested, sipFutureValue: Math.round(sipFutureValue), sipInvested: sipInvested, combinedFutureValue: Math.round(combinedFutureValue), totalInvested: totalInvested, totalWealthGained: Math.round(totalWealthGained), rows: rows, }); setIsCalculating(false); }, 1000); setCalculationTimer(timer); }; var tableNumberFormatter = function (value) { var num = typeof value === 'string' ? parseFloat(value) : value; if (isNaN(num)) return value.toString(); return formatCurrency(num, { maximumFractionDigits: 0 }); }; var getInvestmentTips = function () { if (!results) return null; var tips = []; if (parseFloat(inputs.years) >= 10) { tips.push("A long investment horizon gives your portfolio ample time to grow and recover from market downturns."); } if (results.totalWealthGained > results.totalInvested) { tips.push("Your total wealth gained is projected to be more than your total investment. This demonstrates the power of compounding."); } if (parseFloat(inputs.lumpsumRate) > 10 || parseFloat(inputs.sipRate) > 10) { tips.push("The assumed rates of return are optimistic. Higher returns usually come with higher risks."); } return tips; }; var openReportModal = function (isWhatsApp) { setIsWhatsAppReport(isWhatsApp); setShowReportModal(true); }; var handleReportSubmit = function (options) { if (!results) return; // Create report data var reportData = { title: "Lumpsum + SIP Calculator Report", totalInvested: results.totalInvested, futureValue: results.combinedFutureValue, wealthGained: results.totalWealthGained, monthlyInvestment: parseFloat(inputs.sipAmount), duration: parseFloat(inputs.years), annualReturn: parseFloat(inputs.sipRate) // Using SIP rate as a general indicator }; // Generate text report content for fallback var reportContent = generateReport(options); if (isWhatsAppReport && options.mobileNumber) { // For WhatsApp sharing sendReportViaWhatsApp(reportContent, options.mobileNumber, options, reportData, results.rows); setReportSuccess("Report sent to WhatsApp!"); } else { // For downloading downloadReport(reportContent, options.clientName, options, reportData, results.rows); setReportSuccess("Report downloaded successfully!"); } setShowReportModal(false); setTimeout(function () { return setReportSuccess(null); }, 3000); }; /* ───────── JSX ───────── */ return (jsxRuntime.jsxs(CalculatorContainer, { title: "Future Value of Lumpsum + SIP", description: "Calculate the combined future value of a one-time lumpsum investment and a recurring systematic investment plan (SIP).", explanation: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("p", { children: "This tool helps you project the total value of two types of investments working together:" }), jsxRuntime.jsxs("ul", { children: [jsxRuntime.jsxs("li", { children: [jsxRuntime.jsx("strong", { children: "Lumpsum:" }), " A single, large investment made at the beginning."] }), jsxRuntime.jsxs("li", { children: [jsxRuntime.jsx("strong", { children: "SIP:" }), " Regular, fixed-amount investments made over time (e.g., monthly)."] })] })] }), theme: calculatorTheme, children: [jsxRuntime.jsx(InputSection, { title: "Investment Details", children: jsxRuntime.jsxs("div", { className: "input-groups", children: [jsxRuntime.jsxs("div", { className: "input-group", children: [jsxRuntime.jsx("h3", { className: "input-group-title", children: "Lumpsum Details" }), jsxRuntime.jsxs("div", { className: "input-row", children: [jsxRuntime.jsx(InputField, { name: "lumpsumPrincipal", label: "Lumpsum Investment", value: inputs.lumpsumPrincipal, onChange: handleInputChange, error: errors.lumpsumPrincipal, tooltip: "The one-time amount you are investing upfront.", type: "number", prefix: "\u20B9", showNumberToWords: true }), jsxRuntime.jsx(InputField, { name: "lumpsumRate", label: "Lumpsum Annual Return", value: inputs.lumpsumRate, onChange: handleInputChange, error: errors.lumpsumRate, tooltip: "Expected annual return rate for your lumpsum investment.", type: "number", suffix: "%", showNumberToWords: true })] }), jsxRuntime.jsx("div", { className: "input-row single", children: jsxRuntime.jsx(SelectField, { name: "lumpsumFrequency", label: "Lumpsum Compounding", value: lumpsumFrequency, onChange: handleFrequencyChange, options: [ { value: "Annually", label: "Annually" }, { value: "Semi-Annually", label: "Semi-Annually" }, { value: "Quarterly", label: "Quarterly" }, { value: "Monthly", label: "Monthly" }, ], tooltip: "How often the interest on your lumpsum investment is calculated and added to the principal.", className: "full-width-input" }) })] }), jsxRuntime.jsxs("div", { className: "input-group", children: [jsxRuntime.jsx("h3", { className: "input-group-title", children: "SIP Details" }), jsxRuntime.jsxs("div", { className: "input-row", children: [jsxRuntime.jsx(InputField, { name: "sipAmount", label: "Monthly SIP Amount", value: inputs.sipAmount, onChange: handleInputChange, error: errors.sipAmount, tooltip: "The fixed amount you will invest every month.", type: "number", prefix: "\u20B9", showNumberToWords: true }), jsxRuntime.jsx(InputField, { name: "sipRate", label: "SIP Annual Return", value: inputs.sipRate, onChange: handleInputChange, error: errors.sipRate, tooltip: "Expected annual return rate for your SIP investments.", type: "number", suffix: "%", showNumberToWords: true })] })] }), jsxRuntime.jsxs("div", { className: "input-group", children: [jsxRuntime.jsx("h3", { className: "input-group-title", children: "Time Horizon" }), jsxRuntime.jsx("div", { className: "input-row single", children: jsxRuntime.jsx(InputField, { name: "years", label: "Investment Duration (Years)", value: inputs.years, onChange: handleInputChange, error: errors.years, tooltip: "The total number of years you plan to stay invested.", type: "number", suffix: "years", showNumberToWords: true }) })] })] }) }), jsxRuntime.jsx("div", { className: "button-container", children: jsxRuntime.jsx(Button, { onClick: handleCalculate, disabled: isCalculating, children: isCalculating ? "Calculating..." : "Calculate" }) }), isCalculating && (jsxRuntime.jsx("div", { className: "loading-container", children: jsxRuntime.jsx(LoadingIndicator, { size: "large", text: "Projecting your future wealth..." }) })), reportSuccess && jsxRuntime.jsx("div", { className: "success-message", children: reportSuccess }), !isCalculating && results && (jsxRuntime.jsxs(ResultsSection, { children: [jsxRuntime.jsxs("div", { className: "result-cards", children: [jsxRuntime.jsx(ResultCard, { label: "Total Investment", value: formatCurrency(results.totalInvested), description: "(".concat(numberToWords(results.totalInvested), ")") }), jsxRuntime.jsx(ResultCard, { label: "Combined Future Value", value: formatCurrency(results.combinedFutureValue), description: "(".concat(numberToWords(results.combinedFutureValue), ")"), highlight: true }), jsxRuntime.jsx(ResultCard, { label: "Total Wealth Gained", value: formatCurrency(results.totalWealthGained), description: "(".concat(numberToWords(results.totalWealthGained), ")") })] }), jsxRuntime.jsx(ResultsSummary, { title: "Investment Breakdown", children: jsxRuntime.jsxs("div", { className: "breakdown", children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("h4", { children: "Lumpsum" }), jsxRuntime.jsxs("p", { children: ["Invested: ", jsxRuntime.jsx("strong", { children: formatCurrency(results.lumpsumInvested) })] }), jsxRuntime.jsxs("p", { children: ["Matures to: ", jsxRuntime.jsx("strong", { children: formatCurrency(results.lumpsumFutureValue) })] })] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("h4", { children: "SIP" }), jsxRuntime.jsxs("p", { children: ["Invested: ", jsxRuntime.jsx("strong", { children: formatCurrency(results.sipInvested) })] }), jsxRuntime.jsxs("p", { children: ["Grows to: ", jsxRuntime.jsx("strong", { children: formatCurrency(results.sipFutureValue) })] })] })] }) }), jsxRuntime.jsx(TipBox, { title: "Personalized Investment Tips", type: "info", children: (_b = getInvestmentTips()) === null || _b === void 0 ? void 0 : _b.map(function (tip, index) { return jsxRuntime.jsx("p", { children: tip }, index); }) }), jsxRuntime.jsxs("div", { className: "chart-container", children: [jsxRuntime.jsx(ChartToggle, { options: [ { value: 'line', label: 'Growth Chart', icon: jsxRuntime.jsx(ChartIcons.Line, {}) }, { value: 'bar', label: 'Interest Chart', icon: jsxRuntime.jsx(ChartIcons.Bar, {}) }, ], value: chartType, onChange: function (value) { return setChartType(value); } }), chartType === 'line' && (jsxRuntime.jsx("div", { id: "growth-chart", className: "chart-container", children: jsxRuntime.jsx(LineChart, { data: results.rows, xKey: "year", lines: [{ key: "closingBalance", name: "Year-End Balance", color: calculatorTheme.secondary }], xAxisLabel: "Year", yAxisLabel: "Amount (\u20B9)" }) })), chartType === 'bar' && (jsxRuntime.jsx("div", { id: "interest-chart", className: "chart-container", children: jsxRuntime.jsx(BarChart, { data: results.rows, xKey: "year", bars: [{ key: "interestEarned", name: "Interest Earned", color: calculatorTheme.secondary }], xAxisLabel: "Year", yAxisLabel: "Amount (\u20B9)" }) })), jsxRuntime.jsxs("div", { className: "chart-description", children: [chartType === 'line' && "Shows the growth of your combined investment balance over the years.", chartType === 'bar' && "Shows the approximate interest earned each year from both investments."] }), jsxRuntime.jsxs("div", { style: { display: 'none' }, children: [chartType !== 'line' && (jsxRuntime.jsx("div", { id: "growth-chart-hidden", style: { width: '600px', height: '300px' }, children: jsxRuntime.jsx(LineChart, { data: results.rows, xKey: "year", lines: [{ key: "closingBalance", name: "Year-End Balance", color: calculatorTheme.secondary }], xAxisLabel: "Year", yAxisLabel: "Amount (\u20B9)" }) })), chartType !== 'bar' && (jsxRuntime.jsx("div", { id: "interest-chart-hidden", style: { width: '600px', height: '300px' }, children: jsxRuntime.jsx(BarChart, { data: results.rows, xKey: "year", bars: [{ key: "interestEarned", name: "Interest Earned", color: calculatorTheme.secondary }], xAxisLabel: "Year", yAxisLabel: "Amount (\u20B9)" }) }))] })] }), jsxRuntime.jsx(ResultsTable, { caption: "Year-wise Combined Breakdown", columns: [ { key: 'year', header: 'Year' }, { key: 'openingBalance', header: 'Opening Balance', formatter: tableNumberFormatter, align: 'right' }, { key: 'interestEarned', header: 'Interest Earned', formatter: tableNumberFormatter, align: 'right' }, { key: 'closingBalance', header: 'Closing Balance', formatter: tableNumberFormatter, align: 'right' } ], data: results.rows, maxHeight: "400px" }), jsxRuntime.jsx(Disclaimer, { title: "Important Considerations", collapsible: true, children: jsxRuntime.jsxs("ul", { children: [jsxRuntime.jsx("li", { children: "This calculation assumes the stated return rates remain constant. Actual market returns will vary." }), jsxRuntime.jsx("li", { children: "Taxes, fees, and inflation are not factored in and will affect the final amount." }), jsxRuntime.jsx("li", { children: "Past performance does not guarantee future returns. This tool is for illustrative purposes only." })] }) }), jsxRuntime.jsxs("div", { className: "action-buttons", children: [jsxRuntime.jsx(Button, { onClick: function () { return openReportModal(false); }, children: "Download Report" }), jsxRuntime.jsx(Button, { onClick: function () { return openReportModal(true); }, children: "Share on WhatsApp" })] })] })), jsxRuntime.jsx(ReportDialog, { isOpen: showReportModal, onClose: function () { return setShowReportModal(false); }, title: isWhatsAppReport ? "Send Report via WhatsApp" : "Download Report Options", size: "medium", children: jsxRuntime.jsx(ReportOptions, { isWhatsApp: isWhatsAppReport, onSubmit: handleReportSubmit, onCancel: function () { return setShowReportModal(false); } }) }), jsxRuntime.jsx("style", { children: "\n .input-groups {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n }\n .input-group {\n background-color: ".concat(calculatorTheme.background, ";\n border-radius: 8px;\n padding: 1.5rem;\n border: 1px solid #E5E5E5;\n }\n .input-group-title {\n font-size: 1rem;\n color: ").concat(calculatorTheme.primary, ";\n margin-bottom: 1rem;\n font-weight: 600;\n }\n .input-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 1rem;\n }\n .input-row.single {\n grid-template-columns: 1fr;\n }\n .button-container {\n margin: 1.5rem 0;\n display: flex;\n justify-content: center;\n }\n .loading-container {\n display: flex;\n justify-content: center;\n margin: 2rem 0;\n }\n .success-message {\n background-color: ").concat(calculatorTheme.accent, ";\n color: ").concat(calculatorTheme.primary, ";\n padding: 0.75rem 1rem;\n border-radius: 6px;\n margin: 0 0 1.5rem 0;\n text-align: center;\n }\n .result-cards {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 1rem;\n margin-bottom: 2rem;\n }\n .breakdown {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1.5rem;\n margin: 1rem 0;\n }\n .breakdown h4 {\n margin-bottom: 0.5rem;\n color: ").concat(calculatorTheme.textPrimary, ";\n }\n .breakdown p {\n margin: 0.25rem 0;\n color: ").concat(calculatorTheme.textSecondary, ";\n }\n .chart-container {\n margin: 1.5rem 0 2rem;\n }\n .chart-description {\n text-align: center;\n font-style: italic;\n color: ").concat(calculatorTheme.textSecondary, ";\n opacity: 0.8;\n font-size: 0.9rem;\n margin-top: 0.75rem;\n }\n .action-buttons {\n display: flex;\n gap: 1rem;\n margin: 1.5rem 0;\n flex-wrap: wrap;\n justify-content: center;\n }\n .full-width-input {\n grid-column: 1 / -1;\n }\n \n @media (max-width: 768px) {\n .input-row {\n grid-template-columns: 1fr;\n }\n .result-cards, .breakdown {\n grid-template-columns: 1fr;\n }\n .action-buttons {\n flex-direction: column;\n }\n }\n ") })] })); }; /* Retirement Need Based Calculator Component ---------------------------------------------------------------- */ /* ────────────── Component ────────────── */ var RetirementCalculator = function (_a) { var _b; var _c = _a.theme, theme = _c === void 0 ? {} : _c; var defaultTheme = { primary: "#0C875E", secondary: "#108E66", accent: "#D6F5E8", background: "#FAFAFA", textPrimary: "#333333", textSecondary: "#666666" }; var calculatorTheme = __assign(__assign({}, defaultTheme), theme); var _d = react.useState({ currentAge: "", retirementAge: "", lifeExpectancy: "", monthlyExpensesToday: "", inflationRate: "", expectedReturnPreRetirement: "", expectedReturnPostRetirement: "", existingCorpus: "", }), inputs = _d[0], setInputs = _d[1]; var _e = react.useState({}), errors = _e[0], setErrors = _e[1]; var _f = react.useState(null), results = _f[0], setResults = _f[1]; var _g = react.useState(false), isCalculating = _g[0], setIsCalculating = _g[1]; var _h = react.useState("line"), chartType = _h[0], setChartType = _h[1]; var _j = react.useState(null), calculationTimer = _j[0], setCalculationTimer = _j[1]; // Report related state var _k = react.useState(false), showReportModal = _k[0], setShowReportModal = _k[1]; var _l = react.useState(false), isWhatsAppReport = _l[0], setIsWhatsAppReport = _l[1]; var _m = react.useState(null), reportSuccess = _m[0], setReportSuccess = _m[1]; react.useEffect(function () { return function () { if (calculationTimer) clearTimeout(calculationTimer); }; }, [calculationTimer]); var handleInputChange = function (e) { var _a = e.target, name = _a.name, value = _a.value; setInputs(function (prev) { var _a; return (__assign(__assign({}, prev), (_a = {}, _a[name] = value, _a))); }); }; var validateInputs = function () { var requiredErrors = validateRequiredFields(inputs, [ 'currentAge', 'retirementAge', 'lifeExpectancy', 'monthlyExpensesToday', 'inflationRate', 'expectedReturnPreRetirement', 'expectedReturnPostRetirement', 'existingCorpus' ]); var numericErrors = validateNumericFields(inputs, { currentAge: { min: 18, max: 100, errorMessage: 'Age must be between 18-100 years' }, retirementAge: { min: 40, max: 100, errorMessage: 'Retirement age must be between 40-100 years' }, lifeExpectancy: { min: 50, max: 120, errorMessage: 'Life expectancy must be between 50-120 years' }, monthlyExpensesToday: { min: 1000, errorMessage: 'Monthly expenses must be at least ₹1,000' }, inflationRate: { min: 0, max: 20, errorMessage: 'Inflation rate must be between 0-20%' }, expectedReturnPreRetirement: { min: 0, max: 30, errorMessage: 'Return rate must be between 0-30%' }, expectedReturnPostRetirement: { min: 0, max: 20, errorMessage: 'Return rate must be between 0-20%' }, existingCorpus: { min: 0, errorMessage: 'Existing corpus must be ≥ 0' } }); // Additional validation var additionalErrors = {}; var currentAge = parseFloat(inputs.currentAge); var retirementAge = parseFloat(inputs.retirementAge); var lifeExpectancy = parseFloat(inputs.lifeExpectancy); if (!isNaN(currentAge) && !isNaN(retirementAge) && currentAge >= retirementAge) { additionalErrors.retirementAge = "Retirement age must be greater than current age"; } if (!isNaN(retirementAge) && !isNaN(lifeExpectancy) && retirementAge >= lifeExpectancy) { additionalErrors.lifeExpectancy = "Life expectancy must be greater than retirement age"; } var allErrors = mergeErrors(requiredErrors, numericErrors, additionalErrors); setErrors(allErrors); return !hasErrors(allErrors); }; var handleCalculate = function () { if (!validateInputs()) return; setIsCalculating(true); setResults(null); var currentAge = parseFloat(inputs.currentAge); var retirementAge = parseFloat(inputs.retirementAge); var lifeExpectancy = parseFloat(inputs.lifeExpectancy); var monthlyExpensesToday = parseFloat(inputs.monthlyExpensesToday); var inflationRate = parseFloat(inputs.inflationRate) / 100; var returnRatePreRetirement = parseFloat(inputs.expectedReturnPreRetirement) / 100; var returnRatePostRetirement = parseFloat(inputs.expectedReturnPostRetirement) / 100; var existingCorpus = parseFloat(inputs.existingCorpus); var timer = setTimeout(function () { // Calculate annual expenses today var annualExpensesToday = monthlyExpensesToday * 12; // Years until retirement var yearsToRetirement = retirementAge - currentAge; // Years in retirement var yearsInRetirement = lifeExpectancy - retirementAge; // Calculate expenses at retirement var annualExpensesAtRetirement = annualExpensesToday * Math.pow(1 + inflationRate, yearsToRetirement); // Calculate total corpus needed at retirement using annuity formula for growing perpetuity var realReturnPostRetirement = (returnRatePostRetirement - inflationRate) / (1 + inflationRate); var totalRetirementCorpus; if (realReturnPostRetirement <= 0) { // If returns don't beat inflation, corpus is sum of all future expenses totalRetirementCorpus = Array.from({ length: yearsInRetirement }).reduce(function (acc, _, i) { return acc + (annualExpensesAtRetirement * Math.pow(1 + inflationRate, i)); }, 0); } else { totalRetirementCorpus = (annualExpensesAtRetirement / realReturnPostRetirement) * (1 - (1 / Math.pow(1 + realReturnPostRetirement, yearsInRetirement))); } // Calculate future value of existing corpus var existingCorpusAtRetirement = existingCorpus * Math.pow(1 + returnRatePreRetirement, yearsToRetirement); // Additional corpus needed beyond existing savings var additionalCorpusNeeded = Math.max(0, totalRetirementCorpus - existingCorpusAtRetirement); // Calculate the monthly investment needed to reach the additional corpus // Using PMT formula: PMT = FV * r / ((1 + r)^n - 1) var monthlyRate = returnRatePreRetirement / 12; var totalMonths = yearsToRetirement * 12; var monthlyInvestmentNeeded = 0; if (additionalCorpusNeeded > 0) { if (monthlyRate > 0) { monthlyInvestmentNeeded = additionalCorpusNeeded * monthlyRate / (Math.pow(1 + monthlyRate, totalMonths) - 1); } else { monthlyInvestmentNeeded = additionalCorpusNeeded / totalMonths; } } // Generate year-by-year data var rows = []; var currentCorpus = existingCorpus; var currentYear = new Date().getFullYear(); // Accumulation phase for (var i = 0; i < yearsToRetirement; i++) { var age = currentAge + i; var year = currentYear + i; var annualSavingsNeeded = monthlyInvestmentNeeded * 12; currentCorpus = currentCorpus * (1 + returnRatePreRetirement) + annualSavingsNeeded; rows.push({ year: year, age: age, annualExpenses: 0, annualSavingsNeeded: Math.round(annualSavingsNeeded), corpusValue: Math.round(currentCorpus), phase: "Accumulation" }); } // Distribution phase for (var i = 0; i < yearsInRetirement; i++) { var age = retirementAge + i; var year = currentYear + yearsToRetirement + i; var annualExpenses = annualExpensesAtRetirement * Math.pow(1 + inflationRate, i); currentCorpus = currentCorpus * (1 + returnRatePostRetirement) - annualExpenses; rows.push({ year: year, age: age, annualExpenses: Math.round(annualExpenses), annualSavingsNeeded: 0, corpusValue: Math.round(Math.max(0, currentCorpus)), phase: "Distribution" }); } setResults({ totalRetirementCorpus: Math.round(totalRetirementCorpus), monthlyInvestmentNeeded: Math.round(monthlyInvestmentNeeded), existingCorpusAtRetirement: Math.round(existingCorpusAtRetirement), additionalCorpusNeeded: Math.round(additionalCorpusNeeded), rows: rows }); setIsCalculating(false); }, 2000); setCalculationTimer(timer); }; var tableNumberFormatter = function (value) { var num = typeof value === 'string' ? parseFloat(value) : value; if (isNaN(num)) return value.toString(); return formatCurrency(num, { maximumFractionDigits: 0 }); }; var getRetirementTips = function () { if (!results) return null; var tips = []; var yearsToRetirement = parseFloat(inputs.retirementAge) - parseFloat(inputs.currentAge); if (yearsToRetirement > 15) { tips.push("You have a good runway for retirement planning. Consider investing more in equity-oriented instruments to maximize growth."); } else if (yearsToRetirement < 10) { tips.push("With fewer years to retirement, focus on capital preservation. Consider a more conservative asset allocation."); } if (results.monthlyInvestmentNeeded > parseFloat(inputs.monthlyExpensesToday) * 0.5) { tips.push("Your required monthly investment is high relative to your expenses. Consider reviewing your retirement expectations or increasing your existing corpus."); } if (parseFloat(inputs.expectedReturnPostRetirement) > 8) { tips.push("Your post-retirement return expectation is optimistic. Consider using a more conservative estimate to avoid shortfall risk."); } return tips; }; var openReportModal = function (isWhatsApp) { setIsWhatsAppReport(isWhatsApp); setShowReportModal(true); }; var handleReportSubmit = function (options) { if (!results) return; var totalInvested = (results.monthlyInvestmentNeeded * (parseFloat(inputs.retirementAge) - parseFloat(inputs.currentAge)) * 12) + parseFloat(inputs.existingCorpus); ({ title: "Retirement Calculator Report", totalInvested: totalInvested, futureValue: results.totalRetirementCorpus, wealthGained: results.totalRetirementCorpus - totalInvested, monthlyInvestment: results.monthlyInvestmentNeeded, duration: parseFloat(inputs.retirementAge) - parseFloat(inputs.currentAge), annualReturn: parseFloat(inputs.expectedReturnPreRetirement) }); var reportContent = generateReport(options); results.rows.map(function (row) { return ({ year: row.year, openingBalance: 0, // Placeholder interestEarned: 0, // Placeholder closingBalance: row.corpusValue, }); }); if (isWhatsAppReport && options.mobileNumber) { sendReportViaWhatsApp(reportContent, options.mobileNumber); setReportSuccess("Report sent to WhatsApp!"); } else { downloadReport(reportContent, options.clientName); setReportSuccess("Report downloaded successfully!"); } setShowReportModal(false); setTimeout(function () { return setReportSuccess(null); }, 3000); }; /* ───────── JSX ───────── */ return (jsxRuntime.jsxs(CalculatorContainer, { title: "Retirement Need Based Calculator", description: "Plan your retirement by calculating the corpus required and monthly investment needed.", explanation: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("p", { children: "This calculator helps you plan for retirement based on your needs:" }), jsxRuntime.jsxs("ul", { children: [jsxRuntime.jsxs("li", { children: [jsxRuntime.jsx("strong", { children: "Corpus Required:" }), " The total amount you will need at retirement to maintain your lifestyle."] }), jsxRuntime.jsxs("li", { children: [jsxRuntime.jsx("strong", { children: "Monthly Investment:" }), " How much you need to save each month to achieve your retirement goal."] })] })] }), children: [jsxRuntime.jsx(InputSection, { title: "Retirement Planning Parameters", children: jsxRuntime.jsxs("div", { className: "input-groups", children: [jsxRuntime.jsxs("div", { className: "input-group", children: [jsxRuntime.jsx("h3", { className: "input-group-title", children: "Age Details" }), jsxRuntime.jsxs("div", { className: "input-row", children: [jsxRuntime.jsx(NumberField, { name: "currentAge", label: "Current Age", value: inputs.currentAge, onChange: handleInputChange, error: errors.currentAge, tooltip: "Your current age in years.", type: "number", suffix: "years" }), jsxRuntime.jsx(NumberField, { name: "retirementAge", label: "Retirement Age", value: inputs.retirementAge, onChange: handleInputChange, error: errors.retirementAge, tooltip: "The age at which you plan to retire.", type: "number", suffix: "years" })] }), jsxRuntime.jsx("div", { className: "input-row single", children: jsxRuntime.jsx(NumberField, { name: "lifeExpectancy", label: "Life Expectancy", value: inputs.lifeExpectancy, onChange: handleInputChange, error: errors.lifeExpectancy, tooltip: "The age to which you expect to live (for planning purposes).", type: "number", suffix: "years" }) })] }), jsxRuntime.jsxs("div", { className: "input-group", children: [jsxRuntime.jsx("h3", { className: "input-group-title", children: "Financial Details" }), jsxRuntime.jsxs("div", { className: "input-row", children: [jsxRuntime.jsx(NumberField, { name: "monthlyExpensesToday", label: "Monthly Expenses Today", value: inputs.monthlyExpensesToday, onChange: handleInputChange, error: errors.monthlyExpensesToday, tooltip: "Your current monthly expenses (that you expect to continue in retirement).", type: "number", prefix: "\u20B9", showNumberToWords: true }), jsxRuntime.jsx(NumberField, { name: "inflationRate", label: "Expected Inflation Rate", value: inputs.inflationRate, onChange: handleInputChange, error: errors.inflationRate, tooltip: "The rate at which you expect your expenses to increase annually.", type: "number", suffix: "%" })] })] }), jsxRuntime.jsxs("div", { className: "input-group", children: [jsxRuntime.jsx("h3", { className: "input-group-title", children: "Investment Details" }), jsxRuntime.jsxs("div", { className: "input-row", children: [jsxRuntime.jsx(NumberField, { name: "expectedReturnPreRetirement", label: "Pre-Retirement Return", value: inputs.expectedReturnPreRetirement, onChange: handleInputChange, error: errors.expectedReturnPreRetirement, tooltip: "Expected annual return on your investments before retirement.", type: "number", suffix: "%" }), jsxRuntime.jsx(NumberField, { name: "expectedReturnPostRetirement", label: "Post-Retirement Return", value: inputs.expectedReturnPostRetirement, onChange: handleInputChange, error: errors.expectedReturnPostRetirement, tooltip: "Expected annual return on your investments after retirement (usually lower and more conservative).", type: "number", suffix: "%" })] }), jsxRuntime.jsx("div", { className: "input-row single", children: jsxRuntime.jsx(NumberField, { name: "existingCorpus", label: "Existing Retirement Corpus", value: inputs.existingCorpus, onChange: handleInputChange, error: errors.existingCorpus, tooltip: "Amount already saved for retirement.", type: "number", prefix: "\u20B9", showNumberToWords: true }) })] })] }) }), jsxRuntime.jsx("div", { className: "button-container", children: jsxRuntime.jsx(ButtonPrimary, { onClick: handleCalculate, disabled: isCalculating, children: isCalculating ? "Calculating..." : "Calculate" }) }), isCalculating && (jsxRuntime.jsx("div", { className: "loading-container", children: jsxRuntime.jsx(LoadingSpinner, { size: "large", text: "Analyzing your retirement needs..." }) })), reportSuccess && jsxRuntime.jsx("div", { className: "success-message", children: reportSuccess }), !isCalculating && results && (jsxRuntime.jsxs(ResultsSection, { children: [jsxRuntime.jsxs("div", { className: "result-cards", children: [jsxRuntime.jsx(ResultCard, { label: "Retirement Corpus Needed", value: formatCurrency(results.totalRetirementCorpus), description: "(".concat(numberToWords(results.totalRetirementCorpus), ")"), highlight: true }), jsxRuntime.jsx(ResultCard, { label: "Monthly Investment Required", value: formatCurrency(results.monthlyInvestmentNeeded), description: "(".concat(numberToWords(results.monthlyInvestmentNeeded), ")") }), jsxRuntime.jsx(ResultCard, { label: "Additional Corpus Needed", value: formatCurrency(results.additionalCorpusNeeded), description: "(".concat(numberToWords(results.additionalCorpusNeeded), ")") })] }), jsxRuntime.jsx(ResultsSummary, { title: "Retirement Plan Summary", children: jsxRuntime.jsxs("div", { className: "plan-summary", children: [jsxRuntime.jsxs("div", { className: "summary-item", children: [jsxRuntime.jsx("h4", { children: "Accumulation Phase" }), jsxRuntime.jsxs("p", { children: ["Current Age: ", jsxRuntime.jsxs("strong", { children: [inputs.currentAge, " years"] })] }), jsxRuntime.jsxs("p", { children: ["Retirement Age: ", jsxRuntime.jsxs("strong", { children: [inputs.retirementAge, " years"] })] }), jsxRuntime.jsxs("p", { children: ["Years to Retirement: ", jsxRuntime.jsxs("strong", { children: [parseInt(inputs.retirementAge) - parseInt(inputs.currentAge), " years"] })] }), jsxRuntime.jsxs("p", { children: ["Monthly Investment Needed: ", jsxRuntime.jsx("strong", { children: formatCurrency(results.monthlyInvestmentNeeded) })] })] }), jsxRuntime.jsxs("div", { className: "summary-item", children: [jsxRuntime.jsx("h4", { children: "Distribution Phase" }), jsxRuntime.jsxs("p", { children: ["Retirement Age: ", jsxRuntime.jsxs("strong", { children: [inputs.retirementAge, " years"] })] }), jsxRuntime.jsxs("p", { children: ["Life Expectancy: ", jsxRuntime.jsxs("strong", { children: [inputs.lifeExpectancy, " years"] })] }), jsxRuntime.jsxs("p", { children: ["Years in Retirement: ", jsxRuntime.jsxs("strong", { children: [parseInt(inputs.lifeExpectancy) - parseInt(inputs.retirementAge), " years"] })] }), jsxRuntime.jsxs("p", { children: ["Monthly Expenses at Retirement: ", jsxRuntime.jsx("strong", { children: formatCurrency(parseFloat(inputs.monthlyExpensesToday) * Math.pow(1 + parseFloat(inputs.inflationRate) / 100, parseInt(inputs.retirementAge) - parseInt(inputs.currentAge))) })] })] }), jsxRuntime.jsxs("div", { className: "summary-item", children: [jsxRuntime.jsx("h4", { children: "Corpus Breakdown" }), jsxRuntime.jsxs("p", { children: ["Existing Corpus: ", jsxRuntime.jsx("strong", { children: formatCurrency(parseFloat(inputs.existingCorpus)) })] }), jsxRuntime.jsxs("p", { children: ["Future Value of Existing Corpus: ", jsxRuntime.jsx("strong", { children: formatCurrency(results.existingCorpusAtRetirement) })] }), jsxRuntime.jsxs("p", { children: ["Additional Corpus Needed: ", jsxRuntime.jsx("strong", { children: formatCurrency(results.additionalCorpusNeeded) })] }), jsxRuntime.jsxs("p", { children: ["Total Retirement Corpus: ", jsxRuntime.jsx("strong", { children: formatCurrency(results.totalRetirementCorpus) })] })] })] }) }), jsxRuntime.jsx(TipBox, { title: "Personalized Retirement Tips", type: "info", children: (_b = getRetirementTips()) === null || _b === void 0 ? void 0 : _b.map(function (tip, index) { return jsxRuntime.jsx("p", { children: tip }, index); }) }), jsxRuntime.jsxs("div", { className: "chart-container", children: [jsxRuntime.jsx(ChartToggle, { options: [ { value: 'line', label: 'Corpus Growth', icon: jsxRuntime.jsx(ChartIcons.Line, {}) }, { value: 'bar', label: 'Cash Flow', icon: jsxRuntime.jsx(ChartIcons.Bar, {}) }, ], value: chartType, onChange: function (value) { return setChartType(value); } }), chartType === "line" && (jsxRuntime.jsx("div", { id: "growth-chart", children: jsxRuntime.jsx(LineChart, { data: results.rows, xKey: "age", lines: [ { key: 'corpusValue', name: 'Corpus Value', color: calculatorTheme.secondary }, ], xAxisLabel: 'Age', yAxisLabel: 'Amount (\u20B9)' }) })), chartType === "bar" && (jsxRuntime.jsx("div", { id: "interest-chart", children: jsxRuntime.jsx(BarChart, { data: results.rows, xKey: "age", bars: [ { key: 'annualSavingsNeeded', name: 'Annual Savings', color: '#6DC1B9' }, { key: 'annualExpenses', name: 'Annual Expenses', color: '#FF6B6B' }, ], xAxisLabel: 'Age', yAxisLabel: 'Amount (\u20B9)', stacked: true }) })), jsxRuntime.jsxs("div", { className: "chart-description", children: [chartType === 'line' && "Shows the growth of your retirement corpus over the years.", chartType === 'bar' && "Shows your annual savings needed pre-retirement and expenses post-retirement."] }), jsxRuntime.jsxs("div", { style: { display: 'none' }, children: [chartType !== 'line' && (jsxRuntime.jsx("div", { id: "growth-chart-hidden", style: { width: '600px', height: '300px' }, children: jsxRuntime.jsx(LineChart, { data: results.rows, xKey: "age", lines: [ { key: 'corpusValue', name: 'Corpus Value', color: calculatorTheme.secondary }, ], xAxisLabel: 'Age', yAxisLabel: 'Amount (\u20B9)' }) })), chartType !== 'bar' && (jsxRuntime.jsx("div", { id: "interest-chart-hidden", style: { width: '600px', height: '300px' }, children: jsxRuntime.jsx(BarChart, { data: results.rows, xKey: "age", bars: [ { key: 'annualSavingsNeeded', name: 'Annual Savings', color: '#6DC1B9' }, { key: 'annualExpenses', name: 'Annual Expenses', color: '#FF6B6B' },