feat: add category-driven ticket workflows

This commit is contained in:
hailin
2026-07-03 16:17:32 +08:00
parent 6586889733
commit 7692374067
16 changed files with 777 additions and 87 deletions

View File

@@ -10,6 +10,7 @@ import {
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
import { Switch } from '@/components/ui/switch'
import { Textarea } from '@/components/ui/textarea'
import type { TicketStatus } from './types'
@@ -26,7 +27,15 @@ type TicketTransitionDialogProps = {
status: TicketStatus | null
actionLabel?: string
onOpenChange: (open: boolean) => void
onSubmit: (status: TicketStatus, remark: string) => Promise<void>
onSubmit: (
status: TicketStatus,
values: {
remark: string
resolution: string
is_resolved: boolean
replied_to_customer: boolean
}
) => Promise<void>
}
export function TicketTransitionDialog({
@@ -37,22 +46,33 @@ export function TicketTransitionDialog({
onSubmit
}: TicketTransitionDialogProps) {
const [remark, setRemark] = React.useState('')
const [resolution, setResolution] = React.useState('')
const [repliedToCustomer, setRepliedToCustomer] = React.useState(true)
const [submitting, setSubmitting] = React.useState(false)
const actionLabel = customActionLabel ?? (status ? STATUS_ACTION_LABELS[status] ?? '调整状态' : '调整状态')
const isResolveAction = status === 'resolved'
const canSubmit = isResolveAction ? Boolean((resolution || remark).trim()) : Boolean(remark.trim())
React.useEffect(() => {
if (!open) {
setRemark('')
setResolution('')
setRepliedToCustomer(true)
setSubmitting(false)
}
}, [open])
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
if (!status || !remark.trim()) return
if (!status || !canSubmit) return
setSubmitting(true)
try {
await onSubmit(status, remark.trim())
await onSubmit(status, {
remark: remark.trim(),
resolution: resolution.trim(),
is_resolved: status === 'resolved',
replied_to_customer: repliedToCustomer
})
onOpenChange(false)
} finally {
setSubmitting(false)
@@ -65,23 +85,46 @@ export function TicketTransitionDialog({
<form className="grid gap-4" onSubmit={handleSubmit}>
<DialogHeader>
<DialogTitle>{actionLabel}</DialogTitle>
<DialogDescription>请说明本次流转的原因、处理动作或待确认事项。</DialogDescription>
<DialogDescription>
{isResolveAction ? '填写本次处理结论,系统会同步记录到工单详情和流转时间线。' : '请说明本次流转的原因、处理动作或待确认事项。'}
</DialogDescription>
</DialogHeader>
<AppField label="处理说明" htmlFor="transition-remark" required>
{isResolveAction ? (
<AppField label="解决方案" htmlFor="transition-resolution" required>
<Textarea
id="transition-resolution"
required
minLength={2}
placeholder="例如:已查询采集进度,当前已完成 80%,预计今日完成。"
value={resolution}
onChange={(event) => setResolution(event.target.value)}
/>
</AppField>
) : null}
<AppField label={isResolveAction ? '处理说明' : '处理说明'} htmlFor="transition-remark" required={!isResolveAction}>
<Textarea
id="transition-remark"
required
required={!isResolveAction}
minLength={2}
placeholder="例如:已完成日志排查,定位为配置变更导致,提交业务侧确认。"
placeholder={isResolveAction ? '可补充内部排查说明。' : '例如:已完成日志排查,定位为配置变更导致,提交业务侧确认。'}
value={remark}
onChange={(event) => setRemark(event.target.value)}
/>
</AppField>
{isResolveAction ? (
<div className="flex items-center justify-between rounded-md border border-border p-3">
<div className="grid gap-1">
<span className="text-sm font-medium text-foreground">已回复客户</span>
<span className="text-xs text-muted-foreground">开启后会在工单结果中记录为已回复客户。</span>
</div>
<Switch checked={repliedToCustomer} onCheckedChange={setRepliedToCustomer} />
</div>
) : null}
<DialogFooter>
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
取消
</Button>
<Button type="submit" disabled={!remark.trim() || submitting}>
<Button type="submit" disabled={!canSubmit || submitting}>
{submitting ? '提交中...' : '确认提交'}
</Button>
</DialogFooter>