diff --git a/src/components/ArchiveWithConfirm.tsx b/src/components/ArchiveWithConfirm.tsx new file mode 100644 index 0000000..be77039 --- /dev/null +++ b/src/components/ArchiveWithConfirm.tsx @@ -0,0 +1,46 @@ +import { Archive } from 'iconsax-react' +import { type FC, useState } from 'react' +import ModalConfrim from './ModalConfrim' + +interface Props { + onArchive: () => void + isloading?: boolean + colorIcon?: string + title?: string +} + +const ArchiveWithConfirm: FC = ({ + onArchive, + isloading = false, + colorIcon, + title = 'آرشیو', +}) => { + const [isConfirm, setIsConfirm] = useState(false) + + return ( +
+ + + setIsConfirm(false)} + onConfrim={() => { + onArchive() + setIsConfirm(false) + }} + isloading={isloading} + title_header="آرشیو پیش‌فاکتور" + label="آیا از آرشیو کردن این پیش‌فاکتور مطمئن هستید؟" + /> +
+ ) +} + +export default ArchiveWithConfirm diff --git a/src/pages/chat/components/ChatMessage.tsx b/src/pages/chat/components/ChatMessage.tsx index 62ab61f..d0de7bc 100644 --- a/src/pages/chat/components/ChatMessage.tsx +++ b/src/pages/chat/components/ChatMessage.tsx @@ -11,7 +11,7 @@ import { getChatSenderName } from '../type/Types' type Props = { content: string - attachments?: ChatAttachmentType[] + attachments?: ChatAttachmentType[] | null senderName?: string senderLabel?: string createdAt?: string @@ -29,7 +29,7 @@ type Props = { const ChatMessage: FC = ({ content, - attachments = [], + attachments, senderName, senderLabel = 'پشتیبان', createdAt, @@ -44,8 +44,9 @@ const ChatMessage: FC = ({ onDelete, isDeleting = false, }) => { - 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 b02838f..21385e6 100644 --- a/src/pages/home/components/Stats.tsx +++ b/src/pages/home/components/Stats.tsx @@ -33,7 +33,7 @@ const Stats: FC = () => { Number(amount).toLocaleString('fa-IR') + ' ریال'; @@ -110,9 +113,26 @@ const InvoiceItemTableRow: FC<{ item: InvoiceItemType }> = ({ item }) => { const DetailPerfomaInvoice: FC = () => { const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [isArchiveConfirmOpen, setIsArchiveConfirmOpen] = useState(false); const { data: invoiceData, isLoading, refetch, isFetching, isError } = useGetInvoiceDetail(id); + const { mutate: archiveInvoice, isPending: isArchiving } = useArchiveInvoice(); const invoice = invoiceData?.data; + const handleArchive = () => { + if (!id) return; + archiveInvoice(id, { + onSuccess: () => { + setIsArchiveConfirmOpen(false); + toast.success('پیش‌فاکتور با موفقیت آرشیو شد'); + navigate(Paths.perfomaInvoice.list); + }, + onError: (error) => { + toast.error(extractErrorMessage(error)); + }, + }); + }; + if (isLoading) { return (
@@ -154,18 +174,44 @@ const DetailPerfomaInvoice: FC = () => {
refetch()} isLoading={isFetching} /> - - - + {invoice.status !== 'archived' && ( + <> + + + + + + )} + {invoice.status === 'archived' && ( + + آرشیو شده + + )}
+ setIsArchiveConfirmOpen(false)} + onConfrim={handleArchive} + isloading={isArchiving} + title_header="آرشیو پیش‌فاکتور" + label="آیا از آرشیو کردن این پیش‌فاکتور مطمئن هستید؟" + /> +
اطلاعات پیش فاکتور
diff --git a/src/pages/invoice/ProformaInvoice.tsx b/src/pages/invoice/ProformaInvoice.tsx index a91e538..ff1e125 100644 --- a/src/pages/invoice/ProformaInvoice.tsx +++ b/src/pages/invoice/ProformaInvoice.tsx @@ -1,6 +1,10 @@ import Tabs from '@/components/Tabs' -import { useMemo, useState, type FC } from 'react' -import { ProformaInvoiceStatusEnum } from './enum/InvoiceEnum' +import { useCallback, useMemo, useState, type FC } from 'react' +import { + invoiceListTabs, + invoiceListTabStatuses, + ProformaInvoiceStatusEnum, +} from './enum/InvoiceEnum' import Filters from '@/components/Filters' import RefreshButton from '@/components/RefreshButton' import Table from '@/components/Table' @@ -9,15 +13,13 @@ import { Link, useSearchParams } from 'react-router-dom' import { Paths } from '@/config/Paths' import moment from 'moment-jalaali' import type { FilterValues } from '@/components/Filters' -import { useGetInvoice } from './hooks/useInvoiceData' +import { useArchiveInvoice, useGetInvoice } from './hooks/useInvoiceData' import type { InvoiceType } from './types/Types' +import ArchiveWithConfirm from '@/components/ArchiveWithConfirm' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/config/func' -const INVOICE_TABS: ProformaInvoiceStatusEnum[] = [ - ProformaInvoiceStatusEnum.ALL, - ProformaInvoiceStatusEnum.NOT_CONFIRMED, - ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED, - ProformaInvoiceStatusEnum.CONFIRMED, -] +const INVOICE_TABS = invoiceListTabs.map((tab) => tab.value) const isValidInvoiceTab = (value: string | null): value is ProformaInvoiceStatusEnum => value !== null && INVOICE_TABS.includes(value as ProformaInvoiceStatusEnum) @@ -25,43 +27,48 @@ const isValidInvoiceTab = (value: string | null): value is ProformaInvoiceStatus const ProformaInvoice: FC = () => { const [searchParams, setSearchParams] = useSearchParams() const tabParam = searchParams.get('tab') - const activeTab = isValidInvoiceTab(tabParam) ? tabParam : ProformaInvoiceStatusEnum.ALL + const activeTab = isValidInvoiceTab(tabParam) + ? tabParam + : ProformaInvoiceStatusEnum.PENDING const [page, setPage] = useState(1) const [filters, setFilters] = useState({}) + const statuses = invoiceListTabStatuses[activeTab] + const setActiveTab = (tab: ProformaInvoiceStatusEnum) => { + setPage(1) setSearchParams( (current) => { const next = new URLSearchParams(current) - if (tab === ProformaInvoiceStatusEnum.ALL) { - next.delete('tab') - } else { - next.set('tab', tab) - } + next.set('tab', tab) return next }, { replace: true }, ) } - const { data, isLoading, refetch, isFetching } = useGetInvoice(page) + const { data, isLoading, refetch, isFetching } = useGetInvoice({ page, statuses }) + const { mutate: archiveInvoice, isPending: isArchiving } = useArchiveInvoice() const meta = data?.meta + const handleArchive = useCallback( + (id: string) => { + archiveInvoice(id, { + onSuccess: () => { + refetch() + toast.success('پیش‌فاکتور با موفقیت آرشیو شد') + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, + }) + }, + [archiveInvoice, refetch], + ) + const filteredData = useMemo(() => { - const list = data?.data ?? [] - - if (activeTab === ProformaInvoiceStatusEnum.CONFIRMED) { - return list.filter((o) => o.confirmStatus === 'confirmed') - } - if (activeTab === ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED) { - return list.filter((o) => o.confirmStatus === 'partially_confirmed') - } - if (activeTab === ProformaInvoiceStatusEnum.NOT_CONFIRMED) { - return list.filter((o) => o.confirmStatus === 'pending') - } - - let result = list + let result = data?.data ?? [] const search = (filters.search ?? '').trim() if (search) { @@ -85,7 +92,7 @@ const ProformaInvoice: FC = () => { } return result - }, [data?.data, activeTab, filters.search, filters.date]) + }, [data?.data, filters.search, filters.date]) const getPaymentStatus = (item: InvoiceType) => { const total = Number(item.total) || 0 @@ -127,12 +134,7 @@ const ProformaInvoice: FC = () => {
setActiveTab(tab as ProformaInvoiceStatusEnum)} /> @@ -195,15 +197,16 @@ const ProformaInvoice: FC = () => { : '-', }, { - key: 'confirmStatus', + key: 'status', title: 'وضعیت تایید', render: (item) => { const statusMap = { confirmed: { className: 'bg-green-100 text-green-800', text: 'تایید شده' }, partially_confirmed: { className: 'bg-blue-100 text-blue-800', text: 'تایید جزئی' }, - pending: { className: 'bg-amber-100 text-amber-800', text: 'تایید نشده' }, + pending: { className: 'bg-amber-100 text-amber-800', text: 'در انتظار تایید' }, + archived: { className: 'bg-gray-100 text-gray-700', text: 'آرشیو شده' }, } - const status = statusMap[item.confirmStatus] ?? statusMap.pending + const status = statusMap[item.status] ?? statusMap.pending return ( { - - - + {item.status !== 'archived' && ( + <> + + + + handleArchive(item.id)} + isloading={isArchiving} + /> + + )}
), }, @@ -251,4 +262,4 @@ const ProformaInvoice: FC = () => { ) } -export default ProformaInvoice \ No newline at end of file +export default ProformaInvoice 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 f3ec09d..c7cae6e 100644 --- a/src/pages/invoice/hooks/useInvoiceData.ts +++ b/src/pages/invoice/hooks/useInvoiceData.ts @@ -13,10 +13,10 @@ export const useCreateInvoice = () => { }); }; -export const useGetInvoice = (page = 1) => { +export const useGetInvoice = (params: api.GetInvoicesParams) => { return useQuery({ - queryKey: ["invoice", page], - queryFn: () => api.getInvoice(page), + queryKey: ["invoice", params.page, params.statuses?.join(",") ?? ""], + queryFn: () => api.getInvoice(params), }); }; @@ -52,3 +52,14 @@ export const useUpdateInvoice = () => { }, }); }; + +export const useArchiveInvoice = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (id: string) => api.archiveInvoice(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["invoice"] }); + }, + }); +}; diff --git a/src/pages/invoice/service/InvoiceService.ts b/src/pages/invoice/service/InvoiceService.ts index b41ea12..f10d2c2 100644 --- a/src/pages/invoice/service/InvoiceService.ts +++ b/src/pages/invoice/service/InvoiceService.ts @@ -6,15 +6,27 @@ import type { InvoiceType, UpdatePreInvoiceType, } from "../types/Types"; +import type { InvoiceConfirmStatus } from "../enum/InvoiceEnum"; + +export type GetInvoicesParams = { + page?: number; + statuses?: InvoiceConfirmStatus[]; +}; export const createInvoice = async (params: CreatePreInvoiceType) => { const { data } = await axios.post("/admin/invoice", params); return data; }; -export const getInvoice = async (page = 1): Promise => { +export const getInvoice = async ( + params: GetInvoicesParams = {}, +): Promise => { + const { page = 1, statuses } = params; const { data } = await axios.get("/admin/invoice", { - params: { page }, + params: { + page, + ...(statuses?.length ? { statuses: statuses.join(",") } : {}), + }, }); return data; }; @@ -35,3 +47,8 @@ export const updateInvoice = async (id: string, params: UpdatePreInvoiceType) => const { data } = await axios.patch(`/admin/invoice/${id}`, params); return data; }; + +export const archiveInvoice = async (id: string) => { + const { data } = await axios.patch(`/admin/invoice/${id}/archive`); + return data; +}; diff --git a/src/pages/invoice/types/Types.ts b/src/pages/invoice/types/Types.ts index 6de3d02..336b37f 100644 --- a/src/pages/invoice/types/Types.ts +++ b/src/pages/invoice/types/Types.ts @@ -104,7 +104,7 @@ export type InvoiceType = { total: number; paidAmount: number; balance: number; - confirmStatus: 'pending' | 'partially_confirmed' | 'confirmed'; + status: 'pending' | 'partially_confirmed' | 'confirmed' | 'archived'; enableTax: boolean; approvalDeadline: string; attachments: InvoiceAttachmentType[];