| import { useState } from 'react'; | |
| import { useNavigate } from 'react-router-dom'; | |
| import { | |
| Search, Filter, ChevronDown, Coffee, ShoppingCart, Zap, | |
| Home as HomeIcon, Car, Utensils, Briefcase, CreditCard | |
| } from 'lucide-react'; | |
| import { cn } from '@/lib/utils'; | |
| import { Transaction } from '../dashboard/RecentTransactions'; | |
| const CATEGORY_ICONS: Record<string, any> = { | |
| food: Utensils, | |
| coffee: Coffee, | |
| shopping: ShoppingCart, | |
| utilities: Zap, | |
| housing: HomeIcon, | |
| transport: Car, | |
| salary: Briefcase, | |
| other: CreditCard | |
| }; | |
| interface TransactionListProps { | |
| transactions: Transaction[]; | |
| currency?: string; | |
| } | |
| const TransactionList = ({ | |
| transactions, | |
| currency = "$" | |
| }: TransactionListProps) => { | |
| const navigate = useNavigate(); | |
| const [searchTerm, setSearchTerm] = useState(''); | |
| const [showFilters, setShowFilters] = useState(false); | |
| const formatDate = (date: Date) => { | |
| return new Intl.DateTimeFormat('en-US', { | |
| month: 'short', | |
| day: 'numeric' | |
| }).format(date); | |
| }; | |
| const formatCurrency = (amount: number) => { | |
| return new Intl.NumberFormat('en-US', { | |
| style: 'currency', | |
| currency: 'USD', | |
| currencyDisplay: 'symbol', | |
| }).format(Math.abs(amount)).replace('$', ''); | |
| }; | |
| const getCategoryIcon = (category: string) => { | |
| const IconComponent = CATEGORY_ICONS[category.toLowerCase()] || CreditCard; | |
| return <IconComponent size={16} />; | |
| }; | |
| const filteredTransactions = transactions.filter(transaction => | |
| transaction.title.toLowerCase().includes(searchTerm.toLowerCase()) | |
| ); | |
| return ( | |
| <div className="space-y-4 animate-fade-in"> | |
| <div className="relative"> | |
| <Search size={18} className="absolute left-3 top-3 text-muted-foreground" /> | |
| <input | |
| type="text" | |
| placeholder="Search transactions..." | |
| value={searchTerm} | |
| onChange={(e) => setSearchTerm(e.target.value)} | |
| className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-border focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all bg-card" | |
| /> | |
| </div> | |
| <div className="flex justify-between items-center"> | |
| <button | |
| onClick={() => setShowFilters(!showFilters)} | |
| className="flex items-center space-x-2 text-sm text-muted-foreground hover:text-foreground transition-colors" | |
| > | |
| <Filter size={16} /> | |
| <span>Filter</span> | |
| <ChevronDown size={16} className={cn("transition-transform", showFilters && "rotate-180")} /> | |
| </button> | |
| <div className="text-sm text-muted-foreground"> | |
| {filteredTransactions.length} transactions | |
| </div> | |
| </div> | |
| {showFilters && ( | |
| <div className="p-4 border border-border rounded-lg bg-card/50 animate-scale-in"> | |
| <div className="flex flex-wrap gap-2"> | |
| <button className="px-3 py-1.5 bg-primary/10 text-primary text-sm rounded-full"> | |
| All | |
| </button> | |
| <button className="px-3 py-1.5 bg-secondary text-foreground text-sm rounded-full"> | |
| Expenses | |
| </button> | |
| <button className="px-3 py-1.5 bg-secondary text-foreground text-sm rounded-full"> | |
| Income | |
| </button> | |
| <button className="px-3 py-1.5 bg-secondary text-foreground text-sm rounded-full"> | |
| Last 7 days | |
| </button> | |
| <button className="px-3 py-1.5 bg-secondary text-foreground text-sm rounded-full"> | |
| This month | |
| </button> | |
| </div> | |
| </div> | |
| )} | |
| {filteredTransactions.length > 0 ? ( | |
| <div className="space-y-3"> | |
| {filteredTransactions.map((transaction) => ( | |
| <div | |
| key={transaction.id} | |
| onClick={() => navigate(`/transactions/${transaction.id}`)} | |
| className="flex items-center p-3 bg-card rounded-xl border border-border/60 subtle-shadow transition-all duration-300 hover:card-shadow hover:-translate-y-0.5 cursor-pointer" | |
| > | |
| <div className={cn( | |
| "w-10 h-10 rounded-full flex items-center justify-center", | |
| transaction.type === "expense" ? "bg-red-100 text-red-600" : "bg-green-100 text-green-600" | |
| )}> | |
| {getCategoryIcon(transaction.category)} | |
| </div> | |
| <div className="ml-3 flex-1"> | |
| <p className="font-medium">{transaction.title}</p> | |
| <p className="text-xs text-muted-foreground">{formatDate(transaction.date)}</p> | |
| </div> | |
| <div className={cn( | |
| "font-medium", | |
| transaction.type === "expense" ? "text-red-600" : "text-green-600" | |
| )}> | |
| {transaction.type === "expense" ? "- " : "+ "} | |
| {currency}{formatCurrency(transaction.amount)} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| ) : ( | |
| <div className="text-center p-8 bg-muted/50 rounded-xl border border-border"> | |
| <p className="text-muted-foreground">No transactions found</p> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default TransactionList; | |