Files
SweetHome/app/components/admin/BookingRequests.js
mouazkh c46173d7c6
All checks were successful
Build frontend / build (push) Successful in 45s
fixed some things
2026-04-16 21:18:31 +03:00

1475 lines
62 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
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,
History,
Loader2,
CreditCard
} from 'lucide-react';
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
import toast, { Toaster } from 'react-hot-toast';
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 = [
'ط£ط¹ظ…ط§ظ„ طµظٹط§ظ†ط© ظپظٹ ط§ظ„ط¹ظ‚ط§ط±',
'ط§ظ„ط¹ظ‚ط§ط± ط؛ظٹط± ظ…طھط§ط­ ظپظٹ ظ‡ط°ظ‡ ط§ظ„طھظˆط§ط±ظٹط®',
'ظ…ط´ظƒظ„ط© ظپظٹ ظˆط«ط§ط¦ظ‚ ط§ظ„ظ…ط³طھط£ط¬ط±',
'ط§ظ„ظ…ط§ظ„ظƒ ط؛ظٹط± ظ…طھط§ط­ ظ„ظ„طھط³ظ„ظٹظ…',
'طھط£ط®ط± ظپظٹ ط¯ظپط¹ ط§ظ„ط¶ظ…ط§ظ†',
'ط³ط¨ط¨ ط¢ط®ط±'
];
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>
<p className="text-sm text-gray-500 mt-1">ظٹط±ط¬ظ طھط­ط¯ظٹط¯ ط³ط¨ط¨ ط§ظط±ظپط</p>
</div>
<div className="space-y-3">
{commonReasons.map((r) => (
<button
key={r}
onClick={() => {
if (r === 'ط³ط¨ط¨ ط¢ط®ط±') {
} else {
onConfirm(r);
}
}}
className="w-full text-right p-3 border rounded-xl hover:bg-gray-50 transition-colors"
>
{r}
</button>
))}
<textarea
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()}
>
طھط£ظƒظٹط¯ ط§ظط±ظپط
</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"
>
ط¥ظط؛ط§ط،
</button>
</div>
</div>
</motion.div>
</motion.div>
);
};
const PDFExportButton = ({ request, onExportComplete }) => {
const [isExporting, setIsExporting] = useState(false);
const formatCurrency = (amount) => {
return amount?.toLocaleString() + ' ظ„.ط³';
};
const generatePDF = async () => {
if (!request) {
toast.error('ظ„ط§ طھظˆط¬ط¯ ط¨ظٹط§ظ†ط§طھ ظ„ظ„طھطµط¯ظٹط±');
return;
}
setIsExporting(true);
const loadingToast = toast.loading('ط¬ط§ط±ظٹ ط¥ظ†ط´ط§ط، ظ…ظ„ظپ PDF...', { id: 'pdf-export' });
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>
<div class="pdf-title">طھظ‚ط±ظٹط± ط·ظ„ط¨ ط­ط¬ط² #${request.id}</div>
<div class="pdf-subtitle">طھط§ط±ظٹط® ط§ظ„طھظ‚ط±ظٹط±: ${new Date().toLocaleDateString('ar-SA')} | ${new Date().toLocaleTimeString('ar-SA')}</div>
</div>
<div class="pdf-section">
<div class="pdf-section-title">
<span></span> ظ…ط¹ظ„ظˆظ…ط§طھ ط§ظ„ظ…ط³طھط£ط¬ط±
</div>
<div class="pdf-grid">
<div class="pdf-info-item">
<span class="pdf-label">ط§ظ„ط§ط³ظ… ط§ظ„ظƒط§ظ…ظ„:</span>
<span class="pdf-value">${request.user || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ظ†ظˆط¹ ط§ظ„ظ‡ظˆظٹط©:</span>
<span class="pdf-value">${request.userType === 'syrian' ? 'ظ‡ظˆظٹط© ط³ظˆط±ظٹط©' : 'ط¬ظˆط§ط² ط³ظپط±'}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ط±ظ‚ظ… ط§ظ„ظ‡ظˆظٹط©:</span>
<span class="pdf-value">${request.identityNumber || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ط§ظ„ط¨ط±ظٹط¯ ط§ظ„ط¥ظ„ظƒطھط±ظˆظ†ظٹ:</span>
<span class="pdf-value">${request.userEmail || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ط±ظ‚ظ… ط§ظ„ظ‡ط§طھظپ:</span>
<span class="pdf-value">${request.userPhone || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
</div>
</div>
</div>
<div class="pdf-section">
<div class="pdf-section-title">
<span></span> ظ…ط¹ظ„ظˆظ…ط§طھ ط§ظ„ط¹ظ‚ط§ط±
</div>
<div class="pdf-grid">
<div class="pdf-info-item">
<span class="pdf-label">ط§ظ„ط¹ظ‚ط§ط±:</span>
<span class="pdf-value">${request.property || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ط§ظ„ط³ط¹ط± ط§ظ„ظٹظˆظ…ظٹ:</span>
<span class="pdf-value">${formatCurrency(request.dailyPrice)}</span>
</div>
</div>
</div>
<div class="pdf-section">
<div class="pdf-section-title">
<span></span> طھظپط§طµظٹظ„ ط§ظ„ط­ط¬ط²
</div>
<div class="pdf-grid">
<div class="pdf-info-item">
<span class="pdf-label">طھط§ط±ظٹط® ط§ظ„ط¨ط¯ط§ظٹط©:</span>
<span class="pdf-value">${request.startDate || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">طھط§ط±ظٹط® ط§ظ„ظ†ظ‡ط§ظٹط©:</span>
<span class="pdf-value">${request.endDate || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ط¹ط¯ط¯ ط§ظ„ط£ظٹط§ظ…:</span>
<span class="pdf-value">${request.days || 0} ظٹظˆظ…</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ط§ظ„ط­ط§ظ„ط©:</span>
<span class="pdf-value">
<span class="pdf-status pdf-status-${request.status}">
${request.status === 'pending' ? 'ظ‚ظٹط¯ ط§ظ„ط§ظ†طھط¸ط§ط±' :
request.status === 'owner_approved' ? 'ظ…ظˆط§ظپظ‚ط© ط§ظ„ظ…ط§ظ„ظƒ' :
request.status === 'admin_approved' ? 'ظ…ظˆط§ظپظ‚ط© ط§ظ„ط¥ط¯ط§ط±ط©' :
request.status === 'active' ? 'ط¥ظٹط¬ط§ط± ظ†ط´ط·' :
request.status === 'completed' ? 'ظ…ظ†طھظ‡ظٹ' : 'ظ…ط±ظپظˆط¶'}
</span>
</span>
</div>
</div>
</div>
<div class="pdf-section">
<div class="pdf-section-title">
<span></span> ط§ظ„ظ…ط¹ظ„ظˆظ…ط§طھ ط§ظ„ظ…ط§ظ„ظٹط©
</div>
<div class="pdf-grid">
<div class="pdf-info-item">
<span class="pdf-label">ط³ظ„ظپط© ط§ظ„ط¶ظ…ط§ظ†:</span>
<span class="pdf-value">${formatCurrency(request.securityDeposit)}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ط§ظ„ظ…ط¨ظ„ط؛ ط§ظ„ط¥ط¬ظ…ط§ظ„ظٹ:</span>
<span class="pdf-value pdf-amount">${formatCurrency(request.totalAmount)}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ظ†ط³ط¨ط© ط§ظ„ط¹ظ…ظˆظ„ط©:</span>
<span class="pdf-value">${request.commissionRate || 0}%</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ظ†ظˆط¹ ط§ظ„ط¹ظ…ظˆظ„ط©:</span>
<span class="pdf-value">${request.commissionType || 'ط؛ظٹط± ظ…ط­ط¯ط¯'}</span>
</div>
<div class="pdf-info-item">
<span class="pdf-label">ظ‚ظٹظ…ط© ط§ظ„ط¹ظ…ظˆظ„ط©:</span>
<span class="pdf-value">${formatCurrency(request.commissionAmount)}</span>
</div>
</div>
</div>
${request.notes ? `
<div class="pdf-section">
<div class="pdf-section-title">
<span></span> ظ…ظ„ط§ط­ط¸ط§طھ
</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">
<span></span> ط³ط¬ظ„ ط§ظ„ط¥ط¬ط±ط§ط،ط§طھ
</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>
<span class="pdf-value">طھظ… ط¥ظ†ط´ط§ط، ط§ظ„ط·ظ„ط¨</span>
</div>
${request.ownerApproved ? `
<div style="display: flex; align-items: center; gap: 10px;">
<span class="pdf-badge">✓</span>
<span class="pdf-value">طھظ…طھ ظ…ظˆط§ظپظ‚ط© ط§ظ„ظ…ط§ظ„ظƒ</span>
</div>
` : ''}
${request.adminApproved ? `
<div style="display: flex; align-items: center; gap: 10px;">
<span class="pdf-badge">✓</span>
<span class="pdf-value">طھظ…طھ ظ…ظˆط§ظپظ‚ط© ط§ظ„ط¥ط¯ط§ط±ط©</span>
</div>
` : ''}
${request.ownerDelivered ? `
<div style="display: flex; align-items: center; gap: 10px;">
<span class="pdf-badge"></span>
<span class="pdf-value">طھظ… طھط³ظ„ظٹظ… ط§ظ„ظ…ظپطھط§ط­</span>
</div>
` : ''}
${request.tenantReceived ? `
<div style="display: flex; align-items: center; gap: 10px;">
<span class="pdf-badge"></span>
<span class="pdf-value">طھظ… ط§ط³طھظ„ط§ظ… ط§ظ„ط¹ظ‚ط§ط±</span>
</div>
` : ''}
</div>
</div>
<div class="pdf-footer">
<div>طھظ‚ط±ظٹط± طµط§ط¯ط± ط¹ظ† ظ†ط¸ط§ظ… SweetHome ظ„ط¥ط¯ط§ط±ط© ط§ظ„ط¹ظ‚ط§ط±ط§طھ</div>
<div style="margin-top: 5px;">ط¬ظ…ظٹط¹ ط§ظ„ط­ظ‚ظˆظ‚ ظ…ط­ظپظˆط¸ط© آ© ${new Date().getFullYear()} SweetHome</div>
<div style="margin-top: 5px; font-size: 9px;">ظ‡ط°ط§ ط§ظ„طھظ‚ط±ظٹط± طھظ… ط¥ظ†ط´ط§ط¤ظ‡ طھظ„ظ‚ط§ط¦ظٹط§ظ‹ ظˆظ„ط§ ظٹط­طھط§ط¬ ط¥ظ„ظ‰ طھظˆظ‚ظٹط¹</div>
</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++;
}
const fileName = `طھظط±ظٹط±_ط·ظ„ط¨_${request.id}_${new Date().toISOString().split('T')[0]}.pdf`;
pdf.save(fileName);
document.body.removeChild(printContent);
toast.success(`طھظ… طھطµط¯ظٹط± ط§ظ„طھظ‚ط±ظٹط± ط¨ظ†ط¬ط§ط­! (${pageCount} طµظپط­ط©)`, { id: 'pdf-export' });
if (onExportComplete) {
onExportComplete();
}
} catch (error) {
console.error('Error generating PDF:', error);
toast.error('ط­ط¯ط« ط®ط·ط£ ط£ط«ظ†ط§ط، ط¥ظ†ط´ط§ط، ظ…ظ„ظپ PDF', { id: 'pdf-export' });
} 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" />
ط¬ط§ط±ظٹ ط§ظطھطµط¯ظٹط±...
</>
) : (
<>
<Download className="w-5 h-5" />
طھطµط¯ظٹط± PDF
</>
)}
</button>
);
};
const RequestDetailsDialog = ({ request, isOpen, onClose }) => {
if (!isOpen || !request) return null;
const formatCurrency = (amount) => {
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()}
>
<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" />
طھظپط§طµظٹظ ط§ظط·ظط¨ #{request.id}
</h2>
<p className="text-amber-100 text-sm mt-1">ط¬ظظٹط¹ ظط¹ظظˆظط§طھ ط§ظط·ظط¨ ظپظٹ ظظƒط§ظ ظˆط§ط­ط¯</p>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-white/20 rounded-full transition-colors"
>
<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" />
ظط¹ظظˆظط§طھ ط§ظظط³طھط£ط¬ط±
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-xs text-gray-500">ط§ظط§ط³ظ ط§ظظƒط§ظظ</label>
<div className="font-medium">{request.user}</div>
</div>
<div>
<label className="text-xs text-gray-500">ظظˆط¹ ط§ظظظˆظٹط©</label>
<div className="font-medium">
{request.userType === 'syrian' ? 'ًں‡¸ًں‡¾ ظ‡ظˆظٹط© ط³ظˆط±ظٹط©' : 'ط¬ظˆط§ط² ط³ظپط±'}
<span className="text-xs text-gray-500 mr-2">{request.identityNumber}</span>
</div>
</div>
<div>
<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>
<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" />
ظط¹ظظˆظط§طھ ط§ظط¹ظط§ط±
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-xs text-gray-500">ط§ظط¹ظط§ط±</label>
<div className="font-medium">{request.property}</div>
</div>
<div>
<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" />
طھظپط§طµظٹظ ط§ظط­ط¬ط²
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-xs text-gray-500">طھط§ط±ظٹط® ط§ظط¨ط¯ط§ظٹط©</label>
<div className="font-medium">{request.startDate}</div>
</div>
<div>
<label className="text-xs text-gray-500">طھط§ط±ظٹط® ط§ظظظط§ظٹط©</label>
<div className="font-medium">{request.endDate}</div>
</div>
<div>
<label className="text-xs text-gray-500">ط¹ط¯ط¯ ط§ظط£ظٹط§ظ</label>
<div className="font-medium">{request.days} ظٹظˆظ</div>
</div>
<div>
<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" />
ط§ظظط¹ظظˆظط§طھ ط§ظظط§ظظٹط©
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-xs text-gray-500">ط³ظظپط© ط§ظطظط§ظ</label>
<div className="font-medium text-blue-600">{formatCurrency(request.securityDeposit)}</div>
</div>
<div>
<label className="text-xs text-gray-500">ط­ط§ظط© ط§ظط±ط¹ط¨ظˆظ</label>
<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'
}`}>
{request.securityDepositPaid ? '✓ طھظ… ط§ظ„ط¯ظپط¹' : 'âڈ³ ظپظٹ ط§ظ†طھط¸ط§ط± ط§ظ„ط¯ظپط¹'}
</div>
</div>
<div>
<label className="text-xs text-gray-500">ظط³ط¨ط© ط§ظط¹ظظˆظط©</label>
<div className="font-medium">{request.commissionRate}%</div>
</div>
<div>
<label className="text-xs text-gray-500">ظظˆط¹ ط§ظط¹ظظˆظط©</label>
<div className="font-medium text-amber-600">{request.commissionType}</div>
</div>
<div>
<label className="text-xs text-gray-500">ظظٹظط© ط§ظط¹ظظˆظط©</label>
<div className="font-medium text-amber-600">{formatCurrency(request.commissionAmount)}</div>
</div>
</div>
</div>
<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" />
ط³ط¬ظ ط§ظط¥ط¬ط±ط§ط،ط§طھ
</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>
<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>
<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>
<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>
<span className="text-gray-600">طھظ طھط³ظظٹظ ط§ظظظپطھط§ط­ ظظ ط§ظظط§ظظƒ</span>
</div>
)}
{request.notes && (
<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>
<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" />
ط·ط¨ط§ط¹ط© ط§ظطھظپط§طµظٹظ
</button>
<PDFExportButton request={request} />
</div>
</motion.div>
</motion.div>
);
};
const RequestCard = ({ request, onAction, onViewDetails, confirmingDepositId }) => {
const [expanded, setExpanded] = useState(false);
const isConfirmingDeposit = confirmingDepositId === request.id;
const formatCurrency = (amount) => {
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 = {
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">
<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" />
<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"
>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<div className="bg-blue-50 p-2 rounded-lg">
<div className="text-xs text-gray-500">ط³ظظپط© طظط§ظ</div>
<div className="font-bold text-blue-600">{formatCurrency(request.securityDeposit)}</div>
</div>
<div className={`p-2 rounded-lg ${request.securityDepositPaid ? 'bg-green-50' : 'bg-yellow-50'}`}>
<div className="text-xs text-gray-500">ط­ط§ظط© ط§ظط±ط¹ط¨ظˆظ</div>
<div className={`font-bold text-xs ${request.securityDepositPaid ? 'text-green-600' : 'text-yellow-600'}`}>
{request.securityDepositPaid ? 'طھظ… ط§ظ„ط¯ظپط¹' : ' ط¨ط§ظ†طھط¸ط§ط±'}
</div>
</div>
<div className="bg-amber-50 p-2 rounded-lg">
<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">
<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" />
ظط¹ظظˆظط§طھ ط§ظط§طھطµط§ظ
</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" />
ظظˆط§ظپظط© ط§ظظط§ظظƒ
</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" />
ط±ظپط
</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" />
ظظˆط§ظپظط© ط§ظط¥ط¯ط§ط±ط©
</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" />
ط±ظپط ط¥ط¯ط§ط±ظٹ
</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">
<div className="grid grid-cols-3 gap-3">
<button
onClick={() => onAction('confirm_deposit', request)}
disabled={request.securityDepositPaid || isConfirmingDeposit}
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'
: isConfirmingDeposit
? 'bg-purple-300 text-white cursor-wait'
: 'bg-purple-600 text-white hover:bg-purple-700 transform hover:scale-105'
}`}
>
{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>
</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" />
<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" />
<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" />
{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" />
{request.ownerReceived ? 'طھظ… ط§ظ„ط§ط³طھظ„ط§ظ…' : 'ط§ط³طھظ„ط§ظ… ط§ظ„ط¹ظ‚ط§ط±'}
</button>
</div>
{request.actualStartDate && (
<div className="bg-gray-100 p-3 rounded-lg">
<div className="flex justify-between text-sm mb-1">
<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',
user: 'ط£ط­ظ…ط¯ ظ…ط­ظ…ط¯',
userEmail: 'ahmed@example.com',
userPhone: '0938123456',
userType: 'syrian',
identityNumber: '123456789',
property: 'ظپظٹظ„ط§ ظپط§ط®ط±ط© ظپظٹ ط¯ظ…ط´ظ‚',
propertyId: 1,
startDate: '2024-03-01',
endDate: '2024-03-10',
days: 10,
totalAmount: 5000000,
dailyPrice: 500000,
commissionRate: 5,
commissionType: 'ظ…ظ† ط§ظ„ظ…ط§ظ„ظƒ',
commissionAmount: 250000,
securityDeposit: 500000,
status: 'pending',
requestDate: '2024-02-25',
ownerApproved: false,
adminApproved: false,
ownerDelivered: false,
tenantReceived: false,
tenantLeft: false,
ownerReceived: false,
securityDepositPaid: false,
securityDepositReturned: null,
contractSigned: false,
notes: '',
actualStartDate: null,
actualEndDate: null
},
{
id: 'REQ002',
user: 'ط³ط§ط±ط© ط£ط­ظ…ط¯',
userEmail: 'sara@example.com',
userPhone: '0945123789',
userType: 'passport',
identityNumber: 'AB123456',
property: 'ط´ظ‚ط© ط­ط¯ظٹط«ط© ظپظٹ ط­ظ„ط¨',
propertyId: 2,
startDate: '2024-03-05',
endDate: '2024-03-15',
days: 10,
totalAmount: 2500000,
dailyPrice: 250000,
commissionRate: 7,
commissionType: 'ظ…ظ† ط§ظ„ظ…ط³طھط£ط¬ط±',
commissionAmount: 175000,
securityDeposit: 250000,
status: 'owner_approved',
requestDate: '2024-02-24',
ownerApproved: true,
adminApproved: false,
ownerDelivered: false,
tenantReceived: false,
tenantLeft: false,
ownerReceived: false,
securityDepositPaid: false,
securityDepositReturned: null,
contractSigned: false,
notes: '',
actualStartDate: null,
actualEndDate: null
},
{
id: 'REQ003',
user: 'ظ…ط­ظ…ط¯ ط§ظ„ط­ظ„ط¨ظٹ',
userEmail: 'mohammed@example.com',
userPhone: '0956123456',
userType: 'syrian',
identityNumber: '987654321',
property: 'ط´ظ‚ط© ط¨ط¬ط§ظ†ط¨ ط§ظ„ط¨ط­ط± ظپظٹ ط§ظ„ظ„ط§ط°ظ‚ظٹط©',
propertyId: 3,
startDate: '2024-02-20',
endDate: '2024-03-20',
days: 30,
totalAmount: 9000000,
dailyPrice: 300000,
commissionRate: 5,
commissionType: 'ظ…ظ† ط§ظ„ط§ط«ظ†ظٹظ†',
commissionAmount: 450000,
securityDeposit: 500000,
status: 'active',
requestDate: '2024-02-15',
ownerApproved: true,
adminApproved: true,
ownerDelivered: true,
tenantReceived: true,
tenantLeft: false,
ownerReceived: false,
securityDepositPaid: true,
securityDepositReturned: null,
contractSigned: true,
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 });
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;
case 'confirm_deposit':
handleDepositConfirmation(data);
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',
notes: 'طھظ…طھ ط§ظ„ظ…ظˆط§ظپظ‚ط© ظ…ظ† ظ‚ط¨ظ„ ط§ظ„ظ…ط§ظ„ظƒ'
}
: req
)
);
};
const handleAdminApprove = (requestId) => {
setRequests(prev =>
prev.map(req =>
req.id === requestId
? {
...req,
adminApproved: true,
status: 'admin_approved',
notes: 'طھظ…طھ ط§ظ„ظ…ظˆط§ظپظ‚ط© ظ…ظ† ظ‚ط¨ظ„ ط§ظ„ط¥ط¯ط§ط±ط©'
}
: req
)
);
};
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);
}
};
const handleKeyDelivery = (requestId, userType) => {
setRequests(prev =>
prev.map(req => {
if (req.id === requestId) {
const updates = {};
if (userType === 'owner') {
updates.ownerDelivered = true;
updates.notes = 'طھظ… طھط³ظ„ظٹظ… ط§ظ„ظ…ظپطھط§ط­ ظ…ظ† ظ‚ط¨ظ„ ط§ظ„ظ…ط§ظ„ظƒ';
}
if (userType === 'tenant') {
updates.tenantReceived = true;
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];
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;
updates.notes = 'ط؛ط§ط¯ط± ط§ظ„ظ…ط³طھط£ط¬ط± ط§ظ„ط¹ظ‚ط§ط±';
}
if (userType === 'owner') {
updates.ownerReceived = true;
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;
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">
<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>
<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>
<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>
<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>
<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">
<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">
{[
{ 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) => (
<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>
<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}
title={reasonDialog.type === 'owner' ? 'ط±ظپط¶ ظ…ظ† ط§ظ„ظ…ط§ظ„ظƒ' : 'ط±ظپط¶ ط¥ط¯ط§ط±ظٹ'}
/>
<RequestDetailsDialog
request={detailsDialog.request}
isOpen={detailsDialog.isOpen}
onClose={() => setDetailsDialog({ isOpen: false, request: null })}
/>
</div>
);
}