detail invoice
This commit is contained in:
+111
-70
@@ -4,8 +4,13 @@ import { Printer, TickCircle } from 'iconsax-react'
|
||||
import Button from '@/components/Button'
|
||||
import Table from '@/components/Table'
|
||||
import type { RowDataType } from '@/components/types/TableTypes'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useGetInvoiceDetail } from './hooks/useInvoiceData'
|
||||
import { NumberFormat } from '@/config/func'
|
||||
import moment from 'moment-jalaali'
|
||||
import type { InvoiceItem as ApiInvoiceItem } from './types/InvoiceTypes'
|
||||
|
||||
interface InvoiceItem extends RowDataType {
|
||||
interface TableInvoiceItem extends RowDataType {
|
||||
id: string
|
||||
image: string | null
|
||||
title: string
|
||||
@@ -16,40 +21,42 @@ interface InvoiceItem extends RowDataType {
|
||||
confirmed: boolean
|
||||
}
|
||||
|
||||
const formatPrice = (num: number) => `${NumberFormat(num)} تومان`
|
||||
|
||||
const InvoiceDetail: FC = () => {
|
||||
const { id } = useParams()
|
||||
const { data, isPending } = useGetInvoiceDetail(id!)
|
||||
const invoice = data?.data
|
||||
|
||||
const invoiceData = {
|
||||
invoiceNumber: '#۱۲۳۴۵',
|
||||
confirmDate: '۱۴۰۴/۰۵/۰۳',
|
||||
customerName: 'آلومینیوم نوین',
|
||||
orderDate: '۱۴۰۴/۰۵/۰۳',
|
||||
phoneNumber: '۰۸۶۳۳۲۳۳۳۴۵',
|
||||
orderNumber: '۰۹۱۲۳۴۵۶۷۸۹',
|
||||
address: 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است.لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است.',
|
||||
}
|
||||
|
||||
const items: InvoiceItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
image: null,
|
||||
title: 'شاپینگ بک : پوشش UV برای و پوشش سلفون مات و UV برای و پرجسته',
|
||||
quantity: '۲۰۰۰',
|
||||
unitPrice: '۳,۲۰۵,۰۰۰ ریال',
|
||||
discount: '۱۰٪',
|
||||
totalPrice: '۸,۸۵۰,۰۰۰,۰۰۰ ریال',
|
||||
confirmed: true
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
image: null,
|
||||
title: 'شاپینگ بک : پوشش UV برای و پوشش سلفون مات و UV برای و پرجسته',
|
||||
quantity: '۱۰۰۰',
|
||||
unitPrice: '۳,۲۰۵,۰۰۰ ریال',
|
||||
discount: '۰',
|
||||
totalPrice: '۳,۲۰۵,۰۰۰,۰۰۰ ریال',
|
||||
confirmed: true
|
||||
const invoiceData = invoice
|
||||
? {
|
||||
invoiceNumber: String(invoice.invoiceNumber),
|
||||
confirmDate: invoice.items?.[0]?.confirmedAt
|
||||
? moment(invoice.items[0].confirmedAt).format('jYYYY/jMM/jDD')
|
||||
: '-',
|
||||
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,
|
||||
})) ?? []
|
||||
|
||||
const handlePrint = () => {
|
||||
window.print()
|
||||
@@ -59,47 +66,22 @@ const InvoiceDetail: FC = () => {
|
||||
{
|
||||
key: 'title',
|
||||
title: 'عنوان',
|
||||
render: (item: InvoiceItem) => (
|
||||
<div className='text-right'>
|
||||
<div className='text-sm text-black'>{item.title}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'quantity',
|
||||
title: 'تعداد',
|
||||
render: (item: InvoiceItem) => (
|
||||
<div className='text-center'>
|
||||
<div className='text-black'>{item.quantity}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'unitPrice',
|
||||
title: 'مبلغ واحد',
|
||||
render: (item: InvoiceItem) => (
|
||||
<div className='text-center'>
|
||||
<div className='text-black'>{item.unitPrice}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'discount',
|
||||
title: 'تخفیف',
|
||||
render: (item: InvoiceItem) => (
|
||||
<div className='text-center'>
|
||||
<div className='text-black'>{item.discount}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'totalPrice',
|
||||
title: 'مبلغ کل',
|
||||
render: (item: InvoiceItem) => (
|
||||
<div className='text-center'>
|
||||
<div className='text-black'>{item.totalPrice}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'image',
|
||||
@@ -116,6 +98,24 @@ const InvoiceDetail: FC = () => {
|
||||
}
|
||||
]
|
||||
|
||||
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'>
|
||||
<Helmet>
|
||||
@@ -135,8 +135,14 @@ const InvoiceDetail: FC = () => {
|
||||
<Printer size={20} color='black' />
|
||||
<span className='text-xs'>چاپ</span>
|
||||
</Button>
|
||||
<Button className='bg-[#E8F0FF] text-[#0047FF] w-[120px] h-9 text-xs'>
|
||||
پرداخت نشده
|
||||
<Button
|
||||
className={
|
||||
isPaid
|
||||
? 'bg-[#E8F5E9] text-[#01AC45] w-[120px] h-9 text-xs'
|
||||
: 'bg-[#E8F0FF] text-[#0047FF] w-[120px] h-9 text-xs'
|
||||
}
|
||||
>
|
||||
{isPaid ? 'پرداخت شده' : 'پرداخت نشده'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,33 +151,33 @@ const InvoiceDetail: FC = () => {
|
||||
<div className='bg-white rounded-2xl p-8'>
|
||||
<div className='text-sm mb-6'>اطلاعات صورت حساب</div>
|
||||
|
||||
<div className='flex justify-between gap-x-12 gap-y-5 text-xs'>
|
||||
<div className='flex flex-col gap-1.5'>
|
||||
<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 flex-col gap-1.5'>
|
||||
<div className='flex gap-1.5'>
|
||||
<span className='text-[#8C90A3]'>شماره همراه:</span>
|
||||
<span className='text-black'>{invoiceData.orderNumber}</span>
|
||||
<span className='text-black'>{invoiceData?.phoneNumber}</span>
|
||||
</div>
|
||||
<div className='flex flex-col gap-1.5'>
|
||||
<div className='flex gap-1.5'>
|
||||
<span className='text-[#8C90A3]'>تاریخ تایید پیش فاکتور:</span>
|
||||
<span className='text-black'>{invoiceData.confirmDate}</span>
|
||||
</div>
|
||||
<div className='flex flex-col gap-1.5'>
|
||||
<div className='flex gap-1.5'>
|
||||
<span className='text-[#8C90A3]'>مشتری:</span>
|
||||
<span className='text-black'>{invoiceData.customerName}</span>
|
||||
</div>
|
||||
<div className='flex flex-col gap-1.5'>
|
||||
<span className='text-[#8C90A3]'>شماره ثابت:</span>
|
||||
<span className='text-black'>{invoiceData.phoneNumber}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-5 flex flex-col gap-1.5 text-xs'>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* بخش دوم: اقلام درخواستی */}
|
||||
@@ -182,8 +188,43 @@ const InvoiceDetail: FC = () => {
|
||||
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'>
|
||||
|
||||
@@ -7,3 +7,11 @@ export const useGetInvoices = () => {
|
||||
queryFn: api.getInvoices,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetInvoiceDetail = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["invoice", id],
|
||||
queryFn: () => api.getInvoiceDetail(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import axios from "@/config/axios";
|
||||
import type { InvoicesResponseType } from "../types/InvoiceTypes";
|
||||
import type {
|
||||
InvoicesResponseType,
|
||||
InvoiceDetailResponseType,
|
||||
} from "../types/InvoiceTypes";
|
||||
|
||||
export const getInvoices = async (): Promise<InvoicesResponseType> => {
|
||||
const { data } = await axios.get<InvoicesResponseType>("/public/invoice");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getInvoiceDetail = async (
|
||||
id: string
|
||||
): Promise<InvoiceDetailResponseType> => {
|
||||
const { data } = await axios.get<InvoiceDetailResponseType>(
|
||||
`/public/invoice/${id}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -73,3 +73,4 @@ export type Invoice = {
|
||||
};
|
||||
|
||||
export type InvoicesResponseType = BaseResponse<Invoice[]>;
|
||||
export type InvoiceDetailResponseType = BaseResponse<Invoice>;
|
||||
|
||||
Reference in New Issue
Block a user