/** * HealthScoreModal Component * Displays detailed financial health analysis with premium glassmorphism design */ import React, { useEffect, useState } from 'react'; import { Icon } from '@iconify/react'; import { formatCurrency } from '../../../utils/format'; import { getFinancialAdvice } from '../../../services/aiService'; import './HealthScoreModal.css'; interface HealthScoreModalProps { isOpen: boolean; onClose: () => void; score: number; totalAssets: number; totalLiabilities: number; todaySpend: number; yesterdaySpend: number; breakdown: { solvency: number; budget: number; habit: number; activity: number; }; streakDays: number; metrics: { debtRatio: number; survivalMonths: number; budgetHealth: number; }; tips: string[]; } export const HealthScoreModal: React.FC = ({ isOpen, onClose, score, totalAssets, totalLiabilities, // todaySpend, // Unused // yesterdaySpend, // Unused breakdown, streakDays, metrics, tips }) => { const [showRules, setShowRules] = useState(false); const [animate, setAnimate] = useState(false); const [aiAdvice, setAiAdvice] = useState(''); const [loadingAdvice, setLoadingAdvice] = useState(false); // --- Logic & Calculations --- const debtRatio = totalAssets > 0 ? (totalLiabilities / totalAssets) * 100 : 0; // const spendDiff = todaySpend - yesterdaySpend; // Unused now useEffect(() => { let isMounted = true; if (isOpen) { setTimeout(() => setAnimate(true), 50); // Fetch AI Advice if not already fetched if (!aiAdvice && !loadingAdvice) { // Only fetch if not already fetched or loading setLoadingAdvice(true); getFinancialAdvice({ score, totalAssets, totalLiabilities, metrics, tips }) .then(advice => { if (isMounted) setAiAdvice(advice); }) .catch(err => { console.error("Failed to load AI advice", err); }) .finally(() => { if (isMounted) setLoadingAdvice(false); }); } } else { setAnimate(false); setShowRules(false); // Reset view on close } return () => { isMounted = false; }; }, [isOpen, score, totalAssets, totalLiabilities, metrics, tips]); if (!isOpen) return null; // Status Levels const getLevel = (s: number) => { if (s >= 95) return { label: 'SSS', title: '财务自由', color: '#10b981', icon: 'solar:crown-star-bold-duotone', desc: '完美无瑕的资产结构' }; if (s >= 90) return { label: 'S', title: '卓越', color: '#34d399', icon: 'solar:cup-star-bold-duotone', desc: '财务状况极佳' }; if (s >= 80) return { label: 'A', title: '优秀', color: '#3b82f6', icon: 'solar:medal-star-bold-duotone', desc: '资产配置健康' }; if (s >= 70) return { label: 'B', title: '良好', color: '#6366f1', icon: 'solar:check-circle-bold-duotone', desc: '继续保持记账习惯' }; if (s >= 60) return { label: 'C', title: '及格', color: '#f59e0b', icon: 'solar:info-circle-bold-duotone', desc: '需注意控制负债' }; return { label: 'D', title: '危险', color: '#ef4444', icon: 'solar:danger-circle-bold-duotone', desc: '建议立即调整开支' }; }; const level = getLevel(score); // --- Render Helpers --- return (
e.stopPropagation()} > {/* Header Controls */}
{showRules ? ( // --- Rules View ---

评分细则

{breakdown.solvency}/40

财务结构 (40分)

由“负债率”与“应急流动性”共同决定。不仅关注负债水平,更看重现金流能否支撑至少3-6个月的生活开支。
这是抵御风险的基石。

{breakdown.budget}/30

收支控制 (30分)

考察预算执行率与时间进度的关系。月中花费50%是健康的,月初花费50%则会扣分。
{breakdown.budget === 30 ? '当前预算控制极佳。' : '需注意平滑消费曲线。'}

{breakdown.habit}/20

记账习惯 (20分)

连续记账天数(Streak)奖励。坚持越久得分越高。
当前连续: {streakDays} 天

{breakdown.activity}/10

活跃度 (10分)

近48小时内是否有记账行为。保持关注有助于及时发现财务风险。

) : ( // --- Score View --- <>
{/* Background Ring */} {/* Progress Ring */}
{score} 健康分
{level.title} · {level.desc}
{/* Debt Card */}
负债率
{debtRatio.toFixed(1)}% 30 ? 'rgba(239, 68, 68, 0.2)' : 'rgba(16, 185, 129, 0.2)', color: debtRatio > 30 ? '#ef4444' : '#10b981' }}> {debtRatio > 30 ? '一般' : '优秀'}
{debtRatio === 0 ? '无负债一身轻' : `负债 ${formatCurrency(totalLiabilities)}`}
{/* Survival Time Card (Replacing Spend) */}
生存期
{metrics?.survivalMonths > 99 ? '>99' : (metrics?.survivalMonths || 0).toFixed(1)} 个月
{(metrics?.survivalMonths || 0) >= 6 ? '资金充裕' : (metrics?.survivalMonths || 0) >= 3 ? '健康' : '急需储备'}
{/* Suggestion Box */}
AI 智能建议
{/* Static Tips */} {tips && tips.length > 0 && tips.map((tip, i) => (
{tip}
))} {/* AI Dynamic Advice */} {loadingAdvice ? (
CFO 正在分析您的财务报表...
) : aiAdvice && (
{aiAdvice}
)}
)}
); };