Compare commits
2 Commits
d2c230b4f9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| feb2f2c37e | |||
| c852ad0d80 |
@@ -18,6 +18,13 @@ The system SHALL allow the user to list recorded expenses and delete a specific
|
||||
- **WHEN** the user requests expenses for the app
|
||||
- **THEN** the system returns stored expenses in a stable order with their recorded fields
|
||||
|
||||
### Requirement: User can browse expense history by month
|
||||
The system SHALL allow the user to select a `YYYY-MM` month when reviewing expense history and SHALL return the expenses recorded for that month.
|
||||
|
||||
#### Scenario: Prior month entries are visible
|
||||
- **WHEN** the user selects February 2026 in the add-expense history view
|
||||
- **THEN** the system shows the expenses recorded in February 2026 and exposes delete actions for deletable entries in that month
|
||||
|
||||
#### Scenario: Expense is deleted
|
||||
- **WHEN** the user deletes an existing expense
|
||||
- **THEN** the system removes that expense and it no longer appears in future listings or aggregates
|
||||
@@ -0,0 +1,26 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User can switch between light and dark themes
|
||||
The system SHALL allow the user to toggle between light and dark themes with a persistent preference.
|
||||
|
||||
#### Scenario: User toggles dark mode on
|
||||
- **WHEN** the user activates the theme toggle while the app is in light mode
|
||||
- **THEN** the system applies the dark theme and saves the preference for future visits
|
||||
|
||||
#### Scenario: User toggles dark mode off
|
||||
- **WHEN** the user activates the theme toggle while the app is in dark mode
|
||||
- **THEN** the system applies the light theme and saves the preference for future visits
|
||||
|
||||
### Requirement: Theme preference respects system defaults on first visit
|
||||
The system SHALL use the user's system color scheme preference when no saved preference exists.
|
||||
|
||||
#### Scenario: No stored preference uses system theme
|
||||
- **WHEN** the user opens the app for the first time without a saved theme preference
|
||||
- **THEN** the system applies dark mode when the operating system prefers dark color schemes and light mode otherwise
|
||||
|
||||
### Requirement: Theme selection loads without a flash of the wrong theme
|
||||
The system SHALL initialize the theme before the first visible paint so the page does not briefly render in the wrong theme.
|
||||
|
||||
#### Scenario: Initial paint matches saved theme
|
||||
- **WHEN** the app loads and a saved theme preference exists
|
||||
- **THEN** the document theme is applied before the page content is visibly rendered
|
||||
@@ -0,0 +1,12 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Expense tracking UI renders correctly in both themes
|
||||
The system SHALL render the expense tracking workspace, history list, forms, and item states with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Expense workspace renders in dark mode
|
||||
- **WHEN** the user opens the expense tracking view while dark mode is active
|
||||
- **THEN** the form card, history card, inputs, actions, and expense rows use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Expense workspace renders in light mode
|
||||
- **WHEN** the user opens the expense tracking view while light mode is active
|
||||
- **THEN** the form card, history card, inputs, actions, and expense rows use light-compatible colors and remain readable
|
||||
@@ -0,0 +1,12 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Monthly dashboard UI renders correctly in both themes
|
||||
The system SHALL render dashboard sections, insight cards, category bars, stat tiles, and empty states with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Dashboard renders in dark mode
|
||||
- **WHEN** the user opens the dashboard while dark mode is active
|
||||
- **THEN** the summary cards, comparison cards, progress bars, and empty states use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Dashboard renders in light mode
|
||||
- **WHEN** the user opens the dashboard while light mode is active
|
||||
- **THEN** the summary cards, comparison cards, progress bars, and empty states use light-compatible colors and remain readable
|
||||
@@ -0,0 +1,12 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Paycheck tracking UI renders correctly in both themes
|
||||
The system SHALL render the paycheck tracking workspace, schedule panel, form, and list items with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Paycheck workspace renders in dark mode
|
||||
- **WHEN** the user opens the paycheck tracking view while dark mode is active
|
||||
- **THEN** the schedule panel, form card, inputs, and paycheck rows use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Paycheck workspace renders in light mode
|
||||
- **WHEN** the user opens the paycheck tracking view while light mode is active
|
||||
- **THEN** the schedule panel, form card, inputs, and paycheck rows use light-compatible colors and remain readable
|
||||
@@ -21,5 +21,5 @@
|
||||
|
||||
## 5. Verification
|
||||
|
||||
- [ ] 5.1 Visually verify light and dark modes across the dashboard, add-expense, and income pages in the browser.
|
||||
- [ ] 5.2 Verify that theme preference persists across page refreshes and that there is no flash of the wrong theme on load.
|
||||
- [x] 5.1 Visually verify light and dark modes across the dashboard, add-expense, and income pages in the browser.
|
||||
- [x] 5.2 Verify that theme preference persists across page refreshes and that there is no flash of the wrong theme on load.
|
||||
27
openspec/specs/category-suggestion/spec.md
Normal file
27
openspec/specs/category-suggestion/spec.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## Purpose
|
||||
|
||||
TBD
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: System suggests categories from merchant names
|
||||
The system SHALL support merchant-name-based category suggestion for expense entry while keeping all suggestion logic fully offline.
|
||||
|
||||
#### Scenario: Known merchant resolves from deterministic rules
|
||||
- **WHEN** the user enters a merchant or shop name that matches a known merchant rule
|
||||
- **THEN** the system assigns the mapped category without needing model inference
|
||||
|
||||
#### Scenario: Unknown merchant falls back to local model
|
||||
- **WHEN** the user enters a merchant or shop name that does not match a known merchant rule
|
||||
- **THEN** the system asks the local AI service for a category suggestion and returns the suggested category
|
||||
|
||||
### Requirement: Ambiguous suggestions remain user-controlled
|
||||
The system SHALL keep the final saved category under user control for ambiguous or model-generated suggestions.
|
||||
|
||||
#### Scenario: User confirms model suggestion before save
|
||||
- **WHEN** the category suggestion comes from model inference instead of a deterministic rule
|
||||
- **THEN** the user can review and confirm or change the category before the expense is saved
|
||||
|
||||
#### Scenario: No cloud fallback is used
|
||||
- **WHEN** the local suggestion service is unavailable
|
||||
- **THEN** the system continues to allow manual category selection and does not send merchant data to a hosted provider
|
||||
30
openspec/specs/dark-mode/spec.md
Normal file
30
openspec/specs/dark-mode/spec.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## Purpose
|
||||
|
||||
TBD
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: User can switch between light and dark themes
|
||||
The system SHALL allow the user to toggle between light and dark themes with a persistent preference.
|
||||
|
||||
#### Scenario: User toggles dark mode on
|
||||
- **WHEN** the user activates the theme toggle while the app is in light mode
|
||||
- **THEN** the system applies the dark theme and saves the preference for future visits
|
||||
|
||||
#### Scenario: User toggles dark mode off
|
||||
- **WHEN** the user activates the theme toggle while the app is in dark mode
|
||||
- **THEN** the system applies the light theme and saves the preference for future visits
|
||||
|
||||
### Requirement: Theme preference respects system defaults on first visit
|
||||
The system SHALL use the user's system color scheme preference when no saved preference exists.
|
||||
|
||||
#### Scenario: No stored preference uses system theme
|
||||
- **WHEN** the user opens the app for the first time without a saved theme preference
|
||||
- **THEN** the system applies dark mode when the operating system prefers dark color schemes and light mode otherwise
|
||||
|
||||
### Requirement: Theme selection loads without a flash of the wrong theme
|
||||
The system SHALL initialize the theme before the first visible paint so the page does not briefly render in the wrong theme.
|
||||
|
||||
#### Scenario: Initial paint matches saved theme
|
||||
- **WHEN** the app loads and a saved theme preference exists
|
||||
- **THEN** the document theme is applied before the page content is visibly rendered
|
||||
47
openspec/specs/expense-tracking/spec.md
Normal file
47
openspec/specs/expense-tracking/spec.md
Normal file
@@ -0,0 +1,47 @@
|
||||
## Purpose
|
||||
|
||||
TBD
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: User can record categorized expenses
|
||||
The system SHALL allow the user to create an expense with a title, amount, category, and local calendar date using fixed starter categories.
|
||||
|
||||
#### Scenario: Valid expense is created
|
||||
- **WHEN** the user submits a title, positive amount, valid category, and valid local date
|
||||
- **THEN** the system stores the expense and returns the created record
|
||||
|
||||
#### Scenario: Invalid expense is rejected
|
||||
- **WHEN** the user submits a missing title, invalid amount, invalid category, or invalid date
|
||||
- **THEN** the system rejects the request with a validation error and does not store the expense
|
||||
|
||||
### Requirement: User can review and delete expenses
|
||||
The system SHALL allow the user to list recorded expenses and delete a specific expense by identifier.
|
||||
|
||||
#### Scenario: Expenses are listed
|
||||
- **WHEN** the user requests expenses for the app
|
||||
- **THEN** the system returns stored expenses in a stable order with their recorded fields
|
||||
|
||||
### Requirement: User can browse expense history by month
|
||||
The system SHALL allow the user to select a `YYYY-MM` month when reviewing expense history and SHALL return the expenses recorded for that month.
|
||||
|
||||
#### Scenario: Prior month entries are visible
|
||||
- **WHEN** the user selects February 2026 in the add-expense history view
|
||||
- **THEN** the system shows the expenses recorded in February 2026 and exposes delete actions for deletable entries in that month
|
||||
|
||||
#### Scenario: Expense is deleted
|
||||
- **WHEN** the user deletes an existing expense
|
||||
- **THEN** the system removes that expense and it no longer appears in future listings or aggregates
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Expense tracking UI renders correctly in both themes
|
||||
The system SHALL render the expense tracking workspace, history list, forms, and item states with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Expense workspace renders in dark mode
|
||||
- **WHEN** the user opens the expense tracking view while dark mode is active
|
||||
- **THEN** the form card, history card, inputs, actions, and expense rows use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Expense workspace renders in light mode
|
||||
- **WHEN** the user opens the expense tracking view while light mode is active
|
||||
- **THEN** the form card, history card, inputs, actions, and expense rows use light-compatible colors and remain readable
|
||||
40
openspec/specs/monthly-dashboard/spec.md
Normal file
40
openspec/specs/monthly-dashboard/spec.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## Purpose
|
||||
|
||||
TBD
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Dashboard shows month-specific financial totals
|
||||
The system SHALL return month-specific dashboard data for a requested `YYYY-MM` month using the local machine timezone for month boundaries.
|
||||
|
||||
#### Scenario: Dashboard totals are calculated for a populated month
|
||||
- **WHEN** the user requests the dashboard for a month with expenses and paychecks
|
||||
- **THEN** the system returns total expenses, total paychecks, net cash flow, and a category breakdown for that month
|
||||
|
||||
#### Scenario: Dashboard supports partial current-month data
|
||||
- **WHEN** the user requests the dashboard for the current month before the month is complete
|
||||
- **THEN** the system returns meaningful month-to-date totals and comparisons using the transactions recorded so far
|
||||
|
||||
### Requirement: Dashboard includes derived spending comparisons
|
||||
The system SHALL provide derived comparisons for the selected month, including highest category, largest expense, average daily spend, and paycheck coverage information.
|
||||
|
||||
#### Scenario: Derived comparisons are available
|
||||
- **WHEN** the selected month contains enough data for comparisons
|
||||
- **THEN** the system returns the highest category, largest single expense, average daily spend, and spend-versus-paycheck coverage values
|
||||
|
||||
#### Scenario: Derived comparisons degrade safely for sparse data
|
||||
- **WHEN** the selected month has no expenses or otherwise insufficient data for a comparison
|
||||
- **THEN** the system returns null or empty-safe comparison fields instead of failing
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Monthly dashboard UI renders correctly in both themes
|
||||
The system SHALL render dashboard sections, insight cards, category bars, stat tiles, and empty states with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Dashboard renders in dark mode
|
||||
- **WHEN** the user opens the dashboard while dark mode is active
|
||||
- **THEN** the summary cards, comparison cards, progress bars, and empty states use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Dashboard renders in light mode
|
||||
- **WHEN** the user opens the dashboard while light mode is active
|
||||
- **THEN** the summary cards, comparison cards, progress bars, and empty states use light-compatible colors and remain readable
|
||||
34
openspec/specs/monthly-insights/spec.md
Normal file
34
openspec/specs/monthly-insights/spec.md
Normal file
@@ -0,0 +1,34 @@
|
||||
## Purpose
|
||||
|
||||
TBD
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: User can generate monthly AI insights on demand
|
||||
The system SHALL allow the user to manually generate monthly AI insights for any month with existing or sparse data by sending structured monthly context to a fully offline local inference runtime.
|
||||
|
||||
#### Scenario: Insights are generated for a month with data
|
||||
- **WHEN** the user requests insight generation for a month with recorded activity
|
||||
- **THEN** the system sends monthly aggregates plus transaction samples to the local AI service and returns a rendered narrative summary with structured supporting totals
|
||||
|
||||
#### Scenario: Prior month insights can be generated
|
||||
- **WHEN** the user requests insight generation for a previous month that has recorded data
|
||||
- **THEN** the system generates and stores insight output for that requested month
|
||||
|
||||
### Requirement: Insight generation is read-only and safe for sparse months
|
||||
The system SHALL keep AI insight generation read-only and return a safe fallback summary when a month does not have enough data for meaningful guidance.
|
||||
|
||||
#### Scenario: Sparse month returns fallback insight
|
||||
- **WHEN** the user requests insight generation for a month with empty or near-empty data
|
||||
- **THEN** the system returns a fallback message instead of low-confidence advice
|
||||
|
||||
#### Scenario: AI does not mutate financial records
|
||||
- **WHEN** the system generates or stores monthly insights
|
||||
- **THEN** no expense or paycheck records are created, updated, or deleted as part of that request
|
||||
|
||||
### Requirement: Insight generation remains private and resilient offline
|
||||
The system SHALL keep monthly insight generation fully offline and provide a clear fallback response when the local model runtime or selected model is unavailable.
|
||||
|
||||
#### Scenario: Local runtime is unavailable
|
||||
- **WHEN** the user requests monthly insights while the local AI runtime is not running or the configured model is unavailable
|
||||
- **THEN** the system returns a clear setup or availability message instead of attempting a cloud fallback
|
||||
40
openspec/specs/paycheck-tracking/spec.md
Normal file
40
openspec/specs/paycheck-tracking/spec.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## Purpose
|
||||
|
||||
TBD
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: User can record paychecks by pay date
|
||||
The system SHALL allow the user to create a paycheck with a positive amount and a local pay date.
|
||||
|
||||
#### Scenario: Valid paycheck is created
|
||||
- **WHEN** the user submits a positive amount and valid local pay date
|
||||
- **THEN** the system stores the paycheck and returns the created record
|
||||
|
||||
#### Scenario: Invalid paycheck is rejected
|
||||
- **WHEN** the user submits a missing or invalid amount or date
|
||||
- **THEN** the system rejects the request with a validation error and does not store the paycheck
|
||||
|
||||
### Requirement: User can review and delete paychecks
|
||||
The system SHALL allow the user to list recorded paychecks and delete a specific paycheck by identifier.
|
||||
|
||||
#### Scenario: Paychecks are listed
|
||||
- **WHEN** the user requests paychecks for the app
|
||||
- **THEN** the system returns stored paychecks in a stable order with their recorded fields
|
||||
|
||||
#### Scenario: Paycheck is deleted
|
||||
- **WHEN** the user deletes an existing paycheck
|
||||
- **THEN** the system removes that paycheck and it no longer appears in future dashboard totals or insight inputs
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Paycheck tracking UI renders correctly in both themes
|
||||
The system SHALL render the paycheck tracking workspace, schedule panel, form, and list items with readable contrast in both light and dark themes.
|
||||
|
||||
#### Scenario: Paycheck workspace renders in dark mode
|
||||
- **WHEN** the user opens the paycheck tracking view while dark mode is active
|
||||
- **THEN** the schedule panel, form card, inputs, and paycheck rows use dark-compatible colors and remain readable
|
||||
|
||||
#### Scenario: Paycheck workspace renders in light mode
|
||||
- **WHEN** the user opens the paycheck tracking view while light mode is active
|
||||
- **THEN** the schedule panel, form card, inputs, and paycheck rows use light-compatible colors and remain readable
|
||||
@@ -1,5 +1,6 @@
|
||||
import { HomeDashboard } from "@/components/home-dashboard";
|
||||
import { getCurrentMonthKey } from "@/lib/date";
|
||||
|
||||
export default function Home() {
|
||||
return <HomeDashboard />;
|
||||
return <HomeDashboard initialMonth={getCurrentMonthKey()} />;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState, type FormEvent } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState, type FormEvent } from "react";
|
||||
|
||||
import { getCategoryLabel, type CategoryValue } from "@/lib/categories";
|
||||
import { getCurrentMonthKey, getLocalToday } from "@/lib/date";
|
||||
import { getCurrentMonthKey, getLocalToday, getMonthLabel } from "@/lib/date";
|
||||
import { formatCurrencyFromCents } from "@/lib/money";
|
||||
|
||||
type SuggestionResponse = {
|
||||
@@ -34,6 +34,7 @@ type Props = {
|
||||
|
||||
export function ExpenseWorkspace({ categoryOptions }: Props) {
|
||||
const [expenses, setExpenses] = useState<ExpenseRecord[]>([]);
|
||||
const [selectedMonth, setSelectedMonth] = useState("");
|
||||
const [formState, setFormState] = useState<{
|
||||
title: string;
|
||||
amount: string;
|
||||
@@ -53,23 +54,33 @@ export function ExpenseWorkspace({ categoryOptions }: Props) {
|
||||
const [lastSuggestedMerchant, setLastSuggestedMerchant] = useState("");
|
||||
const [suggestedCategory, setSuggestedCategory] = useState<CategoryValue | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadExpenses() {
|
||||
const month = getCurrentMonthKey();
|
||||
const loadExpenses = useCallback(async (month: string) => {
|
||||
const response = await fetch(`/expenses?month=${month}`, { cache: "no-store" });
|
||||
const payload = (await response.json().catch(() => null)) as { expenses?: ExpenseRecord[] } | null;
|
||||
setExpenses(payload?.expenses ?? []);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
setSelectedMonth(getCurrentMonthKey());
|
||||
setFormState((current) => (current.date ? current : { ...current, date: getLocalToday() }));
|
||||
}, 0);
|
||||
|
||||
void loadExpenses();
|
||||
|
||||
return () => window.clearTimeout(timeoutId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedMonth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
void loadExpenses(selectedMonth);
|
||||
}, 0);
|
||||
|
||||
return () => window.clearTimeout(timeoutId);
|
||||
}, [loadExpenses, selectedMonth]);
|
||||
|
||||
const totalSpent = useMemo(
|
||||
() => expenses.reduce((sum, expense) => sum + expense.amountCents, 0),
|
||||
[expenses],
|
||||
@@ -188,6 +199,10 @@ export function ExpenseWorkspace({ categoryOptions }: Props) {
|
||||
setExpenses((current) => [payload.expense, ...current]);
|
||||
}
|
||||
|
||||
if (selectedMonth) {
|
||||
await loadExpenses(selectedMonth);
|
||||
}
|
||||
|
||||
setFormState((current) => ({ ...current, title: "", amount: "" }));
|
||||
setSuggestionMessage(null);
|
||||
setNeedsSuggestionConfirmation(false);
|
||||
@@ -208,6 +223,11 @@ export function ExpenseWorkspace({ categoryOptions }: Props) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedMonth) {
|
||||
await loadExpenses(selectedMonth);
|
||||
return;
|
||||
}
|
||||
|
||||
setExpenses((current) => current.filter((expense) => expense.id !== id));
|
||||
}
|
||||
|
||||
@@ -327,15 +347,31 @@ export function ExpenseWorkspace({ categoryOptions }: Props) {
|
||||
</section>
|
||||
|
||||
<section className="rounded-[2rem] border border-stone-200 bg-[#fffaf2] p-6 shadow-[0_24px_60px_rgba(120,90,50,0.08)] dark:border-stone-700 dark:bg-stone-900/60">
|
||||
<div className="mb-5">
|
||||
<div className="mb-5 flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-semibold uppercase tracking-[0.24em] text-stone-500 dark:text-stone-400">Recent entries</p>
|
||||
<h2 className="mt-2 text-2xl font-semibold text-stone-950 dark:text-white">Expense history</h2>
|
||||
{selectedMonth ? (
|
||||
<p className="mt-2 text-sm text-stone-600 dark:text-stone-400">
|
||||
Showing {getMonthLabel(selectedMonth)} entries.
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<label className="grid gap-2 text-sm font-medium text-stone-700 dark:text-stone-300">
|
||||
Month
|
||||
<input
|
||||
type="month"
|
||||
value={selectedMonth}
|
||||
onChange={(event) => setSelectedMonth(event.target.value)}
|
||||
className="rounded-2xl border border-stone-300 bg-stone-50 px-3 py-2 text-sm font-medium text-stone-700 outline-none transition focus:border-stone-900 dark:border-stone-600 dark:bg-stone-800 dark:text-stone-300 dark:focus:border-stone-400"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{expenses.length === 0 ? (
|
||||
<div className="rounded-3xl border border-dashed border-stone-300 px-4 py-6 text-sm text-stone-600 dark:border-stone-600 dark:text-stone-400">
|
||||
No expenses yet. Add your first entry to start the month.
|
||||
{selectedMonth ? `No expenses recorded for ${getMonthLabel(selectedMonth)} yet.` : "No expenses yet. Add your first entry to start the month."}
|
||||
</div>
|
||||
) : (
|
||||
expenses.map((expense) => (
|
||||
|
||||
@@ -40,8 +40,12 @@ type OllamaStatus = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export function HomeDashboard() {
|
||||
const [selectedMonth, setSelectedMonth] = useState("");
|
||||
type HomeDashboardProps = {
|
||||
initialMonth: string;
|
||||
};
|
||||
|
||||
export function HomeDashboard({ initialMonth }: HomeDashboardProps) {
|
||||
const [selectedMonth, setSelectedMonth] = useState(initialMonth);
|
||||
const [snapshot, setSnapshot] = useState<DashboardSnapshot | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [insightBusy, setInsightBusy] = useState(false);
|
||||
@@ -67,14 +71,6 @@ export function HomeDashboard() {
|
||||
setOllamaStatus(payload);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
setSelectedMonth(getCurrentMonthKey());
|
||||
}, 0);
|
||||
|
||||
return () => window.clearTimeout(timeoutId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedMonth) {
|
||||
return;
|
||||
|
||||
@@ -18,6 +18,7 @@ vi.mock("@/lib/db", () => {
|
||||
db: {
|
||||
expense: { findMany: vi.fn() },
|
||||
paycheck: { findMany: vi.fn() },
|
||||
recurringExpense: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
paySchedule: { findFirst: vi.fn().mockResolvedValue(null) },
|
||||
monthlyInsight,
|
||||
},
|
||||
@@ -35,6 +36,7 @@ describe("generateMonthlyInsight", () => {
|
||||
|
||||
vi.mocked(db.expense.findMany).mockResolvedValue([]);
|
||||
vi.mocked(db.paycheck.findMany).mockResolvedValue([]);
|
||||
vi.mocked(db.recurringExpense.findMany).mockResolvedValue([]);
|
||||
|
||||
const result = await generateMonthlyInsight("2026-03");
|
||||
|
||||
@@ -72,6 +74,7 @@ describe("generateMonthlyInsight", () => {
|
||||
createdAt: new Date("2026-03-01T10:00:00.000Z"),
|
||||
},
|
||||
]);
|
||||
vi.mocked(db.recurringExpense.findMany).mockResolvedValue([]);
|
||||
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
@@ -120,6 +123,7 @@ describe("generateMonthlyInsight", () => {
|
||||
createdAt: new Date("2026-03-01T10:00:00.000Z"),
|
||||
},
|
||||
]);
|
||||
vi.mocked(db.recurringExpense.findMany).mockResolvedValue([]);
|
||||
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user