Updated various dashboard components to improve layout and responsiveness. Adjusted class names to include 'min-w-0' for better handling of overflow and added flex properties to ensure proper alignment. Refined grid structures and skeleton loading states for a more cohesive user experience. Co-authored-by: Cursor <[email protected]>
103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
import { AlertTriangle, CheckCircle, Info } from 'lucide-react'
|
|
|
|
import { DashboardFramePanel } from '@/components/dashboard/dashboard-frame-panel'
|
|
import { Badge } from '@/components/reui/badge'
|
|
import {
|
|
Timeline,
|
|
TimelineContent,
|
|
TimelineHeader,
|
|
TimelineIndicator,
|
|
TimelineItem,
|
|
TimelineSeparator,
|
|
TimelineTitle,
|
|
} from '@/components/reui/timeline'
|
|
import { cn } from '@evobgp/ui/lib/utils'
|
|
|
|
import { recentPlatformActivity } from '@/lib/metrics'
|
|
import type { JobRow, PeerRow, RevisionRow, SpeakerRow } from '@/types/api'
|
|
|
|
const KIND_META = {
|
|
job: { icon: Info, className: 'text-info' },
|
|
revision: { icon: CheckCircle, className: 'text-success' },
|
|
network: { icon: AlertTriangle, className: 'text-warning' },
|
|
} as const
|
|
|
|
function statusBadgeVariant(status: string) {
|
|
const s = status.toLowerCase()
|
|
if (['ok', 'success', 'completed', 'done'].includes(s)) return 'success-light' as const
|
|
if (['running', 'queued', 'pending'].includes(s)) return 'info-light' as const
|
|
if (['warning', 'mismatch'].includes(s)) return 'warning-light' as const
|
|
if (['failed', 'error', 'cancelled'].includes(s)) return 'destructive-light' as const
|
|
return 'outline' as const
|
|
}
|
|
|
|
export function DashboardActivityTimeline({
|
|
jobs,
|
|
revisions,
|
|
peers,
|
|
speakers,
|
|
loading,
|
|
}: {
|
|
jobs: JobRow[]
|
|
revisions: RevisionRow[]
|
|
peers: PeerRow[]
|
|
speakers: SpeakerRow[]
|
|
loading?: boolean
|
|
}) {
|
|
const items = recentPlatformActivity(jobs, revisions, peers, speakers, 6)
|
|
|
|
return (
|
|
<DashboardFramePanel
|
|
title="Недавняя активность"
|
|
description="Задачи, ревизии и сетевые события"
|
|
className="h-full min-w-0"
|
|
>
|
|
{loading ? (
|
|
<p className="text-muted-foreground px-4 py-6 text-sm">Загрузка…</p>
|
|
) : items.length === 0 ? (
|
|
<p className="text-muted-foreground px-4 py-6 text-sm">Нет недавних событий</p>
|
|
) : (
|
|
<div className="px-4 py-4">
|
|
<Timeline defaultValue={items.length}>
|
|
{items.map((item, index) => {
|
|
const meta = KIND_META[item.kind]
|
|
const Icon = meta.icon
|
|
return (
|
|
<TimelineItem
|
|
key={item.id}
|
|
step={index + 1}
|
|
className="group-data-[orientation=vertical]/timeline:ms-8 group-data-[orientation=vertical]/timeline:not-last:pb-4"
|
|
>
|
|
<TimelineHeader>
|
|
<TimelineSeparator className="bg-border! group-data-[orientation=vertical]/timeline:-left-6 group-data-[orientation=vertical]/timeline:top-2 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem)] group-data-[orientation=vertical]/timeline:translate-y-5" />
|
|
<TimelineIndicator className="border-none bg-transparent group-data-[orientation=vertical]/timeline:-left-6">
|
|
<span
|
|
className={cn(
|
|
'bg-muted/70 flex size-7 items-center justify-center rounded-full',
|
|
meta.className,
|
|
)}
|
|
>
|
|
<Icon className="size-3.5" aria-hidden />
|
|
</span>
|
|
</TimelineIndicator>
|
|
</TimelineHeader>
|
|
<TimelineContent className="min-w-0 pb-1 text-foreground">
|
|
<TimelineTitle className="text-sm leading-snug font-normal break-words">
|
|
{item.message}
|
|
</TimelineTitle>
|
|
<div className="mt-2">
|
|
<Badge variant={statusBadgeVariant(item.status)} size="sm">
|
|
{item.statusLabel ?? item.status}
|
|
</Badge>
|
|
</div>
|
|
</TimelineContent>
|
|
</TimelineItem>
|
|
)
|
|
})}
|
|
</Timeline>
|
|
</div>
|
|
)}
|
|
</DashboardFramePanel>
|
|
)
|
|
}
|