detail invoice

This commit is contained in:
hamid zarghami
2026-02-21 15:39:19 +03:30
parent ad8d7e5f5e
commit 6a3e8fe5e5
4 changed files with 133 additions and 71 deletions
+111 -70
View File
@@ -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,
});
};
+13 -1
View File
@@ -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;
};
+1
View File
@@ -73,3 +73,4 @@ export type Invoice = {
};
export type InvoicesResponseType = BaseResponse<Invoice[]>;
export type InvoiceDetailResponseType = BaseResponse<Invoice>;