invoice
This commit is contained in:
@@ -17,13 +17,15 @@ export type StatusConfig = {
|
|||||||
*/
|
*/
|
||||||
export const statusRegistry: Record<string, StatusConfig> = {
|
export const statusRegistry: Record<string, StatusConfig> = {
|
||||||
// --- ProformaInvoiceStatusEnum ---
|
// --- ProformaInvoiceStatusEnum ---
|
||||||
[ProformaInvoiceStatusEnum.ALL]: { label: 'همه', variant: 'neutral' },
|
[ProformaInvoiceStatusEnum.PENDING]: { label: 'در انتظار تایید', variant: 'warning' },
|
||||||
[ProformaInvoiceStatusEnum.NOT_CONFIRMED]: { label: 'تایید نشده', variant: 'warning' },
|
|
||||||
[ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED]: { label: 'تایید جزئی', variant: 'info' },
|
|
||||||
[ProformaInvoiceStatusEnum.CONFIRMED]: { label: 'تایید شده', variant: 'success' },
|
[ProformaInvoiceStatusEnum.CONFIRMED]: { label: 'تایید شده', variant: 'success' },
|
||||||
|
[ProformaInvoiceStatusEnum.ARCHIVED]: { label: 'آرشیو شده', variant: 'neutral' },
|
||||||
|
|
||||||
// --- Invoice confirm status (from API; partially_confirmed/confirmed covered by enum above) ---
|
// --- Invoice confirm status (from API) ---
|
||||||
pending: { label: 'تایید نشده', variant: 'warning' },
|
pending: { label: 'در انتظار تایید', variant: 'warning' },
|
||||||
|
partially_confirmed: { label: 'تایید جزئی', variant: 'info' },
|
||||||
|
confirmed: { label: 'تایید شده', variant: 'success' },
|
||||||
|
archived: { label: 'آرشیو شده', variant: 'neutral' },
|
||||||
|
|
||||||
// --- OrderStatusEnum ---
|
// --- OrderStatusEnum ---
|
||||||
[OrderStatusEnum.CREATED]: { label: 'ایجاد شده', variant: 'info' },
|
[OrderStatusEnum.CREATED]: { label: 'ایجاد شده', variant: 'info' },
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { getChatSenderName } from '../type/Types'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
content: string
|
content: string
|
||||||
attachments?: ChatAttachmentType[]
|
attachments?: ChatAttachmentType[] | null
|
||||||
senderName?: string
|
senderName?: string
|
||||||
senderLabel?: string
|
senderLabel?: string
|
||||||
createdAt?: string
|
createdAt?: string
|
||||||
@@ -25,7 +25,7 @@ type Props = {
|
|||||||
|
|
||||||
const ChatMessage: FC<Props> = ({
|
const ChatMessage: FC<Props> = ({
|
||||||
content,
|
content,
|
||||||
attachments = [],
|
attachments,
|
||||||
senderName,
|
senderName,
|
||||||
senderLabel = 'پشتیبان',
|
senderLabel = 'پشتیبان',
|
||||||
createdAt,
|
createdAt,
|
||||||
@@ -37,8 +37,9 @@ const ChatMessage: FC<Props> = ({
|
|||||||
parentSenderLabel,
|
parentSenderLabel,
|
||||||
onReply,
|
onReply,
|
||||||
}) => {
|
}) => {
|
||||||
const fileAttachments = attachments.filter((a) => a.type !== 'voice')
|
const safeAttachments = attachments ?? []
|
||||||
const voiceAttachments = attachments.filter((a) => a.type === 'voice')
|
const fileAttachments = safeAttachments.filter((a) => a.type !== 'voice')
|
||||||
|
const voiceAttachments = safeAttachments.filter((a) => a.type === 'voice')
|
||||||
|
|
||||||
const handleOpenLink = async (key: string) => {
|
const handleOpenLink = async (key: string) => {
|
||||||
const url = await getPresignedUrl(key)
|
const url = await getPresignedUrl(key)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export type ChatParentMessageType = {
|
|||||||
export type ChatMessageType = {
|
export type ChatMessageType = {
|
||||||
admin?: ChatParticipantType | null
|
admin?: ChatParticipantType | null
|
||||||
user?: ChatParticipantType | null
|
user?: ChatParticipantType | null
|
||||||
attachments: ChatAttachmentType[]
|
attachments?: ChatAttachmentType[] | null
|
||||||
content: string
|
content: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const Stats: FC = () => {
|
|||||||
<StatCard
|
<StatCard
|
||||||
count={data?.data.invoicesCount}
|
count={data?.data.invoicesCount}
|
||||||
description={t('home.factureCount')}
|
description={t('home.factureCount')}
|
||||||
to={`${Paths.proformaInvoice}?tab=${ProformaInvoiceStatusEnum.NOT_CONFIRMED}`}
|
to={`${Paths.proformaInvoice}?tab=${ProformaInvoiceStatusEnum.PENDING}`}
|
||||||
icon={<ReceiptText
|
icon={<ReceiptText
|
||||||
size={27}
|
size={27}
|
||||||
color={COLORS.primary}
|
color={COLORS.primary}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import Tabs from '@/components/Tabs'
|
import Tabs from '@/components/Tabs'
|
||||||
import { useMemo, useState, type FC } from 'react'
|
import { useMemo, useState, type FC } from 'react'
|
||||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom'
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { ProformaInvoiceStatusEnum } from './enum/InvoiceEnum'
|
import {
|
||||||
|
invoiceListTabs,
|
||||||
|
invoiceListTabStatuses,
|
||||||
|
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 { useGetInvoices } from './hooks/useInvoiceData'
|
||||||
@@ -14,11 +18,10 @@ import moment from 'moment-jalaali'
|
|||||||
import StatusWithText from '@/components/StatusWithText'
|
import StatusWithText from '@/components/StatusWithText'
|
||||||
import { Eye } from 'iconsax-react'
|
import { Eye } from 'iconsax-react'
|
||||||
|
|
||||||
|
const INVOICE_TABS = invoiceListTabs.map((tab) => tab.value)
|
||||||
|
|
||||||
const isInvoiceTab = (value: string | null): value is ProformaInvoiceStatusEnum =>
|
const isInvoiceTab = (value: string | null): value is ProformaInvoiceStatusEnum =>
|
||||||
value === ProformaInvoiceStatusEnum.ALL ||
|
value !== null && INVOICE_TABS.includes(value as ProformaInvoiceStatusEnum)
|
||||||
value === ProformaInvoiceStatusEnum.NOT_CONFIRMED ||
|
|
||||||
value === ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED ||
|
|
||||||
value === ProformaInvoiceStatusEnum.CONFIRMED
|
|
||||||
|
|
||||||
const getServiceNames = (invoice: Invoice): string => {
|
const getServiceNames = (invoice: Invoice): string => {
|
||||||
const names = [...new Set(invoice.items.map((item) => item.product.title))]
|
const names = [...new Set(invoice.items.map((item) => item.product.title))]
|
||||||
@@ -27,29 +30,32 @@ const getServiceNames = (invoice: Invoice): string => {
|
|||||||
|
|
||||||
const ProformaInvoice: FC = () => {
|
const ProformaInvoice: FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const tabFromQuery = searchParams.get('tab')
|
const tabFromQuery = searchParams.get('tab')
|
||||||
const [activeTab, setActiveTab] = useState<ProformaInvoiceStatusEnum>(
|
const activeTab = isInvoiceTab(tabFromQuery)
|
||||||
isInvoiceTab(tabFromQuery) ? tabFromQuery : ProformaInvoiceStatusEnum.ALL,
|
? tabFromQuery
|
||||||
)
|
: ProformaInvoiceStatusEnum.PENDING
|
||||||
const [filters, setFilters] = useState<FilterValues>({})
|
const [filters, setFilters] = useState<FilterValues>({})
|
||||||
|
|
||||||
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 invoices = useMemo(() => data?.data ?? [], [data?.data])
|
||||||
|
|
||||||
const filteredInvoices = useMemo(() => {
|
const filteredInvoices = useMemo(() => {
|
||||||
let result = invoices
|
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()
|
const search = filters.search?.toString().trim()
|
||||||
if (search) {
|
if (search) {
|
||||||
const lower = search.toLowerCase()
|
const lower = search.toLowerCase()
|
||||||
@@ -62,7 +68,6 @@ const ProformaInvoice: FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// فیلتر تاریخ
|
|
||||||
const dateFilter = filters.date?.toString()
|
const dateFilter = filters.date?.toString()
|
||||||
if (dateFilter) {
|
if (dateFilter) {
|
||||||
result = result.filter((inv) => {
|
result = result.filter((inv) => {
|
||||||
@@ -72,7 +77,7 @@ const ProformaInvoice: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}, [invoices, activeTab, filters])
|
}, [invoices, filters])
|
||||||
|
|
||||||
const columns: ColumnType<Invoice>[] = [
|
const columns: ColumnType<Invoice>[] = [
|
||||||
{
|
{
|
||||||
@@ -101,15 +106,16 @@ const ProformaInvoice: FC = () => {
|
|||||||
render: (item) => <span>{NumberFormat(item.total)} ریال</span>,
|
render: (item) => <span>{NumberFormat(item.total)} ریال</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'confirmStatus',
|
key: 'status',
|
||||||
title: 'وضعیت تایید',
|
title: 'وضعیت تایید',
|
||||||
render: (item) => {
|
render: (item) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
confirmed: { variant: 'success' as const, text: 'تایید شده' },
|
confirmed: { variant: 'success' as const, text: 'تایید شده' },
|
||||||
partially_confirmed: { variant: 'info' 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 (
|
return (
|
||||||
<StatusWithText
|
<StatusWithText
|
||||||
variant={status.variant}
|
variant={status.variant}
|
||||||
@@ -118,19 +124,6 @@ const ProformaInvoice: FC = () => {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'paymentStatus',
|
|
||||||
title: 'وضعیت پرداخت',
|
|
||||||
render: (item) => {
|
|
||||||
const isPaid = item.paidAmount >= item.total && item.total > 0
|
|
||||||
return (
|
|
||||||
<StatusWithText
|
|
||||||
variant={isPaid ? 'success' : 'info'}
|
|
||||||
text={isPaid ? 'پرداخت شده' : 'پرداخت نشده'}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'action',
|
key: 'action',
|
||||||
title: '',
|
title: '',
|
||||||
@@ -157,12 +150,7 @@ const ProformaInvoice: FC = () => {
|
|||||||
|
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<Tabs
|
<Tabs
|
||||||
items={[
|
items={[...invoiceListTabs]}
|
||||||
{ label: 'همه', value: ProformaInvoiceStatusEnum.ALL },
|
|
||||||
{ label: 'تایید نشده', value: ProformaInvoiceStatusEnum.NOT_CONFIRMED },
|
|
||||||
{ label: 'تایید جزئی', value: ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED },
|
|
||||||
{ label: 'تایید شده', value: ProformaInvoiceStatusEnum.CONFIRMED },
|
|
||||||
]}
|
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
onTabChange={(tab) => setActiveTab(tab as ProformaInvoiceStatusEnum)}
|
onTabChange={(tab) => setActiveTab(tab as ProformaInvoiceStatusEnum)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,26 @@
|
|||||||
export const enum ProformaInvoiceStatusEnum {
|
export const enum ProformaInvoiceStatusEnum {
|
||||||
ALL = 'all',
|
PENDING = 'pending',
|
||||||
NOT_CONFIRMED = 'not_confirmed',
|
|
||||||
PARTIALLY_CONFIRMED = 'partially_confirmed',
|
|
||||||
CONFIRMED = 'confirmed',
|
CONFIRMED = 'confirmed',
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import * as api from "../service/InvoiceService";
|
import * as api from "../service/InvoiceService";
|
||||||
|
|
||||||
export const useGetInvoices = () => {
|
export const useGetInvoices = (params: api.GetInvoicesParams) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["invoices"],
|
queryKey: ["invoices", params.statuses?.join(",") ?? ""],
|
||||||
queryFn: api.getInvoices,
|
queryFn: () => api.getInvoices(params),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,21 @@ import type {
|
|||||||
InvoicesResponseType,
|
InvoicesResponseType,
|
||||||
InvoiceDetailResponseType,
|
InvoiceDetailResponseType,
|
||||||
} from "../types/InvoiceTypes";
|
} from "../types/InvoiceTypes";
|
||||||
|
import type { InvoiceConfirmStatus } from "../enum/InvoiceEnum";
|
||||||
|
|
||||||
export const getInvoices = async (): Promise<InvoicesResponseType> => {
|
export type GetInvoicesParams = {
|
||||||
const { data } = await axios.get<InvoicesResponseType>("/public/invoice");
|
statuses?: InvoiceConfirmStatus[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInvoices = async (
|
||||||
|
params: GetInvoicesParams = {},
|
||||||
|
): Promise<InvoicesResponseType> => {
|
||||||
|
const { statuses } = params;
|
||||||
|
const { data } = await axios.get<InvoicesResponseType>("/public/invoice", {
|
||||||
|
params: {
|
||||||
|
...(statuses?.length ? { statuses: statuses.join(",") } : {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export type Invoice = {
|
|||||||
total: number;
|
total: number;
|
||||||
paidAmount: number;
|
paidAmount: number;
|
||||||
balance: number;
|
balance: number;
|
||||||
confirmStatus: 'pending' | 'partially_confirmed' | 'confirmed';
|
status: 'pending' | 'partially_confirmed' | 'confirmed' | 'archived';
|
||||||
enableTax: boolean;
|
enableTax: boolean;
|
||||||
approvalDeadline: string;
|
approvalDeadline: string;
|
||||||
attachments: unknown[];
|
attachments: unknown[];
|
||||||
|
|||||||
Reference in New Issue
Block a user