import React, { useState, useMemo } from 'react';
import {
TrendingUp, Landmark, Users, Plus, Trash2, Home, Wallet,
Activity, CreditCard, LayoutDashboard, Target, Zap,
PlusCircle, Construction, Building2, Receipt, ArrowDownCircle,
ArrowUpCircle, PlusSquare, Coffee, Car, Lightbulb, HeartPulse, ShoppingBag,
UserCircle2, PieChart, BadgeDollarSign, UserPlus, HeartHandshake, Heart,
BarChart3, FileSearch, Scale, Scroll, GraduationCap, ShieldCheck, Settings, Save,
UserCog, MapPin, Banknote, CalendarRange, LineChart, Briefcase
} from 'lucide-react';
// --- 圖示對照表 ---
const iconMap = {
Coffee, ShoppingBag, Home, Car, Lightbulb, Zap, HeartPulse, Landmark, Receipt,
ShoppingCart: ShoppingBag, GraduationCap, Scale, Scroll, MapPin, Banknote, CalendarRange, LineChart, Briefcase
};
const App = () => {
const [activeTab, setActiveTab] = useState('family');
const [isTaxEditMode, setIsTaxEditMode] = useState(false);
// --- 初始資料 (全數清空預設值) ---
const initialBaseData = {
fillDate: new Date().toISOString().split('T')[0],
family: [
// 修正:角色名稱變更,欄位留空,新增 salaryGrowth (薪資漲幅)
{ id: 'h', role: '本人', birthday: '', gender: '男', income: '', salaryGrowth: '' },
{ id: 'w', role: '配偶', birthday: '', gender: '女', income: '', salaryGrowth: '' }
],
// 修正:預設不帶入任何親屬
relatives: [],
completedHouses: [], // 清空
presaleHouses: [], // 清空
movableAssets: [
// 僅保留結構,數值清空
{ id: 1, type: '現金存款', amount: '', monthlyIncome: '', pledgeAmount: '', pledgeRate: '', faceValue: '', returnRate: '' },
{ id: 2, type: '累積型投資(如:股票)目前帳面金額', amount: '', monthlyIncome: '', pledgeAmount: '', pledgeRate: '', faceValue: '', returnRate: '' },
{ id: 3, type: '配息型投資(如:基金)目前帳面金額', amount: '', monthlyIncome: '', pledgeAmount: '', pledgeRate: '', faceValue: '', returnRate: '' },
{ id: 4, type: '利變、分紅保單', amount: '', monthlyIncome: '', pledgeAmount: '', pledgeRate: '', faceValue: '', returnRate: '' },
],
debts: [], // 清空
otherIncomes: [],
livingExpenses: [
{ id: 1, type: '食 (伙食外食)', amount: '', icon: 'Coffee' },
{ id: 2, type: '衣 (治裝費)', amount: '', icon: 'ShoppingBag' },
{ id: 3, type: '住 (水電/管理費)', amount: '', icon: 'Home' },
{ id: 4, type: '行 (交通/油資)', amount: '', icon: 'Car' },
{ id: 5, type: '育 (教育/進修)', amount: '', icon: 'Lightbulb' },
{ id: 6, type: '樂 (娛樂/交際)', amount: '', icon: 'Zap' },
{ id: 7, type: '保險 (不還本/醫療)', amount: '', icon: 'HeartPulse' },
{ id: 8, type: '保險 (還本/儲蓄)', amount: '', icon: 'Landmark' },
],
// 稅務設定 (保留法規預設值,因這是客觀數據)
taxSettings: {
exemption: 1333,
funeralDeduction: 138,
spouseDeduction: 553,
childDeduction: 56,
parentDeduction: 138,
siblingDeduction: 56,
handicapDeduction: 693,
insuranceExemption: 3330
},
taxPeople: {
spouse: '', children: '', parents: '', siblings: '', handicap: ''
},
// 經濟假設參數 (部分保留預設以便計算,資產相關清空)
assumptions: {
cpi: '2', // 預設 2% 較合理
realEstateGrowth: '', // 不動產增值率不預設
pledgeEndAge: '80',
childIndependenceAge: '22',
}
};
const [data, setData] = useState(initialBaseData);
const toNum = (val) => {
if (val === '' || val === null || val === undefined) return 0;
const n = parseFloat(val);
return isNaN(n) ? 0 : n;
};
const handleInputChange = (path, value) => {
const keys = path.split('.');
setData(prev => {
const newState = JSON.parse(JSON.stringify(prev));
let current = newState;
for (let i = 0; i < keys.length - 1; i++) {
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
return newState;
});
};
// --- 輔助:年齡計算 ---
const calculateAge = (birthday) => {
if (!birthday) return 0;
const birthDate = new Date(birthday);
const ageDifMs = Date.now() - birthDate.getTime();
const ageDate = new Date(ageDifMs);
return Math.abs(ageDate.getUTCFullYear() - 1970);
};
// --- 輔助:判斷是否扶養 (中華民國定義參考) ---
const checkIsDependent = (person) => {
const age = calculateAge(person.birthday);
// 父母/祖父母 (直系尊親屬): 滿 60 歲
if (person.relationship === 'parent' || person.relationship === 'grandparent') {
return age >= 60;
}
// 子女/兄弟姐妹 (直系卑親屬/旁系): 未滿 20 歲
if (person.relationship === 'child' || person.relationship === 'sibling') {
return age < 20;
}
return false; // 其他情況預設否,可手動改
};
// --- 核心計算引擎 ---
const calculations = useMemo(() => {
// 1. 房產計算
const houseCalcs = data.completedHouses.map(h => {
const principal = toNum(h.loanBalance) * 10000;
const monthlyRate = (toNum(h.rate)/100)/12;
const totalMonths = toNum(h.loanYear)*12;
const gracePay = Math.round(principal * monthlyRate);
let amortizePay = 0;
if (principal > 0 && totalMonths > 0) {
if (monthlyRate > 0) {
const factor = Math.pow(1 + monthlyRate, totalMonths);
amortizePay = Math.round((principal * monthlyRate * factor) / (factor - 1));
} else amortizePay = Math.round(principal / totalMonths);
}
const selectedPay = h.repayType === 'amortize' ? amortizePay : gracePay;
const estimatedAssessedValue = h.assessedValue ? toNum(h.assessedValue) : Math.round(toNum(h.marketValue) * 0.4);
return { ...h, gracePay, amortizePay, effectivePay: selectedPay, estimatedAssessedValue };
});
const presaleCalcs = data.presaleHouses.map(p => {
const principal = toNum(p.expectedLoan) * 10000;
const monthlyRate = (toNum(p.rate)/100)/12;
const totalMonths = toNum(p.loanYear)*12;
const gracePay = Math.round(principal * monthlyRate);
let amortizePay = 0;
if (principal > 0 && totalMonths > 0) {
if (monthlyRate > 0) {
const factor = Math.pow(1 + monthlyRate, totalMonths);
amortizePay = Math.round((principal * monthlyRate * factor) / (factor - 1));
} else amortizePay = Math.round(principal / totalMonths);
}
const selectedPay = p.repayType === 'amortize' ? amortizePay : gracePay;
const baseValue = p.marketValue ? toNum(p.marketValue) : toNum(p.contractPrice);
const estimatedAssessedValue = p.assessedValue ? toNum(p.assessedValue) : Math.round(baseValue * 0.4);
return { ...p, gracePay, amortizePay, effectivePay: selectedPay, estimatedAssessedValue, baseValue };
});
const debtCalcs = data.debts.map(d => {
const principal = toNum(d.amount) * 10000;
const monthlyRate = (toNum(d.rate)/100)/12;
const totalMonths = toNum(d.years)*12;
let amortizePay = 0;
if (principal > 0 && totalMonths > 0) {
if (monthlyRate > 0) {
const factor = Math.pow(1 + monthlyRate, totalMonths);
amortizePay = Math.round((principal * monthlyRate * factor) / (factor - 1));
} else amortizePay = Math.round(principal / totalMonths);
}
return { ...d, calculatedMonthlyPay: amortizePay };
});
// 2. 質押計算
const pledgeCalcs = data.movableAssets.map(a => {
const principal = toNum(a.pledgeAmount) * 10000;
const monthlyRate = (toNum(a.pledgeRate) / 100) / 12;
const monthlyInterest = Math.round(principal * monthlyRate);
return { id: a.id, principal, monthlyInterest };
});
const totalPledgeAmount = data.movableAssets.reduce((s, a) => s + toNum(a.pledgeAmount), 0);
const totalPledgeMonthlyInterest = pledgeCalcs.reduce((s, p) => s + p.monthlyInterest, 0);
// 3. 收支彙整與明細
// 薪資計算 (Page 1 連動)
const monthlySalary = data.family.reduce((s, f) => s + (toNum(f.income) * 10000 / 12), 0);
const totalRent = houseCalcs.reduce((s, h) => s + toNum(h.rent), 0) + presaleCalcs.reduce((s, p) => s + toNum(p.rent), 0);
const totalAssetIncome = data.movableAssets.reduce((s, a) => s + toNum(a.monthlyIncome), 0);
const extraIncome = data.otherIncomes.reduce((s, i) => s + toNum(i.amount), 0);
const totalIncome = monthlySalary + totalRent + totalAssetIncome + extraIncome;
const totalHousePay = houseCalcs.reduce((s, h) => s + h.effectivePay, 0);
const totalPresalePay = presaleCalcs.reduce((s, p) => s + p.effectivePay, 0);
const totalDebtPay = debtCalcs.reduce((s, d) => s + d.calculatedMonthlyPay, 0);
const totalLoanPayment = totalHousePay + totalPresalePay + totalDebtPay;
const totalSupportCost = data.relatives.reduce((s, c) => s + toNum(c.monthlyCost), 0);
const totalLivingExp = data.livingExpenses.reduce((s, e) => s + toNum(e.amount), 0);
const totalExpense = totalLoanPayment + totalSupportCost + totalLivingExp + totalPledgeMonthlyInterest;
const netCashFlow = totalIncome - totalExpense;
const incomeBreakdown = [
{ label: '薪資收入', value: monthlySalary },
{ label: '房租收益', value: totalRent },
{ label: '投資配息', value: totalAssetIncome },
{ label: '其他收入', value: extraIncome },
];
const expenseBreakdown = [
{ label: '貸款還款', value: totalLoanPayment },
{ label: '扶養支出', value: totalSupportCost },
{ label: '生活開銷', value: totalLivingExp },
{ label: '質押利息', value: totalPledgeMonthlyInterest },
];
// 4. 資產與負債明細
const totalMovable = data.movableAssets.reduce((s, a) => s + toNum(a.amount), 0);
const totalDebtPrincipal = data.debts.reduce((s, d) => s + toNum(d.amount), 0);
const totalCompletedMarket = houseCalcs.reduce((s, h) => s + toNum(h.marketValue), 0);
const totalPresaleMarket = presaleCalcs.reduce((s, p) => s + p.baseValue, 0);
const totalLoanBalance = houseCalcs.reduce((s, h) => s + toNum(h.loanBalance), 0);
const totalPresaleLoan = presaleCalcs.reduce((s, p) => s + toNum(p.expectedLoan), 0);
const totalLiabilities = totalLoanBalance + totalPresaleLoan + totalDebtPrincipal + totalPledgeAmount;
const totalAssets = totalCompletedMarket + totalPresaleMarket + totalMovable;
const netWorth = totalAssets - totalLiabilities;
const assetBreakdown = [
{ label: '成屋市值', value: totalCompletedMarket },
{ label: '預售價值', value: totalPresaleMarket },
{ label: '動產現值', value: totalMovable },
];
const liabilityBreakdown = [
{ label: '房貸餘額', value: totalLoanBalance + totalPresaleLoan },
{ label: '信用/車貸', value: totalDebtPrincipal },
{ label: '動產質押', value: totalPledgeAmount },
];
// 5. 遺產稅計算 (Page 5)
const totalRealEstateAssessed = houseCalcs.reduce((s, h) => s + h.estimatedAssessedValue, 0) + presaleCalcs.reduce((s, p) => s + p.estimatedAssessedValue, 0);
const insuranceAssets = data.movableAssets.filter(a => a.type.includes('保單') || a.type.includes('利變'));
const normalMovableAssets = data.movableAssets.filter(a => !a.type.includes('保單') && !a.type.includes('利變'));
const grossEstate = totalRealEstateAssessed + totalMovable;
const totalLifeInsuranceFaceValue = data.movableAssets.reduce((s, a) => s + toNum(a.faceValue), 0);
const autoSpouseCount = data.family.length >= 2 ? 1 : 0;
const autoParentCount = data.relatives.filter(p => p.relationship === 'parent' && (p.isDependent !== false)).length; // 預設使用 isDependent
const autoChildCount = data.relatives.filter(p => p.relationship === 'child' && (p.isDependent !== false)).length;
const autoSiblingCount = data.relatives.filter(p => p.relationship === 'sibling' && (p.isDependent !== false)).length;
const finalSpouseCount = data.taxPeople.spouse !== '' ? toNum(data.taxPeople.spouse) : autoSpouseCount;
const finalChildCount = data.taxPeople.children !== '' ? toNum(data.taxPeople.children) : autoChildCount;
const finalParentCount = data.taxPeople.parents !== '' ? toNum(data.taxPeople.parents) : autoParentCount;
const finalSiblingCount = data.taxPeople.siblings !== '' ? toNum(data.taxPeople.siblings) : autoSiblingCount;
const finalHandicapCount = data.taxPeople.handicap !== '' ? toNum(data.taxPeople.handicap) : 0;
const deductions =
toNum(data.taxSettings.exemption) +
toNum(data.taxSettings.funeralDeduction) +
(finalSpouseCount * toNum(data.taxSettings.spouseDeduction)) +
(finalChildCount * toNum(data.taxSettings.childDeduction)) +
(finalParentCount * toNum(data.taxSettings.parentDeduction)) +
(finalSiblingCount * toNum(data.taxSettings.siblingDeduction)) +
(finalHandicapCount * toNum(data.taxSettings.handicapDeduction));
const netTaxableEstate = Math.max(0, grossEstate - totalLiabilities - deductions);
let estateTax = 0;
if (netTaxableEstate <= 5000) estateTax = netTaxableEstate * 0.10;
else if (netTaxableEstate <= 10000) estateTax = (netTaxableEstate * 0.15) - 250;
else estateTax = (netTaxableEstate * 0.20) - 750;
// 6. 家庭責任額 (簡易計算:總負債 + 預估10年生活費 - 流動資產)
const annualLivingCost = (totalExpense - totalLoanPayment - totalPledgeMonthlyInterest) * 12; // 扣除負債後的純生活費
const familyResponsibility = Math.max(0, (annualLivingCost * 10) + totalLiabilities - totalMovable);
return {
totalIncome, totalExpense, netCashFlow, netWorth, totalAssets,
totalLiabilities, totalLoanPayment, totalPledgeMonthlyInterest,
incomeBreakdown, expenseBreakdown, assetBreakdown, liabilityBreakdown,
totalRealEstateAssessed, grossEstate, deductions, netTaxableEstate, estateTax,
totalLifeInsuranceFaceValue, insuranceAssets, normalMovableAssets,
spouseCount: finalSpouseCount, childCount: finalChildCount, parentCount: finalParentCount, siblingCount: finalSiblingCount, handicapCount: finalHandicapCount,
familyResponsibility
};
}, [data]);
// --- 未來資產模擬引擎 (Page 6) ---
const projection = useMemo(() => {
// 若沒有生日資料,預設 30 歲開始
const currentAge = data.family[0].birthday ? calculateAge(data.family[0].birthday) : 30;
const spouseAge = data.family[1] && data.family[1].birthday ? calculateAge(data.family[1].birthday) : currentAge; // 若無配偶或配偶無生日,暫用本人年齡替代
const yearsToSimulate = 100 - currentAge;
const startYear = new Date().getFullYear();
const rows = [];
// 初始值
let currentAssets = {
realEstate: calculations.totalCompletedMarket + calculations.totalPresaleMarket,
movable: calculations.totalMovable,
};
let currentLiabilities = {
mortgageBalance: calculations.totalLiabilities - toNum(data.movableAssets.reduce((s,a)=>s+toNum(a.pledgeAmount),0)),
pledge: calculations.totalPledgeAmount
};
// 取得假設參數 (若為空則預設 0 或 1)
const rates = {
cpi: toNum(data.assumptions.cpi) / 100,
reGrowth: toNum(data.assumptions.realEstateGrowth) / 100,
// 注意:薪資成長率不再全域統一,改用個人設定,這裡僅備用
};
// 計算加權平均投資報酬率
let weightedReturnRate = 0;
let totalInvested = 0;
data.movableAssets.forEach(a => {
const amt = toNum(a.amount);
const rate = a.returnRate ? toNum(a.returnRate) : 0;
weightedReturnRate += amt * rate;
totalInvested += amt;
});
const avgInvRate = totalInvested > 0 ? (weightedReturnRate / totalInvested) / 100 : 0.01;
// 取得成員個別薪資與成長率
const selfIncomeBase = toNum(data.family[0].income) * 10000; // 轉為元
const selfGrowth = toNum(data.family[0].salaryGrowth) / 100;
const spouseIncomeBase = data.family[1] ? toNum(data.family[1].income) * 10000 : 0;
const spouseGrowth = data.family[1] ? toNum(data.family[1].salaryGrowth) / 100 : 0;
for (let i = 0; i <= yearsToSimulate; i++) {
const year = startYear + i;
const myAge = currentAge + i;
const spAge = spouseAge + i; // 配偶年齡
// 1. 收入計算 (含 65 歲退休歸零邏輯)
// 本人薪資
let currentSelfSalary = 0;
if (myAge < 65) {
currentSelfSalary = selfIncomeBase * Math.pow(1 + selfGrowth, i);
}
// 配偶薪資
let currentSpouseSalary = 0;
if (spAge < 65) {
currentSpouseSalary = spouseIncomeBase * Math.pow(1 + spouseGrowth, i);
}
// 總年收入 (薪資 + 房租 + 股息 + 其他) -> 假設房租與其他收入隨CPI成長,股息隱含在動產複利中不重複計入現金流(或可視為領出花用)
// 這裡採取:薪資 + (房租+其他)*CPI成長。股息假設再投入(滾入動產成長)。
const otherIncome = (calculations.totalRent*12 + toNum(data.otherIncomes.reduce((s,x)=>s+toNum(x.amount),0))*12) * Math.pow(1 + rates.cpi, i);
const yearlyTotalIncome = currentSelfSalary + currentSpouseSalary + otherIncome;
// 2. 支出計算 (含子女獨立邏輯)
// 基礎生活費 (扣除房貸與質押利息,因為這些另外算)
// 原始非負債月支出 = 總月支 - 貸款月付 - 質押月息
const baseMonthlyExpense = calculations.totalExpense - calculations.totalLoanPayment - calculations.totalPledgeMonthlyInterest;
// 判斷子女是否成年 (減少支出)
// 先算出「目前」的子女與父母扶養費總額
const currentDependentCost = data.relatives.reduce((s, r) => s + toNum(r.monthlyCost), 0);
// 算出「未來該年」應扣除的子女扶養費 (已成年者)
let costReduction = 0;
data.relatives.forEach(r => {
if (r.relationship === 'child') {
const childAge = calculateAge(r.birthday) + i;
if (childAge >= toNum(data.assumptions.childIndependenceAge)) {
costReduction += toNum(r.monthlyCost);
}
}
// 父母百年後? (這裡暫不模擬父母過世,僅模擬子女獨立)
});
// 調整後的年支出 = (基礎月支 - 減少額) * 12 * CPI
const adjustedAnnualExpense = (Math.max(0, baseMonthlyExpense - costReduction)) * 12 * Math.pow(1 + rates.cpi, i);
// 3. 負債支出 (房貸+質押)
// 房貸餘額遞減
let currentMortgageBalance = 0;
let annualMortgagePay = 0;
if (i === 0) {
currentMortgageBalance = currentLiabilities.mortgageBalance;
annualMortgagePay = calculations.totalLoanPayment * 12;
} else {
const prev = rows[i-1];
// 簡易模擬:每年還款額固定,利息隨本金減少,本金償還增加 -> 這裡簡化為餘額線性遞減
// 若要精確需跑每筆貸款的 amortization。這裡假設每年還本 = 總貸款 / 20年 (粗略)
// 修正:使用上一年餘額 - (年還款 - 利息)。太複雜,改用更簡單的:假設 20 年還完。
const reduceAmount = i < 20 ? (currentLiabilities.mortgageBalance / 20) : 0;
currentMortgageBalance = Math.max(0, prev.mortgageBalance - reduceAmount);
annualMortgagePay = i < 20 ? (calculations.totalLoanPayment * 12) : 0; // 20年後不需還款
}
// 質押利息 (80歲前付息,80歲還本)
let pledgeBalance = currentLiabilities.pledge;
let annualPledgePay = calculations.totalPledgeMonthlyInterest * 12;
if (myAge >= toNum(data.assumptions.pledgeEndAge)) {
pledgeBalance = 0;
annualPledgePay = 0;
}
// 當年總流出
const yearlyTotalOutflow = adjustedAnnualExpense + annualMortgagePay + annualPledgePay;
// 當年淨現金流 (儲蓄)
const yearlyNetFlow = yearlyTotalIncome - yearlyTotalOutflow;
// 4. 資產成長
// 不動產
const reValue = currentAssets.realEstate * Math.pow(1 + rates.reGrowth, i);
// 動產 (上一年動產 * (1+報酬率) + 當年儲蓄 - 80歲還款)
let newMovable = 0;
if (i === 0) {
newMovable = currentAssets.movable;
} else {
const prevRow = rows[i-1];
newMovable = prevRow.movable * (1 + avgInvRate) + yearlyNetFlow;
// 80歲那年,動產需扣除質押本金
if (myAge === toNum(data.assumptions.pledgeEndAge) && rows[i-1].pledgeBalance > 0) {
newMovable -= currentLiabilities.pledge;
}
}
const totalLiab = currentMortgageBalance + pledgeBalance;
const netW = reValue + newMovable - totalLiab;
rows.push({
year,
age: myAge,
realEstate: Math.round(reValue),
movable: Math.round(newMovable),
totalAssets: Math.round(reValue + newMovable),
mortgageBalance: Math.round(currentMortgageBalance),
pledgeBalance: Math.round(pledgeBalance),
totalLiabilities: Math.round(totalLiab),
netWorth: Math.round(netW),
yearlyIncome: Math.round(yearlyTotalIncome), // 顯示用
yearlyExpense: Math.round(yearlyTotalOutflow), // 顯示用
yearlyCashFlow: Math.round(yearlyNetFlow)
});
}
return rows;
}, [data, calculations]);
const n = (val) => (val === 0 || !val ? '0' : Math.round(val).toLocaleString());
const addRow = (type) => {
const id = Date.now();
if (type === 'family') setData(p => ({...p, family: [...p.family, { id, role: '新成員', birthday: '', gender: '男', age: 0, income: '' }]}));
// 修正:新增親屬時關係欄位為空,強制選擇
if (type === 'support') setData(p => ({...p, relatives: [...p.relatives, { id, relationship: '', name: '', birthday: '', monthlyCost: '', note: '' }]}));
if (type === 'house') setData(p => ({...p, completedHouses: [...p.completedHouses, { ...initialBaseData.completedHouses[0], id }]}));
if (type === 'presale') setData(p => ({...p, presaleHouses: [...p.presaleHouses, { ...initialBaseData.presaleHouses[0], id }]}));
if (type === 'movable') setData(p => ({...p, movableAssets: [...p.movableAssets, { id, type: '自定義資產', amount: '', monthlyIncome: '', pledgeAmount: '', pledgeRate: '', faceValue: '', returnRate: '' }]}));
if (type === 'debt') setData(p => ({...p, debts: [...p.debts, { id, type: '新增貸款項目', amount: '', rate: '', years: 5 }]}));
if (type === 'incomeItem') setData(p => ({...p, otherIncomes: [...p.otherIncomes, { id, type: '新增收入項目', amount: '' }]}));
if (type === 'livingItem') setData(p => ({...p, livingExpenses: [...p.livingExpenses, { id, type: '其他自定義支出', amount: '', icon: 'ShoppingBag' }]}));
};
const removeRow = (type, id) => {
if(type === 'relatives') setData(p => ({ ...p, relatives: p.relatives.filter(item => item.id !== id) }));
else setData(p => ({ ...p, [type]: p[type].filter(item => item.id !== id) }));
};
const inputBaseStyle = "bg-pink-50 border border-pink-100 rounded-xl px-4 py-2 text-sm text-slate-700 outline-none focus:bg-white focus:border-pink-300 transition-all shadow-inner font-medium no-spinner";
const tableInputStyle = "bg-slate-100/80 border border-slate-200/50 rounded-lg px-2 py-1 text-xs text-slate-700 outline-none focus:bg-white focus:border-pink-300 transition-all shadow-inner font-mono no-spinner";
const labelOnlyStyle = "bg-transparent border-none px-0 py-1 text-xs font-black uppercase tracking-widest text-slate-500 outline-none w-full cursor-default";
const taxInputStyle = "bg-white/10 border border-orange-400/30 rounded px-2 py-1 text-white font-mono font-bold text-sm text-right w-full outline-none focus:border-orange-400 transition-colors placeholder-white/30 no-spinner";
return (
{/* Header & Nav */}
Insma | 資產規劃系統
專業資產配置與財務規劃方案
精準財務終端 v12.1
{/* Page 1: Family */}
{activeTab === 'family' && (
{/* ... Family inputs ... */}
{data.family.map((member, idx) => (
))}
{/* 家庭責任額試算 (保持不變) */}
建議家庭責任額 (壽險缺口)
計算公式:(總負債 + 未來10年生活費) - 現有流動資產{n(calculations.familyResponsibility)} 萬
{data.relatives && data.relatives.map((person, idx) => {
const age = calculateAge(person.birthday);
const isDependent = checkIsDependent(person);
return (
)})}
{data.relatives.length === 0 &&
尚未新增任何扶養對象
}
)}
{/* Page 2 (Balance Sheet) & Page 3 (Cash Flow) are mostly same logic, preserved */}
{activeTab === 'balanceSheet' && (
{/* 房產表格 (保持不變) */}
不動產配置詳情
{/* ... table omitted for brevity, use same code ... */}
1. 成屋項目 (已完工)
| 建案案名 / 地址 | 市值 / 貸款(萬) | 利率 / 年期 | 月還款試算 (併列選取) | 租金月收 | 操作 |
{data.completedHouses.map((h, idx) => { const { gracePay, amortizePay } = calculateMortgage(h.loanBalance, h.rate, h.loanYear); return ( | handleInputChange(`completedHouses.${idx}.name`, e.target.value)} /> handleInputChange(`completedHouses.${idx}.address`, e.target.value)} /> | 市值 handleInputChange(`completedHouses.${idx}.marketValue`, e.target.value)} /> 貸款 handleInputChange(`completedHouses.${idx}.loanBalance`, e.target.value)} /> | handleInputChange(`completedHouses.${idx}.rate`, e.target.value)} />% handleInputChange(`completedHouses.${idx}.loanYear`, e.target.value)} />y | | handleInputChange(`completedHouses.${idx}.rent`, e.target.value)} /> | |
); })}
{/* 預售項目 */}
2. 預售項目 (壓力測試)
| 案名 / 交屋期 / 地址 | 合約 / 市價 / 已付(萬) | 預計貸款 / 利率 / 年期 | 交屋後還款模擬 | 預估租金 | 操作 |
{data.presaleHouses.map((p, idx) => { const { gracePay, amortizePay } = calculateMortgage(p.expectedLoan, p.rate, p.loanYear); return ( | handleInputChange(`presaleHouses.${idx}.name`, e.target.value)} /> handleInputChange(`presaleHouses.${idx}.delivery`, e.target.value)} /> handleInputChange(`presaleHouses.${idx}.address`, e.target.value)} /> | 合約 handleInputChange(`presaleHouses.${idx}.contractPrice`, e.target.value)} /> 市價 handleInputChange(`presaleHouses.${idx}.marketValue`, e.target.value)} /> 已付 handleInputChange(`presaleHouses.${idx}.paidEngineering`, e.target.value)} /> | 貸款 handleInputChange(`presaleHouses.${idx}.expectedLoan`, e.target.value)} /> handleInputChange(`presaleHouses.${idx}.rate`, e.target.value)} />% / handleInputChange(`presaleHouses.${idx}.loanYear`, e.target.value)} />y | | handleInputChange(`presaleHouses.${idx}.rent`, e.target.value)} /> | |
); })}
{/* Movable Assets (Expanded) */}
動產配置明細
{data.movableAssets.map((asset, idx) => {
const isAccumulative = asset.type.includes('現金') || asset.type.includes('累積型') || asset.type.includes('利變');
const isInsurance = asset.type.includes('利變') || asset.type.includes('保單') || asset.type.includes('儲蓄險');
return (
{!isAccumulative ? (
月配息收益 handleInputChange(`movableAssets.${idx}.monthlyIncome`, e.target.value)} />
) :
}
); })}
非房貸貸款項目
{data.debts.map((debt, idx) => { const { amortizePay } = calculateMortgage(debt.amount, debt.rate, debt.years); return (
); })}
)}
{activeTab === 'cashFlow' && (
{/* 保持 CashFlow 不變 */}
月收入項目
{data.family.map((f, i) => (
換算月薪+{n(Math.round((toNum(f.income) * 10000) / 12))}
))}
{/* ... Rents & Investments logic ... */}
+{n(Math.round(calculations.totalRent))}
+{n(Math.round(calculations.totalAssetIncome))} {data.movableAssets.filter(a => toNum(a.monthlyIncome) > 0).map((a, i) => { const realIndex = data.movableAssets.findIndex(x => x.id === a.id); return (
); })}
{data.otherIncomes.map((item, idx) => (
))}
{/* Right Col */}
月支出明細
系統彙總數據房貸、車貸與還本支出
-{n(Math.round(calculations.totalLoanPayment))} 元{calculations.totalSupportMonthlyCost > 0 && (
扶養與照護預算支出-{n(calculations.totalSupportMonthlyCost)} 元
)}{calculations.totalPledgeMonthlyInterest > 0 && (
質押利息支出-{n(calculations.totalPledgeMonthlyInterest)} 元
)}
{data.livingExpenses.map((item, idx) => { const IconComponent = iconMap[item.icon] || ShoppingBag; return (
); })}
)}
{activeTab === 'analysis' && (
總體資產分析結算報告
Integrated Financial Architecture Decision Support Summary
{/* Left: Net Worth */}
預估個人淨值結算
個人總資產減去負債後結餘
{n(calculations.netWorth)}萬
{/* Breakdown Table Left */}
資產與負債總覽
總資產{n(calculations.totalAssets)} 萬
• 成屋市值{n(calculations.totalCompletedMarket)}
• 預售價值{n(calculations.totalPresaleMarket)}
• 動產現值{n(calculations.totalMovable)}
總負債{n(calculations.totalLiabilities)} 萬
• 房貸餘額{n(calculations.totalLoanPayment * 12 * 20 / 10000)} (估)
{/* 簡易顯示 */}
{/* Right: Cash Flow */}
扣除所有房產還本壓力後剩餘
= 0 ? 'text-emerald-400' : 'text-red-400'}`}>{calculations.netCashFlow >= 0 ? '+' : ''}{n(Math.round(calculations.netCashFlow))}元 / 月
{/* Breakdown Table Right */}
月收入
總計+{n(Math.round(calculations.totalIncome))}
{calculations.incomeBreakdown.map((item, idx) => (
{item.label}+{n(Math.round(item.value))}
))}
月支出
總計-{n(Math.round(calculations.totalExpense))}
{calculations.expenseBreakdown.map((item, idx) => (
{item.label}-{n(Math.round(item.value))}
))}
)}
{/* 5. Inheritance */}
{activeTab === 'inheritance' && (
{/* ... (Inheritance section same as before) ... */}
資產傳承稅務試算
Estate Tax & Inheritance Planning
預估遺產總額{n(calculations.grossEstate)} 萬
扣除總負債{n(calculations.totalLiabilities)} 萬
預估應納稅額{n(Math.round(calculations.estateTax))} 萬
{/* ... (Details Grid omitted for brevity but logic is preserved) ... */}
不動產繼承 (公告現值)
總計 {n(calculations.totalRealEstateAssessed)} 萬{[...data.completedHouses, ...data.presaleHouses].map((h, i) => (
{h.name || '未命名物件'}{h.address || '無地址'}
預估稅基{n(h.estimatedAssessedValue)} 萬
))}
動產與保險
總計 {n(calculations.totalMovable)} 萬一般動產
{calculations.normalMovableAssets.map((a, i) => (
{a.type}{n(a.amount)}
))}
利變、分紅保單
{calculations.insuranceAssets.map((a, i) => (
{a.type}{n(a.amount)}
))}
最低稅負制-死亡給付免稅額{n(data.taxSettings.insuranceExemption)} 萬
總身故保額 data.taxSettings.insuranceExemption ? 'text-red-400' : 'text-emerald-400'}`}>{n(calculations.totalLifeInsuranceFaceValue)} 萬
)}
{/* Page 6: Projection */}
{activeTab === 'projection' && (
| 年度 / 年齡 |
房產 |
動產 |
總資產 |
總負債 |
淨值 |
年收入(含薪) |
年支出 |
現金流 |
{projection.map((row, idx) => (
| {row.year} ({row.age}歲) |
{n(row.realEstate)} |
{n(row.movable)} |
{n(row.totalAssets)} |
{n(row.totalLiabilities)} |
{n(row.netWorth)} |
+{n(row.yearlyIncome)} |
-{n(row.yearlyExpense)} |
= 0 ? 'text-slate-700 font-bold' : 'text-red-400'}`}>{n(row.yearlyCashFlow)} |
))}
)}
);
};
export default App;