131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
'use client'
|
|
import { useState, useEffect } from 'react'
|
|
import { useParams, useRouter } from 'next/navigation'
|
|
import TicketDetail from '@/components/tickets/TicketDetail'
|
|
import ProcessForm from '@/components/tickets/ProcessForm'
|
|
import { ArrowLeft } from 'lucide-react'
|
|
|
|
interface HistoryTicket {
|
|
id: number
|
|
content: string | null
|
|
fault_category: string | null
|
|
current_status: string
|
|
assign_time: string | null
|
|
}
|
|
|
|
export default function TicketDetailPage() {
|
|
const params = useParams()
|
|
const router = useRouter()
|
|
const [data, setData] = useState<any>(null)
|
|
const [history, setHistory] = useState<HistoryTicket[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [showProcessForm, setShowProcessForm] = useState(false)
|
|
const [currentUser, setCurrentUser] = useState<{ id: number; display_name: string; role: string } | null>(null)
|
|
|
|
useEffect(() => {
|
|
fetch('/api/auth/me')
|
|
.then(r => r.json())
|
|
.then(u => { if (u.user) setCurrentUser(u.user) })
|
|
.catch(() => {})
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
const id = params.id as string
|
|
fetch(`/api/tickets/${id}`)
|
|
.then(r => r.json())
|
|
.then(d => {
|
|
if (d.ticket) {
|
|
setData(d)
|
|
const ip = d.ticket.device_ip
|
|
if (ip) {
|
|
fetch(`/api/tickets/by-asset?ip=${encodeURIComponent(ip)}`)
|
|
.then(r => r.json())
|
|
.then(h => {
|
|
const tickets: HistoryTicket[] = (h.tickets || [])
|
|
.filter((t: HistoryTicket) => t.id !== d.ticket.id)
|
|
.sort((a: HistoryTicket, b: HistoryTicket) => {
|
|
const ta = a.assign_time || ''
|
|
const tb = b.assign_time || ''
|
|
return tb.localeCompare(ta)
|
|
})
|
|
setHistory(tickets)
|
|
})
|
|
.catch(() => {})
|
|
}
|
|
}
|
|
})
|
|
.catch(() => {})
|
|
.finally(() => setLoading(false))
|
|
}, [params.id])
|
|
|
|
if (loading) return <div className="text-center py-12 text-slate-500 dark:text-slate-400">加载中...</div>
|
|
if (!data) return <div className="text-center py-12 text-slate-500 dark:text-slate-400">工单不存在</div>
|
|
|
|
const ticket = data.ticket
|
|
|
|
async function handleDelete() {
|
|
if (!confirm('确定要删除此工单吗?')) return
|
|
try {
|
|
const res = await fetch(`/api/tickets/${ticket.id}`, { method: 'DELETE' })
|
|
const data = await res.json()
|
|
if (res.ok) {
|
|
handleBack()
|
|
} else {
|
|
alert(data.error || '删除失败')
|
|
}
|
|
} catch (e) {
|
|
alert('删除失败:网络错误')
|
|
}
|
|
}
|
|
const isPending = (ticket.current_status === 'open' || ticket.current_status === 'in_progress') && !ticket.close_time
|
|
const isAdmin = currentUser?.role === 'admin'
|
|
const isCreator = currentUser?.id != null && currentUser.id === ticket.created_by
|
|
const canDelete = isAdmin || (isPending && isCreator)
|
|
const buttonLabel = ticket.current_status === 'in_progress' ? '继续处理' : '开始处理'
|
|
|
|
function handleBack() {
|
|
if (window.history.length > 1) {
|
|
router.back()
|
|
} else {
|
|
router.push(isPending ? '/tickets/pending' : '/tickets/completed')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<button onClick={handleBack} className="p-2 rounded-lg text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors">
|
|
<ArrowLeft size={20} />
|
|
</button>
|
|
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">工单详情</h1>
|
|
</div>
|
|
</div>
|
|
<TicketDetail
|
|
ticket={ticket}
|
|
steps={data.steps || []}
|
|
assetInfo={data.assetInfo || null}
|
|
history={history}
|
|
showEdit={isAdmin && !isPending}
|
|
showProcess={isPending && !showProcessForm}
|
|
processLabel={buttonLabel}
|
|
onProcess={() => setShowProcessForm(true)}
|
|
onDelete={handleDelete}
|
|
canDelete={canDelete}
|
|
/>
|
|
|
|
{showProcessForm && (
|
|
<ProcessForm
|
|
ticket={ticket}
|
|
currentUserDisplayName={currentUser?.display_name || ''}
|
|
onSuccess={() => {
|
|
router.push('/tickets/completed')
|
|
router.refresh()
|
|
}}
|
|
onCancel={() => setShowProcessForm(false)}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|