Edit admin page
Edit home image Added properties page
This commit is contained in:
352
app/components/admin/LedgerBook.js
Normal file
352
app/components/admin/LedgerBook.js
Normal file
@ -0,0 +1,352 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
DollarSign,
|
||||
Calendar,
|
||||
User,
|
||||
Home,
|
||||
Download,
|
||||
Filter,
|
||||
Search,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Wallet,
|
||||
Shield
|
||||
} from 'lucide-react';
|
||||
import { formatCurrency } from '@/app/utils/calculations';
|
||||
|
||||
export default function LedgerBook({ userType = 'admin' }) {
|
||||
const [transactions, setTransactions] = useState([]);
|
||||
const [filteredTransactions, setFilteredTransactions] = useState([]);
|
||||
const [dateRange, setDateRange] = useState({ start: '', end: '' });
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [summary, setSummary] = useState({
|
||||
totalRevenue: 0,
|
||||
pendingPayments: 0,
|
||||
securityDeposits: 0,
|
||||
commissionEarned: 0
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadTransactions();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
filterTransactions();
|
||||
calculateSummary();
|
||||
}, [transactions, dateRange, searchTerm]);
|
||||
|
||||
const loadTransactions = async () => {
|
||||
const mockTransactions = [
|
||||
{
|
||||
id: 'T001',
|
||||
date: '2024-02-20',
|
||||
type: 'rent_payment',
|
||||
description: 'دفعة إيجار - فيلا في دمشق',
|
||||
amount: 500000,
|
||||
commission: 25000,
|
||||
fromUser: 'أحمد محمد',
|
||||
toUser: 'مالك العقار',
|
||||
propertyId: 1,
|
||||
propertyName: 'luxuryVillaDamascus',
|
||||
status: 'completed',
|
||||
paymentMethod: 'cash'
|
||||
},
|
||||
{
|
||||
id: 'T002',
|
||||
date: '2024-02-19',
|
||||
type: 'security_deposit',
|
||||
description: 'سلفة ضمان - شقة في حلب',
|
||||
amount: 250000,
|
||||
commission: 0,
|
||||
fromUser: 'سارة أحمد',
|
||||
toUser: 'مالك العقار',
|
||||
propertyId: 2,
|
||||
propertyName: 'modernApartmentAleppo',
|
||||
status: 'pending_refund',
|
||||
paymentMethod: 'cash'
|
||||
},
|
||||
{
|
||||
id: 'T003',
|
||||
date: '2024-02-18',
|
||||
type: 'commission',
|
||||
description: 'عمولة منصة - فيلا في درعا',
|
||||
amount: 30000,
|
||||
commission: 30000,
|
||||
fromUser: 'محمد الحلبي',
|
||||
toUser: 'المنصة',
|
||||
propertyId: 5,
|
||||
propertyName: 'villaDaraa',
|
||||
status: 'completed',
|
||||
paymentMethod: 'cash'
|
||||
}
|
||||
];
|
||||
setTransactions(mockTransactions);
|
||||
};
|
||||
|
||||
const filterTransactions = () => {
|
||||
let filtered = [...transactions];
|
||||
|
||||
if (dateRange.start && dateRange.end) {
|
||||
filtered = filtered.filter(t =>
|
||||
t.date >= dateRange.start && t.date <= dateRange.end
|
||||
);
|
||||
}
|
||||
|
||||
if (searchTerm) {
|
||||
filtered = filtered.filter(t =>
|
||||
t.description.includes(searchTerm) ||
|
||||
t.fromUser.includes(searchTerm) ||
|
||||
t.toUser.includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
setFilteredTransactions(filtered);
|
||||
};
|
||||
|
||||
const calculateSummary = () => {
|
||||
const summary = filteredTransactions.reduce((acc, t) => {
|
||||
if (t.type === 'rent_payment' || t.type === 'commission') {
|
||||
acc.totalRevenue += t.amount;
|
||||
}
|
||||
if (t.type === 'security_deposit' && t.status === 'pending_refund') {
|
||||
acc.securityDeposits += t.amount;
|
||||
}
|
||||
if (t.commission) {
|
||||
acc.commissionEarned += t.commission;
|
||||
}
|
||||
if (t.status === 'pending') {
|
||||
acc.pendingPayments += t.amount;
|
||||
}
|
||||
return acc;
|
||||
}, {
|
||||
totalRevenue: 0,
|
||||
pendingPayments: 0,
|
||||
securityDeposits: 0,
|
||||
commissionEarned: 0
|
||||
});
|
||||
|
||||
setSummary(summary);
|
||||
};
|
||||
|
||||
const getTransactionIcon = (type) => {
|
||||
switch(type) {
|
||||
case 'rent_payment':
|
||||
return <Home className="w-4 h-4 text-blue-600" />;
|
||||
case 'security_deposit':
|
||||
return <Shield className="w-4 h-4 text-green-600" />;
|
||||
case 'commission':
|
||||
return <TrendingUp className="w-4 h-4 text-amber-600" />;
|
||||
default:
|
||||
return <DollarSign className="w-4 h-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const exportToExcel = () => {
|
||||
const csvContent = [
|
||||
['التاريخ', 'الوصف', 'من', 'إلى', 'المبلغ', 'العمولة', 'الحالة'],
|
||||
...filteredTransactions.map(t => [
|
||||
t.date,
|
||||
t.description,
|
||||
t.fromUser,
|
||||
t.toUser,
|
||||
t.amount,
|
||||
t.commission,
|
||||
t.status
|
||||
])
|
||||
].map(row => row.join(',')).join('\n');
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `ledger_${new Date().toISOString()}.csv`;
|
||||
a.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-gradient-to-br from-blue-600 to-blue-700 text-white rounded-xl p-5"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<Wallet className="w-8 h-8 opacity-80" />
|
||||
<span className="text-sm opacity-90">إجمالي الإيرادات</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold">{formatCurrency(summary.totalRevenue)}</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-gradient-to-br from-amber-600 to-amber-700 text-white rounded-xl p-5"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<TrendingUp className="w-8 h-8 opacity-80" />
|
||||
<span className="text-sm opacity-90">أرباح المنصة</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold">{formatCurrency(summary.commissionEarned)}</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="bg-gradient-to-br from-green-600 to-green-700 text-white rounded-xl p-5"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<Shield className="w-8 h-8 opacity-80" />
|
||||
<span className="text-sm opacity-90">سلف الضمان</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold">{formatCurrency(summary.securityDeposits)}</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="bg-gradient-to-br from-red-600 to-red-700 text-white rounded-xl p-5"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<TrendingDown className="w-8 h-8 opacity-80" />
|
||||
<span className="text-sm opacity-90">المدفوعات المعلقة</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold">{formatCurrency(summary.pendingPayments)}</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl p-5 shadow-sm border">
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="بحث في المعاملات..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="date"
|
||||
value={dateRange.start}
|
||||
onChange={(e) => setDateRange({...dateRange, start: e.target.value})}
|
||||
className="px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
<span className="text-gray-500 self-center">إلى</span>
|
||||
<input
|
||||
type="date"
|
||||
value={dateRange.end}
|
||||
onChange={(e) => setDateRange({...dateRange, end: e.target.value})}
|
||||
className="px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={exportToExcel}
|
||||
className="px-4 py-2 bg-green-600 text-white rounded-lg flex items-center gap-2 hover:bg-green-700"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
تصدير
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">التاريخ</th>
|
||||
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">الوصف</th>
|
||||
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">من</th>
|
||||
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">إلى</th>
|
||||
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">المبلغ</th>
|
||||
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">العمولة</th>
|
||||
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">الحالة</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{filteredTransactions.map((transaction, index) => (
|
||||
<motion.tr
|
||||
key={transaction.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className="hover:bg-gray-50"
|
||||
>
|
||||
<td className="px-6 py-4 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-gray-400" />
|
||||
{transaction.date}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{getTransactionIcon(transaction.type)}
|
||||
<span className="text-sm font-medium">{transaction.description}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-sm">{transaction.fromUser}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-sm">{transaction.toUser}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm font-bold">
|
||||
{formatCurrency(transaction.amount)}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-amber-600">
|
||||
{transaction.commission ? formatCurrency(transaction.commission) : '-'}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`px-2 py-1 rounded-full text-xs ${
|
||||
transaction.status === 'completed' ? 'bg-green-100 text-green-800' :
|
||||
transaction.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
|
||||
'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{transaction.status === 'completed' ? 'مكتمل' :
|
||||
transaction.status === 'pending' ? 'معلق' : 'بإنتظار الرد'}
|
||||
</span>
|
||||
</td>
|
||||
</motion.tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{filteredTransactions.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<Wallet className="w-12 h-12 text-gray-300 mx-auto mb-3" />
|
||||
<p className="text-gray-500">لا توجد معاملات في هذه الفترة</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{userType === 'owner' && (
|
||||
<div className="bg-blue-50 rounded-xl p-5">
|
||||
<h3 className="font-bold mb-4 flex items-center gap-2">
|
||||
<User className="w-5 h-5" />
|
||||
أرصدة المستأجرين
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user