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 (
درخواست تسویه یافت نشد
navigate('/admin/settlements')}>
بازگشت به لیست
);
}
return (
جزئیات تسویه
{s.representation_name}
{s.status === 'pending' && (
setApproveOpen(true)}>
تأیید
setRejectOpen(true)}>
رد
)}
{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])} />
receiptInputRef.current?.click()}>
{paying ? 'در حال آپلود...' : (s.receipt ? 'تغییر رسید' : 'آپلود رسید')}
{/* دکمهی تکمیل فقط بعد از آپلود رسید فعال میشود */}
completeMut.mutate()}
title={!s.receipt ? 'ابتدا رسید را آپلود کنید' : undefined}>
{completeMut.isPending ? 'در حال تکمیل...' : 'تکمیل'}
{s.receipt
? 'با زدن «تکمیل»، وضعیت به «پرداخت شده» تغییر میکند. مبلغ هنگام ثبت درخواست از کیفپول کسر شده است.'
: 'ابتدا رسید پرداخت را آپلود کنید، سپس دکمهی «تکمیل» فعال میشود.'}
)}
)}
approveMut.mutate()}
onCancel={() => setApproveOpen(false)}
/>
{ setRejectOpen(false); setRejectReason(''); }}
footer={
<>
{ setRejectOpen(false); setRejectReason(''); }} className="btn ghost sm">لغو
rejectMut.mutate()} disabled={!rejectReason || rejectMut.isPending} className="btn danger sm">
{rejectMut.isPending ? 'در حال ارسال...' : 'رد کردن'}
>
}
>
دلیل رد
);
}