♻️ refactor(web): consolidate design-system primitives and responsive data views
This commit is contained in:
+35
-44
@@ -29,9 +29,17 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/design-system/table'
|
||||
import { ErrorState } from '@/components/error-state'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { StatusBadge, type StatusVariant } from '@/components/status-badge'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -41,14 +49,6 @@ import {
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -73,9 +73,9 @@ const INSTANCE_SKELETON_KEYS = [
|
||||
'system-instance-skeleton-3',
|
||||
]
|
||||
|
||||
const STATUS_CLASS_NAME: Record<SystemInstanceStatus, string> = {
|
||||
online: 'bg-success/10 text-success',
|
||||
stale: 'bg-warning/10 text-warning',
|
||||
const STATUS_VARIANT: Record<SystemInstanceStatus, StatusVariant> = {
|
||||
online: 'success',
|
||||
stale: 'warning',
|
||||
}
|
||||
|
||||
const STATUS_DOT_CLASS_NAME: Record<SystemInstanceStatus, string> = {
|
||||
@@ -296,18 +296,19 @@ function SystemInstancesList(props: SystemInstancesTableProps) {
|
||||
{shouldConfigure && (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
className='inline-flex shrink-0 rounded-full focus-visible:ring-2 focus-visible:outline-none'
|
||||
aria-label={t('Configure NODE_NAME')}
|
||||
>
|
||||
<Badge
|
||||
variant='outline'
|
||||
className='border-warning/30 bg-warning/10 text-warning'
|
||||
>
|
||||
<AlertTriangle
|
||||
className='size-3'
|
||||
aria-hidden='true'
|
||||
render={
|
||||
<StatusBadge
|
||||
render={<button type='button' />}
|
||||
variant='warning'
|
||||
appearance='outline'
|
||||
aria-label={t('Configure NODE_NAME')}
|
||||
/>
|
||||
</Badge>
|
||||
}
|
||||
>
|
||||
<AlertTriangle
|
||||
data-icon='inline-start'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align='start' className='w-80'>
|
||||
<PopoverHeader>
|
||||
@@ -346,31 +347,23 @@ function SystemInstancesList(props: SystemInstancesTableProps) {
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className='py-2.5 align-middle'>
|
||||
<Badge
|
||||
variant='secondary'
|
||||
className={cn(
|
||||
'gap-1.5',
|
||||
STATUS_CLASS_NAME[instance.status]
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'size-1.5 rounded-full',
|
||||
STATUS_DOT_CLASS_NAME[instance.status]
|
||||
)}
|
||||
aria-hidden='true'
|
||||
/>
|
||||
<StatusBadge variant={STATUS_VARIANT[instance.status]}>
|
||||
{t(instance.status)}
|
||||
</Badge>
|
||||
</StatusBadge>
|
||||
</TableCell>
|
||||
<TableCell className='py-2.5 align-middle'>
|
||||
<TooltipProvider delay={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
className='inline-flex shrink-0 rounded-full focus-visible:ring-2 focus-visible:outline-none'
|
||||
aria-label={t('Node role')}
|
||||
render={
|
||||
<StatusBadge
|
||||
render={<button type='button' />}
|
||||
appearance='outline'
|
||||
aria-label={t('Node role')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Badge variant='outline'>{roleLabel(instance)}</Badge>
|
||||
{roleLabel(instance)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t(roleDescriptionKey(instance))}
|
||||
@@ -650,7 +643,6 @@ export function SystemInstancesPanel() {
|
||||
<Button
|
||||
type='button'
|
||||
variant='destructive'
|
||||
size='sm'
|
||||
onClick={() => setDeleteAllConfirmOpen(true)}
|
||||
disabled={
|
||||
staleInstances.length === 0 ||
|
||||
@@ -676,7 +668,6 @@ export function SystemInstancesPanel() {
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() => void instancesQuery.refetch()}
|
||||
disabled={instancesQuery.isFetching}
|
||||
aria-label={t('Refresh')}
|
||||
|
||||
+103
-111
@@ -18,13 +18,10 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { ListChecks, RefreshCw } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { ErrorState } from '@/components/error-state'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -32,7 +29,12 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
} from '@/components/design-system/table'
|
||||
import { ErrorState } from '@/components/error-state'
|
||||
import { StatusBadge, type StatusVariant } from '@/components/status-badge'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { listSystemTasks } from '@/features/system-settings/api'
|
||||
import type {
|
||||
SystemTask,
|
||||
@@ -44,28 +46,20 @@ import { cn } from '@/lib/utils'
|
||||
|
||||
const TASK_LIMIT = 20
|
||||
const ACTIVE_POLL_INTERVAL_MS = 8000
|
||||
const TASK_SKELETON_KEYS = [
|
||||
'system-task-skeleton-1',
|
||||
'system-task-skeleton-2',
|
||||
'system-task-skeleton-3',
|
||||
'system-task-skeleton-4',
|
||||
]
|
||||
|
||||
const STATUS_VARIANT: Record<SystemTaskStatus, 'secondary' | 'destructive'> = {
|
||||
pending: 'secondary',
|
||||
running: 'secondary',
|
||||
succeeded: 'secondary',
|
||||
const STATUS_VARIANT: Record<SystemTaskStatus, StatusVariant> = {
|
||||
pending: 'warning',
|
||||
running: 'info',
|
||||
succeeded: 'success',
|
||||
failed: 'destructive',
|
||||
}
|
||||
|
||||
const STATUS_CLASS_NAME: Record<SystemTaskStatus, string> = {
|
||||
pending: 'bg-warning/10 text-warning',
|
||||
running: 'bg-info/10 text-info [&_span]:bg-info',
|
||||
succeeded: 'bg-success/10 text-success',
|
||||
failed: '',
|
||||
}
|
||||
|
||||
const STATUS_DOT_CLASS_NAME: Record<SystemTaskStatus, string> = {
|
||||
pending: 'bg-warning',
|
||||
running: 'bg-info',
|
||||
succeeded: 'bg-success',
|
||||
failed: 'bg-destructive',
|
||||
}
|
||||
|
||||
const PROGRESS_BAR_CLASS_NAME: Record<SystemTaskStatus, string> = {
|
||||
pending: '[&_[data-slot=progress-indicator]]:bg-warning',
|
||||
running: '[&_[data-slot=progress-indicator]]:bg-info',
|
||||
@@ -145,19 +139,9 @@ function SystemTasksTable(props: SystemTasksTableProps) {
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className='py-3 align-middle'>
|
||||
<Badge
|
||||
variant={STATUS_VARIANT[task.status]}
|
||||
className={cn('gap-1.5', STATUS_CLASS_NAME[task.status])}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'size-1.5 rounded-full',
|
||||
STATUS_DOT_CLASS_NAME[task.status]
|
||||
)}
|
||||
aria-hidden='true'
|
||||
/>
|
||||
<StatusBadge variant={STATUS_VARIANT[task.status]}>
|
||||
{t(task.status)}
|
||||
</Badge>
|
||||
</StatusBadge>
|
||||
</TableCell>
|
||||
<TableCell className='py-3 align-middle'>
|
||||
<div className='flex items-center gap-2'>
|
||||
@@ -227,6 +211,88 @@ export function SystemTasksPanel() {
|
||||
const activeTasks = tasks.filter((task) => isActiveStatus(task.status))
|
||||
const historyTasks = tasks.filter((task) => !isActiveStatus(task.status))
|
||||
|
||||
let tasksContent: ReactNode
|
||||
if (loading) {
|
||||
tasksContent = (
|
||||
<div className='space-y-2 p-4 sm:p-5'>
|
||||
{TASK_SKELETON_KEYS.map((key) => (
|
||||
<Skeleton key={key} className='h-9 w-full rounded-md' />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
} else if (tasksQuery.isError) {
|
||||
tasksContent = (
|
||||
<ErrorState
|
||||
title={t('We could not load system tasks.')}
|
||||
description={
|
||||
tasksQuery.error instanceof Error
|
||||
? tasksQuery.error.message
|
||||
: undefined
|
||||
}
|
||||
onRetry={() => {
|
||||
void tasksQuery.refetch()
|
||||
}}
|
||||
className='min-h-[260px]'
|
||||
/>
|
||||
)
|
||||
} else if (tasks.length === 0) {
|
||||
tasksContent = (
|
||||
<div className='px-4 py-10 text-center sm:px-5'>
|
||||
<div className='bg-muted mx-auto mb-3 flex size-10 items-center justify-center rounded-lg'>
|
||||
<ListChecks
|
||||
className='text-muted-foreground size-5'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
</div>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t('No system tasks yet.')}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
tasksContent = (
|
||||
<div className='space-y-4 p-4 sm:p-5'>
|
||||
<div>
|
||||
<div className='mb-2 flex items-center justify-between gap-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium'>{t('Active Tasks')}</h4>
|
||||
<p className='text-muted-foreground mt-0.5 text-xs'>
|
||||
{t('Tasks currently pending or running.')}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant='outline'>{activeTasks.length}</Badge>
|
||||
</div>
|
||||
{activeTasks.length > 0 ? (
|
||||
<SystemTasksTable tasks={activeTasks} />
|
||||
) : (
|
||||
<div className='text-muted-foreground rounded-md border border-dashed px-4 py-6 text-center text-sm'>
|
||||
{t('No active system tasks.')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className='mb-2 flex items-center justify-between gap-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium'>{t('Task History')}</h4>
|
||||
<p className='text-muted-foreground mt-0.5 text-xs'>
|
||||
{t('Recently completed or failed system task runs.')}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant='outline'>{historyTasks.length}</Badge>
|
||||
</div>
|
||||
{historyTasks.length > 0 ? (
|
||||
<SystemTasksTable tasks={historyTasks} />
|
||||
) : (
|
||||
<div className='text-muted-foreground rounded-md border border-dashed px-4 py-6 text-center text-sm'>
|
||||
{t('No historical system tasks.')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className='bg-card overflow-hidden rounded-lg border shadow-xs'>
|
||||
<div className='flex flex-col gap-3 border-b px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-5'>
|
||||
@@ -266,7 +332,6 @@ export function SystemTasksPanel() {
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() => void tasksQuery.refetch()}
|
||||
disabled={tasksQuery.isFetching}
|
||||
aria-label={t('Refresh')}
|
||||
@@ -281,80 +346,7 @@ export function SystemTasksPanel() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-busy={tasksQuery.isFetching}>
|
||||
{loading ? (
|
||||
<div className='space-y-2 p-4 sm:p-5'>
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Skeleton key={i} className='h-9 w-full rounded-md' />
|
||||
))}
|
||||
</div>
|
||||
) : tasksQuery.isError ? (
|
||||
<ErrorState
|
||||
title={t('We could not load system tasks.')}
|
||||
description={
|
||||
tasksQuery.error instanceof Error
|
||||
? tasksQuery.error.message
|
||||
: undefined
|
||||
}
|
||||
onRetry={() => {
|
||||
void tasksQuery.refetch()
|
||||
}}
|
||||
className='min-h-[260px]'
|
||||
/>
|
||||
) : tasks.length === 0 ? (
|
||||
<div className='px-4 py-10 text-center sm:px-5'>
|
||||
<div className='bg-muted mx-auto mb-3 flex size-10 items-center justify-center rounded-lg'>
|
||||
<ListChecks
|
||||
className='text-muted-foreground size-5'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
</div>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t('No system tasks yet.')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className='space-y-4 p-4 sm:p-5'>
|
||||
<div>
|
||||
<div className='mb-2 flex items-center justify-between gap-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium'>{t('Active Tasks')}</h4>
|
||||
<p className='text-muted-foreground mt-0.5 text-xs'>
|
||||
{t('Tasks currently pending or running.')}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant='outline'>{activeTasks.length}</Badge>
|
||||
</div>
|
||||
{activeTasks.length > 0 ? (
|
||||
<SystemTasksTable tasks={activeTasks} />
|
||||
) : (
|
||||
<div className='text-muted-foreground rounded-md border border-dashed px-4 py-6 text-center text-sm'>
|
||||
{t('No active system tasks.')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className='mb-2 flex items-center justify-between gap-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium'>{t('Task History')}</h4>
|
||||
<p className='text-muted-foreground mt-0.5 text-xs'>
|
||||
{t('Recently completed or failed system task runs.')}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant='outline'>{historyTasks.length}</Badge>
|
||||
</div>
|
||||
{historyTasks.length > 0 ? (
|
||||
<SystemTasksTable tasks={historyTasks} />
|
||||
) : (
|
||||
<div className='text-muted-foreground rounded-md border border-dashed px-4 py-6 text-center text-sm'>
|
||||
{t('No historical system tasks.')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div aria-busy={tasksQuery.isFetching}>{tasksContent}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SectionPageLayout } from '@/components/layout'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
|
||||
import { SystemInstancesPanel } from './components/system-instances-panel'
|
||||
import { SystemTasksPanel } from './components/system-tasks-panel'
|
||||
@@ -32,9 +32,9 @@ export function SystemInfo() {
|
||||
<SectionPageLayout.Title>
|
||||
<span className='inline-flex min-w-0 items-center gap-2'>
|
||||
<span className='truncate'>{t('System Info')}</span>
|
||||
<Badge variant='outline' className='shrink-0'>
|
||||
<StatusBadge appearance='outline' className='shrink-0'>
|
||||
Root
|
||||
</Badge>
|
||||
</StatusBadge>
|
||||
</span>
|
||||
</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Content>
|
||||
|
||||
Reference in New Issue
Block a user