Files
Novault-Frontend-web/src/components/home/DailyInsightCard/DailyInsightCard.tsx
2026-01-28 21:48:50 +08:00

143 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { Icon } from '@iconify/react';
import { formatCurrency } from '../../../utils/format';
import { getDailyInsight } from '../../../services/aiService';
import './DailyInsightCard.css';
interface DailyInsightCardProps {
todaySpend: number;
yesterdaySpend: number;
monthlyBudget: number;
monthlySpent: number;
topCategory?: { name: string; amount: number };
maxTransaction?: { note?: string; amount: number };
lastWeekSpend?: number;
streakDays?: number;
}
export const DailyInsightCard: React.FC<DailyInsightCardProps> = ({
todaySpend,
yesterdaySpend,
monthlyBudget,
monthlySpent,
topCategory,
maxTransaction,
lastWeekSpend,
streakDays
}) => {
const [aiData, setAiData] = useState<{ spending: string; budget: string } | null>(null);
const [isAiLoading, setIsAiLoading] = useState(false);
useEffect(() => {
// Only fetch if has meaningful data or at least budget is set
if (monthlyBudget === 0 && todaySpend === 0 && yesterdaySpend === 0) return;
const fetchAI = async () => {
setIsAiLoading(true);
try {
const today = new Date().getDate();
const daysInMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate();
const result = await getDailyInsight({
todaySpend,
yesterdaySpend,
monthlyBudget,
monthlySpent,
monthProgress: today / daysInMonth,
topCategory,
maxTransaction: maxTransaction ? {
note: maxTransaction.note || '',
amount: maxTransaction.amount
} : undefined,
lastWeekSpend,
streakDays
});
setAiData(result);
} catch (e) {
console.warn('AI insight fetch failed, sticking to local logic');
} finally {
setIsAiLoading(false);
}
};
const timer = setTimeout(fetchAI, 500);
return () => clearTimeout(timer);
}, [todaySpend, yesterdaySpend, monthlyBudget, monthlySpent, topCategory, maxTransaction, lastWeekSpend, streakDays]);
const getSpendingInsight = (today: number, yesterday: number) => {
if (aiData) return { text: <span>{aiData.spending}</span>, type: 'ai' };
if (today === 0) return { text: "今天还没有花钱,保持这种“零消费”状态就是最大的赚钱!", type: 'success' };
if (yesterday === 0) return { text: <span> <strong className="daily-insight__highlight">{formatCurrency(today)}</strong></span>, type: 'normal' };
const diff = today - yesterday;
if (Math.abs(diff) < 10) return { text: <span> <strong className="daily-insight__highlight">{formatCurrency(today)}</strong></span>, type: 'normal' };
if (today < yesterday) {
const percent = Math.round(((yesterday - today) / yesterday) * 100);
return { text: <span> <strong className="daily-insight__highlight daily-insight__highlight--success">{percent}%</strong></span>, type: 'success' };
} else {
const percent = Math.round(((today - yesterday) / yesterday) * 100);
return { text: <span> <strong className="daily-insight__highlight daily-insight__highlight--warning">{percent}%</strong></span>, type: 'warning' };
}
};
const getBudgetInsight = (spent: number, total: number) => {
if (aiData) return { text: <span>{aiData.budget}</span>, type: 'ai' };
if (total === 0) return { text: "您还没有设置月度预算,建议去设置一个。", type: 'normal' };
const ratio = spent / total;
const today = new Date().getDate();
const daysInMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate();
const timeRatio = today / daysInMonth;
if (ratio >= 1) return { text: <span className="daily-insight__highlight daily-insight__highlight--danger"></span>, type: 'danger' };
if (ratio > timeRatio + 0.15) {
return { text: <span> <strong className="daily-insight__highlight daily-insight__highlight--warning">{Math.round(ratio * 100)}%</strong> ( {Math.round(timeRatio * 100)}%)</span>, type: 'warning' };
}
if (ratio < timeRatio - 0.1) {
return { text: <span> <strong className="daily-insight__highlight daily-insight__highlight--success">{Math.round(ratio * 100)}%</strong> ( {Math.round(timeRatio * 100)}%)</span>, type: 'success' };
}
return { text: <span> <strong>{Math.round(ratio * 100)}%</strong> ( {Math.round(timeRatio * 100)}%)</span>, type: 'normal' };
};
const spendingInsight = getSpendingInsight(todaySpend, yesterdaySpend);
const budgetInsight = getBudgetInsight(monthlySpent, monthlyBudget);
return (
<div className={`daily-insight-card ${aiData ? 'daily-insight-card--ai' : ''}`}>
<div className="daily-insight__header">
<Icon icon={aiData ? "solar:magic-stick-3-bold-duotone" : "solar:stars-minimalistic-bold-duotone"} width="20" />
<span>{aiData ? 'AI 每日简报' : '每日简报'}</span>
{isAiLoading && !aiData && <span className="daily-insight__loading-badge">AI ...</span>}
</div>
<div className="daily-insight__content">
<div className="daily-insight__section">
<div className="section-header-row">
<span className="daily-insight__title"></span>
{lastWeekSpend !== undefined && lastWeekSpend > 0 && (
<span className={`week-diff-badge ${todaySpend <= lastWeekSpend ? 'green' : 'red'}`}>
{todaySpend <= lastWeekSpend ? '↓' : '↑'}{Math.abs(todaySpend - lastWeekSpend).toFixed(0)}
</span>
)}
</div>
<p className="daily-insight__text animate-fade-in">{spendingInsight.text}</p>
</div>
<div className="daily-insight__divider" />
<div className="daily-insight__section">
<span className="daily-insight__title"></span>
<p className="daily-insight__text animate-fade-in">{budgetInsight.text}</p>
</div>
</div>
</div>
);
};