feat(devices): inline rename UI for connected HWID devices

Pairs with the bot commit that adds the user_device_aliases table.
Surfaces the new local_name field on the existing devices list in
both the user-facing subscription page and the admin user-detail
page, with the same inline-edit pattern in both places.

User-side (src/pages/Subscription.tsx):
- pencil button next to each device row toggles edit mode
- input is focused automatically, capped at 64 chars (matches the
  backend ALIAS_MAX_LENGTH and DB column width)
- Enter saves, Escape cancels, empty input clears the alias
- display priority: local_name → device_model → platform
- works identically in classic / single-tariff / multi-tariff —
  subscriptionId is forwarded as a query param like every other
  device-management endpoint already does

Admin-side (src/pages/AdminUserDetail.tsx):
- same pencil + inline input pattern, admin acts on behalf of the
  user. notify.success on save, loadDevices() refresh.

API (src/api/subscription.ts + src/api/adminUsers.ts):
- new renameDevice(hwid, name, subscriptionId?) on subscriptionApi
- new renameUserDevice(userId, hwid, name) on adminUsersApi
- existing getDevices/getUserDevices contracts widened with
  local_name?: string | null on the returned device shape

Locales (src/locales/ru.json):
- subscription.renameDevice / .renameDeviceSave / .renameDeviceCancel
  / .renameDevicePlaceholder / .deviceRenamed
- admin.users.detail.devices.rename / .renameSave / .renamed
This commit is contained in:
Fringg
2026-05-16 03:02:45 +03:00
parent d85da9e03f
commit 321c65b68b
6 changed files with 421 additions and 100 deletions

View File

@@ -719,7 +719,13 @@ export const adminUsersApi = {
userId: number,
subscriptionId?: number,
): Promise<{
devices: { hwid: string; platform: string; device_model: string; created_at: string | null }[];
devices: {
hwid: string;
platform: string;
device_model: string;
created_at: string | null;
local_name?: string | null;
}[];
total: number;
device_limit: number;
}> => {
@@ -729,6 +735,19 @@ export const adminUsersApi = {
return response.data;
},
// Set / clear local device alias on behalf of the user (admin override).
renameUserDevice: async (
userId: number,
hwid: string,
name: string | null,
): Promise<{ hwid: string; local_name: string | null }> => {
const response = await apiClient.patch(
`/cabinet/admin/users/${userId}/devices/${encodeURIComponent(hwid)}/name`,
{ name },
);
return response.data;
},
// Delete single device
deleteUserDevice: async (
userId: number,

View File

@@ -264,6 +264,7 @@ export const subscriptionApi = {
platform: string;
device_model: string;
created_at: string | null;
local_name?: string | null;
}>;
total: number;
device_limit: number;
@@ -275,6 +276,27 @@ export const subscriptionApi = {
return response.data;
},
/**
* Persist a user-local alias for a device. Pass an empty/null `name` to
* clear the alias. Returns the final `local_name` after server-side
* normalization (trimmed + length-capped).
*
* Scope is per-(user, hwid), so the same alias appears across all of
* the user's subscriptions in multi-tariff mode.
*/
renameDevice: async (
hwid: string,
name: string | null,
subscriptionId?: number,
): Promise<{ hwid: string; local_name: string | null }> => {
const response = await apiClient.patch(
`/cabinet/subscription/devices/${encodeURIComponent(hwid)}/name`,
{ name },
withSubId(subscriptionId),
);
return response.data;
},
deleteDevice: async (
hwid: string,
subscriptionId?: number,