fix(web): remove TanStack devtools palm icon; restore user profile on dashboard

- Drop ReactQueryDevtools floating button (the "palm tree" in corner)
- Dashboard now shows: greeting with user name, stat cards, user profile
  (name/email/roles/orgId), and roadmap
- Add amber banner when API calls fail (typical cause: API not restarted
  after pulling new catalog code) with explicit fix instructions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nurdotnet 2026-04-21 20:33:10 +05:00
parent b6eefd3437
commit 26d529b09b
2 changed files with 37 additions and 4 deletions

View file

@ -1,6 +1,5 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { LoginPage } from '@/pages/LoginPage' import { LoginPage } from '@/pages/LoginPage'
import { DashboardPage } from '@/pages/DashboardPage' import { DashboardPage } from '@/pages/DashboardPage'
import { CountriesPage } from '@/pages/CountriesPage' import { CountriesPage } from '@/pages/CountriesPage'
@ -49,7 +48,6 @@ export default function App() {
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider> </QueryClientProvider>
) )
} }

View file

@ -4,6 +4,14 @@ import { PageHeader } from '@/components/PageHeader'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import type { PagedResult } from '@/lib/types' import type { PagedResult } from '@/lib/types'
interface MeResponse {
sub: string
name: string
email: string
roles: string[]
orgId: string
}
function useCount(url: string) { function useCount(url: string) {
return useQuery({ return useQuery({
queryKey: [url, 'count'], queryKey: [url, 'count'],
@ -33,18 +41,33 @@ function StatCard({ icon: Icon, label, value, isLoading }: StatCardProps) {
} }
export function DashboardPage() { export function DashboardPage() {
const me = useQuery({
queryKey: ['me'],
queryFn: async () => (await api.get<MeResponse>('/api/me')).data,
})
const products = useCount('/api/catalog/products') const products = useCount('/api/catalog/products')
const counterparties = useCount('/api/catalog/counterparties') const counterparties = useCount('/api/catalog/counterparties')
const stores = useCount('/api/catalog/stores') const stores = useCount('/api/catalog/stores')
const retailPoints = useCount('/api/catalog/retail-points') const retailPoints = useCount('/api/catalog/retail-points')
const anyError = [products, counterparties, stores, retailPoints].find(q => q.error)?.error as Error | undefined
return ( return (
<div className="p-6"> <div className="p-6">
<PageHeader <PageHeader
title="Dashboard" title="Dashboard"
description="Общие показатели системы. Детальные отчёты появятся после Phase 2." description={me.data ? `Добро пожаловать, ${me.data.name}` : 'Общие показатели системы'}
/> />
{anyError && (
<div className="mb-4 rounded-lg border border-amber-200 bg-amber-50 dark:bg-amber-950/20 dark:border-amber-900/50 p-3 text-sm text-amber-800 dark:text-amber-200">
<div className="font-medium">API недоступен или ещё не обновился</div>
<div className="text-amber-700 dark:text-amber-300 text-xs mt-0.5">
Перезапусти API после git pull: <code className="font-mono">Ctrl+C dotnet run --project src/food-market.api</code>
</div>
</div>
)}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<StatCard icon={Package} label="Товаров" value={products.data} isLoading={products.isLoading} /> <StatCard icon={Package} label="Товаров" value={products.data} isLoading={products.isLoading} />
<StatCard icon={Users} label="Контрагентов" value={counterparties.data} isLoading={counterparties.isLoading} /> <StatCard icon={Users} label="Контрагентов" value={counterparties.data} isLoading={counterparties.isLoading} />
@ -52,7 +75,19 @@ export function DashboardPage() {
<StatCard icon={Store} label="Точек продаж" value={retailPoints.data} isLoading={retailPoints.isLoading} /> <StatCard icon={Store} label="Точек продаж" value={retailPoints.data} isLoading={retailPoints.isLoading} />
</div> </div>
<section className="mt-8 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-6"> {me.data && (
<section className="mt-6 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-5">
<h2 className="text-sm font-semibold text-slate-900 dark:text-slate-100 mb-2.5">Текущий пользователь</h2>
<dl className="grid grid-cols-1 md:grid-cols-2 gap-2 text-sm">
<div><dt className="text-slate-500 inline">Имя: </dt><dd className="inline text-slate-900 dark:text-slate-100 font-medium">{me.data.name}</dd></div>
<div><dt className="text-slate-500 inline">Email: </dt><dd className="inline">{me.data.email}</dd></div>
<div><dt className="text-slate-500 inline">Роли: </dt><dd className="inline">{me.data.roles.join(', ')}</dd></div>
<div><dt className="text-slate-500 inline">Организация: </dt><dd className="inline font-mono text-xs">{me.data.orgId}</dd></div>
</dl>
</section>
)}
<section className="mt-6 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-6">
<h2 className="text-base font-semibold text-slate-900 dark:text-slate-100 mb-2">Что дальше</h2> <h2 className="text-base font-semibold text-slate-900 dark:text-slate-100 mb-2">Что дальше</h2>
<ul className="text-sm text-slate-600 dark:text-slate-300 space-y-1.5 list-disc list-inside"> <ul className="text-sm text-slate-600 dark:text-slate-300 space-y-1.5 list-disc list-inside">
<li>Phase 2: приёмка товара, розничные продажи, складские остатки</li> <li>Phase 2: приёмка товара, розничные продажи, складские остатки</li>