feat: bulk delete subscription + fix sub-rows showing for filtered single subs

- Add "Удалить подписки" action with red trash icon, destructive
  warning dialog (red border, irreversible action notice)
- Fix canExpand: show sub-rows when isMultiTariff && subs > 0
  (was > 1, hiding single filtered subscription results)
- i18n: deleteSubscription action + warning/hint (ru + en)
This commit is contained in:
Fringg
2026-04-24 06:51:21 +03:00
parent 5969c74f92
commit c726d3d74e
4 changed files with 49 additions and 5 deletions

View File

@@ -11,7 +11,8 @@ export type BulkActionType =
| 'add_balance' | 'add_balance'
| 'assign_promo_group' | 'assign_promo_group'
| 'grant_subscription' | 'grant_subscription'
| 'set_devices'; | 'set_devices'
| 'delete_subscription';
export interface BulkActionRequest { export interface BulkActionRequest {
action: BulkActionType; action: BulkActionType;

View File

@@ -2040,7 +2040,12 @@
"addBalance": "Add balance", "addBalance": "Add balance",
"assignPromoGroup": "Assign promo group", "assignPromoGroup": "Assign promo group",
"grantSubscription": "Grant subscription", "grantSubscription": "Grant subscription",
"setDevices": "Set devices" "setDevices": "Set devices",
"deleteSubscription": "Delete subscriptions"
},
"deleteSubscription": {
"warning": "Selected subscriptions will be permanently deleted from the bot and RemnaWave panel!",
"hint": "Users will lose VPN access for deleted subscriptions. This action cannot be undone."
}, },
"params": { "params": {
"days": "Number of days", "days": "Number of days",

View File

@@ -3788,7 +3788,12 @@
"addBalance": "Начислить баланс", "addBalance": "Начислить баланс",
"assignPromoGroup": "Назначить промогруппу", "assignPromoGroup": "Назначить промогруппу",
"grantSubscription": "Выдать подписку", "grantSubscription": "Выдать подписку",
"setDevices": "Изменить устройства" "setDevices": "Изменить устройства",
"deleteSubscription": "Удалить подписки"
},
"deleteSubscription": {
"warning": "Выбранные подписки будут безвозвратно удалены из бота и панели RemnaWave!",
"hint": "Пользователи потеряют доступ к VPN по удалённым подпискам. Это действие нельзя отменить."
}, },
"params": { "params": {
"days": "Количество дней", "days": "Количество дней",

View File

@@ -141,6 +141,7 @@ const SUBSCRIPTION_LEVEL_ACTIONS: Set<BulkActionType> = new Set([
'change_tariff', 'change_tariff',
'add_traffic', 'add_traffic',
'set_devices', 'set_devices',
'delete_subscription',
]); ]);
function isSubscriptionLevelAction(action: BulkActionType): boolean { function isSubscriptionLevelAction(action: BulkActionType): boolean {
@@ -799,6 +800,7 @@ function ActionModal({
assign_promo_group: 'admin.bulkActions.actions.assignPromoGroup', assign_promo_group: 'admin.bulkActions.actions.assignPromoGroup',
grant_subscription: 'admin.bulkActions.actions.grantSubscription', grant_subscription: 'admin.bulkActions.actions.grantSubscription',
set_devices: 'admin.bulkActions.actions.setDevices', set_devices: 'admin.bulkActions.actions.setDevices',
delete_subscription: 'admin.bulkActions.actions.deleteSubscription',
}; };
const handleSubmit = () => { const handleSubmit = () => {
@@ -965,6 +967,17 @@ function ActionModal({
</div> </div>
</div> </div>
); );
case 'delete_subscription':
return (
<div className="rounded-xl border border-error-500/20 bg-error-500/5 px-3 py-2.5">
<p className="text-sm font-medium text-error-400">
{t('admin.bulkActions.deleteSubscription.warning')}
</p>
<p className="mt-1 text-xs text-error-300/70">
{t('admin.bulkActions.deleteSubscription.hint')}
</p>
</div>
);
default: default:
return null; return null;
} }
@@ -1189,6 +1202,26 @@ function FloatingActionBar({
), ),
colorClass: 'text-accent-400 hover:bg-accent-500/10', colorClass: 'text-accent-400 hover:bg-accent-500/10',
}, },
{
type: 'delete_subscription',
labelKey: 'admin.bulkActions.actions.deleteSubscription',
icon: (
<svg
className="h-3.5 w-3.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
),
colorClass: 'text-error-400 hover:bg-error-500/10',
},
{ {
type: 'add_balance', type: 'add_balance',
labelKey: 'admin.bulkActions.actions.addBalance', labelKey: 'admin.bulkActions.actions.addBalance',
@@ -1779,7 +1812,7 @@ export default function AdminBulkActions() {
const user = row.original; const user = row.original;
const filteredSubs = getFilteredSubs(user.subscriptions ?? []); const filteredSubs = getFilteredSubs(user.subscriptions ?? []);
const subCount = filteredSubs.length; const subCount = filteredSubs.length;
const canExpand = subCount > 1; const canExpand = isMultiTariff && subCount > 0;
const isExpanded = expandedRows[user.id] ?? false; const isExpanded = expandedRows[user.id] ?? false;
return ( return (
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
@@ -2065,7 +2098,7 @@ export default function AdminBulkActions() {
{table.getRowModel().rows.map((row) => { {table.getRowModel().rows.map((row) => {
const user = row.original; const user = row.original;
const filteredSubs = getFilteredSubs(user.subscriptions ?? []); const filteredSubs = getFilteredSubs(user.subscriptions ?? []);
const canExpand = filteredSubs.length > 1; const canExpand = isMultiTariff && filteredSubs.length > 0;
const isExpanded = expandedRows[user.id] ?? false; const isExpanded = expandedRows[user.id] ?? false;
return ( return (