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 AdminRemnawaveSquadDetail = lazyWithRetry(() => import('./pages/AdminRemnawaveSquadDetail'));
|
||||||
const AdminEmailTemplates = lazyWithRetry(() => import('./pages/AdminEmailTemplates'));
|
const AdminEmailTemplates = lazyWithRetry(() => import('./pages/AdminEmailTemplates'));
|
||||||
const AdminTrafficUsage = lazyWithRetry(() => import('./pages/AdminTrafficUsage'));
|
const AdminTrafficUsage = lazyWithRetry(() => import('./pages/AdminTrafficUsage'));
|
||||||
|
const AdminBulkActions = lazyWithRetry(() => import('./pages/AdminBulkActions'));
|
||||||
const AdminSalesStats = lazyWithRetry(() => import('./pages/AdminSalesStats'));
|
const AdminSalesStats = lazyWithRetry(() => import('./pages/AdminSalesStats'));
|
||||||
const AdminUpdates = lazyWithRetry(() => import('./pages/AdminUpdates'));
|
const AdminUpdates = lazyWithRetry(() => import('./pages/AdminUpdates'));
|
||||||
const AdminUserDetail = lazyWithRetry(() => import('./pages/AdminUserDetail'));
|
const AdminUserDetail = lazyWithRetry(() => import('./pages/AdminUserDetail'));
|
||||||
@@ -966,6 +967,16 @@ function App() {
|
|||||||
</PermissionRoute>
|
</PermissionRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/admin/bulk-actions"
|
||||||
|
element={
|
||||||
|
<PermissionRoute permission="users:read">
|
||||||
|
<LazyPage>
|
||||||
|
<AdminBulkActions />
|
||||||
|
</LazyPage>
|
||||||
|
</PermissionRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/admin/payments"
|
path="/admin/payments"
|
||||||
element={
|
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",
|
"salesStats": "Sales Statistics",
|
||||||
"landings": "Landings",
|
"landings": "Landings",
|
||||||
"referralNetwork": "Referral Network",
|
"referralNetwork": "Referral Network",
|
||||||
"news": "News"
|
"news": "News",
|
||||||
|
"bulkActions": "Bulk Actions"
|
||||||
},
|
},
|
||||||
"panel": {
|
"panel": {
|
||||||
"title": "Admin Panel",
|
"title": "Admin Panel",
|
||||||
@@ -2021,6 +2022,62 @@
|
|||||||
"telegramOidcClientSecret": "Client Secret"
|
"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": {
|
"theme": {
|
||||||
"accentColor": "Accent color",
|
"accentColor": "Accent color",
|
||||||
"customizeColors": "Customize colors",
|
"customizeColors": "Customize colors",
|
||||||
|
|||||||
@@ -1146,7 +1146,8 @@
|
|||||||
"salesStats": "Статистика продаж",
|
"salesStats": "Статистика продаж",
|
||||||
"landings": "Лендинги",
|
"landings": "Лендинги",
|
||||||
"referralNetwork": "Реферальная сеть",
|
"referralNetwork": "Реферальная сеть",
|
||||||
"news": "Новости"
|
"news": "Новости",
|
||||||
|
"bulkActions": "Массовые действия"
|
||||||
},
|
},
|
||||||
"panel": {
|
"panel": {
|
||||||
"title": "Панель администратора",
|
"title": "Панель администратора",
|
||||||
@@ -3769,6 +3770,62 @@
|
|||||||
"users": "Пользователи",
|
"users": "Пользователи",
|
||||||
"security": "Безопасность"
|
"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": {
|
"theme": {
|
||||||
"accentColor": "Акцентный цвет",
|
"accentColor": "Акцентный цвет",
|
||||||
"customizeColors": "Настроить цвета",
|
"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" />
|
<path d="M15 8h-5M15 12h-5" />
|
||||||
</SvgIcon>
|
</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: (
|
search: (
|
||||||
<SvgIcon>
|
<SvgIcon>
|
||||||
<circle cx="11" cy="11" r="8" />
|
<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)))',
|
gradient: 'linear-gradient(135deg, rgb(var(--color-accent-400)), rgb(var(--color-error-400)))',
|
||||||
items: [
|
items: [
|
||||||
{ name: 'admin.nav.users', icon: 'users', to: '/admin/users', permission: 'users:read' },
|
{ 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',
|
name: 'admin.nav.tickets',
|
||||||
icon: 'ticket',
|
icon: 'ticket',
|
||||||
|
|||||||
Reference in New Issue
Block a user