add payments in invoice detail

This commit is contained in:
2026-05-24 10:43:35 +03:30
parent 28295dcc58
commit 7fa30e6e81
4 changed files with 388 additions and 101 deletions
@@ -0,0 +1,223 @@
import { type FC, useState } from 'react';
import moment from 'moment-jalaali';
import Table from '@/components/Table';
import ModalConfrim from '@/components/ModalConfrim';
import type { ColumnType } from '@/components/types/TableTypes';
import { toast } from '@/shared/toast';
import { extractErrorMessage } from '@/config/func';
import {
useConfirmCashPayment,
useDenyCashPayment,
useGetPayments,
useVerifyOnlinePayment,
} from '@/pages/payment/hooks/usePaymentData';
import {
PaymentMethodEnum,
PaymentStatusEnum,
} from '@/pages/payment/enum/PaymentEnum';
import type { PaymentType } from '@/pages/payment/types/Types';
type Props = {
invoiceId: string;
};
const methodLabels: Record<PaymentMethodEnum, string> = {
[PaymentMethodEnum.Online]: 'آنلاین',
[PaymentMethodEnum.Cash]: 'نقدی',
[PaymentMethodEnum.Credit]: 'اعتبار',
};
const statusLabels: Record<PaymentStatusEnum, string> = {
[PaymentStatusEnum.Pending]: 'در انتظار',
[PaymentStatusEnum.Paid]: 'پرداخت شده',
[PaymentStatusEnum.Failed]: 'ناموفق',
[PaymentStatusEnum.Refunded]: 'بازگشت داده شده',
};
const statusBadgeClass: Record<PaymentStatusEnum, string> = {
[PaymentStatusEnum.Pending]: 'bg-amber-100 text-amber-800',
[PaymentStatusEnum.Paid]: 'bg-green-100 text-green-800',
[PaymentStatusEnum.Failed]: 'bg-red-100 text-red-800',
[PaymentStatusEnum.Refunded]: 'bg-gray-100 text-gray-800',
};
const formatAmount = (amount: number) =>
Number(amount).toLocaleString('fa-IR') + ' تومان';
const InvoicePaymentsSection: FC<Props> = ({ invoiceId }) => {
const [page, setPage] = useState(1);
const [confirmAction, setConfirmAction] = useState<{
type: 'cash-confirm' | 'cash-deny' | 'online-verify';
payment: PaymentType;
} | null>(null);
const { data, isLoading } = useGetPayments({
page,
limit: 10,
invoiceId,
});
const confirmCash = useConfirmCashPayment();
const denyCash = useDenyCashPayment();
const verifyOnline = useVerifyOnlinePayment();
const payments = data?.data ?? [];
const meta = data?.meta;
const isActionLoading =
confirmCash.isPending || denyCash.isPending || verifyOnline.isPending;
const handleConfirmAction = () => {
if (!confirmAction) return;
const { type, payment } = confirmAction;
const onSuccess = () => {
toast('عملیات با موفقیت انجام شد', 'success');
setConfirmAction(null);
};
const onError = (error: unknown) => {
toast(extractErrorMessage(error), 'error');
};
if (type === 'cash-confirm') {
confirmCash.mutate(payment.id, { onSuccess, onError });
return;
}
if (type === 'cash-deny') {
denyCash.mutate(payment.id, { onSuccess, onError });
return;
}
if (type === 'online-verify' && payment.token) {
verifyOnline.mutate(payment.token, { onSuccess, onError });
}
};
const getConfirmLabel = () => {
if (!confirmAction) return '';
switch (confirmAction.type) {
case 'cash-confirm':
return 'آیا از تایید این پرداخت نقدی اطمینان دارید؟';
case 'cash-deny':
return 'آیا از رد این پرداخت نقدی اطمینان دارید؟';
case 'online-verify':
return 'آیا از تایید این پرداخت آنلاین اطمینان دارید؟';
default:
return '';
}
};
const columns: ColumnType<PaymentType>[] = [
{
title: 'مبلغ',
key: 'amount',
render: (item) => formatAmount(item.amount),
},
{
title: 'روش پرداخت',
key: 'method',
render: (item) => methodLabels[item.method] ?? item.method,
},
{
title: 'وضعیت',
key: 'status',
render: (item) => (
<span
className={`inline-flex h-6 items-center rounded-full px-2.5 text-xs ${statusBadgeClass[item.status] ?? 'bg-gray-100 text-gray-800'}`}
>
{statusLabels[item.status] ?? item.status}
</span>
),
},
{
title: 'تاریخ',
key: 'createdAt',
render: (item) =>
item.createdAt
? moment(item.createdAt).format('jYYYY/jMM/jDD HH:mm')
: '-',
},
{
title: 'عملیات',
key: 'actions',
render: (item) => {
const isPending = item.status === PaymentStatusEnum.Pending;
const isCash = item.method === PaymentMethodEnum.Cash;
const isOnline = item.method === PaymentMethodEnum.Online;
return (
<div className="flex flex-wrap items-center gap-2">
{isPending && isCash && (
<>
<button
type="button"
className="rounded-lg bg-green-600 px-2 py-1 text-xs text-white hover:opacity-90"
onClick={() =>
setConfirmAction({ type: 'cash-confirm', payment: item })
}
>
تایید
</button>
<button
type="button"
className="rounded-lg bg-red-600 px-2 py-1 text-xs text-white hover:opacity-90"
onClick={() =>
setConfirmAction({ type: 'cash-deny', payment: item })
}
>
رد
</button>
</>
)}
{isPending && isOnline && item.token && (
<button
type="button"
className="rounded-lg bg-primary px-2 py-1 text-xs text-black hover:opacity-90"
onClick={() =>
setConfirmAction({ type: 'online-verify', payment: item })
}
>
تایید آنلاین
</button>
)}
</div>
);
},
},
];
return (
<>
<div className="bg-white rounded-3xl p-6 mt-8">
<div className="font-light">پرداختها</div>
<div className="mt-6">
<Table
columns={columns}
data={payments}
isLoading={isLoading}
noDataMessage="پرداختی برای این پیش‌فاکتور یافت نشد."
pagination={
meta && meta.totalPages > 1
? {
currentPage: meta.page,
totalPages: meta.totalPages,
onPageChange: setPage,
}
: undefined
}
/>
</div>
</div>
<ModalConfrim
isOpen={!!confirmAction}
close={() => setConfirmAction(null)}
onConfrim={handleConfirmAction}
isloading={isActionLoading}
label={getConfirmLabel()}
/>
</>
);
};
export default InvoicePaymentsSection;