diff --git a/README.md b/README.md index 49592bffc..84a79efda 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ OpenAPI: `backend/app/openapi.yaml` - Expenses: CRUD `/expenses` - Bills: CRUD `/bills`, pay/mark `/bills/{id}/pay` - Reminders: CRUD `/reminders`, trigger `/reminders/run` -- Insights: `/insights/monthly`, `/insights/budget-suggestion` +- Insights: `/insights/monthly`, `/insights/budget-suggestion`, `/insights/weekly-digest` ## MVP UI/UX Plan - Auth screens: register/login. @@ -73,6 +73,7 @@ OpenAPI: `backend/app/openapi.yaml` - Monthly spend chart, category breakdown donut. - Upcoming bills list with due dates and pay status. - AI budget suggestion card. + - Weekly smart digest with spend trend, category drivers, upcoming bills, and recommendations. - Expenses page: add expense (amount, category, notes, date), list & filter. - Bills page: create bill (name, amount, cadence, due date, channel), toggle WhatsApp/email. - Settings: profile, categories, reminders default channel, export (premium). diff --git a/app/src/api/insights.ts b/app/src/api/insights.ts index 031d1e531..beb30df03 100644 --- a/app/src/api/insights.ts +++ b/app/src/api/insights.ts @@ -21,6 +21,46 @@ export type BudgetSuggestion = { net_flow?: number; }; +export type WeeklyDigest = { + week_start: string; + week_end: string; + currency?: string | null; + summary: { + income: number; + expenses: number; + net_flow: number; + transaction_count: number; + category_breakdown: Array<{ category: string; amount: number }>; + daily_breakdown: Array<{ date: string; amount: number }>; + top_expenses: Array<{ + id: number; + date: string; + description: string; + amount: number; + category: string; + }>; + }; + previous_week: { + week_start: string; + week_end: string; + income: number; + expenses: number; + net_flow: number; + }; + week_over_week_change_pct: number; + upcoming_bills: Array<{ + id: number; + name: string; + amount: number; + currency: string; + due_date: string; + autopay_enabled: boolean; + }>; + insights: string[]; + recommendations: string[]; + method: string; +}; + export async function getBudgetSuggestion(params?: { month?: string; geminiApiKey?: string; @@ -32,3 +72,14 @@ export async function getBudgetSuggestion(params?: { if (params?.persona) headers['X-Insight-Persona'] = params.persona; return api(`/insights/budget-suggestion${monthQuery}`, { headers }); } + +export async function getWeeklyDigest(params?: { + week?: string; + currency?: string; +}): Promise { + const query = new URLSearchParams(); + if (params?.week) query.set('week', params.week); + if (params?.currency) query.set('currency', params.currency); + const suffix = query.toString() ? `?${query.toString()}` : ''; + return api(`/insights/weekly-digest${suffix}`); +} diff --git a/app/src/pages/Analytics.tsx b/app/src/pages/Analytics.tsx index 3efc8acc6..de7c3a231 100644 --- a/app/src/pages/Analytics.tsx +++ b/app/src/pages/Analytics.tsx @@ -10,7 +10,7 @@ import { FinancialCardTitle, } from '@/components/ui/financial-card'; import { useToast } from '@/hooks/use-toast'; -import { getBudgetSuggestion, type BudgetSuggestion } from '@/api/insights'; +import { getBudgetSuggestion, getWeeklyDigest, type BudgetSuggestion, type WeeklyDigest } from '@/api/insights'; import { formatMoney } from '@/lib/currency'; const PERSONAS = [ @@ -22,10 +22,13 @@ const PERSONAS = [ export function Analytics() { const { toast } = useToast(); const [month, setMonth] = useState(() => new Date().toISOString().slice(0, 7)); + const [week, setWeek] = useState(() => new Date().toISOString().slice(0, 10)); + const [currency, setCurrency] = useState(''); const [persona, setPersona] = useState(PERSONAS[0]); const [geminiKey, setGeminiKey] = useState(''); const [loading, setLoading] = useState(true); const [data, setData] = useState(null); + const [weeklyDigest, setWeeklyDigest] = useState(null); const [error, setError] = useState(null); async function load() { @@ -37,7 +40,12 @@ export function Analytics() { persona, geminiApiKey: geminiKey.trim() || undefined, }); + const digest = await getWeeklyDigest({ + week, + currency: currency.trim() || undefined, + }); setData(payload); + setWeeklyDigest(digest); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to load insights'; setError(message); @@ -109,6 +117,27 @@ export function Analytics() { placeholder="AIza..." /> +
+ + setWeek(e.target.value)} + /> +
+
+ + setCurrency(e.target.value.toUpperCase())} + placeholder="USD" + maxLength={10} + /> +