feat: implement MoneyMind personal finance management application

- Complete transformation from boilerplate to full-featured financial app
- Add comprehensive dashboard with KPI cards and interactive charts
- Implement transaction management with predefined expense/income categories
- Create account management system with multiple account types
- Add authentication flow with session management
- Implement analytics overview with demo financial data
- Add budget tracking and goal progress visualization
- Include custom category creation functionality
- Update branding and footer with MoneyMind by RoMoS
- Add shadcn/ui components and Recharts for data visualization

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Rosario Moscato
2025-09-29 14:41:40 +02:00
parent 312f760844
commit 5270bbd40a
37 changed files with 5469 additions and 204 deletions

View File

@@ -0,0 +1,200 @@
"use client";
import { useState, useEffect } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Edit, Trash2, MoreHorizontal, Plus, Minus } from "lucide-react";
interface Transaction {
id: string;
description: string;
amount: string;
type: "income" | "expense";
date: string;
categoryId?: string;
accountId: string;
merchant?: string;
notes?: string;
createdAt: string;
updatedAt: string;
}
interface TransactionListProps {
onTransactionUpdated?: () => void;
}
export function TransactionList({ onTransactionUpdated }: TransactionListProps) {
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchTransactions();
}, []);
const fetchTransactions = async () => {
try {
setLoading(true);
const response = await fetch("/api/transactions?limit=50");
if (response.ok) {
const data = await response.json();
setTransactions(data.transactions || []);
setError(null);
} else {
setError("Failed to fetch transactions");
}
} catch (error) {
console.error("Error fetching transactions:", error);
setError("Error loading transactions");
} finally {
setLoading(false);
}
};
const deleteTransaction = async (id: string) => {
try {
const response = await fetch(`/api/transactions/${id}`, {
method: "DELETE",
});
if (response.ok) {
setTransactions(transactions.filter(t => t.id !== id));
onTransactionUpdated?.();
} else {
console.error("Failed to delete transaction");
}
} catch (error) {
console.error("Error deleting transaction:", error);
}
};
const formatAmount = (amount: string, type: "income" | "expense") => {
const num = parseFloat(amount);
return `${type === "income" ? "+" : "-"}${num.toFixed(2)}`;
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString("it-IT", {
day: "2-digit",
month: "short",
year: "numeric",
});
};
if (loading) {
return (
<Card>
<CardHeader>
<CardTitle>Transactions</CardTitle>
</CardHeader>
<CardContent>
<div className="text-center py-8">Loading transactions...</div>
</CardContent>
</Card>
);
}
if (error) {
return (
<Card>
<CardHeader>
<CardTitle>Transactions</CardTitle>
</CardHeader>
<CardContent>
<div className="text-center py-8 text-red-600">{error}</div>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<CardTitle>Recent Transactions</CardTitle>
<CardDescription>Your latest income and expense transactions</CardDescription>
</CardHeader>
<CardContent>
{transactions.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
<div className="mb-4">
<Plus className="h-12 w-12 mx-auto text-muted-foreground" />
</div>
<p>No transactions yet</p>
<p className="text-sm">Add your first transaction to get started</p>
</div>
) : (
<div className="space-y-2">
{transactions.map((transaction) => (
<div
key={transaction.id}
className="flex items-center justify-between p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="flex items-center space-x-3">
<div className={`p-2 rounded-full ${
transaction.type === "income"
? "bg-green-100 text-green-600"
: "bg-red-100 text-red-600"
}`}>
{transaction.type === "income" ? (
<Plus className="h-4 w-4" />
) : (
<Minus className="h-4 w-4" />
)}
</div>
<div>
<p className="font-medium">{transaction.description}</p>
<p className="text-sm text-muted-foreground">
{formatDate(transaction.date)}
{transaction.merchant && `${transaction.merchant}`}
</p>
</div>
</div>
<div className="flex items-center space-x-3">
<div className="text-right">
<p className={`font-semibold ${
transaction.type === "income" ? "text-green-600" : "text-red-600"
}`}>
{formatAmount(transaction.amount, transaction.type)}
</p>
<Badge variant="secondary" className="text-xs">
{transaction.type}
</Badge>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => deleteTransaction(transaction.id)}>
<Trash2 className="h-4 w-4 mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
);
}