diff --git a/frontend/src/api.ts b/frontend/src/api.ts index afd2491..cb417ce 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -56,6 +56,34 @@ export interface ImportResult { errors: string[]; } +// --- User Settings --- + +export interface SectionSettings { + label: string; + color: string; + cardColor: string; + visible: boolean; + order: number; + expanded: boolean; +} + +export interface DashboardSettings { + sections: Record; +} + +export interface UserSettingsData { + dashboard: DashboardSettings; +} + +export interface UserSettingsResponse { + key: string; + data: UserSettingsData; + updated_at: string; +} + +export const getSettings = () => api.get('/settings/'); +export const updateSettings = (data: UserSettingsData) => api.patch('/settings/', { data }); + export interface Transaction { id: number; amount: number; diff --git a/frontend/src/components/BillingCycleSelector.tsx b/frontend/src/components/BillingCycleSelector.tsx index b21b993..2022af9 100644 --- a/frontend/src/components/BillingCycleSelector.tsx +++ b/frontend/src/components/BillingCycleSelector.tsx @@ -1,6 +1,13 @@ import { useEffect, useState } from 'react'; -import { Calendar, ChevronDown } from 'lucide-react'; +import { Calendar } from 'lucide-react'; import api from '../api'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; export interface CycleOption { year: number; @@ -22,33 +29,34 @@ export default function BillingCycleSelector({ value, onChange }: Props) { api.get('/transactions/cycles').then((r) => setCycles(r.data)); }, []); - const selectedKey = value ? `${value.year}-${value.month}` : ''; + const selectedKey = value ? `${value.year}-${value.month}` : 'all'; return (
- -
- { + if (val === 'all') { + onChange(null); + } else { + const [y, m] = val.split('-').map(Number); + onChange({ year: y, month: m }); + } + }} + > + + + + + All time {cycles.map((c) => ( - + ))} - - -
+ +
); } diff --git a/frontend/src/components/ConfirmDialog.tsx b/frontend/src/components/ConfirmDialog.tsx index 30d384d..8d4df9a 100644 --- a/frontend/src/components/ConfirmDialog.tsx +++ b/frontend/src/components/ConfirmDialog.tsx @@ -1,4 +1,15 @@ -import { AlertTriangle, X } from 'lucide-react'; +import { AlertTriangle } from 'lucide-react'; +import { + AlertDialog, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, + AlertDialogMedia, +} from '@/components/ui/alert-dialog'; interface Props { title: string; @@ -11,45 +22,26 @@ interface Props { export default function ConfirmDialog({ title, message, confirmLabel = 'Delete', onConfirm, onCancel, loading }: Props) { return ( -
-
e.stopPropagation()} - > -
-

{title}

- -
- -
-
-
- -
-

{message}

-
-
- -
- - -
-
-
+ + + + ); } diff --git a/frontend/src/components/DashboardSection.tsx b/frontend/src/components/DashboardSection.tsx new file mode 100644 index 0000000..eb6a95a --- /dev/null +++ b/frontend/src/components/DashboardSection.tsx @@ -0,0 +1,76 @@ +import { Settings } from 'lucide-react'; +import type { SectionSettings } from '../api'; +import { formatAmount } from '@/lib/format'; +import { Card } from '@/components/ui/card'; +import { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from '@/components/ui/accordion'; +import { getColorClasses } from '@/lib/colors'; +import { cn } from '@/lib/utils'; + +interface Props { + sectionId: string; + settings: SectionSettings; + total?: number; + totalCurrency?: string; + onToggleExpanded: (expanded: boolean) => void; + onOpenConfig: () => void; + children: React.ReactNode; +} + +export default function DashboardSection({ + sectionId, + settings, + total, + totalCurrency, + onToggleExpanded, + onOpenConfig, + children, +}: Props) { + const colors = getColorClasses(settings.color); + + return ( + + {/* Settings icon — outside accordion trigger to avoid button-in-button */} + + + onToggleExpanded(value.includes(sectionId))} + > + + +
+ + {settings.label} + + {total != null && totalCurrency && ( + + {formatAmount(total, totalCurrency)} + + )} +
+
+ +
+ {children} +
+
+
+
+
+ ); +} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index f97a686..979c81c 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -7,13 +7,22 @@ import { LogOut, Wallet, Menu, - X, Sun, Moon, } from 'lucide-react'; import { useState } from 'react'; import { useAuth } from '../AuthContext'; import { useTheme } from '../ThemeContext'; +import { Button } from '@/components/ui/button'; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetClose, +} from '@/components/ui/sheet'; +import { Separator } from '@/components/ui/separator'; +import { cn } from '@/lib/utils'; const navItems = [ { to: '/', icon: LayoutDashboard, label: 'Dashboard' }, @@ -34,16 +43,16 @@ export default function Layout() { }; return ( -
+
{/* Top bar */} -
+
-
- +
+
- - WealthySmart + + WealthySmart
@@ -55,11 +64,12 @@ export default function Layout() { to={to} end={to === '/'} className={({ isActive }) => - `flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${ + cn( + 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors', isActive - ? 'bg-[#606C38]/10 text-[#606C38] dark:text-[#7a8a4a]' - : 'text-text-muted hover:text-text-primary hover:bg-surface-hover' - }` + ? 'bg-primary/10 text-primary' + : 'text-muted-foreground hover:text-foreground hover:bg-muted' + ) } > @@ -68,59 +78,80 @@ export default function Layout() { ))} -
- - - + + +
+
- {/* Mobile nav */} - {mobileOpen && ( -
+ {/* Mobile nav sheet */} + + + + +
+ +
+ + WealthySmart + +
+
+ +
- )} -
+ + +
diff --git a/frontend/src/components/PasteImportModal.tsx b/frontend/src/components/PasteImportModal.tsx index 3bfe420..02726d2 100644 --- a/frontend/src/components/PasteImportModal.tsx +++ b/frontend/src/components/PasteImportModal.tsx @@ -1,6 +1,24 @@ import { useState } from 'react'; -import { X, ClipboardPaste, CheckCircle, AlertTriangle } from 'lucide-react'; +import { ClipboardPaste, CheckCircle, AlertTriangle } from 'lucide-react'; import api, { type ImportResult } from '../api'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from '@/components/ui/dialog'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; interface Props { onClose: () => void; @@ -28,114 +46,100 @@ export default function PasteImportModal({ onClose, onImported }: Props) { } }; - const inputClass = - 'w-full bg-input-bg border border-border rounded-lg px-3 py-2.5 text-sm text-text-primary placeholder-text-faint focus:outline-none focus:border-[#606C38]/50 focus:ring-1 focus:ring-[#606C38]/20 transition-colors'; - const labelClass = 'block text-xs font-medium text-text-secondary mb-1 uppercase tracking-wider'; - return ( -
-
-
-
- -

Import Bank Statement

+ { if (!open) onClose(); }}> + + + + + Import Bank Statement + + + + {!result ? ( +
+
+
+ + +
+
+ + +
+
+ +
+ +