feat(web): перевести настройки, VPS-детали и журнал на вкладки из дизайна VPS-Tracker

Настройки — line-вкладки сверху вместо боковой навигации, детали VPS —
line-вкладки и действия в шапке, журнал — общие CountedLineTabs, шапка
страницы 20px. Детальные маршруты VPS и проекта вынесены из вложенности
(vps_.$vpsId, projects_.$projectId): списки не рендерят Outlet, и детали
не открывались.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-11-06 22:02:04 +07:00
1 parent b5300b9082
commit e3403bcb56
6 files changed
+94 -140

No files matched your search

+4 -4
View File
@@ -6,12 +6,12 @@ interface PageHeaderProps {
actions?: ReactNode actions?: ReactNode
} }
/** Page-level section header — etalon EvoBGP. */ /** Page header — design «VPS-Tracker»: 20px title + muted sub, actions bottom-aligned right. */
export function PageHeader({ title, description, actions }: PageHeaderProps) { export function PageHeader({ title, description, actions }: PageHeaderProps) {
return ( return (
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between"> <div className="flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-0.5">
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1> <h1 className="text-xl font-semibold tracking-tight">{title}</h1>
{description ? ( {description ? (
<p className="text-muted-foreground text-sm">{description}</p> <p className="text-muted-foreground text-sm">{description}</p>
) : null} ) : null}
@@ -7,11 +7,9 @@ import {
SettingsIcon, SettingsIcon,
} from 'lucide-react' } from 'lucide-react'
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
import { cn } from '@cfdm/ui/lib/utils'
import { PageShell } from '@/components/page-shell' import { PageShell } from '@/components/page-shell'
import { PageHeader } from '@/components/page-header' import { PageHeader } from '@/components/page-header'
import { Separator } from '@cfdm/ui/components/separator' import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
export interface SettingsTabConfig { export interface SettingsTabConfig {
id: string id: string
@@ -55,70 +53,43 @@ interface SettingsShellProps {
tabs?: SettingsTabConfig[] tabs?: SettingsTabConfig[]
} }
/** Settings shell — preview https://reui.io/preview/base/settings-16 */ /** Settings shell — line tabs on top (design «VPS-Tracker»); sections: https://reui.io/preview/base/settings-16 */
export function SettingsShell({ export function SettingsShell({
title = 'Настройки', title = 'Настройки',
description = 'Параметры приложения и связи с другими сервисами', description = 'Параметры приложения и связи с другими сервисами',
tabs = DEFAULT_TABS, tabs = DEFAULT_TABS,
}: SettingsShellProps) { }: SettingsShellProps) {
const isMobile = useIsMobile()
const pathname = useRouterState({ select: (s) => s.location.pathname }) const pathname = useRouterState({ select: (s) => s.location.pathname })
const active =
tabs.find((tab) =>
tab.exact ? pathname === tab.to || pathname === `${tab.to}/` : pathname.startsWith(tab.to),
) ?? tabs[0]
return ( return (
<PageShell> <PageShell>
<div className="mx-auto flex w-full max-w-4xl flex-col gap-5">
<div className="flex flex-col gap-3">
<PageHeader title={title} description={description} /> <PageHeader title={title} description={description} />
<Separator /> {/* Design «VPS-Tracker»: settings sections as page line tabs. */}
</div>
<div
className={cn(
'flex gap-5',
isMobile ? 'flex-col' : 'flex-row items-start',
)}
>
{tabs.length > 1 ? ( {tabs.length > 1 ? (
<nav <Tabs value={active?.id}>
aria-label="Разделы настроек" <TabsList variant="line" aria-label="Разделы настроек" className="gap-5">
className={cn( {tabs.map((tab) => (
'flex gap-1', <TabsTrigger
isMobile
? 'scrollbar-none -mx-1 overflow-x-auto overflow-y-hidden pb-1'
: 'w-44 shrink-0 flex-col',
)}
>
{tabs.map((tab) => {
const isActive = tab.exact
? pathname === tab.to || pathname === `${tab.to}/`
: pathname.startsWith(tab.to)
return (
<Link
key={tab.id} key={tab.id}
to={tab.to} value={tab.id}
aria-current={isActive ? 'page' : undefined} nativeButton={false}
className={cn( render={<Link to={tab.to} />}
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors', className="text-muted-foreground hover:text-foreground h-auto gap-2 px-0 pb-3 after:bottom-0"
isMobile && 'shrink-0',
!isMobile && 'w-full',
isActive
? 'bg-muted text-foreground font-medium shadow-sm ring-1 ring-border/60'
: 'text-muted-foreground hover:bg-muted/60 hover:text-foreground',
)}
> >
{tab.icon} {tab.icon}
{tab.label} {tab.label}
</Link> </TabsTrigger>
) ))}
})} </TabsList>
</nav> </Tabs>
) : null} ) : null}
<div className="min-w-0">
<div className="min-w-0 flex-1">
<Outlet /> <Outlet />
</div> </div>
</div>
</div>
</PageShell> </PageShell>
) )
} }
+34 -53
View File
@@ -31,11 +31,11 @@ import { Route as AuthAuditRouteImport } from './routes/_auth/audit'
import { Route as AuthAccountsRouteImport } from './routes/_auth/accounts' import { Route as AuthAccountsRouteImport } from './routes/_auth/accounts'
import { Route as AuthSettingsRouteRouteImport } from './routes/_auth/settings/route' import { Route as AuthSettingsRouteRouteImport } from './routes/_auth/settings/route'
import { Route as AuthSettingsIndexRouteImport } from './routes/_auth/settings/index' import { Route as AuthSettingsIndexRouteImport } from './routes/_auth/settings/index'
import { Route as AuthVpsVpsIdRouteImport } from './routes/_auth/vps.$vpsId' import { Route as AuthVpsVpsIdRouteImport } from './routes/_auth/vps_.$vpsId'
import { Route as AuthSettingsSyncRouteImport } from './routes/_auth/settings/sync' import { Route as AuthSettingsSyncRouteImport } from './routes/_auth/settings/sync'
import { Route as AuthSettingsNotificationsRouteImport } from './routes/_auth/settings/notifications' import { Route as AuthSettingsNotificationsRouteImport } from './routes/_auth/settings/notifications'
import { Route as AuthSettingsIntegrationsRouteImport } from './routes/_auth/settings/integrations' import { Route as AuthSettingsIntegrationsRouteImport } from './routes/_auth/settings/integrations'
import { Route as AuthProjectsProjectIdRouteImport } from './routes/_auth/projects.$projectId' import { Route as AuthProjectsProjectIdRouteImport } from './routes/_auth/projects_.$projectId'
const AuthRoute = AuthRouteImport.update({ const AuthRoute = AuthRouteImport.update({
id: '/_auth', id: '/_auth',
@@ -147,9 +147,9 @@ const AuthSettingsIndexRoute = AuthSettingsIndexRouteImport.update({
getParentRoute: () => AuthSettingsRouteRoute, getParentRoute: () => AuthSettingsRouteRoute,
} as any) } as any)
const AuthVpsVpsIdRoute = AuthVpsVpsIdRouteImport.update({ const AuthVpsVpsIdRoute = AuthVpsVpsIdRouteImport.update({
id: '/$vpsId', id: '/vps_/$vpsId',
path: '/$vpsId', path: '/vps/$vpsId',
getParentRoute: () => AuthVpsRoute, getParentRoute: () => AuthRoute,
} as any) } as any)
const AuthSettingsSyncRoute = AuthSettingsSyncRouteImport.update({ const AuthSettingsSyncRoute = AuthSettingsSyncRouteImport.update({
id: '/sync', id: '/sync',
@@ -169,9 +169,9 @@ const AuthSettingsIntegrationsRoute =
getParentRoute: () => AuthSettingsRouteRoute, getParentRoute: () => AuthSettingsRouteRoute,
} as any) } as any)
const AuthProjectsProjectIdRoute = AuthProjectsProjectIdRouteImport.update({ const AuthProjectsProjectIdRoute = AuthProjectsProjectIdRouteImport.update({
id: '/$projectId', id: '/projects_/$projectId',
path: '/$projectId', path: '/projects/$projectId',
getParentRoute: () => AuthProjectsRoute, getParentRoute: () => AuthRoute,
} as any) } as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
@@ -184,7 +184,7 @@ export interface FileRoutesByFullPath {
'/dashboard': typeof AuthDashboardRoute '/dashboard': typeof AuthDashboardRoute
'/geo': typeof AuthGeoRoute '/geo': typeof AuthGeoRoute
'/payments': typeof AuthPaymentsRoute '/payments': typeof AuthPaymentsRoute
'/projects': typeof AuthProjectsRouteWithChildren '/projects': typeof AuthProjectsRoute
'/providers': typeof AuthProvidersRoute '/providers': typeof AuthProvidersRoute
'/renewals': typeof AuthRenewalsRoute '/renewals': typeof AuthRenewalsRoute
'/reports': typeof AuthReportsRoute '/reports': typeof AuthReportsRoute
@@ -193,7 +193,7 @@ export interface FileRoutesByFullPath {
'/sync-journal': typeof AuthSyncJournalRoute '/sync-journal': typeof AuthSyncJournalRoute
'/tariffs': typeof AuthTariffsRoute '/tariffs': typeof AuthTariffsRoute
'/topology': typeof AuthTopologyRoute '/topology': typeof AuthTopologyRoute
'/vps': typeof AuthVpsRouteWithChildren '/vps': typeof AuthVpsRoute
'/auth/callback': typeof AuthCallbackRoute '/auth/callback': typeof AuthCallbackRoute
'/projects/$projectId': typeof AuthProjectsProjectIdRoute '/projects/$projectId': typeof AuthProjectsProjectIdRoute
'/settings/integrations': typeof AuthSettingsIntegrationsRoute '/settings/integrations': typeof AuthSettingsIntegrationsRoute
@@ -211,7 +211,7 @@ export interface FileRoutesByTo {
'/dashboard': typeof AuthDashboardRoute '/dashboard': typeof AuthDashboardRoute
'/geo': typeof AuthGeoRoute '/geo': typeof AuthGeoRoute
'/payments': typeof AuthPaymentsRoute '/payments': typeof AuthPaymentsRoute
'/projects': typeof AuthProjectsRouteWithChildren '/projects': typeof AuthProjectsRoute
'/providers': typeof AuthProvidersRoute '/providers': typeof AuthProvidersRoute
'/renewals': typeof AuthRenewalsRoute '/renewals': typeof AuthRenewalsRoute
'/reports': typeof AuthReportsRoute '/reports': typeof AuthReportsRoute
@@ -220,7 +220,7 @@ export interface FileRoutesByTo {
'/sync-journal': typeof AuthSyncJournalRoute '/sync-journal': typeof AuthSyncJournalRoute
'/tariffs': typeof AuthTariffsRoute '/tariffs': typeof AuthTariffsRoute
'/topology': typeof AuthTopologyRoute '/topology': typeof AuthTopologyRoute
'/vps': typeof AuthVpsRouteWithChildren '/vps': typeof AuthVpsRoute
'/auth/callback': typeof AuthCallbackRoute '/auth/callback': typeof AuthCallbackRoute
'/projects/$projectId': typeof AuthProjectsProjectIdRoute '/projects/$projectId': typeof AuthProjectsProjectIdRoute
'/settings/integrations': typeof AuthSettingsIntegrationsRoute '/settings/integrations': typeof AuthSettingsIntegrationsRoute
@@ -241,7 +241,7 @@ export interface FileRoutesById {
'/_auth/dashboard': typeof AuthDashboardRoute '/_auth/dashboard': typeof AuthDashboardRoute
'/_auth/geo': typeof AuthGeoRoute '/_auth/geo': typeof AuthGeoRoute
'/_auth/payments': typeof AuthPaymentsRoute '/_auth/payments': typeof AuthPaymentsRoute
'/_auth/projects': typeof AuthProjectsRouteWithChildren '/_auth/projects': typeof AuthProjectsRoute
'/_auth/providers': typeof AuthProvidersRoute '/_auth/providers': typeof AuthProvidersRoute
'/_auth/renewals': typeof AuthRenewalsRoute '/_auth/renewals': typeof AuthRenewalsRoute
'/_auth/reports': typeof AuthReportsRoute '/_auth/reports': typeof AuthReportsRoute
@@ -250,13 +250,13 @@ export interface FileRoutesById {
'/_auth/sync-journal': typeof AuthSyncJournalRoute '/_auth/sync-journal': typeof AuthSyncJournalRoute
'/_auth/tariffs': typeof AuthTariffsRoute '/_auth/tariffs': typeof AuthTariffsRoute
'/_auth/topology': typeof AuthTopologyRoute '/_auth/topology': typeof AuthTopologyRoute
'/_auth/vps': typeof AuthVpsRouteWithChildren '/_auth/vps': typeof AuthVpsRoute
'/auth/callback': typeof AuthCallbackRoute '/auth/callback': typeof AuthCallbackRoute
'/_auth/projects/$projectId': typeof AuthProjectsProjectIdRoute '/_auth/projects_/$projectId': typeof AuthProjectsProjectIdRoute
'/_auth/settings/integrations': typeof AuthSettingsIntegrationsRoute '/_auth/settings/integrations': typeof AuthSettingsIntegrationsRoute
'/_auth/settings/notifications': typeof AuthSettingsNotificationsRoute '/_auth/settings/notifications': typeof AuthSettingsNotificationsRoute
'/_auth/settings/sync': typeof AuthSettingsSyncRoute '/_auth/settings/sync': typeof AuthSettingsSyncRoute
'/_auth/vps/$vpsId': typeof AuthVpsVpsIdRoute '/_auth/vps_/$vpsId': typeof AuthVpsVpsIdRoute
'/_auth/settings/': typeof AuthSettingsIndexRoute '/_auth/settings/': typeof AuthSettingsIndexRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
@@ -338,11 +338,11 @@ export interface FileRouteTypes {
| '/_auth/topology' | '/_auth/topology'
| '/_auth/vps' | '/_auth/vps'
| '/auth/callback' | '/auth/callback'
| '/_auth/projects/$projectId' | '/_auth/projects_/$projectId'
| '/_auth/settings/integrations' | '/_auth/settings/integrations'
| '/_auth/settings/notifications' | '/_auth/settings/notifications'
| '/_auth/settings/sync' | '/_auth/settings/sync'
| '/_auth/vps/$vpsId' | '/_auth/vps_/$vpsId'
| '/_auth/settings/' | '/_auth/settings/'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
@@ -508,12 +508,12 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthSettingsIndexRouteImport preLoaderRoute: typeof AuthSettingsIndexRouteImport
parentRoute: typeof AuthSettingsRouteRoute parentRoute: typeof AuthSettingsRouteRoute
} }
'/_auth/vps/$vpsId': { '/_auth/vps_/$vpsId': {
id: '/_auth/vps/$vpsId' id: '/_auth/vps_/$vpsId'
path: '/$vpsId' path: '/vps/$vpsId'
fullPath: '/vps/$vpsId' fullPath: '/vps/$vpsId'
preLoaderRoute: typeof AuthVpsVpsIdRouteImport preLoaderRoute: typeof AuthVpsVpsIdRouteImport
parentRoute: typeof AuthVpsRoute parentRoute: typeof AuthRoute
} }
'/_auth/settings/sync': { '/_auth/settings/sync': {
id: '/_auth/settings/sync' id: '/_auth/settings/sync'
@@ -536,12 +536,12 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthSettingsIntegrationsRouteImport preLoaderRoute: typeof AuthSettingsIntegrationsRouteImport
parentRoute: typeof AuthSettingsRouteRoute parentRoute: typeof AuthSettingsRouteRoute
} }
'/_auth/projects/$projectId': { '/_auth/projects_/$projectId': {
id: '/_auth/projects/$projectId' id: '/_auth/projects_/$projectId'
path: '/$projectId' path: '/projects/$projectId'
fullPath: '/projects/$projectId' fullPath: '/projects/$projectId'
preLoaderRoute: typeof AuthProjectsProjectIdRouteImport preLoaderRoute: typeof AuthProjectsProjectIdRouteImport
parentRoute: typeof AuthProjectsRoute parentRoute: typeof AuthRoute
} }
} }
} }
@@ -563,29 +563,6 @@ const AuthSettingsRouteRouteChildren: AuthSettingsRouteRouteChildren = {
const AuthSettingsRouteRouteWithChildren = const AuthSettingsRouteRouteWithChildren =
AuthSettingsRouteRoute._addFileChildren(AuthSettingsRouteRouteChildren) AuthSettingsRouteRoute._addFileChildren(AuthSettingsRouteRouteChildren)
interface AuthProjectsRouteChildren {
AuthProjectsProjectIdRoute: typeof AuthProjectsProjectIdRoute
}
const AuthProjectsRouteChildren: AuthProjectsRouteChildren = {
AuthProjectsProjectIdRoute: AuthProjectsProjectIdRoute,
}
const AuthProjectsRouteWithChildren = AuthProjectsRoute._addFileChildren(
AuthProjectsRouteChildren,
)
interface AuthVpsRouteChildren {
AuthVpsVpsIdRoute: typeof AuthVpsVpsIdRoute
}
const AuthVpsRouteChildren: AuthVpsRouteChildren = {
AuthVpsVpsIdRoute: AuthVpsVpsIdRoute,
}
const AuthVpsRouteWithChildren =
AuthVpsRoute._addFileChildren(AuthVpsRouteChildren)
interface AuthRouteChildren { interface AuthRouteChildren {
AuthSettingsRouteRoute: typeof AuthSettingsRouteRouteWithChildren AuthSettingsRouteRoute: typeof AuthSettingsRouteRouteWithChildren
AuthAccountsRoute: typeof AuthAccountsRoute AuthAccountsRoute: typeof AuthAccountsRoute
@@ -595,7 +572,7 @@ interface AuthRouteChildren {
AuthDashboardRoute: typeof AuthDashboardRoute AuthDashboardRoute: typeof AuthDashboardRoute
AuthGeoRoute: typeof AuthGeoRoute AuthGeoRoute: typeof AuthGeoRoute
AuthPaymentsRoute: typeof AuthPaymentsRoute AuthPaymentsRoute: typeof AuthPaymentsRoute
AuthProjectsRoute: typeof AuthProjectsRouteWithChildren AuthProjectsRoute: typeof AuthProjectsRoute
AuthProvidersRoute: typeof AuthProvidersRoute AuthProvidersRoute: typeof AuthProvidersRoute
AuthRenewalsRoute: typeof AuthRenewalsRoute AuthRenewalsRoute: typeof AuthRenewalsRoute
AuthReportsRoute: typeof AuthReportsRoute AuthReportsRoute: typeof AuthReportsRoute
@@ -604,7 +581,9 @@ interface AuthRouteChildren {
AuthSyncJournalRoute: typeof AuthSyncJournalRoute AuthSyncJournalRoute: typeof AuthSyncJournalRoute
AuthTariffsRoute: typeof AuthTariffsRoute AuthTariffsRoute: typeof AuthTariffsRoute
AuthTopologyRoute: typeof AuthTopologyRoute AuthTopologyRoute: typeof AuthTopologyRoute
AuthVpsRoute: typeof AuthVpsRouteWithChildren AuthVpsRoute: typeof AuthVpsRoute
AuthProjectsProjectIdRoute: typeof AuthProjectsProjectIdRoute
AuthVpsVpsIdRoute: typeof AuthVpsVpsIdRoute
} }
const AuthRouteChildren: AuthRouteChildren = { const AuthRouteChildren: AuthRouteChildren = {
@@ -616,7 +595,7 @@ const AuthRouteChildren: AuthRouteChildren = {
AuthDashboardRoute: AuthDashboardRoute, AuthDashboardRoute: AuthDashboardRoute,
AuthGeoRoute: AuthGeoRoute, AuthGeoRoute: AuthGeoRoute,
AuthPaymentsRoute: AuthPaymentsRoute, AuthPaymentsRoute: AuthPaymentsRoute,
AuthProjectsRoute: AuthProjectsRouteWithChildren, AuthProjectsRoute: AuthProjectsRoute,
AuthProvidersRoute: AuthProvidersRoute, AuthProvidersRoute: AuthProvidersRoute,
AuthRenewalsRoute: AuthRenewalsRoute, AuthRenewalsRoute: AuthRenewalsRoute,
AuthReportsRoute: AuthReportsRoute, AuthReportsRoute: AuthReportsRoute,
@@ -625,7 +604,9 @@ const AuthRouteChildren: AuthRouteChildren = {
AuthSyncJournalRoute: AuthSyncJournalRoute, AuthSyncJournalRoute: AuthSyncJournalRoute,
AuthTariffsRoute: AuthTariffsRoute, AuthTariffsRoute: AuthTariffsRoute,
AuthTopologyRoute: AuthTopologyRoute, AuthTopologyRoute: AuthTopologyRoute,
AuthVpsRoute: AuthVpsRouteWithChildren, AuthVpsRoute: AuthVpsRoute,
AuthProjectsProjectIdRoute: AuthProjectsProjectIdRoute,
AuthVpsVpsIdRoute: AuthVpsVpsIdRoute,
} }
const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren) const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren)
+10 -17
View File
@@ -24,7 +24,7 @@ import {
} from '@/components/domain/audit-labels' } from '@/components/domain/audit-labels'
import { Button } from '@cfdm/ui/components/button' import { Button } from '@cfdm/ui/components/button'
import { Badge } from '@/components/reui/badge' import { Badge } from '@/components/reui/badge'
import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs' import { CountedLineTabs } from '@/components/counted-line-tabs'
import { TableSkeleton } from '@/components/skeletons' import { TableSkeleton } from '@/components/skeletons'
const auditSearchSchema = z.object({ const auditSearchSchema = z.object({
@@ -197,22 +197,15 @@ function AuditPage() {
actions={<AuditViewToggle view={view} onViewChange={setView} />} actions={<AuditViewToggle view={view} onViewChange={setView} />}
/> />
<Tabs value={actionFilter} onValueChange={setActionFilter}> <CountedLineTabs
<TabsList variant="line" aria-label="Фильтр по действию" className="gap-5"> tabs={ACTION_FILTERS.map((option) => ({
{ACTION_FILTERS.map((option) => ( id: option.value,
<TabsTrigger label: option.label,
key={option.value} count: actionCounts[option.value],
value={option.value} }))}
className="text-muted-foreground hover:text-foreground h-auto gap-2 px-0 pb-3" value={actionFilter}
> onValueChange={setActionFilter}
<span>{option.label}</span> />
<span className="bg-muted text-muted-foreground rounded-md px-1.5 py-0.5 text-[10px] tabular-nums">
{actionCounts[option.value]}
</span>
</TabsTrigger>
))}
</TabsList>
</Tabs>
<QueryState <QueryState
data={data} data={data}
@@ -51,7 +51,7 @@ import {
} from '@/lib/project-analytics' } from '@/lib/project-analytics'
import type { Vps } from '@/types/entities' import type { Vps } from '@/types/entities'
export const Route = createFileRoute('/_auth/projects/$projectId')({ export const Route = createFileRoute('/_auth/projects_/$projectId')({
loader: ({ context: { queryClient } }) => loader: ({ context: { queryClient } }) =>
queryClient.ensureQueryData(snapshotQueryOptions()), queryClient.ensureQueryData(snapshotQueryOptions()),
component: ProjectDetailPage, component: ProjectDetailPage,
@@ -48,7 +48,7 @@ import {
import type { Payment, Vps } from '@/types/entities' import type { Payment, Vps } from '@/types/entities'
import { VpsDomainsCell } from '@/components/integrations/vps-domains-cell' import { VpsDomainsCell } from '@/components/integrations/vps-domains-cell'
export const Route = createFileRoute('/_auth/vps/$vpsId')({ export const Route = createFileRoute('/_auth/vps_/$vpsId')({
loader: ({ context: { queryClient } }) => loader: ({ context: { queryClient } }) =>
queryClient.ensureQueryData(snapshotQueryOptions()), queryClient.ensureQueryData(snapshotQueryOptions()),
component: VpsDetailPage, component: VpsDetailPage,
@@ -153,18 +153,17 @@ function VpsDetailPage() {
title={vps ? (vps.ip || vps.dns || 'VPS') : 'VPS'} title={vps ? (vps.ip || vps.dns || 'VPS') : 'VPS'}
description={account ? accountSelectLabel(account, providerById) : undefined} description={account ? accountSelectLabel(account, providerById) : undefined}
actions={ actions={
vps ? ( <>
<Button variant="outline" render={<Link to="/vps" search={{ edit: vpsId }} />}> <Button variant="outline" render={<Link to="/vps" />}>
Редактировать
</Button>
) : undefined
}
/>
<Button variant="ghost" size="sm" className="w-fit" render={<Link to="/vps" />}>
<ArrowLeftIcon data-icon="inline-start" /> <ArrowLeftIcon data-icon="inline-start" />
К списку VPS К списку VPS
</Button> </Button>
{vps ? (
<Button render={<Link to="/vps" search={{ edit: vpsId }} />}>Редактировать</Button>
) : null}
</>
}
/>
<QueryState <QueryState
data={vps} data={vps}
@@ -189,12 +188,22 @@ function VpsDetailPage() {
} }
> >
{(row) => ( {(row) => (
<Tabs defaultValue="overview"> <Tabs defaultValue="overview" className="flex flex-col gap-4 md:gap-6">
<TabsList> <TabsList variant="line" className="gap-5">
<TabsTrigger value="overview">Обзор</TabsTrigger> {[
<TabsTrigger value="finance">Финансы</TabsTrigger> ['overview', 'Обзор'],
<TabsTrigger value="notes">Заметки</TabsTrigger> ['finance', 'Финансы'],
<TabsTrigger value="sync">Синк</TabsTrigger> ['notes', 'Заметки'],
['sync', 'Синк'],
].map(([value, label]) => (
<TabsTrigger
key={value}
value={value}
className="text-muted-foreground hover:text-foreground h-auto px-0 pb-3 after:bottom-0"
>
{label}
</TabsTrigger>
))}
</TabsList> </TabsList>
<TabsContent value="overview" className="flex flex-col gap-4"> <TabsContent value="overview" className="flex flex-col gap-4">