mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
fix: align frontend bulk action types with backend API contract
- Fix BulkActionType enum: 'extend' → 'extend_subscription', 'cancel' → 'cancel_subscription', 'activate' → 'activate_subscription' - Fix BulkActionParams: 'balance_kopeks' → 'amount_kopeks' to match backend - Fix SSE complete handler: build errors from progress log (backend complete event has no errors array) - Add skipped_count to all BulkActionResult construction paths - Add dry_run support to BulkActionRequest type - Transform backend results[] to frontend errors[] in non-streaming path - Remove unused errors variable in streaming fallback path
This commit is contained in:
@@ -2,9 +2,10 @@ import apiClient from './client';
|
|||||||
import { tokenStorage } from '../utils/token';
|
import { tokenStorage } from '../utils/token';
|
||||||
|
|
||||||
export type BulkActionType =
|
export type BulkActionType =
|
||||||
| 'extend'
|
| 'extend_subscription'
|
||||||
| 'cancel'
|
| 'add_days'
|
||||||
| 'activate'
|
| 'cancel_subscription'
|
||||||
|
| 'activate_subscription'
|
||||||
| 'change_tariff'
|
| 'change_tariff'
|
||||||
| 'add_traffic'
|
| 'add_traffic'
|
||||||
| 'add_balance'
|
| 'add_balance'
|
||||||
@@ -15,13 +16,15 @@ export interface BulkActionRequest {
|
|||||||
action: BulkActionType;
|
action: BulkActionType;
|
||||||
user_ids: number[];
|
user_ids: number[];
|
||||||
params: BulkActionParams;
|
params: BulkActionParams;
|
||||||
|
dry_run?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BulkActionParams {
|
export interface BulkActionParams {
|
||||||
days?: number;
|
days?: number;
|
||||||
tariff_id?: number;
|
tariff_id?: number;
|
||||||
traffic_gb?: number;
|
traffic_gb?: number;
|
||||||
balance_kopeks?: number;
|
amount_kopeks?: number;
|
||||||
|
balance_description?: string;
|
||||||
promo_group_id?: number | null;
|
promo_group_id?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +39,7 @@ export interface BulkActionResult {
|
|||||||
total: number;
|
total: number;
|
||||||
success_count: number;
|
success_count: number;
|
||||||
error_count: number;
|
error_count: number;
|
||||||
|
skipped_count: number;
|
||||||
errors: BulkActionErrorItem[];
|
errors: BulkActionErrorItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,11 +56,10 @@ export interface BulkProgressEvent {
|
|||||||
|
|
||||||
export interface BulkCompleteEvent {
|
export interface BulkCompleteEvent {
|
||||||
type: 'complete';
|
type: 'complete';
|
||||||
success: boolean;
|
|
||||||
total: number;
|
total: number;
|
||||||
success_count: number;
|
success_count: number;
|
||||||
error_count: number;
|
error_count: number;
|
||||||
errors: BulkActionErrorItem[];
|
skipped_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BulkSSEEvent = BulkProgressEvent | BulkCompleteEvent;
|
export type BulkSSEEvent = BulkProgressEvent | BulkCompleteEvent;
|
||||||
@@ -66,7 +69,23 @@ const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
|
|||||||
export const adminBulkActionsApi = {
|
export const adminBulkActionsApi = {
|
||||||
execute: async (data: BulkActionRequest): Promise<BulkActionResult> => {
|
execute: async (data: BulkActionRequest): Promise<BulkActionResult> => {
|
||||||
const response = await apiClient.post('/cabinet/admin/bulk/execute', data);
|
const response = await apiClient.post('/cabinet/admin/bulk/execute', data);
|
||||||
return response.data;
|
const raw = response.data;
|
||||||
|
// Transform backend results[] to frontend errors[]
|
||||||
|
const errors: BulkActionErrorItem[] = (raw.results || [])
|
||||||
|
.filter((r: { success: boolean }) => !r.success)
|
||||||
|
.map((r: { user_id: number; username?: string; message?: string }) => ({
|
||||||
|
user_id: r.user_id,
|
||||||
|
username: r.username,
|
||||||
|
error: r.message || 'Unknown error',
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
success: raw.error_count === 0,
|
||||||
|
total: raw.total,
|
||||||
|
success_count: raw.success_count,
|
||||||
|
error_count: raw.error_count,
|
||||||
|
skipped_count: raw.skipped_count || 0,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
executeWithStream: async (
|
executeWithStream: async (
|
||||||
@@ -128,10 +147,13 @@ export const adminBulkActionsApi = {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: non-streaming JSON response
|
// Fallback: non-streaming JSON response
|
||||||
const result = (await response.json()) as BulkActionResult;
|
const raw = await response.json();
|
||||||
onEvent({
|
onEvent({
|
||||||
type: 'complete',
|
type: 'complete',
|
||||||
...result,
|
total: raw.total,
|
||||||
|
success_count: raw.success_count,
|
||||||
|
error_count: raw.error_count,
|
||||||
|
skipped_count: raw.skipped_count || 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -462,9 +462,10 @@ function ActionModal({
|
|||||||
if (!modal.open || !modal.action) return null;
|
if (!modal.open || !modal.action) return null;
|
||||||
|
|
||||||
const actionLabelKeys: Record<BulkActionType, string> = {
|
const actionLabelKeys: Record<BulkActionType, string> = {
|
||||||
extend: 'admin.bulkActions.actions.extend',
|
extend_subscription: 'admin.bulkActions.actions.extend',
|
||||||
cancel: 'admin.bulkActions.actions.cancel',
|
add_days: 'admin.bulkActions.actions.extend',
|
||||||
activate: 'admin.bulkActions.actions.activate',
|
cancel_subscription: 'admin.bulkActions.actions.cancel',
|
||||||
|
activate_subscription: 'admin.bulkActions.actions.activate',
|
||||||
change_tariff: 'admin.bulkActions.actions.changeTariff',
|
change_tariff: 'admin.bulkActions.actions.changeTariff',
|
||||||
add_traffic: 'admin.bulkActions.actions.addTraffic',
|
add_traffic: 'admin.bulkActions.actions.addTraffic',
|
||||||
add_balance: 'admin.bulkActions.actions.addBalance',
|
add_balance: 'admin.bulkActions.actions.addBalance',
|
||||||
@@ -475,7 +476,7 @@ function ActionModal({
|
|||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const params: BulkActionParams = {};
|
const params: BulkActionParams = {};
|
||||||
switch (modal.action) {
|
switch (modal.action) {
|
||||||
case 'extend':
|
case 'extend_subscription':
|
||||||
params.days = days;
|
params.days = days;
|
||||||
break;
|
break;
|
||||||
case 'change_tariff':
|
case 'change_tariff':
|
||||||
@@ -485,7 +486,7 @@ function ActionModal({
|
|||||||
params.traffic_gb = trafficGb;
|
params.traffic_gb = trafficGb;
|
||||||
break;
|
break;
|
||||||
case 'add_balance':
|
case 'add_balance':
|
||||||
params.balance_kopeks = Math.round(balanceRub * 100);
|
params.amount_kopeks = Math.round(balanceRub * 100);
|
||||||
break;
|
break;
|
||||||
case 'assign_promo_group':
|
case 'assign_promo_group':
|
||||||
params.promo_group_id = promoGroupId;
|
params.promo_group_id = promoGroupId;
|
||||||
@@ -506,7 +507,7 @@ function ActionModal({
|
|||||||
|
|
||||||
const renderInputs = () => {
|
const renderInputs = () => {
|
||||||
switch (modal.action) {
|
switch (modal.action) {
|
||||||
case 'extend':
|
case 'extend_subscription':
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-sm font-medium text-dark-300">
|
<label className="mb-1.5 block text-sm font-medium text-dark-300">
|
||||||
@@ -781,19 +782,19 @@ function FloatingActionBar({
|
|||||||
|
|
||||||
const actions: ActionConfig[] = [
|
const actions: ActionConfig[] = [
|
||||||
{
|
{
|
||||||
type: 'extend',
|
type: 'extend_subscription',
|
||||||
labelKey: 'admin.bulkActions.actions.extend',
|
labelKey: 'admin.bulkActions.actions.extend',
|
||||||
icon: <span aria-hidden="true">+</span>,
|
icon: <span aria-hidden="true">+</span>,
|
||||||
colorClass: 'text-success-400 hover:bg-success-500/10',
|
colorClass: 'text-success-400 hover:bg-success-500/10',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'activate',
|
type: 'activate_subscription',
|
||||||
labelKey: 'admin.bulkActions.actions.activate',
|
labelKey: 'admin.bulkActions.actions.activate',
|
||||||
icon: <span aria-hidden="true">+</span>,
|
icon: <span aria-hidden="true">+</span>,
|
||||||
colorClass: 'text-success-400 hover:bg-success-500/10',
|
colorClass: 'text-success-400 hover:bg-success-500/10',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'cancel',
|
type: 'cancel_subscription',
|
||||||
labelKey: 'admin.bulkActions.actions.cancel',
|
labelKey: 'admin.bulkActions.actions.cancel',
|
||||||
icon: <span aria-hidden="true">-</span>,
|
icon: <span aria-hidden="true">-</span>,
|
||||||
colorClass: 'text-error-400 hover:bg-error-500/10',
|
colorClass: 'text-error-400 hover:bg-error-500/10',
|
||||||
@@ -1073,18 +1074,29 @@ export default function AdminBulkActions() {
|
|||||||
: prev.progress,
|
: prev.progress,
|
||||||
}));
|
}));
|
||||||
} else if (event.type === 'complete') {
|
} else if (event.type === 'complete') {
|
||||||
setModal((prev) => ({
|
setModal((prev) => {
|
||||||
|
// Build errors from accumulated progress log
|
||||||
|
const errors = (prev.progress?.log ?? [])
|
||||||
|
.filter((e) => !e.success)
|
||||||
|
.map((e) => ({
|
||||||
|
user_id: e.user_id,
|
||||||
|
username: e.username,
|
||||||
|
error: e.message || e.error || '',
|
||||||
|
}));
|
||||||
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
loading: false,
|
loading: false,
|
||||||
progress: null,
|
progress: null,
|
||||||
result: {
|
result: {
|
||||||
success: event.success,
|
success: event.error_count === 0,
|
||||||
total: event.total,
|
total: event.total,
|
||||||
success_count: event.success_count,
|
success_count: event.success_count,
|
||||||
error_count: event.error_count,
|
error_count: event.error_count,
|
||||||
errors: event.errors,
|
skipped_count: event.skipped_count || 0,
|
||||||
|
errors,
|
||||||
},
|
},
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
loadUsers();
|
loadUsers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1101,6 +1113,7 @@ export default function AdminBulkActions() {
|
|||||||
total: prev.progress.total,
|
total: prev.progress.total,
|
||||||
success_count: prev.progress.successCount,
|
success_count: prev.progress.successCount,
|
||||||
error_count: prev.progress.errorCount,
|
error_count: prev.progress.errorCount,
|
||||||
|
skipped_count: 0,
|
||||||
errors: prev.progress.log
|
errors: prev.progress.log
|
||||||
.filter((e) => !e.success)
|
.filter((e) => !e.success)
|
||||||
.map((e) => ({
|
.map((e) => ({
|
||||||
@@ -1126,6 +1139,7 @@ export default function AdminBulkActions() {
|
|||||||
total: totalCount,
|
total: totalCount,
|
||||||
success_count: prev.progress?.successCount ?? 0,
|
success_count: prev.progress?.successCount ?? 0,
|
||||||
error_count: totalCount - (prev.progress?.successCount ?? 0),
|
error_count: totalCount - (prev.progress?.successCount ?? 0),
|
||||||
|
skipped_count: 0,
|
||||||
errors: [],
|
errors: [],
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user