Commit Graph

38 Commits

Author SHA1 Message Date
c0mrade
0a89838926 style(cabinet-icons): use regular Phosphor weight and switch wheel to dice
- Drop the Duotone variants (their translucent secondary fill looked murky on
  the dark theme) in favour of the clean regular weight across all icons
- WheelIcon: PiSteeringWheel → PiDiceFive (a steering wheel made no sense for
  the fortune-wheel feature)
2026-05-31 19:24:29 +03:00
c0mrade
d0e0b6b7e3 feat(cabinet): migrate all icons to the panel's Phosphor set
Replace the cabinet's hand-written heroicons-style SVG icon components with
the Remnawave panel's own icon family — Phosphor via react-icons/pi (Duotone).
All icons now live in a single central barrel (src/components/icons:
index + extended-icons + editor-icons, 144 icons); every feature file imports
from it instead of redefining inline SVGs (~440 icon defs removed).

- Add react-icons dependency
- Desktop nav (AppShell) now uses the central Phosphor icons, removing a
  stroke-vs-duotone inconsistency the partial migration introduced
- Icons with custom props (SortIcon's direction, expandable chevrons) kept as
  thin Phosphor wrappers; the RemnawaveIcon brand logo and animated SVGs
  (checkmarks, spinners) are intentionally left as-is
- Restore the original per-call-site icon sizes wherever the centralized
  default differed (211 call sites)
2026-05-31 18:50:50 +03:00
c0mrade
c11bfaf012 refactor(bulk-actions): extract SubscriptionSubRow + StatusBadge
Fourth and final step in the AdminBulkActions decomp.

SubscriptionSubRow: the expanded per-user secondary <tr> shown when
the operator drills into multi-subscription users (tariff, status,
days remaining, traffic bar, devices). Pure presentational.

StatusBadge: the pill used both inside the sub-row and the primary
user-table column (subscription_status). Co-located here because
both consumers want the same visual contract.

AdminBulkActions.tsx: 1367 → 1182 lines (-185).
Cumulative across the 4-step admin decomp: 2539 → 1182 (-1357, -53%).
2026-05-27 09:35:37 +03:00
c0mrade
a8df415931 fix(bulk-actions): drop unused ChevronDownIcon import
After MultiSelectDropdown extraction the parent no longer references
ChevronDownIcon directly — the icon now lives only inside the moved
sub-component. Trailing import cleanup from the previous commit.
2026-05-27 09:34:07 +03:00
c0mrade
8d25e5201a refactor(bulk-actions): extract MultiSelectDropdown
Third step in the AdminBulkActions decomp. Move the 154-line
pop-over multi-select used by the tariff / status / node filters
into src/components/admin/bulkActions/MultiSelectDropdown.tsx.

Pure controlled primitive: closes on outside click, provides
select-all / deselect-all helpers, uses the shared ChevronDownIcon
from DropdownSelect.tsx. Exports MultiSelectOption type alongside.

Parent drops its inline ChevronDownIcon import (now only referenced
by the moved sibling) and imports MultiSelectDropdown + the option
type from the new module.

AdminBulkActions.tsx: 1517 → 1367 lines (-150).
2026-05-27 09:32:54 +03:00
c0mrade
f398d9e95c refactor(bulk-actions): extract FloatingActionBar + actionTargets
Second step in the AdminBulkActions decomp. Two new files:

- actionTargets.ts (27 lines): SUBSCRIPTION_LEVEL_ACTIONS set +
  isSubscriptionLevelAction(). The classification is the source of
  truth shared between FloatingActionBar (renders two grouped menus
  when multi-tariff is on) and the parent page (gates selection-count
  semantics + the active-paid count in delete-subscription).

- FloatingActionBar.tsx (304 lines): the bottom-dock selection
  toolbar. Pure presentational — parent owns selection state and
  supplies onAction (opens the modal with the picked action type)
  + onToggleAllSubscriptions. The full 11-item action config (icons
  + label keys + colour classes) lives inside the file as a private
  ActionConfig array.

Parent now imports FloatingActionBar + isSubscriptionLevelAction
from the new modules; the inline ActionConfig interface is gone.

AdminBulkActions.tsx: 1819 → 1517 lines (-302).
Cumulative across the 2-step admin decomp: 2539 → 1517 (-1022, -40%).
2026-05-27 09:31:10 +03:00
c0mrade
217d60ab19 refactor(bulk-actions): extract ActionModal + DropdownSelect
First and largest step in the AdminBulkActions decomp. Two new
files under src/components/admin/bulkActions/:

- DropdownSelect.tsx (47 lines): the small native-<select> wrapper
  used both by the filter row and the ActionModal's tariff/promo
  picker. Exports DropdownSelect + the shared ChevronDownIcon +
  DropdownOption type.

- ActionModal.tsx (712 lines): the per-action configuration +
  execution modal. Wraps three internal views — ProgressView (live
  SSE log + counters while a job streams), ErrorDetails (collapsible
  per-row error list after completion), and the per-action form
  (days / tariff / traffic / balance / promo-group / grant /
  set-devices / delete-* confirmations). Exports the ModalState
  and ProgressState types alongside.

Self-contained: parent feeds modal state + reference data (tariffs,
promo groups, users for active-paid counting) and receives
onExecute(params) when the operator confirms. No queries or
mutations here — execution flow stays in the parent's
handleExecuteAction.

Parent keeps a local CheckIcon (still used by MultiSelectDropdown
checkboxes + the user-table column defs) and re-imports
ChevronDownIcon / DropdownOption from the new file.

AdminBulkActions.tsx: 2539 → 1796 lines (-743).
2026-05-27 09:28:39 +03:00
c0mrade
cf469a1a9f chore(types): replace deprecated FormEvent with SyntheticEvent
React 19's @types/react flags FormEvent as deprecated — quote: 'FormEvent
doesn't actually exist. You probably meant to use ChangeEvent, InputEvent,
SubmitEvent, or just SyntheticEvent'. All 17 call sites in this repo
typed form onSubmit handlers and only called e.preventDefault(), so
SyntheticEvent is the correct general replacement.

No behavior change — pure type-level cleanup that clears the chronic
deprecation hint that has been showing up after every edit to any
form-bearing page.
2026-05-26 15:24:01 +03:00
c0mrade
bc34e65aac fix(admin-bulk-actions): trap focus + label modal dialog (a11y)
Bulk-action modal had role=dialog on the overlay but no focus trap,
no aria-labelledby, no scroll lock, and an ad-hoc Escape listener
that conflicted with anything else listening to document keydown.

Move role=dialog/aria-modal onto the focus-trapped content element,
wire aria-labelledby to the h3 title, and replace the ad-hoc
keydown effect with useFocusTrap (Tab cycle + Esc + body scroll
lock + focus restore on close). Loading state suppresses Esc but
keeps focus trapped, so Tab still cycles inside the progress view.
2026-05-26 13:44:46 +03:00
c0mrade
7817243253 fix(admin-bulk-actions): plug stale-closure bug in columns useMemo
Header checkbox for 'select all subscriptions' read allVisibleSubscriptionIds,
subscriptionSelection, isMultiTariff, toggleAllSubscriptions from a stale
closure because those values were declared AFTER the columns useMemo. The
warned-about deps were absent, so the header only refreshed by accident
when expandedRows or getFilteredSubs changed.

Relocate filteredUsers / allVisibleSubscriptionIds / toggleAllSubscriptions
above the columns block, list them as deps. Header now reflects current
selection without piggybacking on unrelated re-renders.

Same commit clears the matching AdminInfoPageEditor warning with an
explicit eslint-disable-line comment — the activeLocale omission was
already intentional (initial-content lock), just not silenced.
2026-05-26 13:41:22 +03:00
c0mrade
bc5e95a6e5 perf(admin-bulk-actions): migrate users + 4 lookup loaders to React Query
User list query keys all 8 filter inputs so pagination/search/filter
changes auto-refetch with proper dedup. Tariffs/promoGroups/campaigns/
partners become long-staleTime lookup queries — fired once and cached
across remounts of the page. Existing setUsers/setTariffs/etc. setters
stay; sync useEffects copy query.data into them so downstream selection
and handler code is untouched. loadUsers becomes a thin refetch wrapper
for handleRefresh and mutation handlers.
2026-05-26 13:32:43 +03:00
c0mrade
bd8a4fad6e refactor(theming): replace amber-* with warning-* tokens
Sweep all amber-N color classes to warning-N (uniformly pending/warning semantics).
Yellow and orange left for per-file review (mixed Stars brand + semantic uses).
2026-05-26 11:10:50 +03:00
Fringg
afffab17d3 feat: bulk delete subscription protection for active paid subs
Add force_delete_active_paid guard to prevent accidental deletion of
active paid subscriptions. Shows warning with count and requires
explicit checkbox confirmation. Also fixes allVisibleSubscriptionIds
to use filteredUsers and getFilteredSubs to respect trialOnly filter
on subscription sub-rows.
2026-04-29 11:48:59 +03:00
Fringg
5b1892ddbb fix: campaigns/partners filter 422 — limit=200 exceeds backend max (100) 2026-04-24 16:31:05 +03:00
Fringg
8e767443f0 fix: load filters independently — one API failure no longer blocks others
Promise.all was used for tariffs, promo groups, campaigns, and partners.
If any one endpoint failed (e.g., partners not configured), all four
setCampaigns/setPartners/etc calls were skipped. Now each loads independently.
2026-04-24 16:22:20 +03:00
Fringg
161f630fd8 fix: bulk actions — modal touch targets, deleteFromPanel reset, remove dupe spacer
- Modal close button: 28px → 44px touch target
- deleteFromPanel checkbox: 20px → 24px + 44px label row
- Confirm/cancel buttons: add min-h-[44px]
- Reset deleteFromPanel to true when modal reopens
- Remove duplicate bottom spacer (h-20)
2026-04-24 16:16:31 +03:00
Fringg
b01ffe3309 feat: add campaign/partner filters, delete_user action, and fix modal positioning in AdminBulkActions
Add campaign and partner filter dropdowns for user filtering, implement
delete_user bulk action with delete_from_panel checkbox, fix ActionModal
to use createPortal with viewport-fixed positioning and safe area insets.
2026-04-24 16:04:20 +03:00
Fringg
d43638b34e fix: floating action bar covering pagination — add bottom spacer when selection active 2026-04-24 07:36:33 +03:00
Fringg
cb148d181a fix: persist checkbox selection across page changes and filter switches
Selection (both user and subscription checkboxes) now persists when:
- Switching pages (prev/next)
- Changing filters (status, tariff, promo group, trial)
- Changing per-page limit
- Searching
- Refreshing data

Selection is only cleared after a successful bulk action execution
(modal close with result).
2026-04-24 07:29:02 +03:00
Fringg
e2706c7c26 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)
2026-04-24 07:23:58 +03:00
Fringg
98871af656 fix: multi-tariff filter now works server-side + trial-only checkbox
- Send comma-separated tariff_ids to backend (was only single ID)
  — server now filters with IN() operator, pagination correct
- Remove client-side tariff filtering (server handles it)
- Add "Только триал" checkbox filter: amber checkbox, filters users
  with trial subscriptions client-side
- Remove "trial" from status dropdown (was not working via
  subscription_status server filter)
- i18n: trialOnly key (ru + en)
2026-04-24 07:16:53 +03:00
Fringg
efca40d9c8 fix: multi-tariff filter shows all users when >1 tariffs selected
Server only supports single tariff_id filter. When multiple tariffs
are selected, apply client-side filtering to hide users who have no
subscriptions matching any of the selected tariffs.
2026-04-24 07:08:40 +03:00
Fringg
c32bcfeb9e fix: floating action bar hidden behind mobile bottom nav — raise z-index + bottom padding 2026-04-24 07:04:44 +03:00
Fringg
eeda567622 feat: second header checkbox for bulk subscription selection
Add green checkbox in table header next to the user checkbox for
mass-selecting all visible subscriptions. Shows filled/partial/empty
states matching user checkbox pattern. Only visible in multi-tariff
mode. Wider select column (56px) to fit both checkboxes.
2026-04-24 07:03:53 +03:00
Fringg
3cb15177cc fix: show sub-rows for single-subscription users + fix floating bar position
- Auto-expand ALL users with subscriptions (was only > 1), so users
  with single subscription also show their sub-row for selection
- FloatingActionBar: render via createPortal to document.body to
  ensure fixed positioning works correctly regardless of parent
  overflow/transform contexts (was trapped inside scrollable area)
2026-04-24 06:59:55 +03:00
Fringg
c726d3d74e 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)
2026-04-24 06:51:21 +03:00
Fringg
5969c74f92 feat: devices in subscription rows, set_devices action, table columns
- Remove "Days" column from main table (each subscription shows own)
- Tariff column shows ALL subscription tariffs as comma-separated list
- Subscription sub-rows show device count with phone icon
- New bulk action "Изменить устройства" (set_devices) with input 1-50
- Update UserListItemSubscription type with device_limit field
- i18n: setDevices + deviceLimit keys (ru + en)
2026-04-24 06:36:04 +03:00
Fringg
f13b7239a5 feat: multi-select tariff filter, select-all subscriptions, auto-expand
1. Tariff filter: replace single-select dropdown with MultiSelectDropdown
   component — checkboxes for each tariff, "Select all" / "Deselect all"
   controls, shows selected names or count in button label
2. Client-side subscription filtering: when tariffs selected in filter,
   only show matching subscriptions in expanded sub-rows
3. "Select all subscriptions" button in FloatingActionBar — toggles all
   visible subscription checkboxes and auto-expands relevant rows
4. Auto-expand: all user rows with multiple subscriptions are expanded
   by default when data loads (no need to click chevron)
5. i18n: new keys for tariff multi-select and subscription select-all
2026-04-24 06:20:43 +03:00
Fringg
9673f26469 fix: make subscription checkboxes same size as user checkboxes (h-5 w-5) 2026-04-24 06:11:43 +03:00
Fringg
3801c3626a fix: expand chevron invisible on accent background
ChevronExpandIcon used text-dark-400 (gray) on the accent gradient
circle (green) — completely invisible. Changed to text-white.
2026-04-24 06:08:57 +03:00
Fringg
0844144e23 fix: make checkboxes visible on dark theme in bulk actions
- Increase user checkbox size: h-4.5 → h-5 w-5 with rounded-md border-2
- Increase subscription checkbox: h-3.5 → h-4.5 with rounded-md border-2
- Brighter unselected border: dark-600 → dark-500 with lighter bg
- Hover effect: accent-500/50 border glow on hover
- Selected glow: accent shadow ring for clear visual feedback
- Smooth transition-all duration-150 for all state changes
2026-04-24 06:02:11 +03:00
Fringg
78b41dc338 feat: multi-tariff bulk actions UI — subscription-level selection
- Detect multi-tariff mode when users have multiple subscriptions
- Expandable user rows with chevron: click to show subscription sub-rows
- Each subscription sub-row shows: tariff name, status badge, days
  remaining (color-coded green/amber/red), traffic progress bar
- Independent subscription checkboxes for subscription-level actions
- FloatingActionBar shows dual counters: users (accent) + subscriptions
  (green), actions grouped by target type with disabled state
- Subscription-level actions send subscription_ids, user-level send
  user_ids to the backend
- Backward compatible: single-tariff mode unchanged (no chevrons,
  no sub-rows, user-only selection)
- i18n: subscriptionsSelected, usersSelected, expand/collapse,
  daysUnit, trafficOf, trafficGbUnit (ru + en)
2026-04-24 05:49:38 +03:00
Fringg
161fde4301 fix: bulk actions — add tariff info to user list, fix tariff column display
- Update UserListItem type to include tariff_id, tariff_name,
  traffic_used_gb, traffic_limit_gb, device_limit, days_remaining
  from the backend (now returned in user list API)
- Fix tariff column: was showing subscription_status, now shows
  actual tariff_name from the API response
2026-04-24 05:28:08 +03:00
Fringg
8dd5e4985f fix: bulk progress log shows generic error instead of backend message
The live ProgressView log used entry.error (never sent by backend)
instead of entry.message, falling back to a generic i18n string.
Now checks entry.message first to display the actual error from
the backend (e.g. "No subscription found").
2026-04-24 05:15:13 +03:00
Fringg
fc8170fa1f 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
2026-04-24 04:52:30 +03:00
Fringg
312e0b4927 feat: bulk actions — live progress, grant subscription, error details
- Real-time progress: SSE streaming with animated progress bar,
  live success/error counters, scrollable log of last 10 results
  with auto-scroll, percentage display
- New action "Выдать подписку": tariff selector + days input,
  warning about users with existing subscriptions being skipped
- Error details: collapsible section with per-user error list,
  colored summary line (green successes / red errors)
- Fix: modal cannot be closed during loading (backdrop + Escape)
- Fix: row checkbox aria-labels now user-specific
- i18n: full progress/grant/error translations (ru + en)
2026-04-24 04:40:53 +03:00
Fringg
ebe9d9be48 fix: bulk actions frontend — selectedUserIds mapping, debounce cleanup
- Fix selectedUserIds: was mapping row keys as array indices instead
  of using them directly as user IDs (getRowId returns String(user.id))
- Add useEffect cleanup for search debounce timer on unmount
2026-04-24 04:24:33 +03:00
Fringg
1772d9632e 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
2026-04-24 04:15:28 +03:00