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, });