From af24047a20e2772328e9ad637d01aa223b538677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afra=20=E2=80=8C?= Date: Fri, 25 Sep 2026 18:33:24 +0330 Subject: [PATCH] feat: all users table --- src/pages/Profile/AllUsersTable.tsx | 122 ++++++++++++++++++++++++++++ src/pages/Profile/Profile.tsx | 15 ++-- src/services/usersApi.ts | 21 ++++- src/types/user.ts | 21 +++++ src/utils/formatDate.ts | 14 ++++ 5 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 src/pages/Profile/AllUsersTable.tsx diff --git a/src/pages/Profile/AllUsersTable.tsx b/src/pages/Profile/AllUsersTable.tsx new file mode 100644 index 0000000..409bb19 --- /dev/null +++ b/src/pages/Profile/AllUsersTable.tsx @@ -0,0 +1,122 @@ +import { App, Button, Table, Tooltip, Typography } from 'antd'; +import type { TableColumnsType } from 'antd'; +import { useEffect, useState } from 'react'; +import EmptyValue from '@/components/ui/EmptyValue'; +import { formatDate, formatDateTime } from '@/utils/formatDate'; +import { usersApi } from '@/services/usersApi'; +import type { AdminUserListItem } from '@/types/user'; + +const { Text } = Typography; + +const PAGE_SIZE = 10; + +const GENDER_LABELS: Record = { + m: 'مرد', + f: 'زن', +}; + +const truncateUuid = (value: string) => `${value.slice(0, 8)}…`; + +const renderText = (value: string | null) => value ?? ; + +interface FetchResult { + page: number; + status: 'success' | 'error'; + users: AdminUserListItem[]; + total: number; +} + +interface AllUsersTableProps { + onSelectUuid: (uuid: string) => void; +} + +const AllUsersTable = ({ onSelectUuid }: AllUsersTableProps) => { + const { message } = App.useApp(); + const [page, setPage] = useState(1); + const [result, setResult] = useState(null); + + useEffect(() => { + usersApi + .getAdminUsers({ limit: PAGE_SIZE, offset: (page - 1) * PAGE_SIZE }) + .then(({ results, count }) => { + setResult({ page, status: 'success', users: results, total: count }); + }) + .catch(() => { + setResult({ page, status: 'error', users: [], total: 0 }); + message.error('دریافت لیست کاربران با خطا مواجه شد'); + }); + }, [page, message]); + + const isCurrent = result?.page === page; + const isLoading = !isCurrent; + const users = isCurrent ? result.users : []; + const total = isCurrent ? result.total : 0; + + const columns: TableColumnsType = [ + { + title: 'شناسه یکتا', + dataIndex: 'uuid', + key: 'uuid', + render: (value: string) => ( + + + {truncateUuid(value)} + + + ), + }, + { title: 'نام کاربری', dataIndex: 'username', key: 'username', render: renderText }, + { title: 'ایمیل', dataIndex: 'email', key: 'email', render: renderText }, + { + title: 'شماره تلفن', + dataIndex: 'phone_number', + key: 'phone_number', + render: (value: string | null) => (value ? {value} : ), + }, + { title: 'نام', dataIndex: 'first_name', key: 'first_name', render: renderText }, + { title: 'نام خانوادگی', dataIndex: 'last_name', key: 'last_name', render: renderText }, + { title: 'نام کامل', dataIndex: 'name', key: 'name', render: renderText }, + { + title: 'جنسیت', + dataIndex: 'gender', + key: 'gender', + render: (value: string | null) => (value ? (GENDER_LABELS[value] ?? value) : ), + }, + { + title: 'تاریخ تولد', + dataIndex: 'birthdate', + key: 'birthdate', + render: (value: string | null) => (value ? formatDate(value) : ), + }, + { title: 'تاریخ عضویت', dataIndex: 'date_joined', key: 'date_joined', render: formatDateTime }, + { title: 'آخرین بروزرسانی', dataIndex: 'last_update', key: 'last_update', render: formatDateTime }, + { + title: 'عملیات', + key: 'actions', + render: (_, record) => ( + + ), + }, + ]; + + return ( + + ); +}; + +export default AllUsersTable; diff --git a/src/pages/Profile/Profile.tsx b/src/pages/Profile/Profile.tsx index 5b410d6..21955af 100644 --- a/src/pages/Profile/Profile.tsx +++ b/src/pages/Profile/Profile.tsx @@ -1,4 +1,4 @@ -import { App, Empty, Input, Spin } from 'antd'; +import { App, Empty, Input, Spin, Typography } from 'antd'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { spacing } from '@/design/tokens/spacing'; @@ -8,6 +8,9 @@ import type { UserDetails } from '@/types/user'; import UserInfoCard from '@/pages/Profile/UserInfoCard'; import StatCardsRow from '@/pages/Profile/StatCards'; import ProfileTabs from '@/pages/Profile/ProfileTabs'; +import AllUsersTable from '@/pages/Profile/AllUsersTable'; + +const { Title } = Typography; type SearchStatus = 'idle' | 'loading' | 'error' | 'success'; @@ -69,6 +72,7 @@ const Profile = () => {
{ />
- {status === 'idle' && ( - - )} - {status === 'loading' && (
@@ -101,6 +101,11 @@ const Profile = () => { )} + + + لیست کاربران + +
); }; diff --git a/src/services/usersApi.ts b/src/services/usersApi.ts index e2ae29e..faec8a4 100644 --- a/src/services/usersApi.ts +++ b/src/services/usersApi.ts @@ -2,7 +2,12 @@ import axios from 'axios'; import { env } from '@/config/env'; import { getAccessToken } from '@/utils/authToken'; import { buildAuthUrl } from '@/utils/authUrl'; -import type { UserDetails } from '@/types/user'; +import type { AdminUsersResponse, UserDetails } from '@/types/user'; + +interface GetAdminUsersParams { + limit: number; + offset: number; +} export const usersApi = { getUserDetails: async (uuid: string): Promise => { @@ -17,4 +22,18 @@ export const usersApi = { return response.data; }, + + getAdminUsers: async ({ limit, offset }: GetAdminUsersParams): Promise => { + const response = await axios.get( + buildAuthUrl('/accounts/users/api/admin/users/', env.apiBaseUrl), + { + params: { limit, offset }, + headers: { + Authorization: `Bearer ${getAccessToken()}`, + }, + }, + ); + + return response.data; + }, }; diff --git a/src/types/user.ts b/src/types/user.ts index 03f2ea3..4d608e9 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -20,3 +20,24 @@ export interface UserDetails { postal_code: string; address: string; } + +export interface AdminUserListItem { + uuid: string; + username: string; + email: string | null; + phone_number: string | null; + first_name: string | null; + last_name: string | null; + name: string | null; + gender: string | null; + birthdate: string | null; + date_joined: string; + last_update: string; +} + +export interface AdminUsersResponse { + count: number; + next: string | null; + previous: string | null; + results: AdminUserListItem[]; +} diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts index 3586b75..c6937db 100644 --- a/src/utils/formatDate.ts +++ b/src/utils/formatDate.ts @@ -15,3 +15,17 @@ export const formatDateTime = (value: string): string => { }) .replace(',', ''); }; + +export const formatDate = (value: string): string => { + const date = new Date(value); + + if (Number.isNaN(date.getTime())) { + return value; + } + + return date.toLocaleDateString('en-GB', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }); +};