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:
parent
b6eefd3437
commit
26d529b09b
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue