TTeamDesk 学习手册

src/features/tickets/ticket-events.tsx

此代码来自构建时的仓库快照。修改源码后重新构建即可更新。

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>
  );
}

搜索教程

输入关键词,搜索全部章节。

阅读设置

外观
正文字号

从一张工单出发,读懂数据如何流转。

设置与进度仅保存在当前浏览器。