"use client" import { PlusIcon, RefreshCcwIcon } from "lucide-react" import { useCallback, useEffect, useState } from "react" import { toast } from "sonner" import { OptionCombobox } from "@/components/option-combobox" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { createTicket, fetchTicketSummary, fetchTickets, updateTicket, type CreateTicketPayload, type TicketItem, type TicketListQuery, type TicketStatus, type TicketSummary, type UpdateTicketPayload, } from "@/lib/api/ticket" import { formatDateTime } from "@/lib/utils" import { EditDialog } from "./_components/edit" import { TicketAssignDialog } from "./_components/ticket-assign-dialog" import { TicketStatusBadge } from "./_components/ticket-status-badge" import { TicketStatusDialog } from "./_components/ticket-status-dialog" const emptySummary: TicketSummary = { all: 0, pending: 0, inProgress: 0, done: 0, unassigned: 0, mine: 0, stale: 0, } const statusOptions = [ { value: "", label: "全部状态" }, { value: "pending", label: "待处理" }, { value: "in_progress", label: "处理中" }, { value: "done", label: "已处理" }, ] function isUpdatePayload(payload: CreateTicketPayload | UpdateTicketPayload): payload is UpdateTicketPayload { return "ticketId" in payload } export default function TicketsPage() { const [tickets, setTickets] = useState([]) const [summary, setSummary] = useState(emptySummary) const [keyword, setKeyword] = useState("") const [status, setStatus] = useState("") const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [editOpen, setEditOpen] = useState(false) const [editingId, setEditingId] = useState(null) const [assignTicketItem, setAssignTicketItem] = useState(null) const [statusTicketItem, setStatusTicketItem] = useState(null) const loadData = useCallback(async () => { setLoading(true) try { const query: TicketListQuery = { page: 1, limit: 50, keyword: keyword.trim() || undefined, status: status ? (status as TicketStatus) : undefined, } const [ticketData, summaryData] = await Promise.all([ fetchTickets(query), fetchTicketSummary(), ]) setTickets(Array.isArray(ticketData.results) ? ticketData.results : []) setSummary(summaryData ?? emptySummary) } catch (error) { toast.error(error instanceof Error ? error.message : "加载工单失败") } finally { setLoading(false) } }, [keyword, status]) useEffect(() => { void loadData() }, [loadData]) async function handleEditSubmit(payload: CreateTicketPayload | UpdateTicketPayload) { setSaving(true) try { if (isUpdatePayload(payload)) { await updateTicket(payload) toast.success("工单已更新") } else { await createTicket(payload) toast.success("工单已创建") } setEditOpen(false) setEditingId(null) await loadData() } catch (error) { toast.error(error instanceof Error ? error.message : "保存工单失败") } finally { setSaving(false) } } return (

工单

轻量工单列表临时入口

{[ ["全部", summary.all], ["待处理", summary.pending], ["处理中", summary.inProgress], ["已处理", summary.done], ["未分配", summary.unassigned], ["我的", summary.mine], ["超时未动", summary.stale], ].map(([label, value]) => (
{label}
{value}
))}
setKeyword(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") { void loadData() } }} />
工单
状态
处理人
操作
{tickets.length === 0 ? (
{loading ? "加载中..." : "暂无工单"}
) : ( tickets.map((ticket) => (
{ticket.title}
{ticket.ticketNo} {ticket.updatedAt ? formatDateTime(ticket.updatedAt) : "-"}
{ticket.currentAssigneeName || "未分配"}
)) )}
{ setEditOpen(open) if (!open) { setEditingId(null) } }} onSubmit={handleEditSubmit} /> { if (!open) { setAssignTicketItem(null) } }} onSuccess={loadData} /> { if (!open) { setStatusTicketItem(null) } }} onSuccess={loadData} />
) }