This commit is contained in:
@ -419,6 +419,84 @@ const ReasonDialog = ({ isOpen, onClose, onConfirm, title, defaultReason = '' })
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DepositCommentDialog = ({
|
||||||
|
isOpen,
|
||||||
|
request,
|
||||||
|
isSubmitting,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
}) => {
|
||||||
|
const [comment, setComment] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setComment('');
|
||||||
|
}
|
||||||
|
}, [isOpen, request?.id]);
|
||||||
|
|
||||||
|
if (!isOpen || !request) 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={isSubmitting ? undefined : onClose}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.95, y: 20 }}
|
||||||
|
animate={{ scale: 1, y: 0 }}
|
||||||
|
exit={{ scale: 0.95, y: 20 }}
|
||||||
|
className="bg-white rounded-2xl w-full max-w-lg p-6 shadow-2xl"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="text-center mb-5">
|
||||||
|
<div className="w-16 h-16 bg-indigo-100 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||||
|
<MessageCircle className="w-8 h-8 text-indigo-600" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900">تأكيد العربون</h3>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">
|
||||||
|
يمكنك إضافة تعليق اختياري، أو ترك الحقل فارغاً ليتم إرسال <span className="font-semibold">null</span>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-xl bg-gray-50 px-4 py-3 text-sm text-gray-700 mb-4 space-y-1">
|
||||||
|
<div>رقم الحجز: <span className="font-semibold">#{request.id}</span></div>
|
||||||
|
<div>العقار: <span className="font-semibold">{request.property}</span></div>
|
||||||
|
<div>المستأجر: <span className="font-semibold">{request.user}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
value={comment}
|
||||||
|
onChange={(e) => setComment(e.target.value)}
|
||||||
|
placeholder="اكتب تعليقاً إذا أردت..."
|
||||||
|
className="w-full min-h-32 p-4 border rounded-xl resize-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex gap-3 pt-4">
|
||||||
|
<button
|
||||||
|
onClick={() => onConfirm(comment)}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="flex-1 bg-indigo-600 text-white py-3 rounded-xl font-medium hover:bg-indigo-700 transition-colors disabled:opacity-60 disabled:cursor-wait flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
{isSubmitting ? <Loader2 className="w-5 h-5 animate-spin" /> : <CreditCard className="w-5 h-5" />}
|
||||||
|
<span>{isSubmitting ? 'جاري الإرسال...' : 'تأكيد الإرسال'}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="flex-1 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors disabled:opacity-60"
|
||||||
|
>
|
||||||
|
إلغاء
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const PDFExportButton = ({ request, onExportComplete }) => {
|
const PDFExportButton = ({ request, onExportComplete }) => {
|
||||||
const [isExporting, setIsExporting] = useState(false);
|
const [isExporting, setIsExporting] = useState(false);
|
||||||
|
|
||||||
@ -1023,6 +1101,7 @@ const RequestDetailsDialog = ({ request, isOpen, onClose }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1307,6 +1386,7 @@ export default function BookingRequests() {
|
|||||||
const [requests, setRequests] = useState([]);
|
const [requests, setRequests] = useState([]);
|
||||||
const [filter, setFilter] = useState('depositPaid');
|
const [filter, setFilter] = useState('depositPaid');
|
||||||
const [detailsDialog, setDetailsDialog] = useState({ isOpen: false, request: null });
|
const [detailsDialog, setDetailsDialog] = useState({ isOpen: false, request: null });
|
||||||
|
const [depositDialog, setDepositDialog] = useState({ isOpen: false, request: null });
|
||||||
const [confirmingDepositId, setConfirmingDepositId] = useState(null);
|
const [confirmingDepositId, setConfirmingDepositId] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [loadError, setLoadError] = useState('');
|
const [loadError, setLoadError] = useState('');
|
||||||
@ -1364,7 +1444,16 @@ export default function BookingRequests() {
|
|||||||
loadReservations();
|
loadReservations();
|
||||||
}, [loadReservations]);
|
}, [loadReservations]);
|
||||||
|
|
||||||
const handleDepositConfirmation = async (request) => {
|
const openDepositConfirmationDialog = (request) => {
|
||||||
|
setDepositDialog({ isOpen: true, request });
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDepositConfirmationDialog = () => {
|
||||||
|
if (confirmingDepositId != null) return;
|
||||||
|
setDepositDialog({ isOpen: false, request: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDepositConfirmation = async (request, commentInput = null) => {
|
||||||
if (!AuthService.isAdmin()) {
|
if (!AuthService.isAdmin()) {
|
||||||
console.warn('[Admin] Deposit confirmation blocked: current user is not admin', {
|
console.warn('[Admin] Deposit confirmation blocked: current user is not admin', {
|
||||||
user: AuthService.getUser(),
|
user: AuthService.getUser(),
|
||||||
@ -1394,6 +1483,10 @@ export default function BookingRequests() {
|
|||||||
const adminUser = AuthService.getUser();
|
const adminUser = AuthService.getUser();
|
||||||
const parsedAdminId = Number(adminUser?.id);
|
const parsedAdminId = Number(adminUser?.id);
|
||||||
const adminId = Number.isFinite(parsedAdminId) ? parsedAdminId : adminUser?.id;
|
const adminId = Number.isFinite(parsedAdminId) ? parsedAdminId : adminUser?.id;
|
||||||
|
const normalizedComment =
|
||||||
|
typeof commentInput === 'string' && commentInput.trim()
|
||||||
|
? commentInput.trim()
|
||||||
|
: null;
|
||||||
|
|
||||||
console.log('[Admin] Preparing admin confirm deposit request', {
|
console.log('[Admin] Preparing admin confirm deposit request', {
|
||||||
requestId: request?.id,
|
requestId: request?.id,
|
||||||
@ -1405,7 +1498,7 @@ export default function BookingRequests() {
|
|||||||
payload: {
|
payload: {
|
||||||
reservationId,
|
reservationId,
|
||||||
adminId,
|
adminId,
|
||||||
comment: null,
|
comment: normalizedComment,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1421,11 +1514,13 @@ export default function BookingRequests() {
|
|||||||
setConfirmingDepositId(request.id);
|
setConfirmingDepositId(request.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await adminConfirmDeposit(reservationId, adminId, null);
|
const result = await adminConfirmDeposit(reservationId, adminId, normalizedComment);
|
||||||
|
|
||||||
console.log('[Admin] Deposit confirmation response', {
|
console.log('[Admin] Deposit confirmation response', {
|
||||||
requestId: request?.id,
|
requestId: request?.id,
|
||||||
reservationId,
|
reservationId,
|
||||||
|
adminId,
|
||||||
|
comment: normalizedComment,
|
||||||
status: result?.status,
|
status: result?.status,
|
||||||
ok: result?.ok,
|
ok: result?.ok,
|
||||||
message: result?.message,
|
message: result?.message,
|
||||||
@ -1444,12 +1539,13 @@ export default function BookingRequests() {
|
|||||||
status: RESERVATION_STATUS.depositConfirmed,
|
status: RESERVATION_STATUS.depositConfirmed,
|
||||||
adminApproved: true,
|
adminApproved: true,
|
||||||
securityDepositPaid: true,
|
securityDepositPaid: true,
|
||||||
notes: 'تم تأكيد العربون من قبل الإدارة',
|
notes: normalizedComment || 'تم تأكيد العربون من قبل الإدارة',
|
||||||
}
|
}
|
||||||
: req,
|
: req,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setDepositDialog({ isOpen: false, request: null });
|
||||||
toast.success('تم تأكيد العربون بنجاح');
|
toast.success('تم تأكيد العربون بنجاح');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Admin] Deposit confirmation failed:', err);
|
console.error('[Admin] Deposit confirmation failed:', err);
|
||||||
@ -1574,7 +1670,7 @@ export default function BookingRequests() {
|
|||||||
<RequestCard
|
<RequestCard
|
||||||
key={request.id}
|
key={request.id}
|
||||||
request={request}
|
request={request}
|
||||||
onConfirmDeposit={handleDepositConfirmation}
|
onConfirmDeposit={openDepositConfirmationDialog}
|
||||||
onViewDetails={(selectedRequest) => setDetailsDialog({ isOpen: true, request: selectedRequest })}
|
onViewDetails={(selectedRequest) => setDetailsDialog({ isOpen: true, request: selectedRequest })}
|
||||||
confirmingDepositId={confirmingDepositId}
|
confirmingDepositId={confirmingDepositId}
|
||||||
/>
|
/>
|
||||||
@ -1603,6 +1699,14 @@ export default function BookingRequests() {
|
|||||||
isOpen={detailsDialog.isOpen}
|
isOpen={detailsDialog.isOpen}
|
||||||
onClose={() => setDetailsDialog({ isOpen: false, request: null })}
|
onClose={() => setDetailsDialog({ isOpen: false, request: null })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DepositCommentDialog
|
||||||
|
isOpen={depositDialog.isOpen}
|
||||||
|
request={depositDialog.request}
|
||||||
|
isSubmitting={confirmingDepositId === depositDialog.request?.id}
|
||||||
|
onClose={closeDepositConfirmationDialog}
|
||||||
|
onConfirm={(comment) => handleDepositConfirmation(depositDialog.request, comment)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -383,12 +383,21 @@ export async function confirmDepositPayment(bookingId) {
|
|||||||
export async function adminConfirmDeposit(reservationId, adminId, comment = null) {
|
export async function adminConfirmDeposit(reservationId, adminId, comment = null) {
|
||||||
const token = AuthService.getToken();
|
const token = AuthService.getToken();
|
||||||
const endpoint = `${API_BASE}/Reservations/AdminConfirmDeposit/admin-confirm-deposit`;
|
const endpoint = `${API_BASE}/Reservations/AdminConfirmDeposit/admin-confirm-deposit`;
|
||||||
const payload = { reservationId, adminId, comment };
|
const normalizedComment =
|
||||||
|
typeof comment === 'string' && comment.trim()
|
||||||
|
? comment.trim()
|
||||||
|
: null;
|
||||||
|
const payload = {
|
||||||
|
reservationId,
|
||||||
|
adminId,
|
||||||
|
comment: normalizedComment,
|
||||||
|
};
|
||||||
|
|
||||||
console.log('[API] AdminConfirmDeposit request', {
|
console.log('[API] AdminConfirmDeposit request', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
endpoint,
|
endpoint,
|
||||||
payload,
|
payload,
|
||||||
|
adminIdSource: 'jwt-user-id',
|
||||||
hasToken: Boolean(token),
|
hasToken: Boolean(token),
|
||||||
tokenPreview: token ? `${token.slice(0, 18)}...${token.slice(-8)}` : null,
|
tokenPreview: token ? `${token.slice(0, 18)}...${token.slice(-8)}` : null,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user