From cea411b9120b7d510e87fde6a853bb61904839ce Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Thu, 23 Jul 2026 18:13:39 +0330 Subject: [PATCH] invoice --- src/config/statusRegistry.ts | 12 ++-- src/pages/chat/components/ChatMessage.tsx | 9 +-- src/pages/chat/type/Types.ts | 2 +- src/pages/home/components/Stats.tsx | 2 +- src/pages/invoice/ProformaInvoice.tsx | 76 +++++++++------------ src/pages/invoice/enum/InvoiceEnum.tsx | 28 ++++++-- src/pages/invoice/hooks/useInvoiceData.ts | 6 +- src/pages/invoice/service/InvoiceService.ts | 16 ++++- src/pages/invoice/types/InvoiceTypes.ts | 2 +- 9 files changed, 88 insertions(+), 65 deletions(-) diff --git a/src/config/statusRegistry.ts b/src/config/statusRegistry.ts index ff5e0a9..366ba7e 100644 --- a/src/config/statusRegistry.ts +++ b/src/config/statusRegistry.ts @@ -17,13 +17,15 @@ export type StatusConfig = { */ export const statusRegistry: Record = { // --- ProformaInvoiceStatusEnum --- - [ProformaInvoiceStatusEnum.ALL]: { label: 'همه', variant: 'neutral' }, - [ProformaInvoiceStatusEnum.NOT_CONFIRMED]: { label: 'تایید نشده', variant: 'warning' }, - [ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED]: { label: 'تایید جزئی', variant: 'info' }, + [ProformaInvoiceStatusEnum.PENDING]: { label: 'در انتظار تایید', variant: 'warning' }, [ProformaInvoiceStatusEnum.CONFIRMED]: { label: 'تایید شده', variant: 'success' }, + [ProformaInvoiceStatusEnum.ARCHIVED]: { label: 'آرشیو شده', variant: 'neutral' }, - // --- Invoice confirm status (from API; partially_confirmed/confirmed covered by enum above) --- - pending: { label: 'تایید نشده', variant: 'warning' }, + // --- Invoice confirm status (from API) --- + pending: { label: 'در انتظار تایید', variant: 'warning' }, + partially_confirmed: { label: 'تایید جزئی', variant: 'info' }, + confirmed: { label: 'تایید شده', variant: 'success' }, + archived: { label: 'آرشیو شده', variant: 'neutral' }, // --- OrderStatusEnum --- [OrderStatusEnum.CREATED]: { label: 'ایجاد شده', variant: 'info' }, diff --git a/src/pages/chat/components/ChatMessage.tsx b/src/pages/chat/components/ChatMessage.tsx index 3491423..a9c667a 100644 --- a/src/pages/chat/components/ChatMessage.tsx +++ b/src/pages/chat/components/ChatMessage.tsx @@ -10,7 +10,7 @@ import { getChatSenderName } from '../type/Types' type Props = { content: string - attachments?: ChatAttachmentType[] + attachments?: ChatAttachmentType[] | null senderName?: string senderLabel?: string createdAt?: string @@ -25,7 +25,7 @@ type Props = { const ChatMessage: FC = ({ content, - attachments = [], + attachments, senderName, senderLabel = 'پشتیبان', createdAt, @@ -37,8 +37,9 @@ const ChatMessage: FC = ({ parentSenderLabel, onReply, }) => { - const fileAttachments = attachments.filter((a) => a.type !== 'voice') - const voiceAttachments = attachments.filter((a) => a.type === 'voice') + const safeAttachments = attachments ?? [] + const fileAttachments = safeAttachments.filter((a) => a.type !== 'voice') + const voiceAttachments = safeAttachments.filter((a) => a.type === 'voice') const handleOpenLink = async (key: string) => { const url = await getPresignedUrl(key) diff --git a/src/pages/chat/type/Types.ts b/src/pages/chat/type/Types.ts index 3fba1e4..9e34f97 100644 --- a/src/pages/chat/type/Types.ts +++ b/src/pages/chat/type/Types.ts @@ -29,7 +29,7 @@ export type ChatParentMessageType = { export type ChatMessageType = { admin?: ChatParticipantType | null user?: ChatParticipantType | null - attachments: ChatAttachmentType[] + attachments?: ChatAttachmentType[] | null content: string createdAt: string id: string diff --git a/src/pages/home/components/Stats.tsx b/src/pages/home/components/Stats.tsx index c5cd47e..e610759 100644 --- a/src/pages/home/components/Stats.tsx +++ b/src/pages/home/components/Stats.tsx @@ -33,7 +33,7 @@ const Stats: FC = () => { tab.value) + const isInvoiceTab = (value: string | null): value is ProformaInvoiceStatusEnum => - value === ProformaInvoiceStatusEnum.ALL || - value === ProformaInvoiceStatusEnum.NOT_CONFIRMED || - value === ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED || - value === ProformaInvoiceStatusEnum.CONFIRMED + value !== null && INVOICE_TABS.includes(value as ProformaInvoiceStatusEnum) const getServiceNames = (invoice: Invoice): string => { const names = [...new Set(invoice.items.map((item) => item.product.title))] @@ -27,29 +30,32 @@ const getServiceNames = (invoice: Invoice): string => { const ProformaInvoice: FC = () => { const navigate = useNavigate() - const [searchParams] = useSearchParams() + const [searchParams, setSearchParams] = useSearchParams() const tabFromQuery = searchParams.get('tab') - const [activeTab, setActiveTab] = useState( - isInvoiceTab(tabFromQuery) ? tabFromQuery : ProformaInvoiceStatusEnum.ALL, - ) + const activeTab = isInvoiceTab(tabFromQuery) + ? tabFromQuery + : ProformaInvoiceStatusEnum.PENDING const [filters, setFilters] = useState({}) - const { data, isPending } = useGetInvoices() + 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 invoices = useMemo(() => data?.data ?? [], [data?.data]) const filteredInvoices = useMemo(() => { let result = invoices - // فیلتر تب - if (activeTab === ProformaInvoiceStatusEnum.NOT_CONFIRMED) { - result = result.filter((inv) => inv.confirmStatus === 'pending') - } else if (activeTab === ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED) { - result = result.filter((inv) => inv.confirmStatus === 'partially_confirmed') - } else if (activeTab === ProformaInvoiceStatusEnum.CONFIRMED) { - result = result.filter((inv) => inv.confirmStatus === 'confirmed') - } - - // فیلتر جستجو const search = filters.search?.toString().trim() if (search) { const lower = search.toLowerCase() @@ -62,7 +68,6 @@ const ProformaInvoice: FC = () => { ) } - // فیلتر تاریخ const dateFilter = filters.date?.toString() if (dateFilter) { result = result.filter((inv) => { @@ -72,7 +77,7 @@ const ProformaInvoice: FC = () => { } return result - }, [invoices, activeTab, filters]) + }, [invoices, filters]) const columns: ColumnType[] = [ { @@ -101,15 +106,16 @@ const ProformaInvoice: FC = () => { render: (item) => {NumberFormat(item.total)} ریال, }, { - key: 'confirmStatus', + 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: 'تایید نشده' }, + pending: { variant: 'warning' as const, text: 'در انتظار تایید' }, + archived: { variant: 'info' as const, text: 'آرشیو شده' }, } - const status = statusMap[item.confirmStatus] ?? statusMap.pending + const status = statusMap[item.status] ?? statusMap.pending return ( { ) }, }, - { - key: 'paymentStatus', - title: 'وضعیت پرداخت', - render: (item) => { - const isPaid = item.paidAmount >= item.total && item.total > 0 - return ( - - ) - }, - }, { key: 'action', title: '', @@ -157,12 +150,7 @@ const ProformaInvoice: FC = () => {
setActiveTab(tab as ProformaInvoiceStatusEnum)} /> diff --git a/src/pages/invoice/enum/InvoiceEnum.tsx b/src/pages/invoice/enum/InvoiceEnum.tsx index 4370bb3..583c923 100644 --- a/src/pages/invoice/enum/InvoiceEnum.tsx +++ b/src/pages/invoice/enum/InvoiceEnum.tsx @@ -1,6 +1,26 @@ export const enum ProformaInvoiceStatusEnum { - ALL = 'all', - NOT_CONFIRMED = 'not_confirmed', - PARTIALLY_CONFIRMED = 'partially_confirmed', + PENDING = 'pending', CONFIRMED = 'confirmed', -} \ No newline at end of file + ARCHIVED = 'archived', +} + +export type InvoiceConfirmStatus = + | 'pending' + | 'partially_confirmed' + | 'confirmed' + | 'archived' + +export const invoiceListTabStatuses: Record< + ProformaInvoiceStatusEnum, + InvoiceConfirmStatus[] +> = { + [ProformaInvoiceStatusEnum.PENDING]: ['pending'], + [ProformaInvoiceStatusEnum.CONFIRMED]: ['partially_confirmed', 'confirmed'], + [ProformaInvoiceStatusEnum.ARCHIVED]: ['archived'], +} + +export const invoiceListTabs = [ + { label: 'در انتظار تایید', value: ProformaInvoiceStatusEnum.PENDING }, + { label: 'تایید شده', value: ProformaInvoiceStatusEnum.CONFIRMED }, + { label: 'آرشیو شده', value: ProformaInvoiceStatusEnum.ARCHIVED }, +] as const diff --git a/src/pages/invoice/hooks/useInvoiceData.ts b/src/pages/invoice/hooks/useInvoiceData.ts index 0b7d276..3a57d65 100644 --- a/src/pages/invoice/hooks/useInvoiceData.ts +++ b/src/pages/invoice/hooks/useInvoiceData.ts @@ -1,10 +1,10 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import * as api from "../service/InvoiceService"; -export const useGetInvoices = () => { +export const useGetInvoices = (params: api.GetInvoicesParams) => { return useQuery({ - queryKey: ["invoices"], - queryFn: api.getInvoices, + queryKey: ["invoices", params.statuses?.join(",") ?? ""], + queryFn: () => api.getInvoices(params), }); }; diff --git a/src/pages/invoice/service/InvoiceService.ts b/src/pages/invoice/service/InvoiceService.ts index 52d142a..9040844 100644 --- a/src/pages/invoice/service/InvoiceService.ts +++ b/src/pages/invoice/service/InvoiceService.ts @@ -3,9 +3,21 @@ import type { InvoicesResponseType, InvoiceDetailResponseType, } from "../types/InvoiceTypes"; +import type { InvoiceConfirmStatus } from "../enum/InvoiceEnum"; -export const getInvoices = async (): Promise => { - const { data } = await axios.get("/public/invoice"); +export type GetInvoicesParams = { + statuses?: InvoiceConfirmStatus[]; +}; + +export const getInvoices = async ( + params: GetInvoicesParams = {}, +): Promise => { + const { statuses } = params; + const { data } = await axios.get("/public/invoice", { + params: { + ...(statuses?.length ? { statuses: statuses.join(",") } : {}), + }, + }); return data; }; diff --git a/src/pages/invoice/types/InvoiceTypes.ts b/src/pages/invoice/types/InvoiceTypes.ts index 54a6b0c..f3e0969 100644 --- a/src/pages/invoice/types/InvoiceTypes.ts +++ b/src/pages/invoice/types/InvoiceTypes.ts @@ -55,7 +55,7 @@ export type Invoice = { total: number; paidAmount: number; balance: number; - confirmStatus: 'pending' | 'partially_confirmed' | 'confirmed'; + status: 'pending' | 'partially_confirmed' | 'confirmed' | 'archived'; enableTax: boolean; approvalDeadline: string; attachments: unknown[];