mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 10:27:49 +00:00
@@ -306,6 +306,7 @@ export const adminUsersApi = {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
email?: string;
|
||||
status?: 'active' | 'blocked' | 'deleted';
|
||||
sort_by?:
|
||||
| 'created_at'
|
||||
|
||||
@@ -1542,6 +1542,7 @@
|
||||
"title": "Users",
|
||||
"subtitle": "Bot user management",
|
||||
"search": "Search by ID, name, username...",
|
||||
"searchEmail": "Search by email...",
|
||||
"noData": "No users found",
|
||||
"purchaseCount": "{{count}} purchases",
|
||||
"noPurchases": "No purchases",
|
||||
|
||||
@@ -1361,6 +1361,7 @@
|
||||
"title": "کاربران",
|
||||
"subtitle": "مدیریت کاربران ربات",
|
||||
"search": "جستجو بر اساس شناسه، نام، نام کاربری...",
|
||||
"searchEmail": "جستجو بر اساس ایمیل...",
|
||||
"noData": "کاربری یافت نشد",
|
||||
"purchaseCount": "{{count}} خرید",
|
||||
"noPurchases": "بدون خرید",
|
||||
|
||||
@@ -2053,6 +2053,7 @@
|
||||
"title": "Пользователи",
|
||||
"subtitle": "Управление пользователями бота",
|
||||
"search": "Поиск по ID, имени, username...",
|
||||
"searchEmail": "Поиск по email...",
|
||||
"noData": "Пользователи не найдены",
|
||||
"purchaseCount": "{{count}} покупок",
|
||||
"noPurchases": "Нет покупок",
|
||||
|
||||
@@ -1360,6 +1360,7 @@
|
||||
"title": "用户",
|
||||
"subtitle": "机器人用户管理",
|
||||
"search": "按ID、姓名、用户名搜索...",
|
||||
"searchEmail": "按邮箱搜索...",
|
||||
"noData": "未找到用户",
|
||||
"purchaseCount": "{{count}} 次购买",
|
||||
"noPurchases": "无购买记录",
|
||||
|
||||
@@ -978,6 +978,7 @@ export default function AdminUsers() {
|
||||
const [stats, setStats] = useState<UsersStatsResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState('');
|
||||
const [emailSearch, setEmailSearch] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState<string>('');
|
||||
const [sortBy, setSortBy] = useState<string>('created_at');
|
||||
const [offset, setOffset] = useState(0);
|
||||
@@ -991,6 +992,7 @@ export default function AdminUsers() {
|
||||
setLoading(true);
|
||||
const params: Record<string, unknown> = { offset, limit, sort_by: sortBy };
|
||||
if (search) params.search = search;
|
||||
if (emailSearch) params.email = emailSearch;
|
||||
if (statusFilter) params.status = statusFilter;
|
||||
|
||||
const data = await adminUsersApi.getUsers(
|
||||
@@ -1003,7 +1005,7 @@ export default function AdminUsers() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [offset, search, statusFilter, sortBy]);
|
||||
}, [offset, search, emailSearch, statusFilter, sortBy]);
|
||||
|
||||
const loadStats = useCallback(async () => {
|
||||
try {
|
||||
@@ -1086,47 +1088,67 @@ export default function AdminUsers() {
|
||||
)}
|
||||
|
||||
{/* Filters */}
|
||||
<div className="mb-4 flex flex-col gap-3 sm:flex-row">
|
||||
<form onSubmit={handleSearch} className="flex-1">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t('admin.users.search')}
|
||||
className="w-full rounded-xl border border-dark-700 bg-dark-800 py-2 pl-10 pr-4 text-dark-100 placeholder-dark-500 focus:border-dark-600 focus:outline-none"
|
||||
/>
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-dark-500">
|
||||
<SearchIcon />
|
||||
<div className="mb-4 flex flex-col gap-3">
|
||||
{/* Search fields row */}
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<form onSubmit={handleSearch} className="flex-1">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t('admin.users.search')}
|
||||
className="w-full rounded-xl border border-dark-700 bg-dark-800 py-2 pl-10 pr-4 text-dark-100 placeholder-dark-500 focus:border-dark-600 focus:outline-none"
|
||||
/>
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-dark-500">
|
||||
<SearchIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => {
|
||||
setStatusFilter(e.target.value);
|
||||
setOffset(0);
|
||||
}}
|
||||
className="rounded-xl border border-dark-700 bg-dark-800 px-3 py-2 text-dark-100"
|
||||
>
|
||||
<option value="">{t('admin.users.filters.allStatuses')}</option>
|
||||
<option value="active">{t('admin.users.status.active')}</option>
|
||||
<option value="blocked">{t('admin.users.status.blocked')}</option>
|
||||
<option value="deleted">{t('admin.users.status.deleted')}</option>
|
||||
</select>
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => {
|
||||
setSortBy(e.target.value);
|
||||
setOffset(0);
|
||||
}}
|
||||
className="rounded-xl border border-dark-700 bg-dark-800 px-3 py-2 text-dark-100"
|
||||
>
|
||||
<option value="created_at">{t('admin.users.filters.byDate')}</option>
|
||||
<option value="balance">{t('admin.users.filters.byBalance')}</option>
|
||||
<option value="last_activity">{t('admin.users.filters.byActivity')}</option>
|
||||
<option value="total_spent">{t('admin.users.filters.bySpent')}</option>
|
||||
</select>
|
||||
</form>
|
||||
<form onSubmit={handleSearch} className="flex-1">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="email"
|
||||
value={emailSearch}
|
||||
onChange={(e) => setEmailSearch(e.target.value)}
|
||||
placeholder={t('admin.users.searchEmail')}
|
||||
className="w-full rounded-xl border border-dark-700 bg-dark-800 py-2 pl-10 pr-4 text-dark-100 placeholder-dark-500 focus:border-dark-600 focus:outline-none"
|
||||
/>
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-dark-500">
|
||||
<SearchIcon />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/* Filters row */}
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => {
|
||||
setStatusFilter(e.target.value);
|
||||
setOffset(0);
|
||||
}}
|
||||
className="rounded-xl border border-dark-700 bg-dark-800 px-3 py-2 text-dark-100"
|
||||
>
|
||||
<option value="">{t('admin.users.filters.allStatuses')}</option>
|
||||
<option value="active">{t('admin.users.status.active')}</option>
|
||||
<option value="blocked">{t('admin.users.status.blocked')}</option>
|
||||
<option value="deleted">{t('admin.users.status.deleted')}</option>
|
||||
</select>
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => {
|
||||
setSortBy(e.target.value);
|
||||
setOffset(0);
|
||||
}}
|
||||
className="rounded-xl border border-dark-700 bg-dark-800 px-3 py-2 text-dark-100"
|
||||
>
|
||||
<option value="created_at">{t('admin.users.filters.byDate')}</option>
|
||||
<option value="balance">{t('admin.users.filters.byBalance')}</option>
|
||||
<option value="last_activity">{t('admin.users.filters.byActivity')}</option>
|
||||
<option value="total_spent">{t('admin.users.filters.bySpent')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Users list */}
|
||||
|
||||
Reference in New Issue
Block a user