mirror of
https://github.com/dat515-2025/Group-8.git
synced 2026-03-22 15:12:08 +01:00
refactor(structure): move to 7project dir
This commit is contained in:
207
7project/frontend/src/components/Transactions.tsx
Normal file
207
7project/frontend/src/components/Transactions.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Plus, Edit, Trash2, Search, Filter } from 'lucide-react';
|
||||
import { useTransactions } from '../hooks/useTransactions';
|
||||
import { TransactionForm } from './TransactionForm';
|
||||
import { Transaction } from '../types';
|
||||
|
||||
export const Transactions: React.FC = () => {
|
||||
const { transactions, categories, createTransaction, updateTransaction, deleteTransaction, loading } = useTransactions();
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingTransaction, setEditingTransaction] = useState<Transaction | undefined>();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filterType, setFilterType] = useState<'all' | 'income' | 'expense' | 'transfer'>('all');
|
||||
|
||||
const filteredTransactions = transactions.filter(transaction => {
|
||||
const matchesSearch = transaction.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
transaction.category?.name.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
const matchesType = filterType === 'all' || transaction.type === filterType;
|
||||
return matchesSearch && matchesType;
|
||||
});
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
if (editingTransaction) {
|
||||
await updateTransaction(editingTransaction.id, data);
|
||||
} else {
|
||||
await createTransaction(data);
|
||||
}
|
||||
setShowForm(false);
|
||||
setEditingTransaction(undefined);
|
||||
};
|
||||
|
||||
const handleEdit = (transaction: Transaction) => {
|
||||
setEditingTransaction(transaction);
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (window.confirm('Are you sure you want to delete this transaction?')) {
|
||||
await deleteTransaction(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseForm = () => {
|
||||
setShowForm(false);
|
||||
setEditingTransaction(undefined);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="animate-pulse space-y-4">
|
||||
<div className="h-8 bg-gray-200 rounded w-1/4"></div>
|
||||
<div className="space-y-3">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className="h-16 bg-gray-200 rounded"></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h1 className="text-2xl font-bold text-gray-900">Transactions</h1>
|
||||
<button
|
||||
onClick={() => setShowForm(true)}
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Transaction
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search and Filter */}
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search transactions..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 pr-4 py-2 w-full border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<Filter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||
<select
|
||||
value={filterType}
|
||||
onChange={(e) => setFilterType(e.target.value as any)}
|
||||
className="pl-10 pr-8 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="all">All Types</option>
|
||||
<option value="income">Income</option>
|
||||
<option value="expense">Expense</option>
|
||||
<option value="transfer">Transfer</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Transactions List */}
|
||||
<div className="bg-white shadow-sm rounded-lg border border-gray-200 overflow-hidden">
|
||||
{filteredTransactions.length > 0 ? (
|
||||
<div className="divide-y divide-gray-200">
|
||||
{filteredTransactions.map((transaction) => (
|
||||
<div key={transaction.id} className="p-6 hover:bg-gray-50 transition-colors">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium ${
|
||||
transaction.type === 'income' ? 'bg-green-100 text-green-800' :
|
||||
transaction.type === 'expense' ? 'bg-red-100 text-red-800' :
|
||||
'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{transaction.category?.name.charAt(0).toUpperCase() || 'T'}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">
|
||||
{transaction.description}
|
||||
</p>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
<span className="text-xs text-gray-500">
|
||||
{transaction.category?.name}
|
||||
</span>
|
||||
<span className="text-gray-300">•</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(transaction.date).toLocaleDateString()}
|
||||
</span>
|
||||
<span className="text-gray-300">•</span>
|
||||
<span className={`text-xs font-medium px-2 py-1 rounded-full ${
|
||||
transaction.type === 'income' ? 'bg-green-100 text-green-800' :
|
||||
transaction.type === 'expense' ? 'bg-red-100 text-red-800' :
|
||||
'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{transaction.type}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className={`text-lg font-semibold ${
|
||||
transaction.type === 'income' ? 'text-green-600' : 'text-red-600'
|
||||
}`}>
|
||||
{transaction.type === 'income' ? '+' : '-'}${transaction.amount.toFixed(2)}
|
||||
</span>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => handleEdit(transaction)}
|
||||
className="p-2 text-gray-400 hover:text-blue-600 transition-colors"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(transaction.id)}
|
||||
className="p-2 text-gray-400 hover:text-red-600 transition-colors"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-12 text-center">
|
||||
<div className="text-gray-400 mb-4">
|
||||
<Plus className="h-12 w-12 mx-auto" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">No transactions found</h3>
|
||||
<p className="text-gray-500 mb-6">
|
||||
{searchTerm || filterType !== 'all'
|
||||
? 'Try adjusting your search or filter criteria.'
|
||||
: 'Get started by adding your first transaction.'
|
||||
}
|
||||
</p>
|
||||
{!searchTerm && filterType === 'all' && (
|
||||
<button
|
||||
onClick={() => setShowForm(true)}
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Your First Transaction
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Transaction Form Modal */}
|
||||
{showForm && (
|
||||
<TransactionForm
|
||||
transaction={editingTransaction}
|
||||
categories={categories}
|
||||
onSubmit={handleSubmit}
|
||||
onClose={handleCloseForm}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user