"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { PlusIcon, RefreshCwIcon, SearchIcon, Trash2Icon } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { Controller, useForm, type Resolver } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod/v4"; import { ListPagination } from "@/components/list-pagination"; import { OptionCombobox } from "@/components/option-combobox"; import { ProjectDialog } from "@/components/project-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Field, FieldContent, FieldError, FieldLabel, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { createTicketResolutionCode, deleteTicketResolutionCode, fetchTicketResolutionCodes, updateTicketResolutionCode, type CreateTicketResolutionCodePayload, type PageResult, type TicketResolutionCode, } from "@/lib/api/ticket-config"; import { getEnumLabel, getEnumOptions } from "@/lib/enums"; import { Status, StatusLabels } from "@/lib/generated/enums"; const listStatusOptions = [ { value: "all", label: "全部状态" }, ...getEnumOptions(StatusLabels) .filter((item) => Number(item.value) !== Status.Deleted) .map((item) => ({ value: String(item.value), label: item.label })), ] as const; const formSchema = z.object({ name: z.string().trim().min(1, "解决码名称不能为空"), code: z.string().trim().min(1, "解决码编码不能为空"), sortNo: z .string() .trim() .min(1, "排序不能为空") .regex(/^\d+$/, "排序值必须是大于等于 0 的整数"), status: z.enum([String(Status.Ok), String(Status.Disabled)], { message: "请选择状态", }), remark: z.string().trim(), }); type EditForm = z.infer; const resolver = zodResolver(formSchema as never) as Resolver< z.input, undefined, z.output >; const emptyForm: EditForm = { name: "", code: "", sortNo: "0", status: String(Status.Ok), remark: "", }; function buildForm(item: TicketResolutionCode | null): EditForm { if (!item) { return emptyForm; } return { name: item.name, code: item.code, sortNo: String(item.sortNo), status: String(item.status) as EditForm["status"], remark: item.remark || "", }; } function buildPayload(form: EditForm): CreateTicketResolutionCodePayload { return { name: form.name.trim(), code: form.code.trim(), sortNo: Number(form.sortNo), status: Number(form.status), remark: form.remark.trim(), }; } export default function TicketResolutionCodesPage() { const [keywordInput, setKeywordInput] = useState(""); const [statusFilterInput, setStatusFilterInput] = useState("all"); const [keyword, setKeyword] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [page, setPage] = useState(1); const [limit, setLimit] = useState(20); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [editingItem, setEditingItem] = useState( null, ); const [result, setResult] = useState>({ results: [], page: { page: 1, limit: 20, total: 0 }, }); const loadData = useCallback(async () => { setLoading(true); try { const data = await fetchTicketResolutionCodes({ name: keyword.trim() || undefined, status: statusFilter === "all" ? undefined : statusFilter, page, limit, }); setResult(data); } catch (error) { toast.error(error instanceof Error ? error.message : "加载解决码失败"); } finally { setLoading(false); } }, [keyword, statusFilter, page, limit]); useEffect(() => { void loadData(); }, [loadData]); function applyFilters() { setKeyword(keywordInput); setStatusFilter(statusFilterInput); setPage(1); } async function handleSubmit(payload: CreateTicketResolutionCodePayload) { if (saving) { return; } setSaving(true); try { if (editingItem) { await updateTicketResolutionCode({ id: editingItem.id, ...payload }); toast.success(`已更新解决码:${payload.name}`); } else { await createTicketResolutionCode(payload); toast.success(`已创建解决码:${payload.name}`); } setDialogOpen(false); setEditingItem(null); await loadData(); } catch (error) { toast.error(error instanceof Error ? error.message : "保存解决码失败"); } finally { setSaving(false); } } async function handleDelete(item: TicketResolutionCode) { try { await deleteTicketResolutionCode(item.id); toast.success(`已删除解决码:${item.name}`); await loadData(); } catch (error) { toast.error(error instanceof Error ? error.message : "删除解决码失败"); } } return ( <>
setKeywordInput(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); applyFilters(); } }} placeholder="按解决码名称筛选" className="pl-9" />
({ value: item.value, label: item.label, }))} />
{loading ? ( ) : result.results.length > 0 ? ( result.results.map((item) => ( )) ) : ( )}
名称 编码 状态 排序 操作
加载中...
{item.name} {item.code} {getEnumLabel(StatusLabels, item.status as Status)} {item.sortNo} } > 操作 { setEditingItem(item); setDialogOpen(true); }} > 编辑 void handleDelete(item)} > 删除
暂无解决码
{ setLimit(value); setPage(1); }} />
{ if (!saving) { setDialogOpen(open); if (!open) { setEditingItem(null); } } }} onSubmit={handleSubmit} /> ); } type TicketResolutionCodeEditDialogProps = { open: boolean; saving: boolean; item: TicketResolutionCode | null; onOpenChange: (open: boolean) => void; onSubmit: (payload: CreateTicketResolutionCodePayload) => Promise; }; function TicketResolutionCodeEditDialog({ open, saving, item, onOpenChange, onSubmit, }: TicketResolutionCodeEditDialogProps) { const formId = "ticket-resolution-code-edit-form"; const form = useForm< z.input, undefined, z.output >({ resolver, defaultValues: emptyForm, }); const { control, register, handleSubmit, reset, formState: { errors }, } = form; useEffect(() => { reset(buildForm(item)); }, [item, reset]); return ( } >
onSubmit(buildPayload(values)), )} className="space-y-4" > 解决码名称 解决码编码
排序 状态 ( item.value !== "all") .map((item) => ({ value: item.value, label: item.label, }))} /> )} />
备注