invoice list
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
VITE_TOKEN_NAME = 'negareh_t'
|
VITE_TOKEN_NAME = 'negareh_t'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'negareh_rt'
|
VITE_REFRESH_TOKEN_NAME = 'negareh_rt'
|
||||||
VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com'
|
# VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com'
|
||||||
# VITE_API_BASE_URL = 'http://10.164.213.88:4000'
|
VITE_API_BASE_URL = 'http://10.164.213.88:4000'
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const StatusWithText: FC<Props> = ({ variant, text, className }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clx(
|
<div className={clx(
|
||||||
'px-2 py-1 rounded-lg text-xs font-medium whitespace-nowrap',
|
'px-2 py-1 w-fit rounded-lg text-xs font-medium whitespace-nowrap',
|
||||||
variantStyles[variant],
|
variantStyles[variant],
|
||||||
className
|
className
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@@ -1,100 +1,185 @@
|
|||||||
import Tabs from '@/components/Tabs'
|
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 { ProformaInvoiceStatusEnum } from './enum/InvoiceEnum'
|
||||||
import Filters from '@/components/Filters'
|
import Filters from '@/components/Filters'
|
||||||
import Table from '@/components/Table'
|
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 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 (
|
return (
|
||||||
<div className='mt-5'>
|
<div className="mt-5">
|
||||||
<h1>
|
<h1 className="text-base font-medium">پیش فاکتورها</h1>
|
||||||
پیش فاکتورها
|
|
||||||
</h1>
|
<div className="mt-8">
|
||||||
<div className='mt-8'>
|
|
||||||
<Tabs
|
<Tabs
|
||||||
items={[
|
items={[
|
||||||
{ label: 'همه', value: 'all' },
|
{ label: 'همه', value: ProformaInvoiceStatusEnum.ALL },
|
||||||
{ label: 'تایید نشده', value: 'not_confirmed' },
|
{ label: 'تایید نشده', value: ProformaInvoiceStatusEnum.NOT_CONFIRMED },
|
||||||
{ label: 'تایید شده', value: 'confirmed' },
|
{ label: 'تایید شده', value: ProformaInvoiceStatusEnum.CONFIRMED },
|
||||||
]}
|
]}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
onTabChange={(tab) => setactiveTab(tab as ProformaInvoiceStatusEnum)}
|
onTabChange={(tab) => setActiveTab(tab as ProformaInvoiceStatusEnum)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8'>
|
<div className="mt-8">
|
||||||
<Filters
|
<Filters
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'search',
|
name: 'search',
|
||||||
placeholder: 'جستجو',
|
placeholder: 'جستجو (شماره، تلفن، خدمات)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'date',
|
type: 'date',
|
||||||
name: 'date',
|
name: 'date',
|
||||||
placeholder: 'تاریخ',
|
placeholder: 'تاریخ صدور',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onChange={() => { }}
|
onChange={setFilters}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8'>
|
<div className="mt-8">
|
||||||
<Table
|
<Table<Invoice>
|
||||||
columns={[
|
columns={columns}
|
||||||
{
|
data={filteredInvoices}
|
||||||
key: 'id',
|
isLoading={isPending}
|
||||||
title: 'شماره',
|
onRowClick={handleRowClick}
|
||||||
},
|
noDataMessage="پیش فاکتوری یافت نشد"
|
||||||
{
|
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -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[]>;
|
||||||
Reference in New Issue
Block a user