fix: select-all subscriptions + per-page selector

- Fix allVisibleSubscriptionIds: include ALL users' filtered subs
  (was only users with >1 subs — single-sub users were excluded)
- Add per-page selector (25/50/100/200) in pagination bar
- Make limit a state variable instead of constant
- perPage i18n keys (ru + en)
This commit is contained in:
Fringg
2026-04-24 07:23:58 +03:00
parent 98871af656
commit e2706c7c26
3 changed files with 32 additions and 12 deletions

View File

@@ -2031,6 +2031,7 @@
"selectUser": "Select user {{name}}", "selectUser": "Select user {{name}}",
"deselectUser": "Deselect user {{name}}", "deselectUser": "Deselect user {{name}}",
"noResults": "No users found", "noResults": "No users found",
"perPage": "Per page",
"actions": { "actions": {
"extend": "Extend subscription", "extend": "Extend subscription",
"cancel": "Deactivate subscriptions", "cancel": "Deactivate subscriptions",

View File

@@ -3779,6 +3779,7 @@
"selectUser": "Выбрать пользователя {{name}}", "selectUser": "Выбрать пользователя {{name}}",
"deselectUser": "Снять выбор пользователя {{name}}", "deselectUser": "Снять выбор пользователя {{name}}",
"noResults": "Пользователи не найдены", "noResults": "Пользователи не найдены",
"perPage": "На странице",
"actions": { "actions": {
"extend": "Продлить подписку", "extend": "Продлить подписку",
"cancel": "Деактивировать подписки", "cancel": "Деактивировать подписки",

View File

@@ -1409,7 +1409,7 @@ export default function AdminBulkActions() {
// Pagination // Pagination
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
const limit = 50; const [limit, setLimit] = useState(50);
// Selection // Selection
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
@@ -1466,7 +1466,7 @@ export default function AdminBulkActions() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [offset, committedSearch, statusFilter, tariffFilter, promoGroupFilter]); }, [offset, limit, committedSearch, statusFilter, tariffFilter, promoGroupFilter]);
useEffect(() => { useEffect(() => {
loadUsers(); loadUsers();
@@ -1583,10 +1583,8 @@ export default function AdminBulkActions() {
for (const user of users) { for (const user of users) {
const subs = user.subscriptions ?? []; const subs = user.subscriptions ?? [];
const filtered = getFilteredSubs(subs); const filtered = getFilteredSubs(subs);
if (filtered.length > 1) { for (const sub of filtered) {
for (const sub of filtered) { ids.push(sub.id);
ids.push(sub.id);
}
} }
} }
return ids; return ids;
@@ -2214,12 +2212,32 @@ export default function AdminBulkActions() {
</div> </div>
)} )}
{/* Pagination */} {/* Pagination + per-page selector */}
{totalPages > 1 && ( <div className="mt-4 flex flex-wrap items-center justify-between gap-3">
<div className="mt-4 flex items-center justify-between"> <div className="flex items-center gap-3">
<div className="text-sm text-dark-400"> <span className="text-sm text-dark-400">
{offset + 1}&ndash;{Math.min(offset + limit, total)} / {total} {offset + 1}&ndash;{Math.min(offset + limit, total)} / {total}
</span>
<div className="flex items-center gap-1.5">
<span className="text-xs text-dark-500">{t('admin.bulkActions.perPage')}</span>
<select
value={limit}
onChange={(e) => {
setLimit(Number(e.target.value));
setOffset(0);
clearAllSelections();
}}
className="rounded-lg border border-dark-700 bg-dark-800 px-2 py-1.5 text-xs text-dark-200 outline-none transition-colors focus:border-accent-500/40"
>
{[25, 50, 100, 200].map((n) => (
<option key={n} value={n}>
{n}
</option>
))}
</select>
</div> </div>
</div>
{totalPages > 1 && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => { onClick={() => {
@@ -2247,8 +2265,8 @@ export default function AdminBulkActions() {
<ChevronRightIcon /> <ChevronRightIcon />
</button> </button>
</div> </div>
</div> )}
)} </div>
{/* Floating action bar — portal to body for correct fixed positioning */} {/* Floating action bar — portal to body for correct fixed positioning */}
{createPortal( {createPortal(