feat(admin-ui): minimal React+Vite+TanStack scaffolding

- Vite 7 + React 19 + TanStack Router (file-based) + TanStack Query
- Tailwind v4 (CSS-first config, OKLCH brand colors)
- i18next + react-i18next с RU/EN switcher в header
- Axios client с Accept-Language interceptor + JWT placeholder
- 2 pages: /dictionaries (list cards) + /dictionaries/$name (records table)
- Dockerfile (node build + nginx serving + API proxy)
- README + .gitignore

Backend:
- Новый GET /api/v1/dictionaries в read-api (DictionaryListController),
  фильтрует по доступному scope через ScopeContext
- DictionaryListItem DTO без heavy schema_json

routeTree.gen.ts — stub; реальное содержимое генерится Vite plugin'ом при
первом 'pnpm dev'.
This commit is contained in:
Александр Зимин
2026-05-04 00:55:04 +03:00
parent adc5315948
commit 1eba2ee59b
20 changed files with 609 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
component: RootLayout,
})
function RootLayout() {
const { t, i18n } = useTranslation()
return (
<div className="min-h-full flex flex-col">
<header className="border-b border-zinc-200 bg-white">
<div className="max-w-6xl mx-auto px-6 py-4 flex items-center gap-6">
<Link to="/" className="font-semibold text-lg text-brand-700">
{t('app.title')}
</Link>
<nav className="flex gap-4 text-sm">
<Link
to="/dictionaries"
className="text-zinc-600 hover:text-brand-700"
activeProps={{ className: 'text-brand-700 font-medium' }}
>
{t('nav.dictionaries')}
</Link>
</nav>
<div className="ml-auto flex items-center gap-2 text-sm">
<button
onClick={() => i18n.changeLanguage('ru-RU')}
className={`px-2 py-1 rounded ${i18n.language === 'ru-RU' ? 'bg-brand-50 text-brand-700' : 'text-zinc-500'}`}
>
RU
</button>
<button
onClick={() => i18n.changeLanguage('en-US')}
className={`px-2 py-1 rounded ${i18n.language === 'en-US' ? 'bg-brand-50 text-brand-700' : 'text-zinc-500'}`}
>
EN
</button>
</div>
</div>
</header>
<main className="flex-1 max-w-6xl mx-auto w-full px-6 py-8">
<Outlet />
</main>
</div>
)
}