Files
cloudflare-domain-manager/apps/web/src/components/reui-kit/ops-dashboard.tsx
T
DenozordecandCursor 781c88175c
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m25s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m49s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 5s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
feat(web): enhance integration settings and UI components
- Updated AppSwitcherEditor to include item separators for better visual organization.
- Improved VpsTrackerIntegrationCard with badges indicating synchronization status and token configuration.
- Refactored SettingsShell to streamline layout and enhance accessibility with improved navigation.
- Added detail fields to KPI cards in DashboardPage for clearer information presentation.
- Introduced new components in the ReUI kit for better integration and user experience.

Co-authored-by: Cursor <[email protected]>
2026-07-10 01:10:34 +07:00

140 lines
4.0 KiB
TypeScript

import type { ReactNode } from 'react'
import { Link } from '@tanstack/react-router'
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from '@/components/reui/frame'
import { cn } from '@cfdm/ui/lib/utils'
import { Item, ItemMedia } from '@cfdm/ui/components/item'
export interface OpsKpiCard {
id: string
typeLabel: string
title: string
metricLabel: string
value: string | number
hint?: ReactNode
detail?: ReactNode
icon: ReactNode
iconBg?: string
}
interface OpsDashboardProps {
title?: string
description?: string
kpiCards: OpsKpiCard[]
charts: ReactNode
queue: ReactNode
}
function KpiCardItem({ card }: { card: OpsKpiCard }) {
return (
<FramePanel>
<div className="flex items-center gap-2.5">
<Item
className={cn(
'p-0',
'border-background flex size-10 items-center justify-center border-2 [background-image:radial-gradient(48.05%_48.05%_at_50%_5.95%,rgba(255,255,255,0.4)_0%,rgba(255,255,255,0)_100%)] shadow-[0_1px_3px_0_rgba(0,0,0,0.14)] dark:border [&_svg]:size-5 [&_svg]:text-white',
card.iconBg ?? 'bg-chart-1',
)}
>
<ItemMedia variant="icon" className="size-auto">
{card.icon}
</ItemMedia>
</Item>
<div className="flex min-w-0 flex-col gap-1">
<p className="text-muted-foreground text-sm leading-tight">
{card.typeLabel}
</p>
<h3 className="truncate text-sm leading-tight font-medium">{card.title}</h3>
</div>
</div>
<div className="mt-5 flex flex-col gap-1.5">
<p className="text-muted-foreground text-sm leading-tight">
{card.metricLabel}
</p>
<div className="flex flex-wrap items-center gap-2">
<span className="text-xl font-medium tracking-tight tabular-nums">
{card.value}
</span>
{card.hint}
</div>
{card.detail ? (
<p className="text-muted-foreground text-xs leading-snug">{card.detail}</p>
) : null}
</div>
</FramePanel>
)
}
export function OpsDashboard({
title = 'Панель управления',
description = 'Обзор доменов, групп, сервисов и сертификатов Cloudflare',
kpiCards,
charts,
queue,
}: OpsDashboardProps) {
return (
<div className="text-foreground @container mx-auto flex w-full max-w-7xl flex-col gap-4 md:gap-6">
<header className="px-1">
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
{description}
</p>
</header>
<section aria-label="Ключевые метрики">
<Frame className="@container w-full">
<div className="grid gap-1 @2xl:grid-cols-2 @5xl:grid-cols-4">
{kpiCards.map((card) => (
<KpiCardItem key={card.id} card={card} />
))}
</div>
</Frame>
</section>
<section
aria-label="Аналитика"
className="grid min-w-0 auto-rows-fr items-start gap-4 @3xl:grid-cols-2"
>
{charts}
</section>
<section aria-label="Требуют внимания">
<Frame dense spacing="sm" className="w-full">
<FrameHeader>
<FrameTitle>Требуют внимания</FrameTitle>
<FrameDescription>
Истекающие сертификаты и домены без группы
</FrameDescription>
</FrameHeader>
<FramePanel>{queue}</FramePanel>
</Frame>
</section>
</div>
)
}
export function OpsDashboardHintLink({
to,
search,
children,
}: {
to: string
search?: Record<string, unknown>
children: ReactNode
}) {
return (
<Link
to={to}
search={search}
className="text-primary text-sm font-medium hover:underline"
>
{children}
</Link>
)
}