import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useParams, useNavigate } from 'react-router'; import { ArrowRightIcon, CheckIcon, XMarkIcon, ArrowUpTrayIcon, DocumentTextIcon } from '@heroicons/react/24/outline'; import { toast } from 'sonner'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import { useAuthStore } from '../stores/authStore'; import { formatDateTime, formatRial } from '../lib/utils'; import StatusBadge from '../components/ui/StatusBadge'; import ConfirmDialog from '../components/ui/ConfirmDialog'; import BackButton from '../components/ui/BackButton'; import Modal from '../components/ui/Modal'; interface SettlementDetail { uuid: string; representation_name: string; representation_mobile: string | null; amount: number; status: 'pending' | 'approved' | 'rejected' | 'paid'; bank_card: string | null; bank_name: string | null; bank_iban: string | null; bank_owner: string | null; reject_reason: string | null; receipt: string | null; requested_at: string; processed_at: string | null; } function Row({ label, value }: { label: string; value: React.ReactNode }) { return (
{label} {value}
); } export default function SettlementDetailPage() { const { uuid } = useParams<{ uuid: string }>(); const navigate = useNavigate(); const qc = useQueryClient(); const authToken = useAuthStore(s => s.token); const [approveOpen, setApproveOpen] = useState(false); const [rejectOpen, setRejectOpen] = useState(false); const [rejectReason, setRejectReason] = useState(''); const [paying, setPaying] = useState(false); const receiptInputRef = React.useRef(null); const { data, isLoading } = useQuery({ queryKey: ['settlement-detail', uuid], queryFn: () => api.get>(`/api/v1/admin/settlement/${uuid}`), enabled: !!uuid, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any const s: SettlementDetail | undefined = (data?.data as any)?.data ?? data?.data; const approveMut = useMutation({ mutationFn: () => api.post>(`/api/v1/settlement/${uuid}/approve`, {}), onSuccess: () => { toast.success('تسویه تأیید شد'); setApproveOpen(false); qc.invalidateQueries({ queryKey: ['settlement-detail', uuid] }); qc.invalidateQueries({ queryKey: ['settlements'] }); }, onError: (e: Error) => toast.error(e.message), }); const rejectMut = useMutation({ mutationFn: () => api.post>(`/api/v1/settlement/${uuid}/reject`, { reason: rejectReason }), onSuccess: () => { toast.success('تسویه رد شد'); setRejectOpen(false); setRejectReason(''); qc.invalidateQueries({ queryKey: ['settlement-detail', uuid] }); qc.invalidateQueries({ queryKey: ['settlements'] }); }, onError: (e: Error) => toast.error(e.message), }); // آپلود رسید (بدون نهایی‌سازی). نهایی‌سازی با دکمه‌ی جداگانه‌ی «تکمیل» انجام می‌شود. const handleReceiptUpload = async (file: File) => { setPaying(true); try { const res = await fetch('/file/upload/clinic_pro/settlement/receipt', { method: 'POST', headers: { 'Content-Disposition': `filename="${file.name}"`, 'Content-Type': file.type || 'application/octet-stream', Authorization: `Bearer ${authToken ?? ''}`, }, body: file, }); const json = await res.json(); const url = json?.data?.url; if (!url) throw new Error('آپلود رسید ناموفق بود'); // رسید را در همان رکورد ذخیره می‌کنیم تا دکمه‌ی «تکمیل» فعال شود (وضعیت همچنان approved). await api.post>(`/api/v1/settlement/${uuid}/receipt`, { receipt: url }); toast.success('رسید آپلود شد'); qc.invalidateQueries({ queryKey: ['settlement-detail', uuid] }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { toast.error(e?.message ?? 'خطا در آپلود رسید'); } finally { setPaying(false); } }; // تکمیل: نهایی‌سازی پرداخت (وضعیت → paid). مبلغ قبلاً هنگام درخواست از کیف‌پول کسر شده. const completeMut = useMutation({ mutationFn: () => api.post>(`/api/v1/settlement/${uuid}/paid`, { receipt: s?.receipt }), onSuccess: () => { toast.success('تسویه تکمیل شد'); qc.invalidateQueries({ queryKey: ['settlement-detail', uuid] }); qc.invalidateQueries({ queryKey: ['settlements'] }); }, onError: (e: Error) => toast.error(e.message), }); if (isLoading) { return (
); } if (!s) { return (

درخواست تسویه یافت نشد

); } return (

جزئیات تسویه

{s.representation_name}
{s.status === 'pending' && (
)}
{formatRial(s.amount)}
{s.representation_mobile} : '—'} /> {s.bank_card} : '—'} /> {s.bank_iban} : '—'} /> {s.reject_reason && }
{/* رسید پرداخت */} {(s.status === 'approved' || s.status === 'paid' || s.receipt) && (
رسید پرداخت
{s.receipt ? ( رسید مشاهده‌ی اندازه‌ی کامل ↗ ) : (

هنوز رسیدی آپلود نشده است.

)} {s.status === 'approved' && (
e.target.files?.[0] && handleReceiptUpload(e.target.files[0])} /> {/* دکمه‌ی تکمیل فقط بعد از آپلود رسید فعال می‌شود */}

{s.receipt ? 'با زدن «تکمیل»، وضعیت به «پرداخت شده» تغییر می‌کند. مبلغ هنگام ثبت درخواست از کیف‌پول کسر شده است.' : 'ابتدا رسید پرداخت را آپلود کنید، سپس دکمه‌ی «تکمیل» فعال می‌شود.'}

)}
)} approveMut.mutate()} onCancel={() => setApproveOpen(false)} /> { setRejectOpen(false); setRejectReason(''); }} footer={ <> } >