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:
1 parent
b5300b9082
commit
e3403bcb56
6 files changed
+94
-140
No files matched your search
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
+1
-1
@@ -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,
|
||||||
+25
-16
@@ -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">
|
||||||
Reference in new issue
Block a user