From 838138af5eec4cafdade630fec0f3e5d1704d0b1 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Mon, 25 May 2026 17:50:23 +0300 Subject: [PATCH] =?UTF-8?q?fix(admin-ui):=20UserPicker=20=E2=80=94=20TextI?= =?UTF-8?q?nput=20fallback=20=D0=BA=D0=BE=D0=B3=D0=B4=D0=B0=20cache=20?= =?UTF-8?q?=D0=BF=D1=83=D1=81=D1=82=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Если /api/v1/admin/users возвращает [] (cache пустой / writer только-что рестартован / у юзера нет INTERNAL scope → 403 → catch → []), picker показывал «Пользователи не найдены» и фильтр становился недоступен. До MR !263 здесь был обычный TextInput, не лишаем юзера возможности вписать UUID руками. При isLoading=false, isError=false, data.length===0 — рендерим TextInput с подсказкой «Список юзеров пуст — введите вручную». Backend cache прогреется по мере того как юзеры будут делать authenticated requests (JwtUserCaptureFilter перепишет user_display_cache); тогда picker включится автоматически. --- .../src/components/audit/UserPicker.tsx | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ordinis-admin-ui/src/components/audit/UserPicker.tsx b/ordinis-admin-ui/src/components/audit/UserPicker.tsx index f0da2f9..d6a0f17 100644 --- a/ordinis-admin-ui/src/components/audit/UserPicker.tsx +++ b/ordinis-admin-ui/src/components/audit/UserPicker.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useMemo, useState } from 'react' import { CaretDownIcon, MagnifyingGlassIcon, UserIcon, XIcon } from '@phosphor-icons/react' import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from 'cmdk' -import { Popover, PopoverTrigger, PopoverContent, FieldLabel } from '@/ui' +import { Popover, PopoverTrigger, PopoverContent, FieldLabel, TextInput } from '@/ui' import { useAllUsers, type UserListItem } from '@/api/queries' import { cn } from '@/lib/utils' @@ -38,11 +38,32 @@ export function UserPicker({ value, onChange, label, placeholder, disabled }: Us const [open, setOpen] = useState(false) const usersQ = useAllUsers() + // Fallback на TextInput если cache пустой или endpoint вернул 403/404 (юзер + // без INTERNAL scope, либо writer был только-что рестартован и cache не + // прогрелся). Без этого user-фильтр становится недоступен — раньше тут был + // TextInput, не лишаем юзера возможности вписать UUID руками. + const usersList = usersQ.data ?? [] + const fallbackToTextInput = + !usersQ.isLoading && !usersQ.isError && usersList.length === 0 + const usersBySub = useMemo(() => { const m = new Map() - for (const u of usersQ.data ?? []) m.set(u.sub, u) + for (const u of usersList) m.set(u.sub, u) return m - }, [usersQ.data]) + }, [usersList]) + + if (fallbackToTextInput) { + return ( + onChange(e.target.value || undefined)} + placeholder="UUID или username" + hint="Список юзеров пуст — введите вручную" + disabled={disabled} + /> + ) + } const selected = value ? usersBySub.get(value) : undefined const triggerLabel = useMemo(() => {