This commit is contained in:
hamid zarghami
2025-09-28 12:27:56 +03:30
parent 946d9f3c7e
commit 992f839a91
8 changed files with 189 additions and 90 deletions
+11 -8
View File
@@ -72,14 +72,17 @@ export const Pages = {
referralCode: {
list: "/referral-code/list",
},
users: {
list: "/users/list",
create: "/users/create",
detail: "/users/detail/",
roleList: "/users/roles-all",
roleCreate: "/users/roles-add",
groupAdd: "/users/group-add",
groupList: "/users/groups",
admin: {
list: "/admin/list",
create: "/admin/create",
detail: "/admin/detail/",
roleList: "/admin/roles-all",
roleCreate: "/admin/roles-add",
groupAdd: "/admin/group-add",
groupList: "/admin/groups",
},
buyUsers: {
list: "/buy-users/list",
},
ads: {
list: "/ads/list",
+103
View File
@@ -0,0 +1,103 @@
import { type FC, useState } from 'react'
import { useGetUsers } from './hooks/useUserData'
import { type User } from './types/Types'
import PageLoading from '../../components/PageLoading'
import Error from '../../components/Error'
import PaginationUi from '../../components/PaginationUi'
import Td from '../../components/Td'
import { Calendar, Call, Card, User as UserIcon } from 'iconsax-react'
const UsersList: FC = () => {
const [currentPage, setCurrentPage] = useState(1);
const { data: usersData, isLoading, error } = useGetUsers(currentPage);
if (isLoading) {
return (
<div className="flex justify-center items-center min-h-[400px]">
<PageLoading />
</div>
);
}
if (error) {
return (
<div className="flex justify-center items-center min-h-[400px]">
<Error errorText="خطا در بارگذاری کاربران" />
</div>
);
}
const users = usersData?.results?.users || [];
const pager = usersData?.results?.pager;
const formatDate = (dateString: string) => {
if (!dateString) return '-';
return new Date(dateString).toLocaleDateString('fa-IR');
};
return (
<div>
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'>
<table className='w-full text-sm'>
<thead className='thead'>
<tr>
<Td text={'نام و نام خانوادگی'} />
<Td text={'شماره تلفن'} />
<Td text={'کد ملی'} />
<Td text={'تاریخ تولد'} />
<Td text={'تاریخ ثبت‌نام'} />
</tr>
</thead>
<tbody>
{users.length === 0 ? (
<tr className='tr'>
<td colSpan={6} className="text-center py-8 text-gray-500">
هیچ کاربری یافت نشد
</td>
</tr>
) : (
users.map((user: User) => (
<tr key={user._id} className='tr'>
<Td text="">
<div className="flex items-center gap-2">
<UserIcon size={16} color="#8C90A3" />
<span>{user.fullName || '-'}</span>
</div>
</Td>
<Td text="">
<div className="flex items-center gap-2">
<Call size={16} color="#8C90A3" />
<span>{user.phoneNumber}</span>
</div>
</Td>
<Td text="">
<div className="flex items-center gap-2">
<Card size={16} color="#8C90A3" />
<span>{user.nationalCode || '-'}</span>
</div>
</Td>
<Td text="">
<div className="flex items-center gap-2">
<Calendar size={16} color="#8C90A3" />
<span>{user.dateOfBirth}</span>
</div>
</Td>
<Td text={formatDate(user.createdAt)} />
</tr>
))
)}
</tbody>
</table>
</div>
<PaginationUi
pager={pager}
onPageChange={(page) => {
setCurrentPage(page);
}}
/>
</div>
)
}
export default UsersList
+9
View File
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import * as api from "../service/UserService";
export const useGetUsers = (page: number = 1, limit: number = 10) => {
return useQuery({
queryKey: ["users", page, limit],
queryFn: () => api.getUsers(page, limit),
});
};
+13
View File
@@ -0,0 +1,13 @@
import axios from "../../../config/axios";
import type { UserResponse } from "../types/Types";
export const getUsers = async (
page: number = 1,
limit: number = 10
): Promise<UserResponse> => {
const { data } = await axios.get("/admin/users", {
params: { page, limit },
});
return data;
};
+32
View File
@@ -0,0 +1,32 @@
export interface User {
_id: string;
fullName: string;
phoneNumber: string;
dateOfBirth: string | null;
address: string | null;
nationalCode: string | null;
homeNumber: string | null;
createdAt: string;
updatedAt: string;
__v: number;
}
export interface Pager {
page: number;
limit: number;
totalItems: number;
totalPages: number;
prevPage: boolean;
nextPage: string | null;
}
export interface UserResults {
users: User[];
pager: Pager;
}
export interface UserResponse {
status: number;
success: boolean;
results: UserResults;
}
+3
View File
@@ -32,6 +32,7 @@ import CreateShipment from '@/pages/shipment/Create'
import CouponList from '@/pages/Coupon/List'
import CreateCoupon from '@/pages/Coupon/Create'
import UpdateCoupon from '@/pages/Coupon/Update'
import UsersList from '@/pages/user/List'
const MainRouter: FC = () => {
@@ -83,6 +84,8 @@ const MainRouter: FC = () => {
<Route path={Pages.coupon.list} element={<CouponList />} />
<Route path={Pages.coupon.create} element={<CreateCoupon />} />
<Route path={`${Pages.coupon.detail}:id`} element={<UpdateCoupon />} />
<Route path={Pages.buyUsers.list} element={<UsersList />} />
</Routes>
</div>
</div>
+18 -18
View File
@@ -46,7 +46,7 @@ const SideBar: FC = () => {
if (split[1] === 'dashboard' || split[1] === 'products' || split[1] === 'orders' ||
split[1] === 'blog' || split[1] === 'shipment' || split[1] === 'financial' ||
split[1] === 'sellers' || split[1] === 'buyers' || split[1] === 'users' ||
split[1] === 'sellers' || split[1] === 'buyers' || split[1] === 'admin' ||
split[1] === 'tickets' || split[1] === 'reports' || split[1] === 'chat' ||
split[1] === 'contact' || split[1] === 'settings' || split[1] === 'pages' ||
split[1] === 'jobs' || split[1] === 'category') {
@@ -164,20 +164,20 @@ const SideBar: FC = () => {
{/* خریداران */}
<SideBarItem
icon={<UserSquare variant={isActive('buyers') ? 'Bold' : 'Outline'} color={isActive('buyers') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
icon={<UserSquare variant={isActive('buy-users') ? 'Bold' : 'Outline'} color={isActive('buy-users') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title="خریداران"
isActive={isActive('buyers')}
link="/buyers"
activeName='buyers'
isActive={isActive('buy-users')}
link={Pages.buyUsers.list}
activeName='buy-users'
/>
{/* کاربران */}
<SideBarItem
icon={<Profile variant={isActive('users') ? 'Bold' : 'Outline'} color={isActive('users') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title="کاربران"
isActive={isActive('users')}
link="/users"
activeName='users'
icon={<Profile variant={isActive('admin') ? 'Bold' : 'Outline'} color={isActive('admin') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title="ادمین ها"
isActive={isActive('admin')}
link="/admin"
activeName='admin'
/>
{/* تیکت ها */}
@@ -271,8 +271,8 @@ const SideBar: FC = () => {
: subMenuName === 'shipment' ? <ShippingSubMenu />
: subMenuName === 'financial' ? <FinancialSubMenu />
: subMenuName === 'sellers' ? <SellersSubMenu />
: subMenuName === 'buyers' ? <BuyersSubMenu />
: subMenuName === 'users' ? <UsersSubMenu />
: subMenuName === 'buy-users' ? <BuyersSubMenu />
: subMenuName === 'admin' ? <UsersSubMenu />
: subMenuName === 'tickets' ? <TicketsSubMenu />
: subMenuName === 'reports' ? <ReportsSubMenu />
: subMenuName === 'chat' ? <ChatSubMenu />
@@ -563,7 +563,7 @@ const BuyersSubMenu: FC = () => {
<SubMenuItem
title={'لیست خریداران'}
isActive={isActive('list')}
link="/buyers/list"
link={Pages.buyUsers.list}
/>
</div>
</div>
@@ -584,25 +584,25 @@ const UsersSubMenu: FC = () => {
variant='Bold'
/>
<div className='text-xs'>
کاربران
ادمین ها
</div>
</div>
<div className='flex flex-col mt-10 gap-4'>
<SubMenuItem
title={'لیست کاربران'}
title={'لیست ادمین ها'}
isActive={isActive('list')}
link="/users/list"
link={Pages.admin.list}
/>
<SubMenuItem
title={'تعریف نقش'}
isActive={isActive('roles')}
link="/users/roles"
link={Pages.admin.roleList}
/>
<SubMenuItem
title={'تعریف گروه کاربری'}
isActive={isActive('groups')}
link="/users/groups"
link={Pages.admin.groupList}
/>
</div>
</div>
-64
View File
@@ -1,64 +0,0 @@
import { Profile } from 'iconsax-react'
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import SubMenuItem from './SubMenuItem'
import { Pages } from '../../config/Pages'
const UsersSubMenu: FC = () => {
const { t } = useTranslation('global')
const isActive = (name: string) => {
return location.pathname.includes(name)
}
return (
<div className='py-12'>
<div className='flex gap-3 items-center border-b pb-10 px-6'>
<Profile
size={24}
color='#000'
variant='Bold'
/>
<div className='text-xs'>
{t('sidebar.users')}
</div>
</div>
<div className='flex flex-col mt-10 gap-4'>
<SubMenuItem
title={t('submenu.user_list')}
isActive={isActive('list')}
link={Pages.users.list}
/>
<SubMenuItem
title={t('submenu.add_user')}
isActive={isActive('create')}
link={Pages.users.create}
/>
<SubMenuItem
title={t('submenu.role_list')}
isActive={isActive('all')}
link={Pages.users.roleList}
/>
<SubMenuItem
title={t('submenu.role_create')}
isActive={isActive('roles-add')}
link={Pages.users.roleCreate}
/>
<SubMenuItem
title={t('user.add_user_group')}
isActive={isActive('group-add')}
link={Pages.users.groupAdd}
/>
<SubMenuItem
title={t('user.list_user_group')}
isActive={isActive('groups')}
link={Pages.users.groupList}
/>
</div>
</div>
)
}
export default UsersSubMenu