Files
ai-ticket-platform/frontend/src/features/tickets/TicketTransitionDialog.tsx
2026-07-03 16:17:32 +08:00

136 lines
4.7 KiB
TypeScript

import * as React from 'react'
import { AppField } from '@/components/app/AppField'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
import { Switch } from '@/components/ui/switch'
import { Textarea } from '@/components/ui/textarea'
import type { TicketStatus } from './types'
const STATUS_ACTION_LABELS: Partial<Record<TicketStatus, string>> = {
in_progress: '开始处理',
pending_confirm: '提交确认',
resolved: '标记解决',
closed: '关闭工单'
}
type TicketTransitionDialogProps = {
open: boolean
status: TicketStatus | null
actionLabel?: string
onOpenChange: (open: boolean) => void
onSubmit: (
status: TicketStatus,
values: {
remark: string
resolution: string
is_resolved: boolean
replied_to_customer: boolean
}
) => Promise<void>
}
export function TicketTransitionDialog({
open,
status,
actionLabel: customActionLabel,
onOpenChange,
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 || !canSubmit) return
setSubmitting(true)
try {
await onSubmit(status, {
remark: remark.trim(),
resolution: resolution.trim(),
is_resolved: status === 'resolved',
replied_to_customer: repliedToCustomer
})
onOpenChange(false)
} finally {
setSubmitting(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-lg">
<form className="grid gap-4" onSubmit={handleSubmit}>
<DialogHeader>
<DialogTitle>{actionLabel}</DialogTitle>
<DialogDescription>
{isResolveAction ? '填写本次处理结论,系统会同步记录到工单详情和流转时间线。' : '请说明本次流转的原因、处理动作或待确认事项。'}
</DialogDescription>
</DialogHeader>
{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={!isResolveAction}
minLength={2}
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={!canSubmit || submitting}>
{submitting ? '提交中...' : '确认提交'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}