Files
negareh-console/src/pages/invoice/Detail.tsx
T
2026-05-17 22:55:18 +03:30

314 lines
14 KiB
TypeScript
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.
import { type FC, useState } from 'react'
import { Printer, TickCircle } from 'iconsax-react'
import ModalConfirm from '@/components/ModalConfirm'
import Button from '@/components/Button'
import Table from '@/components/Table'
import type { RowDataType } from '@/components/types/TableTypes'
import { Link, useParams } from 'react-router-dom'
import { useQueryClient } from '@tanstack/react-query'
import { useConfirmInvoiceItem, useGetInvoiceDetail } from './hooks/useInvoiceData'
import { NumberFormat } from '@/config/func'
import moment from 'moment-jalaali'
import type { InvoiceItem as ApiInvoiceItem } from './types/InvoiceTypes'
import { Paths } from '@/config/Paths'
import { useGetPaymentInvoice } from '../payment/hooks/usePaymentData'
import InvoicePaymentList from './InvoicePaymentList'
interface TableInvoiceItem extends RowDataType {
id: string
image: string | null
title: string
quantity: string
unitPrice: string
discount: string
totalPrice: string
confirmed: boolean
confirmedAt: string | null
}
const formatPrice = (num: number) => `${NumberFormat(num)} تومان`
const InvoiceDetail: FC = () => {
const { id } = useParams()
const queryClient = useQueryClient()
const { data, isPending } = useGetInvoiceDetail(id!)
useGetPaymentInvoice(id)
const { mutate: confirmInvoiceItem, isPending: isConfirming } = useConfirmInvoiceItem()
const [confirmingItemId, setConfirmingItemId] = useState<string | null>(null)
const invoice = data?.data
const invoiceData = invoice
? {
invoiceNumber: String(invoice.invoiceNumber),
confirmDate: invoice.items?.[0]?.confirmedAt
? moment(invoice.items[0].confirmedAt).format('jYYYY/jMM/jDD HH:mm')
: '-',
customerName:
[invoice.user?.firstName, invoice.user?.lastName].filter(Boolean).join(' ') ||
invoice.user?.phone ||
'-',
orderDate: moment(invoice.createdAt).format('jYYYY/jMM/jDD'),
phoneNumber: invoice.user?.phone || '-',
orderNumber: String(invoice.request?.requestNumber ?? '-'),
address: invoice.user?.addresse || '-',
paymentMethod: invoice.paymentMethod || '-',
}
: null
const items: TableInvoiceItem[] =
invoice?.items?.map((item: ApiInvoiceItem) => ({
id: item.id,
image: item.product?.images?.[0] ?? null,
title: item.product?.title || '-',
quantity: String(item.quantity),
unitPrice: formatPrice(item.unitPrice),
discount: item.discount ? `${NumberFormat(item.discount)}٪` : '۰',
totalPrice: formatPrice(item.total ?? 0),
confirmed: !!item.confirmedAt,
confirmedAt: item.confirmedAt,
})) ?? []
const handlePrint = () => {
window.print()
}
const handleConfirmProforma = () => {
if (!confirmingItemId) return
confirmInvoiceItem(confirmingItemId, {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['invoice', id] })
setConfirmingItemId(null)
},
})
}
const columns = [
{
key: 'title',
title: 'عنوان',
},
{
key: 'quantity',
title: 'تعداد',
},
{
key: 'unitPrice',
title: 'مبلغ واحد',
},
{
key: 'discount',
title: 'تخفیف',
},
{
key: 'totalPrice',
title: 'مبلغ کل',
},
{
key: 'image',
title: '',
render: (item: TableInvoiceItem) => (
!item.confirmed ? (
<div onClick={(e) => e.stopPropagation()}>
<Button
onClick={() => setConfirmingItemId(item.id)}
className='bg-[#01AC45] text-white w-fit px-6'
isLoading={isConfirming && confirmingItemId === item.id}
>
<div className='flex items-center gap-2'>
<TickCircle size={20} color='white' />
<span className='text-xs'>تایید پیش فاکتور</span>
</div>
</Button>
</div>
) : (
item.confirmedAt && (
<span className='text-xs text-[#01AC45]'>
تایید شده در {moment(item.confirmedAt).format('jYYYY/jMM/jDD HH:mm')}
</span>
)
)
)
}
]
if (isPending && !invoice) {
return (
<div className='mt-4 text-sm flex items-center justify-center min-h-[200px]'>
<span className='text-[#8C90A3]'>در حال بارگذاری...</span>
</div>
)
}
if (!invoiceData) {
return (
<div className='mt-4 text-sm flex items-center justify-center min-h-[200px]'>
<span className='text-[#8C90A3]'>صورت حساب یافت نشد</span>
</div>
)
}
// const isPaid = invoice && invoice.paidAmount >= invoice.total && invoice.total > 0
return (
<div className='mt-4 text-sm'>
{/* Header */}
<div className='flex justify-between items-center mb-6'>
<div className='text-sm text-[#8C90A3]'>
صورت حساب {invoiceData.invoiceNumber}
</div>
<div className='flex gap-3'>
<Button
onClick={handlePrint}
className='bg-white border border-[#FFF1D7] text-black w-[100px] h-9 flex items-center justify-center gap-2'
>
<Printer size={20} color='black' />
<span className='text-xs'>چاپ</span>
</Button>
<Link to={`${Paths.payment.payInvoice}${id}`}>
<Button
className='bg-blue-100 text-[#0047FF] w-[120px] h-9 '
>
پرداخت
</Button>
</Link>
</div>
</div>
{/* بخش اول: اطلاعات صورت حساب */}
<div className='bg-white rounded-2xl p-8'>
<div className='text-sm mb-6'>اطلاعات صورت حساب</div>
<div className='flex flex-wrap justify-between gap-x-12 gap-y-5 text-xs'>
<div className='flex gap-1.5'>
<span className='text-[#8C90A3]'>تاریخ ایجاد سفارش :</span>
<span className='text-black'>{invoiceData.orderDate}</span>
</div>
<div className='flex gap-1.5'>
<span className='text-[#8C90A3]'>شماره همراه:</span>
<span className='text-black'>{invoiceData?.phoneNumber}</span>
</div>
<div className='flex gap-1.5'>
<span className='text-[#8C90A3]'>تاریخ تایید پیش فاکتور:</span>
<span className='text-black'>{invoiceData.confirmDate}</span>
</div>
<div className='flex gap-1.5'>
<span className='text-[#8C90A3]'>مشتری:</span>
<span className='text-black'>{invoiceData.customerName}</span>
</div>
</div>
<div className='mt-5 flex items-center gap-1.5 text-xs'>
<span className='text-[#8C90A3]'>آدرس:</span>
<span className='text-black leading-6'>{invoiceData.address}</span>
</div>
<div className='mt-5 flex items-center gap-1.5 text-xs'>
<span className='text-[#8C90A3]'>روش پرداخت:</span>
<span className='text-black leading-6'>{invoiceData.paymentMethod}</span>
</div>
{invoice?.description && (
<div className='mt-5 flex flex-col gap-1.5 text-xs'>
<span className='text-[#8C90A3]'>توضیحات:</span>
<span className='text-black leading-6'>{invoice.description}</span>
</div>
)}
{invoice?.attachments && invoice.attachments.length > 0 && (
<div className='mt-5 flex flex-col gap-1.5 text-xs'>
<span className='text-[#8C90A3]'>پیوستها:</span>
<ul className='text-black leading-6 list-disc list-inside space-y-1'>
{invoice.attachments.map((att: unknown, index: number) => {
const url = typeof att === 'string' ? att : (att as { url?: string })?.url
const name = typeof att === 'object' && att && 'name' in (att as object) ? (att as { name?: string }).name : url || `پیوست ${index + 1}`
return url ? (
<li key={index}>
<a href={url} target='_blank' rel='noopener noreferrer' className='text-[#0047FF] hover:underline'>
{name}
</a>
</li>
) : null
})}
</ul>
</div>
)}
</div>
{/* بخش دوم: اقلام درخواستی */}
<div className='mt-6 bg-white rounded-2xl p-8'>
<div className='text-sm mb-6'>اقلام درخواستی</div>
<Table
columns={columns}
data={items}
showHeader={true}
isLoading={isPending}
/>
{/* Price Summary */}
{invoice && (
<div className='mt-6 flex flex-col gap-3 max-w-sm ms-auto border-t border-[#EBEDF5] pt-6'>
<div className='flex justify-between text-xs'>
<span className='text-[#8C90A3]'>جمع کل:</span>
<span className='text-black font-medium'>{formatPrice(invoice.subTotal)}</span>
</div>
{invoice.discount > 0 && (
<div className='flex justify-between text-xs'>
<span className='text-[#8C90A3]'>تخفیف:</span>
<span className='text-black'>{formatPrice(invoice.discount)}</span>
</div>
)}
{invoice.enableTax && invoice.taxAmount > 0 && (
<div className='flex justify-between text-xs'>
<span className='text-[#8C90A3]'>مالیات:</span>
<span className='text-black'>{formatPrice(invoice.taxAmount)}</span>
</div>
)}
<div className='flex justify-between text-sm font-medium'>
<span className='text-[#8C90A3]'>مبلغ کل:</span>
<span className='text-black'>{formatPrice(invoice.total)}</span>
</div>
<div className='flex justify-between text-xs'>
<span className='text-[#8C90A3]'>مبلغ پرداخت شده:</span>
<span className='text-black'>{formatPrice(invoice.paidAmount)}</span>
</div>
<div className='flex justify-between text-xs'>
<span className='text-[#8C90A3]'>مانده:</span>
<span className='text-black font-medium'>{formatPrice(invoice.balance)}</span>
</div>
</div>
)}
{/* Notes Section */}
<div className='mt-8 pt-6 border-t border-[#EBEDF5]'>
<div className='text-xs text-[#8C90A3] leading-6 space-y-2'>
<div>۱- تمام تحویلها، سفارشات با تاکیب مناسب و منعطف، ۱۵ می باشد و مبلغ نهایی براساس تراز نهایی محاسبه میگردد.</div>
<div>۲- سفارشات چاپی با اختلاف رنگ ۱۰ الی ۱۵ درصد یا قابل طراحی شده میباشد</div>
</div>
</div>
</div>
{/* لیست پرداخت‌ها */}
<div className="mt-8">
<InvoicePaymentList invoiceId={id!} />
</div>
<ModalConfirm
open={!!confirmingItemId}
onClose={() => setConfirmingItemId(null)}
onConfirm={handleConfirmProforma}
title='تایید پیش فاکتور'
message='آیا از تایید این پیش فاکتور اطمینان دارید؟'
confirmLabel='تایید'
cancelLabel='انصراف'
isLoading={isConfirming}
/>
</div>
)
}
export default InvoiceDetail