fix(admin-ui): v1.1.1 patch — version routing + guest mode + timeline UX
This commit is contained in:
@@ -18,9 +18,11 @@ import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Badge, Button, EmptyState, LoadingBlock, PageHeader, SearchInput } from '@nstart/ui'
|
||||
import { PlusIcon } from '@phosphor-icons/react'
|
||||
import { useDictionaries, useDictionaryDependents } from '@/api/queries'
|
||||
import { useQueries } from '@tanstack/react-query'
|
||||
import { dictionaryDependentsQuery, useDictionaries, useDictionaryDependents } from '@/api/queries'
|
||||
import type { DataScope, DictionaryDefinition, SchemaDependent } from '@/api/client'
|
||||
import { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog'
|
||||
import { useCanMutate } from '@/auth/useCanMutate'
|
||||
import { SCOPE_DOT, SCOPE_ORDER } from '@/lib/scope-style'
|
||||
|
||||
// ===== Search params =====
|
||||
@@ -101,6 +103,7 @@ function DictionariesPage() {
|
||||
const search = Route.useSearch()
|
||||
const { data, isLoading, error } = useDictionaries()
|
||||
const [createOpen, setCreateOpen] = useState(false)
|
||||
const canMutate = useCanMutate()
|
||||
|
||||
const q = (search.q ?? '').trim().toLowerCase()
|
||||
const deferredQuery = useDeferredValue(q)
|
||||
@@ -130,19 +133,40 @@ function DictionariesPage() {
|
||||
|
||||
const setBundle = (bundle: string | undefined) => setSearch({ bundle })
|
||||
|
||||
// Батчевая загрузка refBy для всех словарей — для активации withDeps filter.
|
||||
// useQueries dedup'ит c per-card useDictionaryDependents (тот же queryKey),
|
||||
// так что N=37 запросов выполнятся один раз и cached. Запускается ТОЛЬКО
|
||||
// когда deps filter активен — для anonymous browsing N+1 burst не нужен.
|
||||
const dependentsResults = useQueries({
|
||||
queries: withDepsOnly && data
|
||||
? data.map((d) => ({ ...dictionaryDependentsQuery(d.name) }))
|
||||
: [],
|
||||
})
|
||||
const dependentsMap = useMemo(() => {
|
||||
const m = new Map<string, number>()
|
||||
if (!withDepsOnly || !data) return m
|
||||
data.forEach((d, i) => {
|
||||
const r = dependentsResults[i]
|
||||
// Пока loading или error — считаем 0 (не показываем). После загрузки —
|
||||
// refBy.length. Уникализация не нужна для бинарного hasDeps теста.
|
||||
m.set(d.name, r?.data?.length ?? 0)
|
||||
})
|
||||
return m
|
||||
}, [data, dependentsResults, withDepsOnly])
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (!data) return []
|
||||
return data.filter((d) => {
|
||||
if (scopeFilter.size > 0 && !scopeFilter.has(d.scope)) return false
|
||||
if (bundleFilter && d.bundle !== bundleFilter) return false
|
||||
if (!matchesQuery(d, deferredQuery)) return false
|
||||
// withDepsOnly: пока нет FK metadata в DictionaryDefinition, фильтр
|
||||
// эффективно no-op. После backend batch FK endpoint — заработает.
|
||||
// TODO: backend пишет fk + refBy в DictionaryDefinition; раскомментить:
|
||||
// if (withDepsOnly && d.fk.length === 0 && d.refBy.length === 0) return false
|
||||
// «Со связями» — справочник используется в других справочниках (refBy > 0).
|
||||
// Outgoing FK потребовал бы загрузки полной schema per dict — not worth
|
||||
// the bandwidth для catalog filter. refBy достаточно для semantic.
|
||||
if (withDepsOnly && (dependentsMap.get(d.name) ?? 0) === 0) return false
|
||||
return true
|
||||
})
|
||||
}, [data, deferredQuery, scopeFilter, bundleFilter])
|
||||
}, [data, deferredQuery, scopeFilter, bundleFilter, withDepsOnly, dependentsMap])
|
||||
|
||||
const bundleCounts = useMemo(() => {
|
||||
const out = new Map<string, number>()
|
||||
@@ -160,7 +184,9 @@ function DictionariesPage() {
|
||||
const filtersActive = Boolean(q) || scopeFilter.size > 0 || bundleFilter || withDepsOnly
|
||||
const resetFilters = () => navigate({ search: {} })
|
||||
|
||||
const createButton = (
|
||||
// Guest mode (anonymous) — кнопка «Создать справочник» скрыта. Backend всё
|
||||
// равно вернёт 401 на POST, но UI hide избегает confusing UX.
|
||||
const createButton = canMutate ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
@@ -170,7 +196,7 @@ function DictionariesPage() {
|
||||
>
|
||||
{t('schema.action.create')}
|
||||
</Button>
|
||||
)
|
||||
) : null
|
||||
|
||||
if (isLoading) return <LoadingBlock size="md" label={t('loading')} />
|
||||
if (error) {
|
||||
|
||||
Reference in New Issue
Block a user