import * as React from 'react' import { CheckCircle2, CircleDot, Clock3, Send, XCircle } from 'lucide-react' import { AppEmptyState } from '@/components/app/AppEmptyState' import { AppDetailLayout } from '@/components/app/AppDetailLayout' import { AppPage } from '@/components/app/AppPage' 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, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { useAgentPageContext } from '@/features/agent-workspace/AgentWorkspaceProvider' import { SolutionLinkPanel } from '@/features/solutions/SolutionLinkPanel' import { selectDefaultTicketSolution } from '@/features/solutions/solutionSelection' import type { TicketSolution } from '@/features/solutions/types' import { formatDateTime } from '@/lib/format' import { getTicket, listTicketEvents, transitionTicket } from './api' import { TicketComments } from './TicketComments' import { getTicketCategoryLabel, getTicketNextStatuses } from './ticketCategories' import { TicketMetaPanel } from './TicketMetaPanel' import { TicketStatusTimeline } from './TicketStatusTimeline' import { TicketTransitionDialog } from './TicketTransitionDialog' import type { Ticket, TicketEvent, TicketStatus } from './types' const STATUS_ACTIONS: Partial }>> = { pending: { label: '转为待处理', icon: CircleDot }, in_progress: { label: '开始处理', icon: CircleDot }, pending_confirm: { label: '提交确认', icon: Send }, resolved: { label: '标记解决', icon: CheckCircle2 }, closed: { label: '关闭工单', icon: XCircle } } function getStatusActionLabel(currentStatus: TicketStatus, targetStatus: TicketStatus) { if (targetStatus === 'closed' && currentStatus !== 'resolved') { return '取消工单' } return STATUS_ACTIONS[targetStatus]?.label ?? '调整状态' } function displayUser(user: Ticket['requester']) { return user?.display_name || user?.username || '未设置' } function getActionVariant(status: TicketStatus) { if (status === 'closed') return 'outline' if (status === 'resolved') return 'default' return 'secondary' } export function TicketDetailPage({ ticketId }: { ticketId: number }) { const [ticket, setTicket] = React.useState(null) const [events, setEvents] = React.useState([]) const [error, setError] = React.useState('') const [targetStatus, setTargetStatus] = React.useState(null) const [targetActionLabel, setTargetActionLabel] = React.useState('') const [transitionDialogOpen, setTransitionDialogOpen] = React.useState(false) const [transitionError, setTransitionError] = React.useState('') const [linkedSolutions, setLinkedSolutions] = React.useState([]) const agentContext = React.useMemo(() => ticket ? ({ routeKey: 'ticket-detail', entityType: 'ticket', entityId: ticket.ticket_no, title: ticket.title, status: ticket.status, metadata: { 优先级: ticket.priority, 关联系统: ticket.related_system || '未填写', 问题对象: ticket.problem_object || '未填写' } }) : null, [ticket]) useAgentPageContext(agentContext) React.useEffect(() => { refreshTicket() refreshEvents() }, [ticketId]) function refreshTicket() { getTicket(ticketId) .then(setTicket) .catch((reason: unknown) => setError(reason instanceof Error ? reason.message : '加载失败')) } function refreshEvents() { listTicketEvents(ticketId).then(setEvents).catch(() => setEvents([])) } function openTransition(status: TicketStatus) { setTargetStatus(status) if (ticket) { setTargetActionLabel(getStatusActionLabel(ticket.status, status)) } setTransitionError('') setTransitionDialogOpen(true) } async function handleTransition( status: TicketStatus, values: { remark: string resolution: string is_resolved?: boolean replied_to_customer: boolean } ) { if (!ticket) return try { const updated = await transitionTicket(ticket.id, status, values) setTicket(updated) setTransitionError('') refreshEvents() } catch (reason) { setTransitionError(reason instanceof Error ? reason.message : '状态流转失败') throw reason } } if (error) { return ( }> ) } if (!ticket) { return ( }> ) } const nextStatuses = getTicketNextStatuses(ticket.category_code, ticket.status) const defaultLinkedSolution = selectDefaultTicketSolution(linkedSolutions) const transitionDefaultResolution = targetStatus === 'resolved' || targetStatus === 'closed' ? ticket.resolution || defaultLinkedSolution?.solution.solution || '' : '' const transitionDefaultRemark = targetStatus === 'closed' && transitionDefaultResolution && !ticket.resolution ? '根据关联解决方案关闭工单' : '' return ( } actions={
{nextStatuses.map((status) => { const action = STATUS_ACTIONS[status] const Icon = action?.icon return ( ) })}
} />} > { refreshTicket() refreshEvents() }} /> } >

问题分类

{getTicketCategoryLabel(ticket.category_code)}

问题表现

{ticket.problem_performance || '未填写'}

问题描述

{ticket.description}

{ticket.resolution ? (

{ticket.resolution}

{ticket.is_resolved ? '已解决' : '未解决'} {ticket.replied_to_customer ? '已回复客户' : '未回复客户'}
) : ( )}
{transitionError ? : null}
) } function TicketOverviewCard({ ticket }: { ticket: Ticket }) { const items = [ { label: '问题分类', value: getTicketCategoryLabel(ticket.category_code) }, { label: '问题对象', value: ticket.problem_object || '未填写' }, { label: '提出人', value: ticket.submitter_name || displayUser(ticket.requester) }, { label: '提出机构', value: ticket.submitter_org || '未填写' }, { label: '请求人', value: displayUser(ticket.requester) }, { label: '处理人', value: displayUser(ticket.assignee) }, { label: '关联系统', value: ticket.related_system || '未填写' }, { label: '更新时间', value: formatDateTime(ticket.updated_at) } ] return ( 工单概览 提出方、责任人与业务上下文
{items.map((item) => (

{item.label}

{item.value}

))}
创建于 {formatDateTime(ticket.created_at)} {ticket.ticket_no}
) }