212 lines
7.3 KiB
TypeScript
212 lines
7.3 KiB
TypeScript
import Tabs from '@/components/Tabs'
|
||
import { useMemo, useState, type FC } from 'react'
|
||
import { Link, useNavigate, useSearchParams } from 'react-router-dom'
|
||
import {
|
||
invoiceListTabs,
|
||
invoiceListTabStatuses,
|
||
ProformaInvoiceStatusEnum,
|
||
} from './enum/InvoiceEnum'
|
||
import Filters from '@/components/Filters'
|
||
import Table from '@/components/Table'
|
||
import { useGetInvoices, useGetInvoiceTabCounts } 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 INVOICE_TABS = invoiceListTabs.map((tab) => tab.value)
|
||
|
||
const isInvoiceTab = (value: string | null): value is ProformaInvoiceStatusEnum =>
|
||
value !== null && INVOICE_TABS.includes(value as ProformaInvoiceStatusEnum)
|
||
|
||
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 [searchParams, setSearchParams] = useSearchParams()
|
||
const tabFromQuery = searchParams.get('tab')
|
||
const activeTab = isInvoiceTab(tabFromQuery)
|
||
? tabFromQuery
|
||
: ProformaInvoiceStatusEnum.PENDING
|
||
const [filters, setFilters] = useState<FilterValues>({})
|
||
|
||
const statuses = invoiceListTabStatuses[activeTab]
|
||
|
||
const setActiveTab = (tab: ProformaInvoiceStatusEnum) => {
|
||
setSearchParams(
|
||
(current) => {
|
||
const next = new URLSearchParams(current)
|
||
next.set('tab', tab)
|
||
return next
|
||
},
|
||
{ replace: true },
|
||
)
|
||
}
|
||
|
||
const { data, isPending } = useGetInvoices({ statuses })
|
||
const { data: tabCounts } = useGetInvoiceTabCounts()
|
||
const invoices = useMemo(() => data?.data ?? [], [data?.data])
|
||
|
||
const tabsWithCount = useMemo(
|
||
() =>
|
||
invoiceListTabs.map((tab) => {
|
||
const total =
|
||
tab.value === ProformaInvoiceStatusEnum.PENDING
|
||
? (tabCounts?.pending ?? 0)
|
||
: tab.value === ProformaInvoiceStatusEnum.CONFIRMED
|
||
? (tabCounts?.confirmed ?? 0)
|
||
: (tabCounts?.archived ?? 0)
|
||
return {
|
||
...tab,
|
||
label:
|
||
total > 0
|
||
? `${tab.label} (${total.toLocaleString('fa-IR')})`
|
||
: tab.label,
|
||
}
|
||
}),
|
||
[tabCounts],
|
||
)
|
||
|
||
const filteredInvoices = useMemo(() => {
|
||
let result = invoices
|
||
|
||
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, 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: 'status',
|
||
title: 'وضعیت تایید',
|
||
render: (item) => {
|
||
const statusMap = {
|
||
confirmed: { variant: 'success' as const, text: 'تایید شده' },
|
||
partially_confirmed: { variant: 'info' as const, text: 'تایید جزئی' },
|
||
pending: { variant: 'warning' as const, text: 'در انتظار تایید' },
|
||
archived: { variant: 'info' as const, text: 'آرشیو شده' },
|
||
}
|
||
const status = statusMap[item.status] ?? statusMap.pending
|
||
return (
|
||
<StatusWithText
|
||
variant={status.variant}
|
||
text={status.text}
|
||
/>
|
||
)
|
||
},
|
||
},
|
||
{
|
||
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 className="text-base font-medium">پیش فاکتورها</h1>
|
||
|
||
<div className="mt-8">
|
||
<Tabs
|
||
items={tabsWithCount}
|
||
activeTab={activeTab}
|
||
onTabChange={(tab) => setActiveTab(tab as ProformaInvoiceStatusEnum)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="mt-8">
|
||
<Filters
|
||
fields={[
|
||
{
|
||
type: 'input',
|
||
name: 'search',
|
||
placeholder: 'جستجو (شماره، تلفن، خدمات)',
|
||
},
|
||
{
|
||
type: 'date',
|
||
name: 'date',
|
||
placeholder: 'تاریخ صدور',
|
||
},
|
||
]}
|
||
onChange={setFilters}
|
||
/>
|
||
</div>
|
||
|
||
<div className="mt-8">
|
||
<Table<Invoice>
|
||
columns={columns}
|
||
data={filteredInvoices}
|
||
isLoading={isPending}
|
||
onRowClick={handleRowClick}
|
||
noDataMessage="پیش فاکتوری یافت نشد"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default ProformaInvoice
|