'use client';
import { useState, useEffect } from 'react';
import { Plus, Edit, Trash2, X, Save, ChevronLeft, ChevronRight } from 'lucide-react';
import Link from 'next/link';
interface Category {
id: number;
name: string;
type: 'income' | 'expense';
}
interface Bank {
id: number;
name: string;
}
interface Transaction {
id: number;
category_id: number;
bank_id: number;
type: 'income' | 'expense';
amount: number;
transaction_date: string;
description: string | null;
}
export default function IncomePage() {
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [categories, setCategories] = useState<Category[]>([]);
const [banks, setBanks] = useState<Bank[]>([]);
const [loading, setLoading] = useState(true);
const [editingId, setEditingId] = useState<number | null>(null);
const [formData, setFormData] = useState({
categoryId: '',
bankId: '',
amount: '',
transactionDate: new Date().toISOString().split('T')[0],
description: '',
});
const [showForm, setShowForm] = useState(false);
const [filters, setFilters] = useState({
month: '',
year: '',
type: 'income',
});
const [pagination, setPagination] = useState({
page: 1,
limit: 50,
total: 0,
});
useEffect(() => {
fetchCategories();
fetchBanks();
}, []);
useEffect(() => {
fetchTransactions();
}, [filters, pagination.page]);
const fetchCategories = async () => {
try {
const response = await fetch('/api/balancesheet/categories?type=income');
const result = await response.json();
if (result.success) {
setCategories(result.categories);
}
} catch (err) {
console.error('Failed to fetch categories:', err);
}
};
const fetchBanks = async () => {
try {
const response = await fetch('/api/balancesheet/banks');
const result = await response.json();
if (result.success) {
setBanks(result.banks);
}
} catch (err) {
console.error('Failed to fetch banks:', err);
}
};
const fetchTransactions = async () => {
setLoading(true);
try {
const params = new URLSearchParams();
params.append('type', 'income');
params.append('page', pagination.page.toString());
params.append('limit', pagination.limit.toString());
if (filters.month) params.append('month', filters.month);
if (filters.year) params.append('year', filters.year);
const response = await fetch(`/api/balancesheet/transactions?${params.toString()}`);
const result = await response.json();
if (result.success) {
setTransactions(result.transactions);
setPagination({ ...pagination, total: result.total });
}
} catch (err) {
console.error('Failed to fetch transactions:', err);
} finally {
setLoading(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
if (editingId) {
// Update
const response = await fetch(`/api/balancesheet/transactions/${editingId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
categoryId: parseInt(formData.categoryId),
bankId: parseInt(formData.bankId),
amount: parseFloat(formData.amount),
transactionDate: formData.transactionDate,
description: formData.description || null,
}),
});
const result = await response.json();
if (result.success) {
await fetchTransactions();
resetForm();
} else {
alert(result.error || 'Failed to update transaction');
}
} else {
// Create
const response = await fetch('/api/balancesheet/transactions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
categoryId: parseInt(formData.categoryId),
bankId: parseInt(formData.bankId),
type: 'income',
amount: parseFloat(formData.amount),
transactionDate: formData.transactionDate,
description: formData.description || null,
}),
});
const result = await response.json();
if (result.success) {
await fetchTransactions();
resetForm();
} else {
alert(result.error || 'Failed to create transaction');
}
}
} catch (err) {
console.error('Error saving transaction:', err);
alert('Failed to save transaction');
}
};
const handleEdit = (transaction: Transaction) => {
setEditingId(transaction.id);
setFormData({
categoryId: transaction.category_id.toString(),
bankId: transaction.bank_id.toString(),
amount: transaction.amount.toString(),
transactionDate: transaction.transaction_date.split('T')[0],
description: transaction.description || '',
});
setShowForm(true);
};
const handleDelete = async (id: number) => {
if (!confirm('Are you sure you want to delete this transaction?')) return;
try {
const response = await fetch(`/api/balancesheet/transactions/${id}`, {
method: 'DELETE',
});
const result = await response.json();
if (result.success) {
await fetchTransactions();
} else {
alert(result.error || 'Failed to delete transaction');
}
} catch (err) {
console.error('Error deleting transaction:', err);
alert('Failed to delete transaction');
}
};
const resetForm = () => {
setFormData({
categoryId: '',
bankId: '',
amount: '',
transactionDate: new Date().toISOString().split('T')[0],
description: '',
});
setEditingId(null);
setShowForm(false);
};
const getCategoryName = (categoryId: number) => {
return categories.find(c => c.id === categoryId)?.name || 'Unknown';
};
const getBankName = (bankId: number) => {
return banks.find(b => b.id === bankId)?.name || 'Unknown';
};
const totalPages = Math.ceil(pagination.total / pagination.limit);
const currentDate = new Date();
const months = [
{ value: 1, label: 'Jan' },
{ value: 2, label: 'Feb' },
{ value: 3, label: 'Mar' },
{ value: 4, label: 'Apr' },
{ value: 5, label: 'May' },
{ value: 6, label: 'Jun' },
{ value: 7, label: 'Jul' },
{ value: 8, label: 'Aug' },
{ value: 9, label: 'Sep' },
{ value: 10, label: 'Oct' },
{ value: 11, label: 'Nov' },
{ value: 12, label: 'Dec' },
];
const years = Array.from({ length: 6 }, (_, i) => currentDate.getFullYear() - i);
return (
<div className="min-h-screen bg-gray-50">
<header className="bg-white shadow">
<div className="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold tracking-tight text-gray-900">
Manage Income
</h1>
<Link
href="/balancesheet"
className="text-sm text-blue-600 hover:text-blue-700"
>
← Back to Dashboard
</Link>
</div>
</div>
</header>
<main className="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
{/* Filters */}
<div className="mb-6 bg-white rounded-lg shadow p-4">
<div className="flex flex-wrap gap-4 items-end">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Month
</label>
<select
value={filters.month}
onChange={(e) => {
setFilters({ ...filters, month: e.target.value });
setPagination({ ...pagination, page: 1 });
}}
className="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">All Months</option>
{months.map((month) => (
<option key={month.value} value={month.value}>
{month.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Year
</label>
<select
value={filters.year}
onChange={(e) => {
setFilters({ ...filters, year: e.target.value });
setPagination({ ...pagination, page: 1 });
}}
className="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">All Years</option>
{years.map((year) => (
<option key={year} value={year}>
{year}
</option>
))}
</select>
</div>
<button
onClick={() => {
setFilters({ month: '', year: '', type: 'income' });
setPagination({ ...pagination, page: 1 });
}}
className="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm font-medium text-gray-700"
>
Clear Filters
</button>
</div>
</div>
{/* Add/Edit Form */}
<div className="mb-6 bg-white rounded-lg shadow p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-gray-900">
{editingId ? 'Edit Income' : 'Add Income'}
</h2>
{showForm && (
<button
onClick={resetForm}
className="text-gray-500 hover:text-gray-700"
>
<X className="h-5 w-5" />
</button>
)}
</div>
{!showForm ? (
<button
onClick={() => setShowForm(true)}
className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"
>
<Plus className="h-4 w-4" />
Add Income
</button>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Category *
</label>
<select
required
value={formData.categoryId}
onChange={(e) => setFormData({ ...formData, categoryId: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Select Category</option>
{categories.map((cat) => (
<option key={cat.id} value={cat.id}>
{cat.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1 flex justify-between">
<span>Account *</span>
<Link href="/balancesheet/banks/add" className="text-xs text-blue-600 hover:text-blue-700">
+ Add New
</Link>
</label>
<select
required
value={formData.bankId}
onChange={(e) => setFormData({ ...formData, bankId: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Select Account</option>
{banks.map((bank) => (
<option key={bank.id} value={bank.id}>
{bank.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Amount *
</label>
<input
type="number"
step="0.01"
required
value={formData.amount}
onChange={(e) => setFormData({ ...formData, amount: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Date *
</label>
<input
type="date"
required
value={formData.transactionDate}
onChange={(e) => setFormData({ ...formData, transactionDate: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex gap-2">
<button
type="submit"
className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"
>
<Save className="h-4 w-4" />
{editingId ? 'Update' : 'Create'}
</button>
<button
type="button"
onClick={resetForm}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200"
>
Cancel
</button>
</div>
</form>
)}
</div>
{/* Transactions Table */}
{loading ? (
<div className="flex items-center justify-center py-12">
<div className="text-gray-700">Loading transactions...</div>
</div>
) : (
<>
<div className="bg-white rounded-lg shadow overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Category
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Account
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Amount
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Description
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{transactions.length > 0 ? (
transactions.map((transaction) => (
<tr key={transaction.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
{new Date(transaction.transaction_date).toLocaleDateString('en-IN')}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
{getCategoryName(transaction.category_id)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
{getBankName(transaction.bank_id)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-green-600">
₹{transaction.amount.toLocaleString('en-IN', { maximumFractionDigits: 2 })}
</td>
<td className="px-6 py-4 text-sm text-gray-700">
{transaction.description || '-'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex justify-end gap-2">
<button
onClick={() => handleEdit(transaction)}
className="text-blue-600 hover:text-blue-900"
>
<Edit className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(transaction.id)}
className="text-red-600 hover:text-red-900"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</td>
</tr>
))
) : (
<tr>
<td colSpan={6} className="px-6 py-4 text-center text-sm text-gray-500">
No income transactions found
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="mt-4 flex items-center justify-between">
<div className="text-sm text-gray-700">
Showing page {pagination.page} of {totalPages} ({pagination.total} total)
</div>
<div className="flex gap-2">
<button
onClick={() => setPagination({ ...pagination, page: pagination.page - 1 })}
disabled={pagination.page === 1}
className="px-3 py-2 border border-gray-300 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
>
<ChevronLeft className="h-4 w-4" />
</button>
<button
onClick={() => setPagination({ ...pagination, page: pagination.page + 1 })}
disabled={pagination.page >= totalPages}
className="px-3 py-2 border border-gray-300 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
>
<ChevronRight className="h-4 w-4" />
</button>
</div>
</div>
)}
</>
)}
</main>
</div>
);
}