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 = (
-
- {data.map((d) => (
-
-
-
- {d.displayName ?? d.name}
-
- {d.scope}
-
- {d.description && (
-
{d.description}
- )}
-
- v{d.schemaVersion}
- ·
- {d.bundle}
- ·
- {d.supportedLocales.join(', ')}
-
-
- ))}
+
+
+ setQuery(e.target.value)}
+ placeholder={t('dict.list.search.placeholder')}
+ aria-label={t('dict.list.search.placeholder')}
+ />
+
+
+ {t('dict.list.found', { shown: filtered.length, total: data.length })}
+
+ {filtered.length === 0 ? (
+
+ ) : (
+
+ {filtered.map((d) => (
+
+
+
+ {d.displayName ?? d.name}
+
+ {d.scope}
+
+ {d.description && (
+
{d.description}
+ )}
+
+
+ v{d.schemaVersion}
+ ·
+ {d.bundle}
+ ·
+ {d.supportedLocales.join(', ')}
+
+ {typeof d.recordCount === 'number' && (
+
+ {t('dict.list.recordCount', { count: d.recordCount })}
+
+ )}
+
+
+ ))}
+
+ )}
+
:at
+ GROUP BY r.dictionaryId
+ """)
+ List