diff --git a/ordinis-admin-ui/src/api/client.ts b/ordinis-admin-ui/src/api/client.ts index 0808a38..95fa61c 100644 --- a/ordinis-admin-ui/src/api/client.ts +++ b/ordinis-admin-ui/src/api/client.ts @@ -35,6 +35,7 @@ export type DictionaryDefinition = { bundle: string supportedLocales: string[] defaultLocale: string + recordCount?: number createdAt: string updatedAt: string } diff --git a/ordinis-admin-ui/src/i18n.ts b/ordinis-admin-ui/src/i18n.ts index ed596c9..40c1e44 100644 --- a/ordinis-admin-ui/src/i18n.ts +++ b/ordinis-admin-ui/src/i18n.ts @@ -61,6 +61,13 @@ i18n 'outbox.dlq.empty': 'DLQ пуста — все события публикуются успешно', 'header.scope': 'Доступный scope', 'dict.list.subtitle': 'Каталоги НСИ ЦУОД ОДХ', + 'dict.list.search.placeholder': 'Поиск по названию, коду или описанию', + 'dict.list.search.empty': 'Ничего не найдено', + 'dict.list.found': 'Показано {{shown}} из {{total}}', + 'dict.list.recordCount_one': '{{count}} запись', + 'dict.list.recordCount_few': '{{count}} записи', + 'dict.list.recordCount_many': '{{count}} записей', + 'dict.list.recordCount_other': '{{count}} записей', 'dict.empty': 'В этом справочнике пока нет записей', 'dict.col.businessKey': 'Бизнес-ключ', 'dict.col.scope': 'Scope', @@ -227,6 +234,11 @@ i18n 'outbox.dlq.empty': 'DLQ empty — all events publish successfully', 'header.scope': 'Allowed scope', 'dict.list.subtitle': 'Reference data catalogues', + 'dict.list.search.placeholder': 'Search by name, code or description', + 'dict.list.search.empty': 'Nothing found', + 'dict.list.found': 'Showing {{shown}} of {{total}}', + 'dict.list.recordCount_one': '{{count}} record', + 'dict.list.recordCount_other': '{{count}} records', 'dict.empty': 'No records in this dictionary yet', 'dict.col.businessKey': 'Business key', 'dict.col.scope': 'Scope', diff --git a/ordinis-admin-ui/src/routes/dictionaries.index.tsx b/ordinis-admin-ui/src/routes/dictionaries.index.tsx index 339da4d..65e76bd 100644 --- a/ordinis-admin-ui/src/routes/dictionaries.index.tsx +++ b/ordinis-admin-ui/src/routes/dictionaries.index.tsx @@ -1,20 +1,45 @@ -import { useState } from 'react' +import { useDeferredValue, useMemo, useState } from 'react' import { createFileRoute, Link, useNavigate } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' -import { Alert, Badge, Button, EmptyState, LoadingBlock, PageHeader } from '@nstart/ui' +import { + Alert, + Badge, + Button, + EmptyState, + LoadingBlock, + PageHeader, + SearchInput, +} from '@nstart/ui' import { PlusIcon } from '@phosphor-icons/react' import { useDictionaries } from '@/api/queries' +import type { DictionaryDefinition } from '@/api/client' import { DictionaryEditorDialog } from '@/components/schema/DictionaryEditorDialog' export const Route = createFileRoute('/dictionaries/')({ component: DictionariesPage, }) +const matchesQuery = (d: DictionaryDefinition, q: string): boolean => { + if (!q) return true + const haystack = [d.name, d.displayName ?? '', d.description ?? ''] + .join(' ') + .toLowerCase() + return haystack.includes(q) +} + function DictionariesPage() { const { t } = useTranslation() const navigate = useNavigate() const { data, isLoading, error } = useDictionaries() const [createOpen, setCreateOpen] = useState(false) + const [query, setQuery] = useState('') + const deferredQuery = useDeferredValue(query) + + const filtered = useMemo(() => { + if (!data) return [] + const q = deferredQuery.trim().toLowerCase() + return data.filter((d) => matchesQuery(d, q)) + }, [data, deferredQuery]) const createButton = (