From 1ed0338b956e8bc0e701398081caa53828470a2a Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 30 Jul 2026 22:12:18 +0300 Subject: [PATCH] =?UTF-8?q?fix(tradein/ui):=20=D0=BF=D0=B0=D0=B3=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BD=D0=B0=20=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=86=D0=B5=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D1=8B=20+=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20=D1=82=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=20=D0=B4=D0=BB=D1=8F=20admin/manager=20(#255?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EmployeeTable: убран ранний return при пустом списке — на offset>0 (ровно 50/100/150 сотрудников) пейджер и кнопка «Назад» теперь остаются доступны; текст различает «сотрудников вообще нет» (offset=0) и «страница за концом списка» (offset>0). team/page.tsx: useEmployees получает enabled=isAllowedRole, вычисленный ДО вызова хука — прямой заход employee/analyst/pilot на /team больше не шлёт обречённый GET до отрисовки role-gate. --- tradein-mvp/frontend/src/app/team/page.tsx | 12 +- .../src/components/team/EmployeeTable.tsx | 177 ++++++++++-------- tradein-mvp/frontend/src/lib/team-api.ts | 8 +- 3 files changed, 112 insertions(+), 85 deletions(-) diff --git a/tradein-mvp/frontend/src/app/team/page.tsx b/tradein-mvp/frontend/src/app/team/page.tsx index bf0016de..b42ef0da 100644 --- a/tradein-mvp/frontend/src/app/team/page.tsx +++ b/tradein-mvp/frontend/src/app/team/page.tsx @@ -29,8 +29,15 @@ const PAGE_LIMIT = 50; export default function TeamPage() { const meQ = useMe(); + // Вычисляем ДО useEmployees (не после) — иначе для employee/analyst/pilot + // при прямом заходе на /team улетает обречённый GET (401/403) ещё до того, + // как ниже отрисуется role-gate. Порядок хуков не меняется — это просто + // производное значение, не условный вызов хука. + const role = meQ.data?.role; + const isAllowedRole = role === "admin" || role === "manager"; + const [offset, setOffset] = useState(0); - const employeesQ = useEmployees(PAGE_LIMIT, offset); + const employeesQ = useEmployees(PAGE_LIMIT, offset, isAllowedRole); const [showCreateForm, setShowCreateForm] = useState(false); const [quotaEmployee, setQuotaEmployee] = useState(null); @@ -49,9 +56,6 @@ export default function TeamPage() { ); } - const role = meQ.data?.role; - const isAllowedRole = role === "admin" || role === "manager"; - if (!isAllowedRole) { return ; } diff --git a/tradein-mvp/frontend/src/components/team/EmployeeTable.tsx b/tradein-mvp/frontend/src/components/team/EmployeeTable.tsx index 62439c5d..ff2177b8 100644 --- a/tradein-mvp/frontend/src/components/team/EmployeeTable.tsx +++ b/tradein-mvp/frontend/src/components/team/EmployeeTable.tsx @@ -82,91 +82,102 @@ export function EmployeeTable({ ); } - if (employees.length === 0) { - return

Сотрудников пока нет.

; - } + const isEmpty = employees.length === 0; + // Пустая ПЕРВАЯ страница = сотрудников вообще нет (предлагаем создать). + // Пустая страница при offset>0 = перешли за границу списка (напр. ровно + // 50/100/150 сотрудников и клик «Далее» после последней полной страницы) + // — это НЕ «сотрудников нет», нужен путь назад, а не тупик без пейджера. + const isEmptyPastEnd = isEmpty && offset > 0; return ( <> - - - - - - - - - - - - - {employees.map((employee) => ( - - - - - - - - + + + ) : null} + + ))} + +
ЛогинИмяСтатусКвотаСозданДействия
{employee.username} - {employee.display_name ?? "—"} - {employee.org_name ? ` · ${employee.org_name}` : ""} - - - {employee.is_active ? "Активен" : "Заблокирован"} - - - - {formatDate(employee.created_at)} -
- - -
+

+ {rowError.message} +

+
+ )}
- - {offset + 1}–{offset + employees.length} - -
diff --git a/tradein-mvp/frontend/src/lib/team-api.ts b/tradein-mvp/frontend/src/lib/team-api.ts index 0c0e4bb0..cbe43b1a 100644 --- a/tradein-mvp/frontend/src/lib/team-api.ts +++ b/tradein-mvp/frontend/src/lib/team-api.ts @@ -81,12 +81,18 @@ const EMPLOYEES_LIST_KEY = ["team", "employees"] as const; /** * GET /api/v1/team/employees?limit=&offset= * Backend сам скоупит по роли (manager → только свои, admin → все). + * + * `enabled` (default true) — вызывающая страница обязана передать `false` + * для ролей, которым эндпоинт всё равно ответит 401/403 (employee/analyst/ + * pilot и т.п.): без этого прямой заход на /team шлёт обречённый round-trip + * ДО отрисовки role-gate, который лишний и палит наличие эндпоинта. */ -export function useEmployees(limit: number, offset: number) { +export function useEmployees(limit: number, offset: number, enabled = true) { return useQuery({ queryKey: [...EMPLOYEES_LIST_KEY, limit, offset], queryFn: () => apiFetch(`${BASE}/employees?limit=${limit}&offset=${offset}`), + enabled, staleTime: 15_000, retry: false, });