Merge pull request #124 from BEDOLAGA-DEV/dev

Dev
This commit is contained in:
Egor
2026-01-31 20:15:45 +03:00
committed by GitHub
6 changed files with 68 additions and 41 deletions

View File

@@ -306,6 +306,7 @@ export const adminUsersApi = {
offset?: number; offset?: number;
limit?: number; limit?: number;
search?: string; search?: string;
email?: string;
status?: 'active' | 'blocked' | 'deleted'; status?: 'active' | 'blocked' | 'deleted';
sort_by?: sort_by?:
| 'created_at' | 'created_at'

View File

@@ -1542,6 +1542,7 @@
"title": "Users", "title": "Users",
"subtitle": "Bot user management", "subtitle": "Bot user management",
"search": "Search by ID, name, username...", "search": "Search by ID, name, username...",
"searchEmail": "Search by email...",
"noData": "No users found", "noData": "No users found",
"purchaseCount": "{{count}} purchases", "purchaseCount": "{{count}} purchases",
"noPurchases": "No purchases", "noPurchases": "No purchases",

View File

@@ -1361,6 +1361,7 @@
"title": "کاربران", "title": "کاربران",
"subtitle": "مدیریت کاربران ربات", "subtitle": "مدیریت کاربران ربات",
"search": "جستجو بر اساس شناسه، نام، نام کاربری...", "search": "جستجو بر اساس شناسه، نام، نام کاربری...",
"searchEmail": "جستجو بر اساس ایمیل...",
"noData": "کاربری یافت نشد", "noData": "کاربری یافت نشد",
"purchaseCount": "{{count}} خرید", "purchaseCount": "{{count}} خرید",
"noPurchases": "بدون خرید", "noPurchases": "بدون خرید",

View File

@@ -2053,6 +2053,7 @@
"title": "Пользователи", "title": "Пользователи",
"subtitle": "Управление пользователями бота", "subtitle": "Управление пользователями бота",
"search": "Поиск по ID, имени, username...", "search": "Поиск по ID, имени, username...",
"searchEmail": "Поиск по email...",
"noData": "Пользователи не найдены", "noData": "Пользователи не найдены",
"purchaseCount": "{{count}} покупок", "purchaseCount": "{{count}} покупок",
"noPurchases": "Нет покупок", "noPurchases": "Нет покупок",

View File

@@ -1360,6 +1360,7 @@
"title": "用户", "title": "用户",
"subtitle": "机器人用户管理", "subtitle": "机器人用户管理",
"search": "按ID、姓名、用户名搜索...", "search": "按ID、姓名、用户名搜索...",
"searchEmail": "按邮箱搜索...",
"noData": "未找到用户", "noData": "未找到用户",
"purchaseCount": "{{count}} 次购买", "purchaseCount": "{{count}} 次购买",
"noPurchases": "无购买记录", "noPurchases": "无购买记录",

View File

@@ -978,6 +978,7 @@ export default function AdminUsers() {
const [stats, setStats] = useState<UsersStatsResponse | null>(null); const [stats, setStats] = useState<UsersStatsResponse | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [emailSearch, setEmailSearch] = useState('');
const [statusFilter, setStatusFilter] = useState<string>(''); const [statusFilter, setStatusFilter] = useState<string>('');
const [sortBy, setSortBy] = useState<string>('created_at'); const [sortBy, setSortBy] = useState<string>('created_at');
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
@@ -991,6 +992,7 @@ export default function AdminUsers() {
setLoading(true); setLoading(true);
const params: Record<string, unknown> = { offset, limit, sort_by: sortBy }; const params: Record<string, unknown> = { offset, limit, sort_by: sortBy };
if (search) params.search = search; if (search) params.search = search;
if (emailSearch) params.email = emailSearch;
if (statusFilter) params.status = statusFilter; if (statusFilter) params.status = statusFilter;
const data = await adminUsersApi.getUsers( const data = await adminUsersApi.getUsers(
@@ -1003,7 +1005,7 @@ export default function AdminUsers() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [offset, search, statusFilter, sortBy]); }, [offset, search, emailSearch, statusFilter, sortBy]);
const loadStats = useCallback(async () => { const loadStats = useCallback(async () => {
try { try {
@@ -1086,47 +1088,67 @@ export default function AdminUsers() {
)} )}
{/* Filters */} {/* Filters */}
<div className="mb-4 flex flex-col gap-3 sm:flex-row"> <div className="mb-4 flex flex-col gap-3">
<form onSubmit={handleSearch} className="flex-1"> {/* Search fields row */}
<div className="relative"> <div className="flex flex-col gap-3 sm:flex-row">
<input <form onSubmit={handleSearch} className="flex-1">
type="text" <div className="relative">
value={search} <input
onChange={(e) => setSearch(e.target.value)} type="text"
placeholder={t('admin.users.search')} value={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" onChange={(e) => setSearch(e.target.value)}
/> placeholder={t('admin.users.search')}
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-dark-500"> 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"
<SearchIcon /> />
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-dark-500">
<SearchIcon />
</div>
</div> </div>
</div> </form>
</form> <form onSubmit={handleSearch} className="flex-1">
<select <div className="relative">
value={statusFilter} <input
onChange={(e) => { type="email"
setStatusFilter(e.target.value); value={emailSearch}
setOffset(0); onChange={(e) => setEmailSearch(e.target.value)}
}} placeholder={t('admin.users.searchEmail')}
className="rounded-xl border border-dark-700 bg-dark-800 px-3 py-2 text-dark-100" 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"
> />
<option value="">{t('admin.users.filters.allStatuses')}</option> <div className="absolute left-3 top-1/2 -translate-y-1/2 text-dark-500">
<option value="active">{t('admin.users.status.active')}</option> <SearchIcon />
<option value="blocked">{t('admin.users.status.blocked')}</option> </div>
<option value="deleted">{t('admin.users.status.deleted')}</option> </div>
</select> </form>
<select </div>
value={sortBy} {/* Filters row */}
onChange={(e) => { <div className="flex flex-col gap-3 sm:flex-row">
setSortBy(e.target.value); <select
setOffset(0); value={statusFilter}
}} onChange={(e) => {
className="rounded-xl border border-dark-700 bg-dark-800 px-3 py-2 text-dark-100" setStatusFilter(e.target.value);
> setOffset(0);
<option value="created_at">{t('admin.users.filters.byDate')}</option> }}
<option value="balance">{t('admin.users.filters.byBalance')}</option> className="rounded-xl border border-dark-700 bg-dark-800 px-3 py-2 text-dark-100"
<option value="last_activity">{t('admin.users.filters.byActivity')}</option> >
<option value="total_spent">{t('admin.users.filters.bySpent')}</option> <option value="">{t('admin.users.filters.allStatuses')}</option>
</select> <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> </div>
{/* Users list */} {/* Users list */}