From 27cfe63432b3a50d2fb19acc96dfd12598429933 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 25 Aug 2025 01:33:48 +0000 Subject: [PATCH 1/4] Add dashboard demo with financial tracking features Co-authored-by: jason --- demo/src/app.tsx | 3 + demo/src/main.tsx | 2 + demo/src/nav.tsx | 5 + demo/src/routes/dashboard.tsx | 701 ++++++++++++++++++++++++++++++++++ demo/src/routes/home.tsx | 7 +- demo/src/style.css | 649 +++++++++++++++++++++++++++++++ 6 files changed, 1363 insertions(+), 4 deletions(-) create mode 100644 demo/src/routes/dashboard.tsx diff --git a/demo/src/app.tsx b/demo/src/app.tsx index 36ff8ac2..caa8cac4 100644 --- a/demo/src/app.tsx +++ b/demo/src/app.tsx @@ -1293,6 +1293,9 @@ export function App() { Music Demo + + Dashboard Demo + diff --git a/demo/src/main.tsx b/demo/src/main.tsx index 1a6b4a6e..f4d23a45 100644 --- a/demo/src/main.tsx +++ b/demo/src/main.tsx @@ -9,6 +9,7 @@ const Components = lazy(() => import('./app.tsx')); const Linear = lazy(() => import('./routes/linear.tsx')); const Chat = lazy(() => import('./routes/chat.tsx')); const Player = lazy(() => import('./routes/player.tsx')); +const Dashboard = lazy(() => import('./routes/dashboard.tsx')); function setViewportVars() { const vv = window.visualViewport; @@ -31,6 +32,7 @@ render( + , diff --git a/demo/src/nav.tsx b/demo/src/nav.tsx index db5178ab..a82b29b5 100644 --- a/demo/src/nav.tsx +++ b/demo/src/nav.tsx @@ -29,6 +29,11 @@ export function Nav() { Music Demo + + + Dashboard Demo + + ); diff --git a/demo/src/routes/dashboard.tsx b/demo/src/routes/dashboard.tsx new file mode 100644 index 00000000..e5c705cf --- /dev/null +++ b/demo/src/routes/dashboard.tsx @@ -0,0 +1,701 @@ +import { + Avatar, + Badge, + Button, + Card, + Input, + Select, + Textarea, + Switch, + Slider, + Progress, + TabList, + Tab, + TabPanel, + Alert, + AlertDialog, + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + Popover, + PopoverTrigger, + PopoverContent, + Tooltip, + Separator, + Calendar, + DatePicker, + Table, + ScrollArea, + Sheet, + SheetTrigger, + SheetContent, + SheetClose, + Drawer, + DrawerTrigger, + DrawerContent, + DrawerClose, + toast, +} from 'pui'; +import { useState, useEffect, useRef } from 'preact/hooks'; + +interface Transaction { + id: number; + title: string; + amount: number; + category: string; + date: string; + type: 'income' | 'expense'; + description?: string; +} + +interface Goal { + id: number; + title: string; + target: number; + current: number; + deadline: string; + category: string; +} + +interface BudgetCategory { + name: string; + budgeted: number; + spent: number; + color: string; +} + +const initialTransactions: Transaction[] = [ + { id: 1, title: 'Salary', amount: 5000, category: 'Income', date: '2024-01-01', type: 'income' }, + { id: 2, title: 'Groceries', amount: 240, category: 'Food', date: '2024-01-02', type: 'expense' }, + { id: 3, title: 'Gas', amount: 60, category: 'Transportation', date: '2024-01-03', type: 'expense' }, + { id: 4, title: 'Freelance', amount: 800, category: 'Income', date: '2024-01-04', type: 'income' }, + { id: 5, title: 'Coffee', amount: 25, category: 'Food', date: '2024-01-05', type: 'expense' }, + { id: 6, title: 'Netflix', amount: 15, category: 'Entertainment', date: '2024-01-06', type: 'expense' }, +]; + +const initialGoals: Goal[] = [ + { id: 1, title: 'Emergency Fund', target: 10000, current: 7500, deadline: '2024-12-31', category: 'Savings' }, + { id: 2, title: 'Vacation Fund', target: 3000, current: 1200, deadline: '2024-08-01', category: 'Travel' }, + { id: 3, title: 'New Laptop', target: 2500, current: 800, deadline: '2024-06-01', category: 'Tech' }, +]; + +const budgetCategories: BudgetCategory[] = [ + { name: 'Food', budgeted: 500, spent: 265, color: '#ef4444' }, + { name: 'Transportation', budgeted: 200, spent: 120, color: '#3b82f6' }, + { name: 'Entertainment', budgeted: 150, spent: 45, color: '#8b5cf6' }, + { name: 'Shopping', budgeted: 300, spent: 180, color: '#f59e0b' }, + { name: 'Bills', budgeted: 800, spent: 750, color: '#10b981' }, +]; + +const categories = ['Food', 'Transportation', 'Entertainment', 'Shopping', 'Bills', 'Healthcare', 'Education', 'Income']; + +function OverviewTab() { + const totalIncome = initialTransactions.filter(t => t.type === 'income').reduce((sum, t) => sum + t.amount, 0); + const totalExpenses = initialTransactions.filter(t => t.type === 'expense').reduce((sum, t) => sum + t.amount, 0); + const netIncome = totalIncome - totalExpenses; + + return ( +
+
+ +
+

Total Income

+ 📈 +
+
${totalIncome.toLocaleString()}
+
+12% from last month
+
+ + +
+

Total Expenses

+ 📉 +
+
${totalExpenses.toLocaleString()}
+
+5% from last month
+
+ + +
+

Net Income

+ 💰 +
+
${netIncome.toLocaleString()}
+
+8% from last month
+
+ + +
+

Savings Rate

+ đŸŽ¯ +
+
{Math.round((netIncome / totalIncome) * 100)}%
+
Target: 20%
+
+
+ +
+ +

Budget Overview

+
+ {budgetCategories.map(category => { + const percentage = (category.spent / category.budgeted) * 100; + const isOverBudget = percentage > 100; + + return ( +
+
+ {category.name} + + ${category.spent} / ${category.budgeted} + +
+ +
+ {percentage.toFixed(0)}% {isOverBudget && '(Over Budget!)'} +
+
+ ); + })} +
+
+ + +

Recent Transactions

+ + {initialTransactions.slice(0, 6).map(transaction => ( +
+
+
{transaction.title}
+
{transaction.category}
+
+
+ {transaction.type === 'income' ? '+' : '-'}${transaction.amount} +
+
+ ))} +
+
+
+
+ ); +} + +function TransactionsTab() { + const [transactions, setTransactions] = useState(initialTransactions); + const [showAddForm, setShowAddForm] = useState(false); + const [newTransaction, setNewTransaction] = useState({ + title: '', + amount: '', + category: 'Food', + type: 'expense' as 'income' | 'expense', + description: '' + }); + const [filter, setFilter] = useState('all'); + + function addTransaction() { + if (!newTransaction.title || !newTransaction.amount) { + toast.show('Please fill in all required fields', { title: 'Validation Error' }); + return; + } + + const transaction: Transaction = { + id: Date.now(), + title: newTransaction.title, + amount: parseFloat(newTransaction.amount), + category: newTransaction.category, + date: new Date().toISOString().split('T')[0], + type: newTransaction.type, + description: newTransaction.description + }; + + setTransactions([transaction, ...transactions]); + setNewTransaction({ title: '', amount: '', category: 'Food', type: 'expense', description: '' }); + setShowAddForm(false); + toast.show('Transaction added successfully!', { title: 'Success', icon: '✅' }); + } + + function deleteTransaction(id: number) { + setTransactions(transactions.filter(t => t.id !== id)); + toast.show('Transaction deleted', { title: 'Deleted' }); + } + + const filteredTransactions = transactions.filter(t => + filter === 'all' || t.type === filter + ); + + return ( +
+
+

Transactions

+
+ + +
+
+ + {showAddForm && ( + +

Add New Transaction

+
+
+ + setNewTransaction({...newTransaction, title: (e.target as HTMLInputElement).value})} + placeholder="Transaction title" + /> +
+
+ + setNewTransaction({...newTransaction, amount: (e.target as HTMLInputElement).value})} + placeholder="0.00" + /> +
+
+
+
+ + +
+
+ + +
+
+
+ +