2026-02-15 01:53:37 +03:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
|
import { motion } from 'framer-motion';
|
|
|
|
|
|
import {
|
|
|
|
|
|
DollarSign,
|
|
|
|
|
|
Calendar,
|
|
|
|
|
|
User,
|
|
|
|
|
|
Home,
|
|
|
|
|
|
Download,
|
|
|
|
|
|
Filter,
|
|
|
|
|
|
Search,
|
|
|
|
|
|
TrendingUp,
|
|
|
|
|
|
TrendingDown,
|
|
|
|
|
|
Wallet,
|
2026-03-27 00:34:59 +03:00
|
|
|
|
Shield,
|
|
|
|
|
|
FileText,
|
|
|
|
|
|
Printer,
|
|
|
|
|
|
X,
|
|
|
|
|
|
CheckCircle
|
2026-02-15 01:53:37 +03:00
|
|
|
|
} from 'lucide-react';
|
|
|
|
|
|
import { formatCurrency } from '@/app/utils/calculations';
|
2026-03-27 00:34:59 +03:00
|
|
|
|
import toast, { Toaster } from 'react-hot-toast';
|
|
|
|
|
|
import * as XLSX from 'xlsx';
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
});
|
2026-03-27 00:34:59 +03:00
|
|
|
|
const [isExporting, setIsExporting] = useState(false);
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
|
|
|
|
|
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" />;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-27 00:34:59 +03:00
|
|
|
|
const exportToExcel = async () => {
|
|
|
|
|
|
if (filteredTransactions.length === 0) {
|
|
|
|
|
|
toast.error('لا توجد معاملات للتصدير');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setIsExporting(true);
|
|
|
|
|
|
toast.loading('جاري تصدير البيانات...', { id: 'export' });
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const exportData = filteredTransactions.map(t => ({
|
|
|
|
|
|
'رقم العملية': t.id,
|
|
|
|
|
|
'التاريخ': t.date,
|
|
|
|
|
|
'نوع العملية': t.type === 'rent_payment' ? 'دفعة إيجار' :
|
|
|
|
|
|
t.type === 'security_deposit' ? 'سلفة ضمان' :
|
|
|
|
|
|
t.type === 'commission' ? 'عمولة' : 'أخرى',
|
|
|
|
|
|
'الوصف': t.description,
|
|
|
|
|
|
'من': t.fromUser,
|
|
|
|
|
|
'إلى': t.toUser,
|
|
|
|
|
|
'المبلغ (ل.س)': t.amount,
|
|
|
|
|
|
'العمولة (ل.س)': t.commission || 0,
|
|
|
|
|
|
'الحالة': t.status === 'completed' ? 'مكتمل' :
|
|
|
|
|
|
t.status === 'pending' ? 'معلق' :
|
|
|
|
|
|
t.status === 'pending_refund' ? 'بإنتظار الاسترداد' : 'مؤكد',
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
const summaryRow = {
|
|
|
|
|
|
'رقم العملية': '',
|
|
|
|
|
|
'التاريخ': '',
|
|
|
|
|
|
'نوع العملية': '',
|
|
|
|
|
|
'الوصف': '',
|
|
|
|
|
|
'من': '',
|
|
|
|
|
|
'إلى': '',
|
|
|
|
|
|
'المبلغ (ل.س)': summary.totalRevenue,
|
|
|
|
|
|
'العمولة (ل.س)': summary.commissionEarned,
|
|
|
|
|
|
'الحالة': ''
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
exportData.push(summaryRow);
|
|
|
|
|
|
|
|
|
|
|
|
const worksheet = XLSX.utils.json_to_sheet(exportData);
|
|
|
|
|
|
|
|
|
|
|
|
const columnWidths = [
|
|
|
|
|
|
{ wch: 12 }, // رقم العملية
|
|
|
|
|
|
{ wch: 12 }, // التاريخ
|
|
|
|
|
|
{ wch: 12 }, // نوع العملية
|
|
|
|
|
|
{ wch: 30 }, // الوصف
|
|
|
|
|
|
{ wch: 20 }, // من
|
|
|
|
|
|
{ wch: 20 }, // إلى
|
|
|
|
|
|
{ wch: 15 }, // المبلغ
|
|
|
|
|
|
{ wch: 15 }, // العمولة
|
|
|
|
|
|
{ wch: 12 }, // الحالة
|
|
|
|
|
|
];
|
|
|
|
|
|
worksheet['!cols'] = columnWidths;
|
|
|
|
|
|
|
|
|
|
|
|
const range = XLSX.utils.decode_range(worksheet['!ref']);
|
|
|
|
|
|
for (let C = range.s.c; C <= range.e.c; ++C) {
|
|
|
|
|
|
const address = XLSX.utils.encode_col(C) + '1';
|
|
|
|
|
|
if (!worksheet[address]) continue;
|
|
|
|
|
|
worksheet[address].s = {
|
|
|
|
|
|
font: { bold: true, sz: 12 },
|
|
|
|
|
|
fill: { fgColor: { rgb: "F59E0B" } },
|
|
|
|
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
2026-03-27 00:34:59 +03:00
|
|
|
|
|
|
|
|
|
|
const workbook = XLSX.utils.book_new();
|
|
|
|
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, 'دفتر الحسابات');
|
|
|
|
|
|
|
|
|
|
|
|
const fileName = `دفتر_الحسابات_${new Date().toISOString().split('T')[0]}.xlsx`;
|
|
|
|
|
|
|
|
|
|
|
|
XLSX.writeFile(workbook, fileName);
|
|
|
|
|
|
|
|
|
|
|
|
toast.success(`تم تصدير ${filteredTransactions.length} معاملة بنجاح!`, { id: 'export' });
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error exporting to Excel:', error);
|
|
|
|
|
|
toast.error('حدث خطأ أثناء تصدير البيانات', { id: 'export' });
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsExporting(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const printReport = () => {
|
|
|
|
|
|
const printWindow = window.open('', '_blank');
|
|
|
|
|
|
printWindow.document.write(`
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html dir="rtl">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<title>تقرير دفتر الحسابات</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
body {
|
|
|
|
|
|
font-family: 'Cairo', Arial, sans-serif;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
direction: rtl;
|
|
|
|
|
|
}
|
|
|
|
|
|
.header {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: 30px;
|
|
|
|
|
|
padding-bottom: 20px;
|
|
|
|
|
|
border-bottom: 2px solid #f59e0b;
|
|
|
|
|
|
}
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
}
|
|
|
|
|
|
.subtitle {
|
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
|
margin-top: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.summary {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
margin-bottom: 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.summary-card {
|
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.summary-value {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #f59e0b;
|
|
|
|
|
|
}
|
|
|
|
|
|
table {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
th, td {
|
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
th {
|
|
|
|
|
|
background: #f59e0b;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
.footer {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-top: 30px;
|
|
|
|
|
|
padding-top: 20px;
|
|
|
|
|
|
border-top: 1px solid #e5e7eb;
|
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
@media print {
|
|
|
|
|
|
.no-print {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="header">
|
|
|
|
|
|
<div class="title">تقرير دفتر الحسابات</div>
|
|
|
|
|
|
<div class="subtitle">الفترة: ${dateRange.start || 'بداية السجلات'} - ${dateRange.end || 'حتى الآن'}</div>
|
|
|
|
|
|
<div class="subtitle">تاريخ التقرير: ${new Date().toLocaleDateString('ar-SA')}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="summary">
|
|
|
|
|
|
<div class="summary-card">
|
|
|
|
|
|
<div>إجمالي الإيرادات</div>
|
|
|
|
|
|
<div class="summary-value">${formatCurrency(summary.totalRevenue)}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="summary-card">
|
|
|
|
|
|
<div>أرباح المنصة</div>
|
|
|
|
|
|
<div class="summary-value">${formatCurrency(summary.commissionEarned)}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="summary-card">
|
|
|
|
|
|
<div>سلف الضمان</div>
|
|
|
|
|
|
<div class="summary-value">${formatCurrency(summary.securityDeposits)}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="summary-card">
|
|
|
|
|
|
<div>المدفوعات المعلقة</div>
|
|
|
|
|
|
<div class="summary-value">${formatCurrency(summary.pendingPayments)}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>التاريخ</th>
|
|
|
|
|
|
<th>الوصف</th>
|
|
|
|
|
|
<th>من</th>
|
|
|
|
|
|
<th>إلى</th>
|
|
|
|
|
|
<th>المبلغ</th>
|
|
|
|
|
|
<th>العمولة</th>
|
|
|
|
|
|
<th>الحالة</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
${filteredTransactions.map(t => `
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>${t.date}</td>
|
|
|
|
|
|
<td>${t.description}</td>
|
|
|
|
|
|
<td>${t.fromUser}</td>
|
|
|
|
|
|
<td>${t.toUser}</td>
|
|
|
|
|
|
<td>${formatCurrency(t.amount)}</td>
|
|
|
|
|
|
<td>${t.commission ? formatCurrency(t.commission) : '-'}</td>
|
|
|
|
|
|
<td>${t.status === 'completed' ? 'مكتمل' : t.status === 'pending' ? 'معلق' : 'بإنتظار الرد'}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
`).join('')}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="footer">
|
|
|
|
|
|
<p>تقرير صادر عن نظام SweetHome لإدارة العقارات</p>
|
|
|
|
|
|
<p>جميع الحقوق محفوظة © ${new Date().getFullYear()}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="no-print" style="text-align: center; margin-top: 20px;">
|
|
|
|
|
|
<button onclick="window.print()" style="padding: 10px 20px; background: #f59e0b; color: white; border: none; border-radius: 8px; cursor: pointer;">
|
|
|
|
|
|
طباعة التقرير
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|
|
|
|
|
|
`);
|
|
|
|
|
|
printWindow.document.close();
|
2026-02-15 01:53:37 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="space-y-6">
|
2026-03-27 00:34:59 +03:00
|
|
|
|
<Toaster position="top-center" reverseOrder={false} />
|
|
|
|
|
|
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<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">
|
2026-03-27 00:34:59 +03:00
|
|
|
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="بحث في المعاملات..."
|
|
|
|
|
|
value={searchTerm}
|
|
|
|
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
2026-03-27 00:34:59 +03:00
|
|
|
|
className="w-full pl-12 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
value={dateRange.start}
|
|
|
|
|
|
onChange={(e) => setDateRange({...dateRange, start: e.target.value})}
|
2026-03-27 00:34:59 +03:00
|
|
|
|
className="px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
/>
|
|
|
|
|
|
<span className="text-gray-500 self-center">إلى</span>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
value={dateRange.end}
|
|
|
|
|
|
onChange={(e) => setDateRange({...dateRange, end: e.target.value})}
|
2026-03-27 00:34:59 +03:00
|
|
|
|
className="px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-27 00:34:59 +03:00
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={exportToExcel}
|
|
|
|
|
|
disabled={isExporting || filteredTransactions.length === 0}
|
|
|
|
|
|
className="px-5 py-3 bg-green-600 text-white rounded-xl flex items-center gap-2 hover:bg-green-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
|
>
|
|
|
|
|
|
{isExporting ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
|
|
|
|
جاري التصدير...
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Download className="w-5 h-5" />
|
|
|
|
|
|
تصدير Excel
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={printReport}
|
|
|
|
|
|
disabled={filteredTransactions.length === 0}
|
|
|
|
|
|
className="px-5 py-3 bg-blue-600 text-white rounded-xl flex items-center gap-2 hover:bg-blue-700 transition-colors disabled:opacity-50"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Printer className="w-5 h-5" />
|
|
|
|
|
|
طباعة
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
2026-03-27 00:34:59 +03:00
|
|
|
|
|
|
|
|
|
|
{(dateRange.start || dateRange.end || searchTerm) && (
|
|
|
|
|
|
<div className="mt-4 pt-4 border-t flex justify-between items-center">
|
|
|
|
|
|
<div className="text-sm text-gray-500">
|
|
|
|
|
|
<span className="font-medium">{filteredTransactions.length}</span> معاملة من إجمالي <span className="font-medium">{transactions.length}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setDateRange({ start: '', end: '' });
|
|
|
|
|
|
setSearchTerm('');
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="text-sm text-red-500 hover:text-red-600 flex items-center gap-1"
|
|
|
|
|
|
>
|
|
|
|
|
|
<X className="w-4 h-4" />
|
|
|
|
|
|
إلغاء الفلترة
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</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>
|
2026-03-27 00:34:59 +03:00
|
|
|
|
<td className="px-6 py-4 text-sm font-bold text-green-600">
|
2026-02-15 01:53:37 +03:00
|
|
|
|
{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">
|
2026-03-27 00:34:59 +03:00
|
|
|
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
|
2026-02-15 01:53:37 +03:00
|
|
|
|
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">
|
2026-03-27 00:34:59 +03:00
|
|
|
|
<p className="text-gray-500 text-sm">لا توجد أرصدة حالياً</p>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|