invoice list

This commit is contained in:
hamid zarghami
2026-02-21 15:10:24 +03:30
parent 4b1788a80c
commit ad8d7e5f5e
6 changed files with 247 additions and 71 deletions
+153 -68
View File
@@ -1,104 +1,189 @@
import Tabs from '@/components/Tabs'
import { useState, type FC } from 'react'
import { useMemo, useState, type FC } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { ProformaInvoiceStatusEnum } from './enum/InvoiceEnum'
import Filters from '@/components/Filters'
import Table from '@/components/Table'
import { useGetInvoices } from './hooks/useInvoiceData'
import type { Invoice } from './types/InvoiceTypes'
import type { ColumnType } from '@/components/types/TableTypes'
import type { FilterValues } from '@/components/Filters'
import { NumberFormat } from '@/config/func'
import { Paths } from '@/config/Paths'
import moment from 'moment-jalaali'
import StatusWithText from '@/components/StatusWithText'
import { Eye } from 'iconsax-react'
const getConfirmStatus = (invoice: Invoice): 'confirmed' | 'not_confirmed' => {
const allConfirmed = invoice.items.length > 0 && invoice.items.every((item) => item.confirmedAt !== null)
return allConfirmed ? 'confirmed' : 'not_confirmed'
}
const getServiceNames = (invoice: Invoice): string => {
const names = [...new Set(invoice.items.map((item) => item.product.title))]
return names.join('، ') || '-'
}
const ProformaInvoice: FC = () => {
const navigate = useNavigate()
const [activeTab, setActiveTab] = useState<ProformaInvoiceStatusEnum>(ProformaInvoiceStatusEnum.ALL)
const [filters, setFilters] = useState<FilterValues>({})
const [activeTab, setactiveTab] = useState<ProformaInvoiceStatusEnum>(ProformaInvoiceStatusEnum.ALL)
const { data, isPending } = useGetInvoices()
const invoices = useMemo(() => data?.data ?? [], [data?.data])
const filteredInvoices = useMemo(() => {
let result = invoices
// فیلتر تب
if (activeTab === ProformaInvoiceStatusEnum.NOT_CONFIRMED) {
result = result.filter((inv) => getConfirmStatus(inv) === 'not_confirmed')
} else if (activeTab === ProformaInvoiceStatusEnum.CONFIRMED) {
result = result.filter((inv) => getConfirmStatus(inv) === 'confirmed')
}
// فیلتر جستجو
const search = filters.search?.toString().trim()
if (search) {
const lower = search.toLowerCase()
result = result.filter(
(inv) =>
String(inv.invoiceNumber).includes(search) ||
inv.user?.phone?.includes(search) ||
inv.description?.toLowerCase().includes(lower) ||
inv.items.some((item) => item.product.title.toLowerCase().includes(lower))
)
}
// فیلتر تاریخ
const dateFilter = filters.date?.toString()
if (dateFilter) {
result = result.filter((inv) => {
const createdAt = moment(inv.createdAt).format('YYYY-MM-DD')
return createdAt === dateFilter
})
}
return result
}, [invoices, activeTab, filters])
const columns: ColumnType<Invoice>[] = [
{
key: 'invoiceNumber',
title: 'شماره',
render: (item) => <span className="font-medium">{item.invoiceNumber}</span>,
},
{
key: 'service',
title: 'خدمت',
render: (item) => <span className="max-w-[150px] truncate block">{getServiceNames(item)}</span>,
},
{
key: 'issueDate',
title: 'تاریخ صدور',
render: (item) => moment(item.createdAt).format('jYYYY/jMM/jDD'),
},
{
key: 'lastConfirmDate',
title: 'آخرین مهلت تایید',
render: (item) => moment(item.approvalDeadline).format('jYYYY/jMM/jDD'),
},
{
key: 'total',
title: 'مجموع',
render: (item) => <span>{NumberFormat(item.total)} تومان</span>,
},
{
key: 'confirmStatus',
title: 'وضعیت تایید',
render: (item) => {
const status = getConfirmStatus(item)
return (
<StatusWithText
variant={status === 'confirmed' ? 'success' : 'warning'}
text={status === 'confirmed' ? 'تایید شده' : 'تایید نشده'}
/>
)
},
},
{
key: 'paymentStatus',
title: 'وضعیت پرداخت',
render: (item) => {
const isPaid = item.paidAmount >= item.total && item.total > 0
return (
<StatusWithText
variant={isPaid ? 'success' : 'info'}
text={isPaid ? 'پرداخت شده' : 'پرداخت نشده'}
/>
)
},
},
{
key: 'action',
title: '',
render: (item) => {
return (
<Link to={Paths.proformaInvoice + `/${item.id}`}>
<Eye
color='black'
size={18}
/>
</Link>
)
},
},
]
const handleRowClick = (id: string) => {
navigate(`${Paths.proformaInvoice}/${id}`)
}
return (
<div className='mt-5'>
<h1>
پیش فاکتورها
</h1>
<div className='mt-8'>
<div className="mt-5">
<h1 className="text-base font-medium">پیش فاکتورها</h1>
<div className="mt-8">
<Tabs
items={[
{ label: 'همه', value: 'all' },
{ label: 'تایید نشده', value: 'not_confirmed' },
{ label: 'تایید شده', value: 'confirmed' },
{ label: 'همه', value: ProformaInvoiceStatusEnum.ALL },
{ label: 'تایید نشده', value: ProformaInvoiceStatusEnum.NOT_CONFIRMED },
{ label: 'تایید شده', value: ProformaInvoiceStatusEnum.CONFIRMED },
]}
activeTab={activeTab}
onTabChange={(tab) => setactiveTab(tab as ProformaInvoiceStatusEnum)}
onTabChange={(tab) => setActiveTab(tab as ProformaInvoiceStatusEnum)}
/>
</div>
<div className='mt-8'>
<div className="mt-8">
<Filters
fields={[
{
type: 'input',
name: 'search',
placeholder: 'جستجو',
placeholder: 'جستجو (شماره، تلفن، خدمات)',
},
{
type: 'date',
name: 'date',
placeholder: 'تاریخ',
placeholder: 'تاریخ صدور',
},
]}
onChange={() => { }}
onChange={setFilters}
/>
</div>
<div className='mt-8'>
<Table
columns={[
{
key: 'id',
title: 'شماره',
},
{
key: 'service',
title: 'خدمت',
},
{
key: 'issueDate',
title: 'تاریخ صدور',
},
{
key: 'lastConfirmDate',
title: 'آخرین مهلت تایید',
},
{
key: 'total',
title: 'مجموع',
},
{
key: 'confirmStatus',
title: 'وضعیت تایید',
},
{
key: 'paymentStatus',
title: 'وضعیت پرداخت',
}
]}
data={[
{
id: '1',
service: 'خدمت 1',
issueDate: '2021-01-01',
lastConfirmDate: '2021-01-01',
total: 100000,
confirmStatus: 'تایید شده',
paymentStatus: 'پرداخت شده',
},
{
id: '2',
service: 'خدمت 2',
issueDate: '2021-01-01',
lastConfirmDate: '2021-01-01',
total: 100000,
confirmStatus: 'تایید شده',
paymentStatus: 'پرداخت شده',
},
]}
<div className="mt-8">
<Table<Invoice>
columns={columns}
data={filteredInvoices}
isLoading={isPending}
onRowClick={handleRowClick}
noDataMessage="پیش فاکتوری یافت نشد"
/>
</div>
</div>
)
}
export default ProformaInvoice
export default ProformaInvoice
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import * as api from "../service/InvoiceService";
export const useGetInvoices = () => {
return useQuery({
queryKey: ["invoices"],
queryFn: api.getInvoices,
});
};
@@ -0,0 +1,7 @@
import axios from "@/config/axios";
import type { InvoicesResponseType } from "../types/InvoiceTypes";
export const getInvoices = async (): Promise<InvoicesResponseType> => {
const { data } = await axios.get<InvoicesResponseType>("/public/invoice");
return data;
};
+75
View File
@@ -0,0 +1,75 @@
import type { BaseResponse } from "@/shared/types/Types";
export type InvoiceUser = {
id: string;
createdAt: string;
deletedAt: string | null;
firstName: string | null;
lastName: string | null;
isActive: boolean;
gender: boolean;
maxCredit: number;
addresse: string | null;
phone: string;
};
export type InvoiceRequest = {
id: string;
createdAt: string;
deletedAt: string | null;
user: string;
requestNumber: number;
status: string;
};
export type InvoiceProduct = {
id: string;
createdAt: string;
deletedAt: string | null;
category: string;
title: string;
desc: string;
quantities: number[];
linkUrl: string | null;
isActive: boolean;
images: string[];
order: number;
};
export type InvoiceItem = {
id: string;
createdAt: string;
deletedAt: string | null;
invoice: string;
product: InvoiceProduct;
quantity: number;
unitPrice: number;
subTotal: number | null;
discount: number;
total: number | null;
description: string | null;
confirmedAt: string | null;
};
export type Invoice = {
id: string;
createdAt: string;
deletedAt: string | null;
user: InvoiceUser;
request: InvoiceRequest;
invoiceNumber: number;
discount: number;
subTotal: number;
taxAmount: number;
total: number;
paidAmount: number;
balance: number;
enableTax: boolean;
approvalDeadline: string;
attachments: unknown[];
paymentMethod: string;
description: string | null;
items: InvoiceItem[];
};
export type InvoicesResponseType = BaseResponse<Invoice[]>;