feat: billboard table added
This commit is contained in:
parent
fedc39a413
commit
b85ff7cbf8
4 changed files with 272 additions and 9 deletions
|
|
@ -1,17 +1,186 @@
|
||||||
import { Card, Typography } from 'antd';
|
import { App, Button, Table, Tag, Tooltip, Typography } from 'antd';
|
||||||
|
import type { TableColumnsType } from 'antd';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { colors } from '@/design/tokens/colors';
|
import { colors } from '@/design/tokens/colors';
|
||||||
import { spacing } from '@/design/tokens/spacing';
|
import { formatDateTime } from '@/utils/formatDate';
|
||||||
|
import { adsApi } from '@/services/adsApi';
|
||||||
|
import type { Ad } from '@/types/ad';
|
||||||
|
|
||||||
const { Title, Paragraph } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
const AD_TYPE_LABELS: Record<number, string> = {
|
||||||
|
1: 'بیلبورد',
|
||||||
|
2: 'بیلبورد کپچر شده',
|
||||||
|
99: 'تبلیغ/پروموشن',
|
||||||
|
100: 'poi',
|
||||||
|
};
|
||||||
|
|
||||||
|
type AdStateColor = 'default' | 'processing' | 'success' | 'warning' | 'error';
|
||||||
|
|
||||||
|
const AD_STATE_LABELS: Record<number, { label: string; color: AdStateColor }> = {
|
||||||
|
0: { label: 'پیشنویس', color: 'default' },
|
||||||
|
2: { label: 'در حال بررسی', color: 'processing' },
|
||||||
|
9: { label: 'رد شده', color: 'error' },
|
||||||
|
18: { label: 'متوقفشده (توسط کاربر)', color: 'warning' },
|
||||||
|
19: { label: 'لغو انتشار شده', color: 'warning' },
|
||||||
|
20: { label: 'منتشر شده', color: 'success' },
|
||||||
|
100: { label: 'حذف شده', color: 'error' },
|
||||||
|
200: { label: 'در حال اجرا', color: 'processing' },
|
||||||
|
400: { label: 'پایانیافته', color: 'success' },
|
||||||
|
500: { label: 'غیرفعال', color: 'default' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const truncateUuid = (value: string) => `${value.slice(0, 8)}…`;
|
||||||
|
|
||||||
|
const DESCRIPTION_MAX_LENGTH = 40;
|
||||||
|
const truncateText = (value: string, maxLength = DESCRIPTION_MAX_LENGTH) =>
|
||||||
|
value.length > maxLength ? `${value.slice(0, maxLength)}…` : value;
|
||||||
|
|
||||||
|
const EmptyValue = () => <Text style={{ color: colors.textSecondary }}>—</Text>;
|
||||||
|
|
||||||
|
interface FetchResult {
|
||||||
|
page: number;
|
||||||
|
status: 'success' | 'error';
|
||||||
|
ads: Ad[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
const Billboards = () => {
|
const Billboards = () => {
|
||||||
|
const { message } = App.useApp();
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [result, setResult] = useState<FetchResult | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
adsApi
|
||||||
|
.getAdminAds({ limit: PAGE_SIZE, offset: (page - 1) * PAGE_SIZE })
|
||||||
|
.then(({ results, count }) => {
|
||||||
|
setResult({ page, status: 'success', ads: results, total: count });
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setResult({ page, status: 'error', ads: [], total: 0 });
|
||||||
|
message.error('دریافت اطلاعات بیلبوردها با خطا مواجه شد');
|
||||||
|
});
|
||||||
|
}, [page, message]);
|
||||||
|
|
||||||
|
const isCurrent = result?.page === page;
|
||||||
|
const isLoading = !isCurrent;
|
||||||
|
const ads = isCurrent ? result.ads : [];
|
||||||
|
const total = isCurrent ? result.total : 0;
|
||||||
|
|
||||||
|
const columns: TableColumnsType<Ad> = [
|
||||||
|
{
|
||||||
|
title: 'شناسه',
|
||||||
|
dataIndex: 'uuid',
|
||||||
|
key: 'uuid',
|
||||||
|
render: (value: string) => (
|
||||||
|
<Tooltip title={value}>
|
||||||
|
<Text copyable={{ text: value }} style={{ direction: 'ltr', fontSize: 12 }}>
|
||||||
|
{truncateUuid(value)}
|
||||||
|
</Text>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'کاربر (ثبتکننده)',
|
||||||
|
dataIndex: 'user',
|
||||||
|
key: 'user',
|
||||||
|
render: (value: string) => (
|
||||||
|
<Tooltip title={value}>
|
||||||
|
<Link to={`/profile?uuid=${value}`} style={{ direction: 'ltr', fontSize: 12 }}>
|
||||||
|
{truncateUuid(value)}
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'عنوان',
|
||||||
|
dataIndex: 'title',
|
||||||
|
key: 'title',
|
||||||
|
ellipsis: true,
|
||||||
|
render: (value: string | null) => value ?? <EmptyValue />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'نوع',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
render: (value: number) => <Tag color={colors.primary}>{AD_TYPE_LABELS[value] ?? value}</Tag>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'توضیحات',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
render: (value: string | null) =>
|
||||||
|
value ? (
|
||||||
|
<Tooltip title={value}>
|
||||||
|
<span>{truncateText(value)}</span>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<EmptyValue />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'برچسبها',
|
||||||
|
dataIndex: 'tags',
|
||||||
|
key: 'tags',
|
||||||
|
render: (tags: string[]) =>
|
||||||
|
tags?.length ? (
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<Tag key={tag}>{tag}</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<EmptyValue />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ title: 'تاریخ ایجاد', dataIndex: 'created_at', key: 'created_at', render: formatDateTime },
|
||||||
|
{ title: 'تاریخ آخرین ویرایش', dataIndex: 'updated_at', key: 'updated_at', render: formatDateTime },
|
||||||
|
{
|
||||||
|
title: 'وضعیت',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
render: (value: number) => {
|
||||||
|
const state = AD_STATE_LABELS[value];
|
||||||
|
return <Tag color={state?.color ?? 'default'}>{state?.label ?? value}</Tag>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: 'تعداد نظرات', dataIndex: 'comments_count', key: 'comments_count' },
|
||||||
|
{ title: 'تعداد لایکها', dataIndex: 'likes_count', key: 'likes_count' },
|
||||||
|
{
|
||||||
|
title: 'فعال/غیرفعال',
|
||||||
|
dataIndex: 'is_active',
|
||||||
|
key: 'is_active',
|
||||||
|
render: (value: boolean) => <Tag color={value ? 'success' : 'default'}>{value ? 'فعال' : 'غیرفعال'}</Tag>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'جزئیات',
|
||||||
|
key: 'details',
|
||||||
|
render: () => (
|
||||||
|
<Button size="small" disabled>
|
||||||
|
مشاهده جزئیات
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card bordered={false} style={{ boxShadow: 'none', padding: 0 }}>
|
<Table
|
||||||
<Title level={2}>بیلبوردها</Title>
|
rowKey="uuid"
|
||||||
<Paragraph style={{ color: colors.textSecondary, marginTop: spacing.sm }}>
|
columns={columns}
|
||||||
مدیریت جدول بیلبوردها بهزودی اضافه خواهد شد.
|
dataSource={ads}
|
||||||
</Paragraph>
|
loading={isLoading}
|
||||||
</Card>
|
scroll={{ x: 'max-content' }}
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
total,
|
||||||
|
showSizeChanger: false,
|
||||||
|
onChange: setPage,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
26
src/services/adsApi.ts
Normal file
26
src/services/adsApi.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { env } from '@/config/env';
|
||||||
|
import { getAccessToken } from '@/utils/authToken';
|
||||||
|
import { buildAuthUrl } from '@/utils/authUrl';
|
||||||
|
import type { AdminAdsResponse } from '@/types/ad';
|
||||||
|
|
||||||
|
interface GetAdminAdsParams {
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const adsApi = {
|
||||||
|
getAdminAds: async ({ limit, offset }: GetAdminAdsParams): Promise<AdminAdsResponse> => {
|
||||||
|
const response = await axios.get<AdminAdsResponse>(
|
||||||
|
buildAuthUrl('/advertising/stores/admin-ads/', env.apiBaseUrl),
|
||||||
|
{
|
||||||
|
params: { limit, offset },
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${getAccessToken()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
51
src/types/ad.ts
Normal file
51
src/types/ad.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
export interface AdLocation {
|
||||||
|
type: string;
|
||||||
|
coordinates: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdMediaAsset {
|
||||||
|
uuid: string;
|
||||||
|
object_key: string;
|
||||||
|
content_type: number;
|
||||||
|
filename: string;
|
||||||
|
state: number;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Ad {
|
||||||
|
uuid: string;
|
||||||
|
campaign: string | null;
|
||||||
|
title: string | null;
|
||||||
|
type: number;
|
||||||
|
details: Record<string, unknown>;
|
||||||
|
content: string;
|
||||||
|
contents: unknown[];
|
||||||
|
refined_contents: AdMediaAsset[];
|
||||||
|
description: string | null;
|
||||||
|
location: AdLocation | null;
|
||||||
|
location_is_approximate: boolean;
|
||||||
|
banner: string | null;
|
||||||
|
refined_banner: AdMediaAsset | null;
|
||||||
|
reward_amount: number;
|
||||||
|
reward_count: number;
|
||||||
|
address: string | null;
|
||||||
|
url: string | null;
|
||||||
|
tags: string[];
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
display_time: number;
|
||||||
|
state: number;
|
||||||
|
status: string;
|
||||||
|
is_active: boolean;
|
||||||
|
user: string;
|
||||||
|
user_uuid: string;
|
||||||
|
comments_count: number;
|
||||||
|
likes_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminAdsResponse {
|
||||||
|
count: number;
|
||||||
|
next: string | null;
|
||||||
|
previous: string | null;
|
||||||
|
results: Ad[];
|
||||||
|
}
|
||||||
17
src/utils/formatDate.ts
Normal file
17
src/utils/formatDate.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
export const formatDateTime = (value: string): string => {
|
||||||
|
const date = new Date(value);
|
||||||
|
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date
|
||||||
|
.toLocaleString('en-GB', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})
|
||||||
|
.replace(',', '');
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue