src/features/tickets/ticket-events.tsx
此代码来自构建时的仓库快照。修改源码后重新构建即可更新。
'use client';
import { useState } from 'react';
import { Alert, Button, Card, Empty, Pagination, Spin, Tag } from 'antd';
import { useQuery } from '@tanstack/react-query';
import dayjs from 'dayjs';
import { useTRPC } from '@/trpc/client';
import { useTeam } from '@/features/teams/team-workspace';
import { statusLabels } from '@/shared/ticket';
import { roleLabels } from '@/shared/team';
const labels = {
REMINDER_SCHEDULED: '设置超时提醒',
TIMEOUT_REMINDED: '发送超时提醒',
CREATED: '创建工单',
STATUS_CHANGED: '变更状态',
ASSIGNED: '分配负责人',
UNASSIGNED: '取消分配',
REPLIED: '发送回复',
ASSIGNEE_RELEASED: '自动解除分配',
HISTORY_STARTED: '历史状态快照',
};
export function TicketEvents({ id }: { id: string }) {
const team = useTeam();
const trpc = useTRPC();
const [page, setPage] = useState(1);
const events = useQuery(
trpc.ticket.events.queryOptions(
{ id, teamId: team.id, page, pageSize: 10 },
{ refetchInterval: 15000 },
),
);
return (
<Card
title="操作记录"
extra={
<Button
loading={events.isFetching}
onClick={() => void events.refetch()}
>
刷新记录
</Button>
}
>
<p className="mb-4 text-xs text-slate-500">
按操作版本倒序排列,历史快照之前的操作未追溯。
</p>
{events.isPending && <Spin />}
{events.isError && (
<Alert
type="error"
title={events.error.message}
action={<Button onClick={() => void events.refetch()}>重试</Button>}
/>
)}
{!events.isPending && !events.isError && !events.data?.items.length && (
<Empty description="暂无操作记录" />
)}
<ol className="space-y-4">
{events.data?.items.map((event) => (
<li key={event.id} className="rounded-lg border border-slate-200 p-4">
<div className="mb-2 flex flex-wrap items-center gap-2">
<strong>{event.actorName}</strong>
{event.actorRole && <Tag>{roleLabels[event.actorRole]}</Tag>}
<span>{labels[event.kind]}</span>
<span className="text-xs text-slate-500">
{dayjs(event.createdAt).format('YYYY-MM-DD HH:mm:ss')} · v
{event.version}
</span>
</div>
{(event.fromStatus || event.toStatus) && (
<p className="text-sm">
{event.fromStatus
? `${statusLabels[event.fromStatus]} → `
: '状态:'}
{event.toStatus ? statusLabels[event.toStatus] : ''}
</p>
)}
{['ASSIGNED', 'UNASSIGNED', 'ASSIGNEE_RELEASED'].includes(
event.kind,
) && (
<p className="text-sm">
负责人:{event.fromAssigneeName ?? '未分配'} →{' '}
{event.toAssigneeName ?? '未分配'}
</p>
)}
{event.kind === 'HISTORY_STARTED' && event.toAssigneeName && (
<p className="text-sm">负责人:{event.toAssigneeName}</p>
)}
{event.reason && (
<p className="mt-2 text-sm break-words whitespace-pre-wrap">
说明:{event.reason}
</p>
)}
</li>
))}
</ol>
{!!events.data?.total && (
<Pagination
className="mt-5"
current={page}
pageSize={10}
total={events.data.total}
showSizeChanger={false}
onChange={setPage}
/>
)}
</Card>
);
}