feat: redesign ticket workbench ui
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
export function Sidebar() {
|
||||
const navItems = [
|
||||
{ href: '#/tickets', label: 'Tickets' },
|
||||
{ href: '#/tickets/new', label: 'Create Ticket' },
|
||||
{ href: '#/solutions', label: 'Solutions' },
|
||||
{ href: '#/design-system', label: 'Design System' }
|
||||
{ href: '#/tickets', label: '工单中心' },
|
||||
{ href: '#/tickets/new', label: '创建工单' },
|
||||
{ href: '#/solutions', label: '解决方案库' },
|
||||
{ href: '#/design-system', label: '设计系统' }
|
||||
]
|
||||
|
||||
return (
|
||||
<aside className="fixed inset-y-0 left-0 hidden w-64 border-r border-border bg-surface lg:block">
|
||||
<div className="border-b border-border px-4 py-3">
|
||||
<p className="text-sm font-semibold text-foreground">AI Ticket Platform</p>
|
||||
<p className="text-sm font-semibold text-foreground">AI 工单平台</p>
|
||||
</div>
|
||||
<nav className="flex flex-col gap-1 p-3">
|
||||
{navItems.map((item) => (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { AppSection } from '@/components/app/AppSection'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { addTicketComment } from './api'
|
||||
@@ -19,21 +19,21 @@ export function TicketComments({ ticketId }: { ticketId: number }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-sm font-medium">Comments</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AppSection title="评论协作" description="记录内部沟通、排查过程和处理补充。">
|
||||
<form className="grid gap-3" onSubmit={handleSubmit}>
|
||||
<Textarea value={content} onChange={(event) => setContent(event.target.value)} />
|
||||
{lastComment ? <p className="text-sm text-muted-foreground">Latest: {lastComment}</p> : null}
|
||||
<Textarea
|
||||
placeholder="请输入处理说明或补充信息"
|
||||
value={content}
|
||||
onChange={(event) => setContent(event.target.value)}
|
||||
/>
|
||||
{lastComment ? <p className="text-sm text-muted-foreground">最新回复:{lastComment}</p> : null}
|
||||
{!lastComment ? <p className="text-sm text-muted-foreground">暂无评论</p> : null}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="secondary">
|
||||
Add Comment
|
||||
发表回复
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AppSection>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as React from 'react'
|
||||
import { Bot } from 'lucide-react'
|
||||
|
||||
import { AppField } from '@/components/app/AppField'
|
||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||
import { AppSection } from '@/components/app/AppSection'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
@@ -41,52 +43,83 @@ export function TicketCreatePage() {
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-background p-6 text-foreground">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<AppPageHeader title="Create Ticket" description="Record a confirmed support request." />
|
||||
<Card className="mt-4">
|
||||
<CardContent>
|
||||
<form className="grid gap-4" onSubmit={handleSubmit}>
|
||||
<AppField label="Title">
|
||||
<Input required value={form.title} onChange={(event) => updateField('title', event.target.value)} />
|
||||
<div className="mx-auto grid max-w-6xl gap-4">
|
||||
<AppPageHeader title="创建工单" description="记录明确的技术支持请求,便于后续分派、排查和复用方案。" />
|
||||
<form className="grid gap-4 xl:grid-cols-[minmax(0,1fr)_340px]" onSubmit={handleSubmit}>
|
||||
<div className="grid gap-4">
|
||||
<AppSection title="问题描述" description="请先说明现象、出现时间和可复现条件。">
|
||||
<AppField label="工单标题">
|
||||
<Input
|
||||
required
|
||||
placeholder="例如:生产环境登录后跳转失败"
|
||||
value={form.title}
|
||||
onChange={(event) => updateField('title', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
<AppField label="Description">
|
||||
<div className="mt-4">
|
||||
<AppField label="问题描述">
|
||||
<Textarea
|
||||
required
|
||||
placeholder="请描述影响范围、报错信息、操作步骤和已尝试的处理方式。"
|
||||
value={form.description}
|
||||
onChange={(event) => updateField('description', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
</div>
|
||||
</AppSection>
|
||||
<AppSection title="处理属性" description="用于技术支持人员快速判断优先级和处理路径。">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<AppField label="Priority">
|
||||
<AppField label="优先级">
|
||||
<Select value={form.priority} onValueChange={(value) => updateField('priority', value as TicketInput['priority'])}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Priority" />
|
||||
<SelectValue placeholder="请选择优先级" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="low">Low</SelectItem>
|
||||
<SelectItem value="medium">Medium</SelectItem>
|
||||
<SelectItem value="high">High</SelectItem>
|
||||
<SelectItem value="urgent">Urgent</SelectItem>
|
||||
<SelectItem value="low">低</SelectItem>
|
||||
<SelectItem value="medium">中</SelectItem>
|
||||
<SelectItem value="high">高</SelectItem>
|
||||
<SelectItem value="urgent">紧急</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</AppField>
|
||||
<AppField label="Related System">
|
||||
<Input value={form.related_system} onChange={(event) => updateField('related_system', event.target.value)} />
|
||||
<AppField label="关联系统">
|
||||
<Input
|
||||
placeholder="例如:CRM、财务系统、数据中台"
|
||||
value={form.related_system}
|
||||
onChange={(event) => updateField('related_system', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
<AppField label="Impact Scope">
|
||||
<Input value={form.impact_scope} onChange={(event) => updateField('impact_scope', event.target.value)} />
|
||||
<AppField label="影响范围">
|
||||
<Input
|
||||
placeholder="例如:单个用户、某部门、全部用户"
|
||||
value={form.impact_scope}
|
||||
onChange={(event) => updateField('impact_scope', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
<AppField label="Environment">
|
||||
<Input value={form.environment} onChange={(event) => updateField('environment', event.target.value)} />
|
||||
<AppField label="运行环境">
|
||||
<Input
|
||||
placeholder="例如:生产环境、测试环境、Windows 终端"
|
||||
value={form.environment}
|
||||
onChange={(event) => updateField('environment', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
</div>
|
||||
</AppSection>
|
||||
{error ? <p className="text-sm text-danger">{error}</p> : null}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">Create</Button>
|
||||
<Button type="submit">提交工单</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<AppSection
|
||||
title="AI 建单辅助"
|
||||
description="后续阶段能力预留"
|
||||
action={<Badge variant="ai"><Bot className="size-3" /> 即将接入</Badge>}
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
后续阶段将接入建单 Agent,用于补全字段、追问缺失信息和推荐相似问题。
|
||||
</p>
|
||||
</AppSection>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
||||
import { AppDetailLayout } from '@/components/app/AppDetailLayout'
|
||||
import { AppField } from '@/components/app/AppField'
|
||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||
import { AppPriorityBadge } from '@/components/app/AppPriorityBadge'
|
||||
import { AppSection } from '@/components/app/AppSection'
|
||||
import { AppStatusBadge } from '@/components/app/AppStatusBadge'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { SolutionLinkPanel } from '@/features/solutions/SolutionLinkPanel'
|
||||
|
||||
import { getTicket, transitionTicket } from './api'
|
||||
@@ -15,6 +17,14 @@ import { TicketStatusTimeline } from './TicketStatusTimeline'
|
||||
import type { Ticket, TicketStatus } from './types'
|
||||
|
||||
const NEXT_STATUSES: TicketStatus[] = ['in_progress', 'pending_confirm', 'resolved', 'closed']
|
||||
const STATUS_ACTION_LABELS: Record<TicketStatus, string> = {
|
||||
pending_info: '退回补充',
|
||||
pending: '转为待处理',
|
||||
in_progress: '开始处理',
|
||||
pending_confirm: '提交确认',
|
||||
resolved: '标记解决',
|
||||
closed: '关闭工单'
|
||||
}
|
||||
|
||||
export function TicketDetailPage({ ticketId }: { ticketId: number }) {
|
||||
const [ticket, setTicket] = React.useState<Ticket | null>(null)
|
||||
@@ -28,7 +38,7 @@ export function TicketDetailPage({ ticketId }: { ticketId: number }) {
|
||||
|
||||
async function handleTransition(status: TicketStatus) {
|
||||
if (!ticket) return
|
||||
const updated = await transitionTicket(ticket.id, status, `Move to ${status}`)
|
||||
const updated = await transitionTicket(ticket.id, status, `状态调整为:${STATUS_ACTION_LABELS[status]}`)
|
||||
setTicket(updated)
|
||||
}
|
||||
|
||||
@@ -54,41 +64,57 @@ export function TicketDetailPage({ ticketId }: { ticketId: number }) {
|
||||
<AppPageHeader
|
||||
title={ticket.title}
|
||||
description={ticket.ticket_no}
|
||||
meta={
|
||||
<>
|
||||
<AppStatusBadge status={ticket.status} />
|
||||
<AppPriorityBadge priority={ticket.priority} />
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{NEXT_STATUSES.map((status) => (
|
||||
<Button key={status} variant="secondary" onClick={() => void handleTransition(status)}>
|
||||
{status}
|
||||
{STATUS_ACTION_LABELS[status]}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Card>
|
||||
<CardContent className="grid gap-4 md:grid-cols-3">
|
||||
<AppField label="Status">
|
||||
<AppStatusBadge status={ticket.status} />
|
||||
</AppField>
|
||||
<AppField label="Priority">
|
||||
<AppPriorityBadge priority={ticket.priority} />
|
||||
</AppField>
|
||||
<AppField label="System">
|
||||
<span className="text-sm">{ticket.related_system || '-'}</span>
|
||||
</AppField>
|
||||
<AppField label="Environment">
|
||||
<span className="text-sm">{ticket.environment || '-'}</span>
|
||||
</AppField>
|
||||
<AppField label="Impact">
|
||||
<span className="text-sm">{ticket.impact_scope || '-'}</span>
|
||||
</AppField>
|
||||
<AppField label="Description">
|
||||
<span className="text-sm">{ticket.description}</span>
|
||||
</AppField>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TicketStatusTimeline ticket={ticket} />
|
||||
<TicketComments ticketId={ticket.id} />
|
||||
<SolutionLinkPanel ticketId={ticket.id} />
|
||||
<AppDetailLayout
|
||||
side={
|
||||
<>
|
||||
<AppSection title="关键字段">
|
||||
<div className="grid gap-3">
|
||||
<AppField label="状态">
|
||||
<AppStatusBadge status={ticket.status} />
|
||||
</AppField>
|
||||
<AppField label="优先级">
|
||||
<AppPriorityBadge priority={ticket.priority} />
|
||||
</AppField>
|
||||
<AppField label="关联系统">
|
||||
<span className="text-sm">{ticket.related_system || '-'}</span>
|
||||
</AppField>
|
||||
<AppField label="运行环境">
|
||||
<span className="text-sm">{ticket.environment || '-'}</span>
|
||||
</AppField>
|
||||
<AppField label="影响范围">
|
||||
<span className="text-sm">{ticket.impact_scope || '-'}</span>
|
||||
</AppField>
|
||||
</div>
|
||||
</AppSection>
|
||||
<SolutionLinkPanel ticketId={ticket.id} />
|
||||
<AppSection title="AI 支持分析" action={<Badge variant="ai">后续接入</Badge>}>
|
||||
<p className="text-sm text-muted-foreground">后续阶段将在这里展示排查建议、相似工单和引用来源。</p>
|
||||
</AppSection>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<AppSection title="工单概览" description="当前工单的核心问题和处理上下文。">
|
||||
<p className="whitespace-pre-wrap text-sm leading-6">{ticket.description}</p>
|
||||
</AppSection>
|
||||
<TicketStatusTimeline ticket={ticket} />
|
||||
<TicketComments ticketId={ticket.id} />
|
||||
</AppDetailLayout>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
|
||||
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
||||
import { AppFilterBar } from '@/components/app/AppFilterBar'
|
||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||
import { AppPriorityBadge } from '@/components/app/AppPriorityBadge'
|
||||
import { AppStatusBadge } from '@/components/app/AppStatusBadge'
|
||||
@@ -13,9 +15,63 @@ import { formatDateTime } from '@/lib/format'
|
||||
import { listTickets } from './api'
|
||||
import type { Ticket } from './types'
|
||||
|
||||
const columns: AppDataTableColumn<Ticket>[] = [
|
||||
{
|
||||
key: 'ticket_no',
|
||||
header: '编号',
|
||||
cell: (ticket) => <span className="font-medium text-foreground">{ticket.ticket_no}</span>
|
||||
},
|
||||
{
|
||||
key: 'title',
|
||||
header: '标题',
|
||||
cell: (ticket) => <span className="line-clamp-1">{ticket.title}</span>
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: '状态',
|
||||
cell: (ticket) => <AppStatusBadge status={ticket.status} />
|
||||
},
|
||||
{
|
||||
key: 'priority',
|
||||
header: '优先级',
|
||||
cell: (ticket) => <AppPriorityBadge priority={ticket.priority} />
|
||||
},
|
||||
{
|
||||
key: 'related_system',
|
||||
header: '关联系统',
|
||||
cell: (ticket) => <span className="text-muted-foreground">{ticket.related_system || '-'}</span>
|
||||
},
|
||||
{
|
||||
key: 'impact_scope',
|
||||
header: '影响范围',
|
||||
cell: (ticket) => <span className="text-muted-foreground">{ticket.impact_scope || '-'}</span>
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
header: '创建时间',
|
||||
cell: (ticket) => <span className="text-muted-foreground">{formatDateTime(ticket.created_at)}</span>
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
header: '操作',
|
||||
cell: (ticket) => (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
window.location.hash = `/tickets/${ticket.id}`
|
||||
}}
|
||||
>
|
||||
查看详情
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
export function TicketListPage() {
|
||||
const [tickets, setTickets] = React.useState<Ticket[]>([])
|
||||
const [status, setStatus] = React.useState('all')
|
||||
const [priority, setPriority] = React.useState('all')
|
||||
const [q, setQ] = React.useState('')
|
||||
const [error, setError] = React.useState('')
|
||||
|
||||
@@ -25,48 +81,81 @@ export function TicketListPage() {
|
||||
.catch((reason: unknown) => setError(reason instanceof Error ? reason.message : '加载失败'))
|
||||
}, [status, q])
|
||||
|
||||
const visibleTickets = priority === 'all' ? tickets : tickets.filter((ticket) => ticket.priority === priority)
|
||||
const metrics = [
|
||||
{ label: '待处理', value: tickets.filter((ticket) => ticket.status === 'pending').length },
|
||||
{ label: '处理中', value: tickets.filter((ticket) => ticket.status === 'in_progress').length },
|
||||
{ label: '待确认', value: tickets.filter((ticket) => ticket.status === 'pending_confirm').length },
|
||||
{ label: '已解决', value: tickets.filter((ticket) => ticket.status === 'resolved' || ticket.status === 'closed').length }
|
||||
]
|
||||
|
||||
function resetFilters() {
|
||||
setStatus('all')
|
||||
setPriority('all')
|
||||
setQ('')
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-background p-6 text-foreground">
|
||||
<div className="mx-auto flex max-w-6xl flex-col gap-4">
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-4">
|
||||
<AppPageHeader
|
||||
title="Tickets"
|
||||
description="Search, filter, and review support tickets."
|
||||
actions={<Button onClick={() => (window.location.hash = '/tickets/new')}>Create Ticket</Button>}
|
||||
title="工单中心"
|
||||
description="统一查看、筛选和处理技术支持工单。"
|
||||
actions={<Button onClick={() => (window.location.hash = '/tickets/new')}>新建工单</Button>}
|
||||
/>
|
||||
<div className="grid gap-2 md:grid-cols-[1fr_180px]">
|
||||
<Input onChange={(event) => setQ(event.target.value)} placeholder="Search ticket no or title" value={q} />
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-4">
|
||||
{metrics.map((metric) => (
|
||||
<Card key={metric.label}>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">{metric.label}</p>
|
||||
<p className="mt-2 text-2xl font-semibold text-foreground">{metric.value}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<AppFilterBar actions={<Button variant="outline" onClick={resetFilters}>重置筛选</Button>}>
|
||||
<Input onChange={(event) => setQ(event.target.value)} placeholder="搜索工单编号或标题" value={q} />
|
||||
<Select onValueChange={setStatus} value={status}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="All statuses" />
|
||||
<SelectValue placeholder="全部状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All statuses</SelectItem>
|
||||
<SelectItem value="pending">Pending</SelectItem>
|
||||
<SelectItem value="in_progress">In progress</SelectItem>
|
||||
<SelectItem value="resolved">Resolved</SelectItem>
|
||||
<SelectItem value="closed">Closed</SelectItem>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="pending">待处理</SelectItem>
|
||||
<SelectItem value="in_progress">处理中</SelectItem>
|
||||
<SelectItem value="pending_confirm">待确认</SelectItem>
|
||||
<SelectItem value="resolved">已解决</SelectItem>
|
||||
<SelectItem value="closed">已关闭</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{error ? <AppEmptyState title="Unable to load tickets" description={error} /> : null}
|
||||
{!error && tickets.length === 0 ? <AppEmptyState title="No tickets" description="Create the first support ticket." /> : null}
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
{tickets.map((ticket) => (
|
||||
<a
|
||||
className="grid gap-3 border-b border-border px-4 py-3 text-sm last:border-b-0 hover:bg-muted md:grid-cols-[150px_1fr_120px_100px_140px]"
|
||||
href={`#/tickets/${ticket.id}`}
|
||||
key={ticket.id}
|
||||
>
|
||||
<span className="font-medium text-foreground">{ticket.ticket_no}</span>
|
||||
<span>{ticket.title}</span>
|
||||
<AppStatusBadge status={ticket.status} />
|
||||
<AppPriorityBadge priority={ticket.priority} />
|
||||
<span className="text-muted-foreground">{formatDateTime(ticket.created_at)}</span>
|
||||
</a>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Select onValueChange={setPriority} value={priority}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="全部优先级" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部优先级</SelectItem>
|
||||
<SelectItem value="low">低</SelectItem>
|
||||
<SelectItem value="medium">中</SelectItem>
|
||||
<SelectItem value="high">高</SelectItem>
|
||||
<SelectItem value="urgent">紧急</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</AppFilterBar>
|
||||
|
||||
{error ? <AppEmptyState title="无法加载工单" description={error} /> : null}
|
||||
{!error ? (
|
||||
<AppDataTable
|
||||
columns={columns}
|
||||
rows={visibleTickets}
|
||||
getRowKey={(ticket) => ticket.id}
|
||||
emptyTitle="暂无工单"
|
||||
emptyDescription="创建第一张工单后,列表会显示在这里。"
|
||||
footer={<p className="text-sm text-muted-foreground">当前显示 {visibleTickets.length} 条工单</p>}
|
||||
onRowClick={(ticket) => (window.location.hash = `/tickets/${ticket.id}`)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
||||
import { AppSection } from '@/components/app/AppSection'
|
||||
import { AppStatusBadge } from '@/components/app/AppStatusBadge'
|
||||
|
||||
import type { Ticket } from './types'
|
||||
|
||||
export function TicketStatusTimeline({ ticket }: { ticket: Ticket }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-sm font-medium">Timeline</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-sm text-muted-foreground">Current status: {ticket.status}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AppSection title="处理动态" description="记录状态流转和关键处理节点。">
|
||||
<div className="flex items-center gap-3 rounded-md border border-border bg-background px-3 py-2">
|
||||
<div className="size-2 rounded-full bg-info" />
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">当前状态</p>
|
||||
<div className="mt-1">
|
||||
<AppStatusBadge status={ticket.status} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppSection>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user