Files
negareh-console/src/pages/invoice/ProformaInvoice.tsx
T
2026-07-23 18:47:48 +03:30

212 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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