Files
SweetHome/app/components/admin/BookingRequests.js

1475 lines
62 KiB
JavaScript
Raw Normal View History

2026-04-16 21:15:21 +03:00
'use client';
2026-03-27 00:34:59 +03:00
import { useState, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
CheckCircle,
XCircle,
Clock,
User,
Home,
Calendar,
DollarSign,
AlertCircle,
Key,
DoorOpen,
Shield,
Phone,
Mail,
MessageCircle,
ChevronDown,
ChevronUp,
FileText,
Download,
Printer,
2026-03-27 00:34:59 +03:00
History,
2026-04-15 12:28:01 +03:00
Loader2,
CreditCard
} from 'lucide-react';
2026-03-27 00:34:59 +03:00
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
import toast, { Toaster } from 'react-hot-toast';
2026-04-16 21:15:21 +03:00
import AuthService from '../../services/AuthService';
import { adminConfirmDeposit } from '../../utils/api';
const ReasonDialog = ({ isOpen, onClose, onConfirm, title, defaultReason = '' }) => {
const [reason, setReason] = useState(defaultReason);
const [otherReason, setOtherReason] = useState('');
const commonReasons = [
2026-04-16 21:15:21 +03:00
'ط£ط¹ظ…ط§ظ„ طµظٹط§ظ†ط© ظپظٹ ط§ظ„ط¹ظ‚ط§ط±',
'ط§ظ„ط¹ظ‚ط§ط± ط؛ظٹط± ظ…طھط§ط­ ظپظٹ ظ‡ط°ظ‡ ط§ظ„طھظˆط§ط±ظٹط®',
'ظ…ط´ظƒظ„ط© ظپظٹ ظˆط«ط§ط¦ظ‚ ط§ظ„ظ…ط³طھط£ط¬ط±',
'ط§ظ„ظ…ط§ظ„ظƒ ط؛ظٹط± ظ…طھط§ط­ ظ„ظ„طھط³ظ„ظٹظ…',
'طھط£ط®ط± ظپظٹ ط¯ظپط¹ ط§ظ„ط¶ظ…ط§ظ†',
'ط³ط¨ط¨ ط¢ط®ط±'
];
if (!isOpen) return null;
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50"
onClick={onClose}
>
<motion.div
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }}
className="bg-white rounded-2xl w-full max-w-md p-6 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="text-center mb-4">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-3">
<AlertCircle className="w-8 h-8 text-red-600" />
</div>
<h3 className="text-xl font-bold text-gray-900">{title}</h3>
2026-04-16 21:15:21 +03:00
<p className="text-sm text-gray-500 mt-1">ظٹط±ط¬ظ طھط­ط¯ظٹط¯ ط³ط¨ط¨ ط§ظط±ظپط</p>
</div>
<div className="space-y-3">
{commonReasons.map((r) => (
<button
key={r}
onClick={() => {
2026-04-16 21:15:21 +03:00
if (r === 'ط³ط¨ط¨ ط¢ط®ط±') {
} else {
onConfirm(r);
}
}}
className="w-full text-right p-3 border rounded-xl hover:bg-gray-50 transition-colors"
>
{r}
</button>
))}
<textarea
2026-04-16 21:15:21 +03:00
placeholder="ط§ظƒطھط¨ ط³ط¨ط¨ط§ظ‹ ط¢ط®ط±..."
value={otherReason}
onChange={(e) => setOtherReason(e.target.value)}
className="w-full p-3 border rounded-xl resize-none focus:ring-2 focus:ring-red-500 focus:border-transparent"
rows="3"
/>
<div className="flex gap-3 pt-3">
<button
onClick={() => onConfirm(otherReason)}
className="flex-1 bg-red-600 text-white py-3 rounded-xl font-medium hover:bg-red-700 transition-colors"
disabled={!otherReason.trim()}
>
2026-04-16 21:15:21 +03:00
طھط£ظƒظٹط¯ ط§ظط±ظپط
</button>
<button
onClick={onClose}
className="flex-1 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors"
>
2026-04-16 21:15:21 +03:00
ط¥ظط؛ط§ط،
</button>
</div>
</div>
</motion.div>
</motion.div>
);
};
2026-03-27 00:34:59 +03:00
const PDFExportButton = ({ request, onExportComplete }) => {
const [isExporting, setIsExporting] = useState(false);
const formatCurrency = (amount) => {
2026-04-16 21:15:21 +03:00
return amount?.toLocaleString() + ' ظ„.ط³';
2026-03-27 00:34:59 +03:00
};
const generatePDF = async () => {
if (!request) {
2026-04-16 21:15:21 +03:00
toast.error('ظ„ط§ طھظˆط¬ط¯ ط¨ظٹط§ظ†ط§طھ ظ„ظ„طھطµط¯ظٹط±');
2026-03-27 00:34:59 +03:00
return;
}
setIsExporting(true);
2026-04-16 21:15:21 +03:00
const loadingToast = toast.loading('ط¬ط§ط±ظٹ ط¥ظ†ط´ط§ط، ظ…ظ„ظپ PDF...', { id: 'pdf-export' });
2026-03-27 00:34:59 +03:00
try {
const printContent = document.createElement('div');
printContent.style.width = '800px';
printContent.style.padding = '40px';
printContent.style.backgroundColor = 'white';
printContent.style.fontFamily = 'Cairo, Arial, sans-serif';
printContent.style.direction = 'rtl';
printContent.style.position = 'absolute';
printContent.style.left = '-9999px';
printContent.style.top = '-9999px';
printContent.style.zIndex = '-9999';
printContent.innerHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Cairo', 'Arial', sans-serif;
}
body {
background: white;
padding: 20px;
}
.pdf-container {
max-width: 800px;
margin: 0 auto;
background: white;
}
.pdf-header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 3px solid #f59e0b;
}
.pdf-logo {
width: 60px;
height: 60px;
background: linear-gradient(135deg, #f59e0b, #d97706);
border-radius: 15px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 15px;
}
.pdf-logo svg {
width: 32px;
height: 32px;
}
.pdf-title {
font-size: 24px;
font-weight: bold;
color: #1f2937;
margin-bottom: 5px;
}
.pdf-subtitle {
font-size: 12px;
color: #6b7280;
}
.pdf-section {
margin-bottom: 25px;
background: #f9fafb;
border-radius: 16px;
padding: 20px;
break-inside: avoid;
}
.pdf-section-title {
font-size: 18px;
font-weight: bold;
color: #f59e0b;
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 2px solid #e5e7eb;
display: flex;
align-items: center;
gap: 8px;
}
.pdf-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.pdf-info-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #f3f4f6;
}
.pdf-label {
font-weight: 600;
color: #4b5563;
font-size: 14px;
}
.pdf-value {
color: #1f2937;
font-weight: 500;
font-size: 14px;
}
.pdf-status {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.pdf-status-pending { background: #fef3c7; color: #d97706; }
.pdf-status-owner_approved { background: #dbeafe; color: #2563eb; }
.pdf-status-admin_approved { background: #d1fae5; color: #059669; }
.pdf-status-active { background: #e0e7ff; color: #4f46e5; }
.pdf-status-completed { background: #e5e7eb; color: #4b5563; }
.pdf-status-rejected { background: #fee2e2; color: #dc2626; }
.pdf-amount {
font-size: 18px;
font-weight: bold;
color: #f59e0b;
}
.pdf-footer {
margin-top: 30px;
text-align: center;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
font-size: 11px;
color: #9ca3af;
}
.pdf-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
background: #f3f4f6;
color: #374151;
}
@media print {
body {
padding: 0;
margin: 0;
}
.pdf-section {
break-inside: avoid;
}
}
</style>
</head>
<body>
<div class="pdf-container">
<div class="pdf-header">
<div class="pdf-logo">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
<path d="M3 9L12 3L21 9L12 15L3 9Z" stroke="white" fill="none"/>
<path d="M12 15V21" stroke="white"/>
<path d="M8 12L4 14" stroke="white"/>
<path d="M16 12L20 14" stroke="white"/>
</svg>
</div>
2026-04-16 21:15:21 +03:00
<div class="pdf-title">طھظط±ظٹط± ط·ظط¨ ط­ط¬ط² #${request.id}</div>
<div class="pdf-subtitle">طھط§ط±ظٹط® ط§ظطھظط±ظٹط±: ${new Date().toLocaleDateString('ar-SA')} | ${new Date().toLocaleTimeString('ar-SA')}</div>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-section">
<div class="pdf-section-title">
2026-04-16 21:15:21 +03:00
<span></span> ظط¹ظظˆظط§طھ ط§ظظط³طھط£ط¬ط±
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-grid">
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط§ظط§ط³ظ ط§ظظƒط§ظظ:</span>
<span class="pdf-value">${request.user || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ظظˆط¹ ط§ظظظˆظٹط©:</span>
<span class="pdf-value">${request.userType === 'syrian' ? 'ظ‡ظˆظٹط© ط³ظˆط±ظٹط©' : 'ط¬ظˆط§ط² ط³ظپط±'}</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط±ظظ ط§ظظظˆظٹط©:</span>
<span class="pdf-value">${request.identityNumber || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط§ظط¨ط±ظٹط¯ ط§ظط¥ظظƒطھط±ظˆظظٹ:</span>
<span class="pdf-value">${request.userEmail || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط±ظظ ط§ظظط§طھظپ:</span>
<span class="pdf-value">${request.userPhone || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
2026-03-27 00:34:59 +03:00
</div>
</div>
</div>
<div class="pdf-section">
<div class="pdf-section-title">
2026-04-16 21:15:21 +03:00
<span></span> ظط¹ظظˆظط§طھ ط§ظط¹ظط§ط±
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-grid">
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط§ظط¹ظط§ط±:</span>
<span class="pdf-value">${request.property || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط§ظط³ط¹ط± ط§ظظٹظˆظظٹ:</span>
2026-03-27 00:34:59 +03:00
<span class="pdf-value">${formatCurrency(request.dailyPrice)}</span>
</div>
</div>
</div>
<div class="pdf-section">
<div class="pdf-section-title">
2026-04-16 21:15:21 +03:00
<span></span> طھظپط§طµظٹظ ط§ظط­ط¬ط²
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-grid">
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">طھط§ط±ظٹط® ط§ظط¨ط¯ط§ظٹط©:</span>
<span class="pdf-value">${request.startDate || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">طھط§ط±ظٹط® ط§ظظظط§ظٹط©:</span>
<span class="pdf-value">${request.endDate || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط¹ط¯ط¯ ط§ظط£ظٹط§ظ:</span>
<span class="pdf-value">${request.days || 0} ظٹظˆظ</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط§ظط­ط§ظط©:</span>
2026-03-27 00:34:59 +03:00
<span class="pdf-value">
<span class="pdf-status pdf-status-${request.status}">
2026-04-16 21:15:21 +03:00
${request.status === 'pending' ? 'ظ‚ظٹط¯ ط§ظ„ط§ظ†طھط¸ط§ط±' :
request.status === 'owner_approved' ? 'ظ…ظˆط§ظپظ‚ط© ط§ظ„ظ…ط§ظ„ظƒ' :
request.status === 'admin_approved' ? 'ظ…ظˆط§ظپظ‚ط© ط§ظ„ط¥ط¯ط§ط±ط©' :
request.status === 'active' ? 'ط¥ظٹط¬ط§ط± ظ†ط´ط·' :
request.status === 'completed' ? 'ظ…ظ†طھظ‡ظٹ' : 'ظ…ط±ظپظˆط¶'}
2026-03-27 00:34:59 +03:00
</span>
</span>
</div>
</div>
</div>
<div class="pdf-section">
<div class="pdf-section-title">
2026-04-16 21:15:21 +03:00
<span></span> ط§ظظط¹ظظˆظط§طھ ط§ظظط§ظظٹط©
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-grid">
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط³ظظپط© ط§ظطظط§ظ:</span>
2026-03-27 00:34:59 +03:00
<span class="pdf-value">${formatCurrency(request.securityDeposit)}</span>
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ط§ظظط¨ظط؛ ط§ظط¥ط¬ظط§ظظٹ:</span>
2026-03-27 00:34:59 +03:00
<span class="pdf-value pdf-amount">${formatCurrency(request.totalAmount)}</span>
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ظط³ط¨ط© ط§ظط¹ظظˆظط©:</span>
2026-03-27 00:34:59 +03:00
<span class="pdf-value">${request.commissionRate || 0}%</span>
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ظظˆط¹ ط§ظط¹ظظˆظط©:</span>
<span class="pdf-value">${request.commissionType || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item">
2026-04-16 21:15:21 +03:00
<span class="pdf-label">ظظٹظط© ط§ظط¹ظظˆظط©:</span>
2026-03-27 00:34:59 +03:00
<span class="pdf-value">${formatCurrency(request.commissionAmount)}</span>
</div>
</div>
</div>
${request.notes ? `
<div class="pdf-section">
<div class="pdf-section-title">
2026-04-16 21:15:21 +03:00
<span></span> ظظط§ط­ط¸ط§طھ
2026-03-27 00:34:59 +03:00
</div>
<div class="pdf-info-item" style="display: block;">
<span class="pdf-value">${request.notes}</span>
</div>
</div>
` : ''}
<div class="pdf-section">
<div class="pdf-section-title">
2026-04-16 21:15:21 +03:00
<span></span> ط³ط¬ظ ط§ظط¥ط¬ط±ط§ط،ط§طھ
2026-03-27 00:34:59 +03:00
</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<div style="display: flex; align-items: center; gap: 10px;">
<span class="pdf-badge"> ${request.requestDate}</span>
2026-04-16 21:15:21 +03:00
<span class="pdf-value">طھظ ط¥ظط´ط§ط، ط§ظط·ظط¨</span>
2026-03-27 00:34:59 +03:00
</div>
${request.ownerApproved ? `
<div style="display: flex; align-items: center; gap: 10px;">
2026-04-16 21:15:21 +03:00
<span class="pdf-badge">âœ</span>
<span class="pdf-value">طھظطھ ظظˆط§ظپظط© ط§ظظط§ظظƒ</span>
2026-03-27 00:34:59 +03:00
</div>
` : ''}
${request.adminApproved ? `
<div style="display: flex; align-items: center; gap: 10px;">
2026-04-16 21:15:21 +03:00
<span class="pdf-badge">âœ</span>
<span class="pdf-value">طھظطھ ظظˆط§ظپظط© ط§ظط¥ط¯ط§ط±ط©</span>
2026-03-27 00:34:59 +03:00
</div>
` : ''}
${request.ownerDelivered ? `
<div style="display: flex; align-items: center; gap: 10px;">
<span class="pdf-badge"></span>
2026-04-16 21:15:21 +03:00
<span class="pdf-value">طھظ طھط³ظظٹظ ط§ظظظپطھط§ط­</span>
2026-03-27 00:34:59 +03:00
</div>
` : ''}
${request.tenantReceived ? `
<div style="display: flex; align-items: center; gap: 10px;">
<span class="pdf-badge"></span>
2026-04-16 21:15:21 +03:00
<span class="pdf-value">طھظ ط§ط³طھظط§ظ ط§ظط¹ظط§ط±</span>
2026-03-27 00:34:59 +03:00
</div>
` : ''}
</div>
</div>
<div class="pdf-footer">
2026-04-16 21:15:21 +03:00
<div>طھظط±ظٹط± طµط§ط¯ط± ط¹ظ ظط¸ط§ظ SweetHome ظط¥ط¯ط§ط±ط© ط§ظط¹ظط§ط±ط§طھ</div>
<div style="margin-top: 5px;">ط¬ظظٹط¹ ط§ظط­ظظˆظ ظط­ظپظˆط¸ط© آ© ${new Date().getFullYear()} SweetHome</div>
<div style="margin-top: 5px; font-size: 9px;">ظط°ط§ ط§ظطھظط±ظٹط± طھظ ط¥ظط´ط§ط¤ظ طھظظط§ط¦ظٹط§ظ ظˆظط§ ظٹط­طھط§ط¬ ط¥ظظ طھظˆظظٹط¹</div>
2026-03-27 00:34:59 +03:00
</div>
</div>
</body>
</html>
`;
document.body.appendChild(printContent);
await new Promise(resolve => setTimeout(resolve, 200));
const canvas = await html2canvas(printContent, {
scale: 2.5,
backgroundColor: '#ffffff',
logging: false,
useCORS: true,
windowWidth: printContent.scrollWidth,
windowHeight: printContent.scrollHeight,
onclone: (clonedDoc, element) => {
}
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4',
compress: true
});
const imgWidth = 210;
const pageHeight = 297;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
let pageCount = 1;
while (heightLeft > 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
pageCount++;
}
2026-04-16 21:15:21 +03:00
const fileName = `طھظط±ظٹط±_ط·ظ„ط¨_${request.id}_${new Date().toISOString().split('T')[0]}.pdf`;
2026-03-27 00:34:59 +03:00
pdf.save(fileName);
document.body.removeChild(printContent);
2026-04-16 21:15:21 +03:00
toast.success(`طھظ… طھطµط¯ظٹط± ط§ظ„طھظ‚ط±ظٹط± ط¨ظ†ط¬ط§ط­! (${pageCount} طµظپط­ط©)`, { id: 'pdf-export' });
2026-03-27 00:34:59 +03:00
if (onExportComplete) {
onExportComplete();
}
} catch (error) {
console.error('Error generating PDF:', error);
2026-04-16 21:15:21 +03:00
toast.error('ط­ط¯ط« ط®ط·ط£ ط£ط«ظ†ط§ط، ط¥ظ†ط´ط§ط، ظ…ظ„ظپ PDF', { id: 'pdf-export' });
2026-03-27 00:34:59 +03:00
} finally {
setIsExporting(false);
}
};
return (
<button
onClick={generatePDF}
disabled={isExporting}
className="flex-1 bg-green-600 text-white py-3 rounded-xl font-medium hover:bg-green-700 transition-all duration-200 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm hover:shadow-md"
>
{isExporting ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
2026-04-16 21:15:21 +03:00
ط¬ط§ط±ظٹ ط§ظطھطµط¯ظٹط±...
2026-03-27 00:34:59 +03:00
</>
) : (
<>
<Download className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
طھطµط¯ظٹط± PDF
2026-03-27 00:34:59 +03:00
</>
)}
</button>
);
};
const RequestDetailsDialog = ({ request, isOpen, onClose }) => {
if (!isOpen || !request) return null;
const formatCurrency = (amount) => {
2026-04-16 21:15:21 +03:00
return amount?.toLocaleString() + ' ظ„.ط³';
};
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50"
onClick={onClose}
>
<motion.div
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }}
className="bg-white rounded-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
2026-03-27 00:34:59 +03:00
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-5 text-white flex justify-between items-center">
<div>
<h2 className="text-xl font-bold flex items-center gap-2">
<FileText className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
طھظپط§طµظٹظ ط§ظط·ظط¨ #{request.id}
2026-03-27 00:34:59 +03:00
</h2>
2026-04-16 21:15:21 +03:00
<p className="text-amber-100 text-sm mt-1">ط¬ظظٹط¹ ظط¹ظظˆظط§طھ ط§ظط·ظط¨ ظپظٹ ظظƒط§ظ ظˆط§ط­ط¯</p>
2026-03-27 00:34:59 +03:00
</div>
<button
onClick={onClose}
2026-03-27 00:34:59 +03:00
className="p-2 hover:bg-white/20 rounded-full transition-colors"
>
2026-03-27 00:34:59 +03:00
<XCircle className="w-6 h-6" />
</button>
</div>
<div className="p-6 space-y-6">
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2 text-blue-800">
<User className="w-4 h-4" />
2026-04-16 21:15:21 +03:00
ظط¹ظظˆظط§طھ ط§ظظط³طھط£ط¬ط±
</h3>
2026-03-27 00:34:59 +03:00
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط§ظط§ط³ظ ط§ظظƒط§ظظ</label>
<div className="font-medium">{request.user}</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ظظˆط¹ ط§ظظظˆظٹط©</label>
<div className="font-medium">
2026-04-16 21:15:21 +03:00
{request.userType === 'syrian' ? 'ًں‡¸ًں‡¾ ظ‡ظˆظٹط© ط³ظˆط±ظٹط©' : 'ط¬ظˆط§ط² ط³ظپط±'}
<span className="text-xs text-gray-500 mr-2">{request.identityNumber}</span>
</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط§ظط¨ط±ظٹط¯ ط§ظط¥ظظƒطھط±ظˆظظٹ</label>
<div className="font-medium flex items-center gap-1">
<Mail className="w-3 h-3 text-gray-400" />
{request.userEmail}
</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط±ظظ ط§ظظط§طھظپ</label>
<div className="font-medium flex items-center gap-1">
<Phone className="w-3 h-3 text-gray-400" />
{request.userPhone}
</div>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-green-50 to-emerald-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2 text-green-800">
<Home className="w-4 h-4" />
2026-04-16 21:15:21 +03:00
ظط¹ظظˆظط§طھ ط§ظط¹ظط§ط±
</h3>
2026-03-27 00:34:59 +03:00
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط§ظط¹ظط§ط±</label>
<div className="font-medium">{request.property}</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط§ظط³ط¹ط± ط§ظظٹظˆظظٹ</label>
<div className="font-medium text-green-600">{formatCurrency(request.dailyPrice)}</div>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-amber-50 to-orange-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2 text-amber-800">
<Calendar className="w-4 h-4" />
2026-04-16 21:15:21 +03:00
طھظپط§طµظٹظ ط§ظط­ط¬ط²
</h3>
2026-03-27 00:34:59 +03:00
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">طھط§ط±ظٹط® ط§ظط¨ط¯ط§ظٹط©</label>
<div className="font-medium">{request.startDate}</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">طھط§ط±ظٹط® ط§ظظظط§ظٹط©</label>
<div className="font-medium">{request.endDate}</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط¹ط¯ط¯ ط§ظط£ظٹط§ظ</label>
<div className="font-medium">{request.days} ظٹظˆظ</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط§ظظط¨ظط؛ ط§ظط¥ط¬ظط§ظظٹ</label>
<div className="font-medium text-green-600">{formatCurrency(request.totalAmount)}</div>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-purple-50 to-pink-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2 text-purple-800">
<DollarSign className="w-4 h-4" />
2026-04-16 21:15:21 +03:00
ط§ظظط¹ظظˆظط§طھ ط§ظظط§ظظٹط©
</h3>
2026-03-27 00:34:59 +03:00
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط³ظظپط© ط§ظطظط§ظ</label>
<div className="font-medium text-blue-600">{formatCurrency(request.securityDeposit)}</div>
</div>
2026-04-15 12:28:01 +03:00
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ط­ط§ظط© ط§ظط±ط¹ط¨ظˆظ</label>
2026-04-15 12:28:01 +03:00
<div className={`font-medium px-3 py-1 rounded-full text-sm inline-block ${
request.securityDepositPaid
? 'bg-green-100 text-green-800'
: 'bg-yellow-100 text-yellow-800'
}`}>
2026-04-16 21:15:21 +03:00
{request.securityDepositPaid ? '✓ طھظ… ط§ظ„ط¯ظپط¹' : 'âڈ³ ظپظٹ ط§ظ†طھط¸ط§ط± ط§ظ„ط¯ظپط¹'}
2026-04-15 12:28:01 +03:00
</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ظط³ط¨ط© ط§ظط¹ظظˆظط©</label>
<div className="font-medium">{request.commissionRate}%</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ظظˆط¹ ط§ظط¹ظظˆظط©</label>
<div className="font-medium text-amber-600">{request.commissionType}</div>
</div>
<div>
2026-04-16 21:15:21 +03:00
<label className="text-xs text-gray-500">ظظٹظط© ط§ظط¹ظظˆظط©</label>
<div className="font-medium text-amber-600">{formatCurrency(request.commissionAmount)}</div>
</div>
</div>
</div>
2026-03-27 00:34:59 +03:00
<div className="bg-gray-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2">
<History className="w-4 h-4" />
2026-04-16 21:15:21 +03:00
ط³ط¬ظ ط§ظط¥ط¬ط±ط§ط،ط§طھ
</h3>
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
2026-04-16 21:15:21 +03:00
<span className="text-gray-600">طھظ ط¥ظط´ط§ط، ط§ظط·ظط¨: {request.requestDate}</span>
</div>
{request.ownerApproved && (
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
2026-04-16 21:15:21 +03:00
<span className="text-gray-600">ظظˆط§ظپظط© ط§ظظط§ظظƒ</span>
</div>
)}
{request.adminApproved && (
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
2026-04-16 21:15:21 +03:00
<span className="text-gray-600">ظظˆط§ظپظط© ط§ظط¥ط¯ط§ط±ط©</span>
</div>
)}
{request.ownerDelivered && (
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-purple-500 rounded-full"></div>
2026-04-16 21:15:21 +03:00
<span className="text-gray-600">طھظ طھط³ظظٹظ ط§ظظظپطھط§ط­ ظظ ط§ظظط§ظظƒ</span>
</div>
)}
{request.notes && (
2026-03-27 00:34:59 +03:00
<div className="mt-3 p-3 bg-white rounded-lg border">
<p className="text-sm text-gray-600">{request.notes}</p>
</div>
)}
</div>
</div>
</div>
2026-03-27 00:34:59 +03:00
<div className="sticky bottom-0 bg-white border-t p-4 flex gap-3 shadow-lg">
<button
onClick={() => window.print()}
className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-medium hover:bg-blue-700 transition-colors flex items-center justify-center gap-2"
>
<Printer className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
ط·ط¨ط§ط¹ط© ط§ظطھظپط§طµظٹظ
</button>
2026-03-27 00:34:59 +03:00
<PDFExportButton request={request} />
</div>
</motion.div>
</motion.div>
);
};
2026-04-16 21:15:21 +03:00
const RequestCard = ({ request, onAction, onViewDetails, confirmingDepositId }) => {
const [expanded, setExpanded] = useState(false);
2026-04-16 21:15:21 +03:00
const isConfirmingDeposit = confirmingDepositId === request.id;
const formatCurrency = (amount) => {
2026-04-16 21:15:21 +03:00
return amount?.toLocaleString() + ' ظ„.ط³';
};
const getStatusColor = (status) => {
switch(status) {
case 'pending': return 'border-yellow-400 bg-yellow-50';
case 'owner_approved': return 'border-blue-400 bg-blue-50';
case 'admin_approved': return 'border-green-400 bg-green-50';
case 'active': return 'border-purple-400 bg-purple-50';
case 'completed': return 'border-gray-400 bg-gray-50';
case 'rejected': return 'border-red-400 bg-red-50';
default: return 'border-gray-200 bg-white';
}
};
const getStatusBadge = (status) => {
const styles = {
pending: 'bg-yellow-500 text-white',
owner_approved: 'bg-blue-500 text-white',
admin_approved: 'bg-green-500 text-white',
active: 'bg-purple-500 text-white',
completed: 'bg-gray-500 text-white',
rejected: 'bg-red-500 text-white'
};
const labels = {
2026-04-16 21:15:21 +03:00
pending: 'ط¨ط§ظ†طھط¸ط§ط± ط§ظ„ظ…ظˆط§ظپظ‚ط©',
owner_approved: 'ظ…ظˆط§ظپظ‚ط© ط§ظ„ظ…ط§ظ„ظƒ',
admin_approved: 'ظ…ظˆط§ظپظ‚ط© ط§ظ„ط¥ط¯ط§ط±ط©',
active: 'ط¥ظٹط¬ط§ط± ظ†ط´ط·',
completed: 'ظ…ظ†طھظ‡ظٹ',
rejected: 'ظ…ط±ظپظˆط¶'
};
return (
<span className={`px-3 py-1 rounded-full text-xs font-medium shadow-sm ${styles[status]}`}>
{labels[status]}
</span>
);
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className={`bg-white border-2 rounded-2xl overflow-hidden transition-all hover:shadow-xl ${getStatusColor(request.status)}`}
>
<div className="p-4 cursor-pointer" onClick={() => setExpanded(!expanded)}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
2026-04-16 21:15:21 +03:00
<span className="text-lg font-bold text-gray-900">ط·ظط¨ #{request.id}</span>
{getStatusBadge(request.status)}
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-gray-500">{request.requestDate}</span>
{expanded ? (
<ChevronUp className="w-5 h-5 text-gray-400" />
) : (
<ChevronDown className="w-5 h-5 text-gray-400" />
)}
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mt-3">
<div className="flex items-center gap-2 text-sm">
<User className="w-4 h-4 text-gray-400" />
<span className="truncate">{request.user}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<Home className="w-4 h-4 text-gray-400" />
<span className="truncate">{request.property}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<Calendar className="w-4 h-4 text-gray-400" />
2026-04-16 21:15:21 +03:00
<span>{request.days} ط£ظٹط§ظ</span>
</div>
<div className="flex items-center gap-2 text-sm">
<DollarSign className="w-4 h-4 text-gray-400" />
<span className="font-bold text-green-600">{formatCurrency(request.totalAmount)}</span>
</div>
</div>
</div>
<AnimatePresence>
{expanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="border-t bg-white p-4"
>
2026-04-15 12:28:01 +03:00
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<div className="bg-blue-50 p-2 rounded-lg">
2026-04-16 21:15:21 +03:00
<div className="text-xs text-gray-500">ط³ظظپط© طظط§ظ</div>
<div className="font-bold text-blue-600">{formatCurrency(request.securityDeposit)}</div>
</div>
2026-04-15 12:28:01 +03:00
<div className={`p-2 rounded-lg ${request.securityDepositPaid ? 'bg-green-50' : 'bg-yellow-50'}`}>
2026-04-16 21:15:21 +03:00
<div className="text-xs text-gray-500">ط­ط§ظط© ط§ظط±ط¹ط¨ظˆظ</div>
2026-04-15 12:28:01 +03:00
<div className={`font-bold text-xs ${request.securityDepositPaid ? 'text-green-600' : 'text-yellow-600'}`}>
2026-04-16 21:15:21 +03:00
{request.securityDepositPaid ? 'طھظ… ط§ظ„ط¯ظپط¹' : ' ط¨ط§ظ†طھط¸ط§ط±'}
2026-04-15 12:28:01 +03:00
</div>
</div>
<div className="bg-amber-50 p-2 rounded-lg">
2026-04-16 21:15:21 +03:00
<div className="text-xs text-gray-500">ط§ظط¹ظظˆظط©</div>
<div className="font-bold text-amber-600">{request.commissionRate}% ({request.commissionType})</div>
</div>
<div className="bg-purple-50 p-2 rounded-lg">
2026-04-16 21:15:21 +03:00
<div className="text-xs text-gray-500">ظط¯ط© ط§ظط¥ظٹط¬ط§ط±</div>
<div className="font-bold text-purple-600 text-xs">{request.startDate} ط¥ظظ {request.endDate}</div>
</div>
</div>
{(request.ownerApproved || request.adminApproved) && (
<div className="bg-green-50 p-3 rounded-lg mb-4">
<h4 className="font-bold text-sm mb-2 flex items-center gap-2 text-green-800">
<Phone className="w-4 h-4" />
2026-04-16 21:15:21 +03:00
ظط¹ظظˆظط§طھ ط§ظط§طھطµط§ظ
</h4>
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="flex items-center gap-1">
<Mail className="w-3 h-3 text-gray-500" />
{request.userEmail}
</div>
<div className="flex items-center gap-1">
<Phone className="w-3 h-3 text-gray-500" />
{request.userPhone}
</div>
</div>
</div>
)}
<div className="space-y-3">
{request.status === 'pending' && (
<div className="flex gap-3">
<button
onClick={() => onAction('owner_approve', request.id)}
className="flex-1 bg-green-600 text-white py-3 rounded-xl font-medium hover:bg-green-700 transition-all transform hover:scale-105 flex items-center justify-center gap-2"
>
<CheckCircle className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
ظظˆط§ظپظط© ط§ظظط§ظظƒ
</button>
<button
onClick={() => onAction('owner_reject', request.id)}
className="flex-1 bg-red-600 text-white py-3 rounded-xl font-medium hover:bg-red-700 transition-all transform hover:scale-105 flex items-center justify-center gap-2"
>
<XCircle className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
ط±ظپط
</button>
<button
onClick={() => onAction('view_details', request)}
className="px-4 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors"
>
<FileText className="w-5 h-5" />
</button>
</div>
)}
{request.status === 'owner_approved' && (
<div className="flex gap-3">
<button
onClick={() => onAction('admin_approve', request.id)}
className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-medium hover:bg-blue-700 transition-all transform hover:scale-105 flex items-center justify-center gap-2"
>
<CheckCircle className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
ظظˆط§ظپظط© ط§ظط¥ط¯ط§ط±ط©
</button>
<button
onClick={() => onAction('admin_reject', request.id)}
className="flex-1 bg-red-600 text-white py-3 rounded-xl font-medium hover:bg-red-700 transition-all transform hover:scale-105 flex items-center justify-center gap-2"
>
<XCircle className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
ط±ظپط ط¥ط¯ط§ط±ظٹ
</button>
<button
onClick={() => onAction('view_details', request)}
className="px-4 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors"
>
<FileText className="w-5 h-5" />
</button>
</div>
)}
{request.status === 'admin_approved' && (
<div className="space-y-3">
2026-04-15 12:28:01 +03:00
<div className="grid grid-cols-3 gap-3">
<button
2026-04-16 21:15:21 +03:00
onClick={() => onAction('confirm_deposit', request)}
disabled={request.securityDepositPaid || isConfirmingDeposit}
2026-04-15 12:28:01 +03:00
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.securityDepositPaid
? 'bg-purple-100 text-purple-800 cursor-default'
2026-04-16 21:15:21 +03:00
: isConfirmingDeposit
? 'bg-purple-300 text-white cursor-wait'
2026-04-15 12:28:01 +03:00
: 'bg-purple-600 text-white hover:bg-purple-700 transform hover:scale-105'
}`}
>
2026-04-16 21:15:21 +03:00
{isConfirmingDeposit ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<CreditCard className="w-5 h-5" />
)}
<span className="hidden md:inline">{request.securityDepositPaid ? 'طھظ… ط¯ظپط¹ ط§ظ„ط±ط¹ط¨ظˆظ†' : 'طھط£ظƒظٹط¯ ط§ظ„ط±ط¹ط¨ظˆظ†'}</span>
<span className="md:hidden">{request.securityDepositPaid ? '✓' : 'ط±ط¹ط¨ظˆظ†'}</span>
2026-04-15 12:28:01 +03:00
</button>
<button
onClick={() => onAction('deliver_key', { id: request.id, type: 'owner' })}
disabled={request.ownerDelivered}
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.ownerDelivered
? 'bg-green-100 text-green-800 cursor-default'
: 'bg-blue-600 text-white hover:bg-blue-700 transform hover:scale-105'
}`}
>
<Key className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
<span className="hidden md:inline">{request.ownerDelivered ? 'طھظ… طھط³ظ„ظٹظ… ط§ظ„ظ…ظپطھط§ط­' : 'طھط³ظ„ظٹظ… ط§ظ„ظ…ظپطھط§ط­'}</span>
<span className="md:hidden">{request.ownerDelivered ? '✓' : 'ظ…ظپطھط§ط­'}</span>
</button>
<button
onClick={() => onAction('receive_property', { id: request.id, type: 'tenant' })}
disabled={request.tenantReceived}
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.tenantReceived
? 'bg-green-100 text-green-800 cursor-default'
: 'bg-green-600 text-white hover:bg-green-700 transform hover:scale-105'
}`}
>
<DoorOpen className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
<span className="hidden md:inline">{request.tenantReceived ? 'طھظ… ط§ظ„ط§ط³طھظ„ط§ظ…' : 'ط§ط³طھظ„ط§ظ… ط§ظ„ط¹ظ‚ط§ط±'}</span>
<span className="md:hidden">{request.tenantReceived ? '✓' : 'ط§ط³طھظ„ط§ظ…'}</span>
</button>
</div>
</div>
)}
{request.status === 'active' && (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<button
onClick={() => onAction('tenant_leave', { id: request.id, type: 'tenant' })}
disabled={request.tenantLeft}
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.tenantLeft
? 'bg-green-100 text-green-800 cursor-default'
: 'bg-amber-600 text-white hover:bg-amber-700 transform hover:scale-105'
}`}
>
<DoorOpen className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
{request.tenantLeft ? 'طھظ… ط§ظ„ظ…ط؛ط§ط¯ط±ط©' : 'ظ…ط؛ط§ط¯ط±ط© ط§ظ„ط¹ظ‚ط§ط±'}
</button>
<button
onClick={() => onAction('owner_receive', { id: request.id, type: 'owner' })}
disabled={request.ownerReceived}
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.ownerReceived
? 'bg-green-100 text-green-800 cursor-default'
: 'bg-purple-600 text-white hover:bg-purple-700 transform hover:scale-105'
}`}
>
<Key className="w-5 h-5" />
2026-04-16 21:15:21 +03:00
{request.ownerReceived ? 'طھظ… ط§ظ„ط§ط³طھظ„ط§ظ…' : 'ط§ط³طھظ„ط§ظ… ط§ظ„ط¹ظ‚ط§ط±'}
</button>
</div>
{request.actualStartDate && (
<div className="bg-gray-100 p-3 rounded-lg">
<div className="flex justify-between text-sm mb-1">
2026-04-16 21:15:21 +03:00
<span>ط¨ط¯ط£ ط§ظط¥ظٹط¬ط§ط±: {request.actualStartDate}</span>
<span>ط§ظظط¯ط©: {request.days} ظٹظˆظ</span>
</div>
<div className="w-full bg-gray-300 rounded-full h-2">
<div
className="bg-purple-600 h-2 rounded-full transition-all duration-500"
style={{ width: '45%' }}
/>
</div>
</div>
)}
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
};
export default function BookingRequests() {
const [requests, setRequests] = useState([
{
id: 'REQ001',
2026-04-16 21:15:21 +03:00
user: 'ط£ط­ظ…ط¯ ظ…ط­ظ…ط¯',
userEmail: 'ahmed@example.com',
userPhone: '0938123456',
userType: 'syrian',
identityNumber: '123456789',
2026-04-16 21:15:21 +03:00
property: 'ظپظٹظ„ط§ ظپط§ط®ط±ط© ظپظٹ ط¯ظ…ط´ظ‚',
propertyId: 1,
startDate: '2024-03-01',
endDate: '2024-03-10',
days: 10,
totalAmount: 5000000,
dailyPrice: 500000,
commissionRate: 5,
2026-04-16 21:15:21 +03:00
commissionType: 'ظ…ظ† ط§ظ„ظ…ط§ظ„ظƒ',
commissionAmount: 250000,
securityDeposit: 500000,
status: 'pending',
requestDate: '2024-02-25',
ownerApproved: false,
adminApproved: false,
ownerDelivered: false,
tenantReceived: false,
tenantLeft: false,
ownerReceived: false,
2026-04-15 12:28:01 +03:00
securityDepositPaid: false,
securityDepositReturned: null,
contractSigned: false,
notes: '',
actualStartDate: null,
actualEndDate: null
},
{
id: 'REQ002',
2026-04-16 21:15:21 +03:00
user: 'ط³ط§ط±ط© ط£ط­ظ…ط¯',
userEmail: 'sara@example.com',
userPhone: '0945123789',
userType: 'passport',
identityNumber: 'AB123456',
2026-04-16 21:15:21 +03:00
property: 'ط´ظ‚ط© ط­ط¯ظٹط«ط© ظپظٹ ط­ظ„ط¨',
propertyId: 2,
startDate: '2024-03-05',
endDate: '2024-03-15',
days: 10,
totalAmount: 2500000,
dailyPrice: 250000,
commissionRate: 7,
2026-04-16 21:15:21 +03:00
commissionType: 'ظ…ظ† ط§ظ„ظ…ط³طھط£ط¬ط±',
commissionAmount: 175000,
securityDeposit: 250000,
status: 'owner_approved',
requestDate: '2024-02-24',
ownerApproved: true,
adminApproved: false,
ownerDelivered: false,
tenantReceived: false,
tenantLeft: false,
ownerReceived: false,
2026-04-15 12:28:01 +03:00
securityDepositPaid: false,
securityDepositReturned: null,
contractSigned: false,
notes: '',
actualStartDate: null,
actualEndDate: null
},
{
id: 'REQ003',
2026-04-16 21:15:21 +03:00
user: 'ظ…ط­ظ…ط¯ ط§ظ„ط­ظ„ط¨ظٹ',
userEmail: 'mohammed@example.com',
userPhone: '0956123456',
userType: 'syrian',
identityNumber: '987654321',
2026-04-16 21:15:21 +03:00
property: 'ط´ظ‚ط© ط¨ط¬ط§ظ†ط¨ ط§ظ„ط¨ط­ط± ظپظٹ ط§ظ„ظ„ط§ط°ظ‚ظٹط©',
propertyId: 3,
startDate: '2024-02-20',
endDate: '2024-03-20',
days: 30,
totalAmount: 9000000,
dailyPrice: 300000,
commissionRate: 5,
2026-04-16 21:15:21 +03:00
commissionType: 'ظ…ظ† ط§ظ„ط§ط«ظ†ظٹظ†',
commissionAmount: 450000,
securityDeposit: 500000,
status: 'active',
requestDate: '2024-02-15',
ownerApproved: true,
adminApproved: true,
ownerDelivered: true,
tenantReceived: true,
tenantLeft: false,
ownerReceived: false,
2026-04-15 12:28:01 +03:00
securityDepositPaid: true,
securityDepositReturned: null,
contractSigned: true,
2026-04-16 21:15:21 +03:00
notes: 'ط¹ظ‚ط¯ ظ…ظˆظ‚ط¹ ط¥ظ„ظƒطھط±ظˆظ†ظٹط§ظ‹',
actualStartDate: '2024-02-20',
actualEndDate: null
}
]);
const [filter, setFilter] = useState('all');
const [reasonDialog, setReasonDialog] = useState({ isOpen: false, requestId: null, type: null });
const [detailsDialog, setDetailsDialog] = useState({ isOpen: false, request: null });
2026-04-16 21:15:21 +03:00
const [confirmingDepositId, setConfirmingDepositId] = useState(null);
const handleAction = (action, data) => {
switch(action) {
case 'owner_approve':
handleOwnerApprove(data);
break;
case 'owner_reject':
setReasonDialog({ isOpen: true, requestId: data, type: 'owner' });
break;
case 'admin_approve':
handleAdminApprove(data);
break;
case 'admin_reject':
setReasonDialog({ isOpen: true, requestId: data, type: 'admin' });
break;
2026-04-15 12:28:01 +03:00
case 'confirm_deposit':
2026-04-16 21:15:21 +03:00
handleDepositConfirmation(data);
2026-04-15 12:28:01 +03:00
break;
case 'deliver_key':
handleKeyDelivery(data.id, data.type);
break;
case 'receive_property':
handleKeyDelivery(data.id, data.type);
break;
case 'tenant_leave':
handleEndRental(data.id, data.type);
break;
case 'owner_receive':
handleEndRental(data.id, data.type);
break;
case 'view_details':
setDetailsDialog({ isOpen: true, request: data });
break;
default:
break;
}
};
const handleRejectWithReason = (reason) => {
const { requestId, type } = reasonDialog;
setRequests(prev =>
prev.map(req =>
req.id === requestId
? {
...req,
status: 'rejected',
[type === 'owner' ? 'ownerApproved' : 'adminApproved']: false,
rejectionReason: reason,
rejectionType: type,
notes: `${type === 'owner' ? 'رفض من المالك' : 'رفض إداري'}: ${reason}`
}
: req
)
);
setReasonDialog({ isOpen: false, requestId: null, type: null });
};
const handleOwnerApprove = (requestId) => {
setRequests(prev =>
prev.map(req =>
req.id === requestId
? {
...req,
ownerApproved: true,
status: 'owner_approved',
2026-04-16 21:15:21 +03:00
notes: 'طھظ…طھ ط§ظ„ظ…ظˆط§ظپظ‚ط© ظ…ظ† ظ‚ط¨ظ„ ط§ظ„ظ…ط§ظ„ظƒ'
}
: req
)
);
};
const handleAdminApprove = (requestId) => {
setRequests(prev =>
prev.map(req =>
req.id === requestId
? {
...req,
adminApproved: true,
status: 'admin_approved',
2026-04-16 21:15:21 +03:00
notes: 'طھظ…طھ ط§ظ„ظ…ظˆط§ظپظ‚ط© ظ…ظ† ظ‚ط¨ظ„ ط§ظ„ط¥ط¯ط§ط±ط©'
}
: req
)
);
};
2026-04-16 21:15:21 +03:00
const handleDepositConfirmation = async (request) => {
if (!AuthService.isAdmin()) {
toast.error('هذا الإجراء متاح للإدارة فقط');
return;
}
const reservationId = Number(request?.reservationId ?? request?.id);
if (!Number.isFinite(reservationId)) {
toast.error('هذا الطلب تجريبي ولا يحتوي على reservationId من الخادم');
return;
}
const adminUser = AuthService.getUser();
const parsedAdminId = Number(adminUser?.id);
const adminId = Number.isFinite(parsedAdminId) ? parsedAdminId : adminUser?.id;
if (adminId == null || adminId === '') {
toast.error('لم نتمكن من تحديد هوية المدير');
return;
}
setConfirmingDepositId(request.id);
try {
const result = await adminConfirmDeposit(reservationId, adminId, null);
if (!result.ok) {
throw new Error(result.message || result.data?.message || `HTTP ${result.status}`);
}
setRequests((prev) =>
prev.map((req) =>
req.id === request.id
? {
...req,
securityDepositPaid: true,
notes: 'تم تأكيد العربون من قبل الإدارة',
}
: req,
),
);
toast.success('تم تأكيد العربون بنجاح');
} catch (err) {
console.error('[Admin] Deposit confirmation failed:', err);
toast.error(err.message || 'فشل تأكيد العربون');
} finally {
setConfirmingDepositId(null);
}
2026-04-15 12:28:01 +03:00
};
const handleKeyDelivery = (requestId, userType) => {
setRequests(prev =>
prev.map(req => {
if (req.id === requestId) {
const updates = {};
if (userType === 'owner') {
updates.ownerDelivered = true;
2026-04-16 21:15:21 +03:00
updates.notes = 'طھظ… طھط³ظ„ظٹظ… ط§ظ„ظ…ظپطھط§ط­ ظ…ظ† ظ‚ط¨ظ„ ط§ظ„ظ…ط§ظ„ظƒ';
}
if (userType === 'tenant') {
updates.tenantReceived = true;
2026-04-16 21:15:21 +03:00
updates.notes = 'طھظ… ط§ط³طھظ„ط§ظ… ط§ظ„ط¹ظ‚ط§ط± ظ…ظ† ظ‚ط¨ظ„ ط§ظ„ظ…ط³طھط£ط¬ط±';
}
if ((userType === 'owner' && req.tenantReceived) ||
(userType === 'tenant' && req.ownerDelivered)) {
updates.status = 'active';
updates.contractSigned = true;
updates.actualStartDate = new Date().toISOString().split('T')[0];
2026-04-16 21:15:21 +03:00
updates.notes = 'ط¨ط¯ط£طھ ظپطھط±ط© ط§ظ„ط¥ظٹط¬ط§ط± ط§ظ„ظپط¹ظ„ظٹط©';
}
return { ...req, ...updates };
}
return req;
})
);
};
const handleEndRental = (requestId, userType) => {
setRequests(prev =>
prev.map(req => {
if (req.id === requestId) {
const updates = {};
if (userType === 'tenant') {
updates.tenantLeft = true;
2026-04-16 21:15:21 +03:00
updates.notes = 'ط؛ط§ط¯ط± ط§ظ„ظ…ط³طھط£ط¬ط± ط§ظ„ط¹ظ‚ط§ط±';
}
if (userType === 'owner') {
updates.ownerReceived = true;
2026-04-16 21:15:21 +03:00
updates.notes = 'ط§ط³طھظ„ظ… ط§ظ„ظ…ط§ظ„ظƒ ط§ظ„ط¹ظ‚ط§ط±';
}
if ((userType === 'tenant' && req.ownerReceived) ||
(userType === 'owner' && req.tenantLeft)) {
const actualEndDate = new Date();
const actualStartDate = new Date(req.actualStartDate || req.startDate);
const actualDays = Math.ceil((actualEndDate - actualStartDate) / (1000 * 60 * 60 * 24));
const actualAmount = req.dailyPrice * actualDays;
const damageDeduction = 0;
const refundAmount = req.securityDeposit - damageDeduction;
updates.status = 'completed';
updates.actualEndDate = actualEndDate.toISOString().split('T')[0];
updates.actualDays = actualDays;
updates.actualAmount = actualAmount;
updates.securityDepositReturned = refundAmount;
updates.damageDeduction = damageDeduction;
2026-04-16 21:15:21 +03:00
updates.notes = `ط§ظ†طھظ‡ظ‰ ط§ظ„ط¥ظٹط¬ط§ط± ط¨ط¹ط¯ ${actualDays} ظٹظˆظ… - ط§ظ„ظ…ط¨ظ„ط؛ ط§ظ„ظپط¹ظ„ظٹ: ${actualAmount.toLocaleString()} ظ„.ط³ - ظ…ط³طھط±ط¯ ط§ظ„ط¶ظ…ط§ظ†: ${refundAmount.toLocaleString()} ظ„.ط³`;
}
return { ...req, ...updates };
}
return req;
})
);
};
const filteredRequests = requests.filter(req =>
filter === 'all' ? true : req.status === filter
);
const stats = {
total: requests.length,
pending: requests.filter(r => r.status === 'pending').length,
active: requests.filter(r => r.status === 'active').length,
completed: requests.filter(r => r.status === 'completed').length
};
return (
<div className="space-y-6">
2026-03-27 00:34:59 +03:00
<Toaster position="top-center" reverseOrder={false} />
<div className="grid 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-2xl p-4 shadow-lg"
>
<div className="text-3xl font-bold mb-1">{stats.total}</div>
2026-04-16 21:15:21 +03:00
<div className="text-sm opacity-90">ط¥ط¬ظط§ظظٹ ط§ظط·ظط¨ط§طھ</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-yellow-600 to-yellow-700 text-white rounded-2xl p-4 shadow-lg"
>
<div className="text-3xl font-bold mb-1">{stats.pending}</div>
2026-04-16 21:15:21 +03:00
<div className="text-sm opacity-90">ظظٹط¯ ط§ظط§ظطھط¸ط§ط±</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-purple-600 to-purple-700 text-white rounded-2xl p-4 shadow-lg"
>
<div className="text-3xl font-bold mb-1">{stats.active}</div>
2026-04-16 21:15:21 +03:00
<div className="text-sm opacity-90">ط¥ظٹط¬ط§ط±ط§طھ ظط´ط·ط©</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-green-600 to-green-700 text-white rounded-2xl p-4 shadow-lg"
>
<div className="text-3xl font-bold mb-1">{stats.completed}</div>
2026-04-16 21:15:21 +03:00
<div className="text-sm opacity-90">ظظطھظظٹط©</div>
</motion.div>
</div>
<div className="bg-white/80 backdrop-blur-sm rounded-2xl p-4 shadow-lg border border-white/20">
<div className="flex items-center justify-between mb-3">
2026-04-16 21:15:21 +03:00
<h3 className="font-bold text-gray-700">طھطµظپظٹط© ط­ط³ط¨ ط§ظط­ط§ظط©</h3>
<span className="text-sm text-gray-500">{filteredRequests.length} ط·ظط¨</span>
</div>
<div className="flex flex-wrap gap-2">
{[
2026-04-16 21:15:21 +03:00
{ id: 'all', label: 'ط§ظ„ظƒظ„', color: 'gray' },
{ id: 'pending', label: 'ظ‚ظٹط¯ ط§ظ„ط§ظ†طھط¸ط§ط±', color: 'yellow' },
{ id: 'owner_approved', label: 'ظ…ظˆط§ظپظ‚ط© ط§ظ„ظ…ط§ظ„ظƒ', color: 'blue' },
{ id: 'admin_approved', label: 'ظ…ظˆط§ظپظ‚ط© ط§ظ„ط¥ط¯ط§ط±ط©', color: 'green' },
{ id: 'active', label: 'ط¥ظٹط¬ط§ط±ط§طھ ظ†ط´ط·ط©', color: 'purple' },
{ id: 'completed', label: 'ظ…ظ†طھظ‡ظٹط©', color: 'gray' }
].map((tab) => (
<button
key={tab.id}
onClick={() => setFilter(tab.id)}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all transform hover:scale-105 ${
filter === tab.id
? `bg-${tab.color}-600 text-white shadow-lg`
: `bg-${tab.color}-100 text-${tab.color}-800 hover:bg-${tab.color}-200`
}`}
>
{tab.label}
</button>
))}
</div>
</div>
<div className="space-y-4">
{filteredRequests.map((request) => (
2026-04-16 21:15:21 +03:00
<RequestCard
key={request.id}
request={request}
onAction={handleAction}
onViewDetails={() => setDetailsDialog({ isOpen: true, request })}
confirmingDepositId={confirmingDepositId}
/>
))}
</div>
{filteredRequests.length === 0 && (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="text-center py-16 bg-white rounded-2xl border-2 border-dashed border-gray-300"
>
<div className="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Clock className="w-12 h-12 text-gray-400" />
</div>
2026-04-16 21:15:21 +03:00
<h3 className="text-xl font-bold text-gray-700 mb-2">ظط§ طھظˆط¬ط¯ ط·ظط¨ط§طھ ط­ط¬ط²</h3>
<p className="text-gray-500">ظط§ طھظˆط¬ط¯ ط·ظط¨ط§طھ ط­ط¬ط² ظپظٹ ظط°ظ ط§ظظپط¦ط©</p>
</motion.div>
)}
<ReasonDialog
isOpen={reasonDialog.isOpen}
onClose={() => setReasonDialog({ isOpen: false, requestId: null, type: null })}
onConfirm={handleRejectWithReason}
2026-04-16 21:15:21 +03:00
title={reasonDialog.type === 'owner' ? 'ط±ظپط¶ ظ…ظ† ط§ظ„ظ…ط§ظ„ظƒ' : 'ط±ظپط¶ ط¥ط¯ط§ط±ظٹ'}
/>
<RequestDetailsDialog
request={detailsDialog.request}
isOpen={detailsDialog.isOpen}
onClose={() => setDetailsDialog({ isOpen: false, request: null })}
/>
</div>
);
2026-04-16 21:15:21 +03:00
}