314 lines
14 KiB
TypeScript
314 lines
14 KiB
TypeScript
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
|