From ad8d7e5f5e35c83553bfdb1566f814895637279d Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 21 Feb 2026 15:10:24 +0330 Subject: [PATCH] invoice list --- .env | 4 +- src/components/StatusWithText.tsx | 2 +- src/pages/invoice/ProformaInvoice.tsx | 221 ++++++++++++++------ src/pages/invoice/hooks/useInvoiceData.ts | 9 + src/pages/invoice/service/InvoiceService.ts | 7 + src/pages/invoice/types/InvoiceTypes.ts | 75 +++++++ 6 files changed, 247 insertions(+), 71 deletions(-) create mode 100644 src/pages/invoice/hooks/useInvoiceData.ts create mode 100644 src/pages/invoice/service/InvoiceService.ts create mode 100644 src/pages/invoice/types/InvoiceTypes.ts diff --git a/.env b/.env index 2d11057..f608736 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ VITE_TOKEN_NAME = 'negareh_t' VITE_REFRESH_TOKEN_NAME = 'negareh_rt' -VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com' -# VITE_API_BASE_URL = 'http://10.164.213.88:4000' +# VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com' +VITE_API_BASE_URL = 'http://10.164.213.88:4000' diff --git a/src/components/StatusWithText.tsx b/src/components/StatusWithText.tsx index 02a40cf..d39f3f9 100644 --- a/src/components/StatusWithText.tsx +++ b/src/components/StatusWithText.tsx @@ -17,7 +17,7 @@ const StatusWithText: FC = ({ variant, text, className }) => { return (
diff --git a/src/pages/invoice/ProformaInvoice.tsx b/src/pages/invoice/ProformaInvoice.tsx index 851c83d..288b918 100644 --- a/src/pages/invoice/ProformaInvoice.tsx +++ b/src/pages/invoice/ProformaInvoice.tsx @@ -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.ALL) + const [filters, setFilters] = useState({}) - const [activeTab, setactiveTab] = useState(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[] = [ + { + key: 'invoiceNumber', + title: 'شماره', + render: (item) => {item.invoiceNumber}, + }, + { + key: 'service', + title: 'خدمت', + render: (item) => {getServiceNames(item)}, + }, + { + 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) => {NumberFormat(item.total)} تومان, + }, + { + key: 'confirmStatus', + title: 'وضعیت تایید', + render: (item) => { + const status = getConfirmStatus(item) + return ( + + ) + }, + }, + { + key: 'paymentStatus', + title: 'وضعیت پرداخت', + render: (item) => { + const isPaid = item.paidAmount >= item.total && item.total > 0 + return ( + + ) + }, + }, + { + key: 'action', + title: '', + render: (item) => { + return ( + + + + ) + }, + }, + ] + + const handleRowClick = (id: string) => { + navigate(`${Paths.proformaInvoice}/${id}`) + } return ( -
-

- پیش فاکتورها -

-
+
+

پیش فاکتورها

+ +
setactiveTab(tab as ProformaInvoiceStatusEnum)} + onTabChange={(tab) => setActiveTab(tab as ProformaInvoiceStatusEnum)} />
-
+
{ }} + onChange={setFilters} />
-
- + + columns={columns} + data={filteredInvoices} + isLoading={isPending} + onRowClick={handleRowClick} + noDataMessage="پیش فاکتوری یافت نشد" /> ) } -export default ProformaInvoice \ No newline at end of file +export default ProformaInvoice diff --git a/src/pages/invoice/hooks/useInvoiceData.ts b/src/pages/invoice/hooks/useInvoiceData.ts new file mode 100644 index 0000000..9333d68 --- /dev/null +++ b/src/pages/invoice/hooks/useInvoiceData.ts @@ -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, + }); +}; diff --git a/src/pages/invoice/service/InvoiceService.ts b/src/pages/invoice/service/InvoiceService.ts new file mode 100644 index 0000000..3b7ac78 --- /dev/null +++ b/src/pages/invoice/service/InvoiceService.ts @@ -0,0 +1,7 @@ +import axios from "@/config/axios"; +import type { InvoicesResponseType } from "../types/InvoiceTypes"; + +export const getInvoices = async (): Promise => { + const { data } = await axios.get("/public/invoice"); + return data; +}; diff --git a/src/pages/invoice/types/InvoiceTypes.ts b/src/pages/invoice/types/InvoiceTypes.ts new file mode 100644 index 0000000..ef49225 --- /dev/null +++ b/src/pages/invoice/types/InvoiceTypes.ts @@ -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;