mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
feat: admin bulk actions page with TanStack Table
Full-featured admin page for mass operations on users: - TanStack Table with checkbox selection (select all / individual) - Server-side pagination, search, and filters (status, tariff, promo group) - Columns: user, subscription status, tariff, balance, days left, promo group - Floating glass-effect action bar with 7 bulk operations: extend subscription, deactivate, activate, change tariff, add traffic, add balance, assign promo group - Action modal dialogs with input fields, loading state, result summary - Responsive design for desktop, mobile, and Telegram Mini App - Full i18n support (ru, en) - Dark theme with accent highlights and status color badges
This commit is contained in:
11
src/App.tsx
11
src/App.tsx
@@ -123,6 +123,7 @@ const AdminRemnawave = lazyWithRetry(() => import('./pages/AdminRemnawave'));
|
||||
const AdminRemnawaveSquadDetail = lazyWithRetry(() => import('./pages/AdminRemnawaveSquadDetail'));
|
||||
const AdminEmailTemplates = lazyWithRetry(() => import('./pages/AdminEmailTemplates'));
|
||||
const AdminTrafficUsage = lazyWithRetry(() => import('./pages/AdminTrafficUsage'));
|
||||
const AdminBulkActions = lazyWithRetry(() => import('./pages/AdminBulkActions'));
|
||||
const AdminSalesStats = lazyWithRetry(() => import('./pages/AdminSalesStats'));
|
||||
const AdminUpdates = lazyWithRetry(() => import('./pages/AdminUpdates'));
|
||||
const AdminUserDetail = lazyWithRetry(() => import('./pages/AdminUserDetail'));
|
||||
@@ -966,6 +967,16 @@ function App() {
|
||||
</PermissionRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/bulk-actions"
|
||||
element={
|
||||
<PermissionRoute permission="users:read">
|
||||
<LazyPage>
|
||||
<AdminBulkActions />
|
||||
</LazyPage>
|
||||
</PermissionRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/payments"
|
||||
element={
|
||||
|
||||
42
src/api/adminBulkActions.ts
Normal file
42
src/api/adminBulkActions.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import apiClient from './client';
|
||||
|
||||
export type BulkActionType =
|
||||
| 'extend'
|
||||
| 'cancel'
|
||||
| 'activate'
|
||||
| 'change_tariff'
|
||||
| 'add_traffic'
|
||||
| 'add_balance'
|
||||
| 'assign_promo_group';
|
||||
|
||||
export interface BulkActionRequest {
|
||||
action: BulkActionType;
|
||||
user_ids: number[];
|
||||
params: BulkActionParams;
|
||||
}
|
||||
|
||||
export interface BulkActionParams {
|
||||
days?: number;
|
||||
tariff_id?: number;
|
||||
traffic_gb?: number;
|
||||
balance_kopeks?: number;
|
||||
promo_group_id?: number | null;
|
||||
}
|
||||
|
||||
export interface BulkActionResult {
|
||||
success: boolean;
|
||||
total: number;
|
||||
success_count: number;
|
||||
error_count: number;
|
||||
errors: Array<{
|
||||
user_id: number;
|
||||
error: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const adminBulkActionsApi = {
|
||||
execute: async (data: BulkActionRequest): Promise<BulkActionResult> => {
|
||||
const response = await apiClient.post('/cabinet/admin/bulk/execute', data);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -1125,7 +1125,8 @@
|
||||
"salesStats": "Sales Statistics",
|
||||
"landings": "Landings",
|
||||
"referralNetwork": "Referral Network",
|
||||
"news": "News"
|
||||
"news": "News",
|
||||
"bulkActions": "Bulk Actions"
|
||||
},
|
||||
"panel": {
|
||||
"title": "Admin Panel",
|
||||
@@ -2021,6 +2022,62 @@
|
||||
"telegramOidcClientSecret": "Client Secret"
|
||||
}
|
||||
},
|
||||
"bulkActions": {
|
||||
"title": "Bulk Actions",
|
||||
"subtitle": "User management",
|
||||
"selectedCount": "Selected: {{count}}",
|
||||
"selectAll": "Select all",
|
||||
"deselectAll": "Deselect all",
|
||||
"noResults": "No users found",
|
||||
"actions": {
|
||||
"extend": "Extend subscription",
|
||||
"cancel": "Deactivate subscriptions",
|
||||
"activate": "Activate subscriptions",
|
||||
"changeTariff": "Change tariff",
|
||||
"addTraffic": "Add traffic",
|
||||
"addBalance": "Add balance",
|
||||
"assignPromoGroup": "Assign promo group"
|
||||
},
|
||||
"params": {
|
||||
"days": "Number of days",
|
||||
"tariff": "Select tariff",
|
||||
"trafficGb": "Traffic amount (GB)",
|
||||
"balanceRub": "Amount (RUB)",
|
||||
"promoGroup": "Promo group",
|
||||
"removePromoGroup": "Remove promo group"
|
||||
},
|
||||
"confirm": "Apply",
|
||||
"cancel": "Cancel",
|
||||
"executing": "Executing...",
|
||||
"complete": "Complete",
|
||||
"successCount": "Succeeded: {{count}}",
|
||||
"errorCount": "Errors: {{count}}",
|
||||
"filters": {
|
||||
"search": "Search by ID, username, email",
|
||||
"status": "Subscription status",
|
||||
"tariff": "Tariff",
|
||||
"promoGroup": "Promo group",
|
||||
"allStatuses": "All statuses",
|
||||
"allTariffs": "All tariffs",
|
||||
"allGroups": "All groups"
|
||||
},
|
||||
"statuses": {
|
||||
"active": "Active",
|
||||
"expired": "Expired",
|
||||
"trial": "Trial",
|
||||
"limited": "Limited",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"columns": {
|
||||
"user": "User",
|
||||
"status": "Status",
|
||||
"tariff": "Tariff",
|
||||
"balance": "Balance",
|
||||
"daysRemaining": "Days",
|
||||
"promoGroup": "Group",
|
||||
"spent": "Spent"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"accentColor": "Accent color",
|
||||
"customizeColors": "Customize colors",
|
||||
|
||||
@@ -1146,7 +1146,8 @@
|
||||
"salesStats": "Статистика продаж",
|
||||
"landings": "Лендинги",
|
||||
"referralNetwork": "Реферальная сеть",
|
||||
"news": "Новости"
|
||||
"news": "Новости",
|
||||
"bulkActions": "Массовые действия"
|
||||
},
|
||||
"panel": {
|
||||
"title": "Панель администратора",
|
||||
@@ -3769,6 +3770,62 @@
|
||||
"users": "Пользователи",
|
||||
"security": "Безопасность"
|
||||
},
|
||||
"bulkActions": {
|
||||
"title": "Массовые действия",
|
||||
"subtitle": "Управление пользователями",
|
||||
"selectedCount": "Выбрано: {{count}}",
|
||||
"selectAll": "Выбрать всех",
|
||||
"deselectAll": "Снять выделение",
|
||||
"noResults": "Пользователи не найдены",
|
||||
"actions": {
|
||||
"extend": "Продлить подписку",
|
||||
"cancel": "Деактивировать подписки",
|
||||
"activate": "Активировать подписки",
|
||||
"changeTariff": "Сменить тариф",
|
||||
"addTraffic": "Начислить трафик",
|
||||
"addBalance": "Начислить баланс",
|
||||
"assignPromoGroup": "Назначить промогруппу"
|
||||
},
|
||||
"params": {
|
||||
"days": "Количество дней",
|
||||
"tariff": "Выберите тариф",
|
||||
"trafficGb": "Объём трафика (ГБ)",
|
||||
"balanceRub": "Сумма (руб.)",
|
||||
"promoGroup": "Промогруппа",
|
||||
"removePromoGroup": "Убрать промогруппу"
|
||||
},
|
||||
"confirm": "Применить",
|
||||
"cancel": "Отмена",
|
||||
"executing": "Выполнение...",
|
||||
"complete": "Выполнено",
|
||||
"successCount": "Успешно: {{count}}",
|
||||
"errorCount": "Ошибок: {{count}}",
|
||||
"filters": {
|
||||
"search": "Поиск по ID, нику, email",
|
||||
"status": "Статус подписки",
|
||||
"tariff": "Тариф",
|
||||
"promoGroup": "Промогруппа",
|
||||
"allStatuses": "Все статусы",
|
||||
"allTariffs": "Все тарифы",
|
||||
"allGroups": "Все группы"
|
||||
},
|
||||
"statuses": {
|
||||
"active": "Активна",
|
||||
"expired": "Истекла",
|
||||
"trial": "Триал",
|
||||
"limited": "Ограничена",
|
||||
"disabled": "Отключена"
|
||||
},
|
||||
"columns": {
|
||||
"user": "Пользователь",
|
||||
"status": "Статус",
|
||||
"tariff": "Тариф",
|
||||
"balance": "Баланс",
|
||||
"daysRemaining": "Дни",
|
||||
"promoGroup": "Группа",
|
||||
"spent": "Потрачено"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"accentColor": "Акцентный цвет",
|
||||
"customizeColors": "Настроить цвета",
|
||||
|
||||
1201
src/pages/AdminBulkActions.tsx
Normal file
1201
src/pages/AdminBulkActions.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -333,6 +333,12 @@ const icons = {
|
||||
<path d="M15 8h-5M15 12h-5" />
|
||||
</SvgIcon>
|
||||
),
|
||||
'list-checks': (
|
||||
<SvgIcon>
|
||||
<path d="M10 6h11M10 12h11M10 18h11" />
|
||||
<path d="m3 6 1 1 2-2M3 12l1 1 2-2M3 18l1 1 2-2" />
|
||||
</SvgIcon>
|
||||
),
|
||||
search: (
|
||||
<SvgIcon>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
@@ -412,6 +418,12 @@ const sections: AdminSection[] = [
|
||||
gradient: 'linear-gradient(135deg, rgb(var(--color-accent-400)), rgb(var(--color-error-400)))',
|
||||
items: [
|
||||
{ name: 'admin.nav.users', icon: 'users', to: '/admin/users', permission: 'users:read' },
|
||||
{
|
||||
name: 'admin.nav.bulkActions',
|
||||
icon: 'list-checks',
|
||||
to: '/admin/bulk-actions',
|
||||
permission: 'users:read',
|
||||
},
|
||||
{
|
||||
name: 'admin.nav.tickets',
|
||||
icon: 'ticket',
|
||||
|
||||
Reference in New Issue
Block a user