import { useState, useEffect, useCallback } from 'react'; import { MessageSquare, Search, Filter, Stethoscope, FileText, User, Clock, CheckCircle2, AlertCircle, XCircle, Send, RefreshCw, ChevronLeft, ChevronRight, ShieldCheck, Building2, Calendar, } from 'lucide-react'; import api from '../services/api'; import { toast } from 'react-hot-toast'; import Button from '../components/ui/Button'; import Modal from '../components/ui/Modal'; interface TicketMessage { id: string; senderId?: string; senderRole: 'USER' | 'ADMIN' | 'VET_DOCTOR'; senderName: string; message: string; attachment?: string; createdAt: string; } interface Ticket { id: string; ticketNumber: string; subject: string; category: 'VET_CONSULTATION' | 'ORDER_SUPPORT' | 'PRODUCT_INQUIRY' | 'GENERAL'; priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT'; status: 'OPEN' | 'IN_PROGRESS' | 'ANSWERED' | 'CLOSED'; createdAt: string; updatedAt: string; user: { id: string; firstName?: string; lastName?: string; mobile: string; }; pet?: { id: string; name: string; type: string; breed: string; }; messages: TicketMessage[]; } export default function Tickets() { const [tickets, setTickets] = useState([]); const [loading, setLoading] = useState(true); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [statusFilter, setStatusFilter] = useState('ALL'); const [categoryFilter, setCategoryFilter] = useState('ALL'); const [searchQuery, setSearchQuery] = useState(''); // Detail / Reply Modal const [selectedTicket, setSelectedTicket] = useState(null); const [fetchingDetail, setFetchingDetail] = useState(false); const [replyMessage, setReplyMessage] = useState(''); const [replyStatus, setReplyStatus] = useState('ANSWERED'); const [sendingReply, setSendingReply] = useState(false); const fetchTickets = useCallback(async () => { try { setLoading(true); const params: Record = { page, limit: 15 }; if (statusFilter !== 'ALL') params.status = statusFilter; if (categoryFilter !== 'ALL') params.category = categoryFilter; if (searchQuery.trim()) params.search = searchQuery.trim(); const res = await api.get('/tickets/admin/all', { params }); setTickets(res.data?.data || []); setTotal(res.data?.meta?.total || 0); } catch { toast.error('خطا در دریافت لیست تیکت‌ها'); } finally { setLoading(false); } }, [page, statusFilter, categoryFilter, searchQuery]); useEffect(() => { fetchTickets(); }, [fetchTickets]); const handleOpenTicket = async (ticketId: string) => { try { setFetchingDetail(true); const res = await api.get(`/tickets/${ticketId}`); setSelectedTicket(res.data?.data || null); setReplyStatus(res.data?.data?.status === 'OPEN' ? 'ANSWERED' : res.data?.data?.status || 'ANSWERED'); } catch { toast.error('خطا در بارگذاری جزئیات تیکت'); } finally { setFetchingDetail(false); } }; const handleSendReply = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedTicket || !replyMessage.trim()) return; try { setSendingReply(true); await api.post(`/tickets/${selectedTicket.id}/messages`, { message: replyMessage.trim(), status: replyStatus, }); toast.success('پاسخ با موفقیت برای کاربر ارسال شد'); setReplyMessage(''); // Reload current ticket handleOpenTicket(selectedTicket.id); fetchTickets(); } catch (err: any) { toast.error(err.response?.data?.message || 'خطا در ارسال پاسخ'); } finally { setSendingReply(false); } }; const handleUpdateStatus = async (status: string) => { if (!selectedTicket) return; try { await api.put(`/tickets/admin/${selectedTicket.id}/status`, { status }); toast.success('وضعیت تیکت تغییر یافت'); setSelectedTicket({ ...selectedTicket, status: status as any }); fetchTickets(); } catch { toast.error('خطا در تغییر وضعیت تیکت'); } }; const getCategoryBadge = (cat: string) => { switch (cat) { case 'VET_CONSULTATION': return { label: 'مشاوره دامپزشک', bg: 'bg-emerald-50 text-emerald-700 border-emerald-200', icon: Stethoscope }; case 'ORDER_SUPPORT': return { label: 'پشتیبانی سفارش', bg: 'bg-blue-50 text-blue-700 border-blue-200', icon: FileText }; case 'PRODUCT_INQUIRY': return { label: 'سوال محصولی / دارویی', bg: 'bg-purple-50 text-purple-700 border-purple-200', icon: Building2 }; default: return { label: 'عمومی', bg: 'bg-gray-50 text-gray-700 border-gray-200', icon: MessageSquare }; } }; const getStatusBadge = (status: string) => { switch (status) { case 'OPEN': return { label: 'در انتظار پاسخ', bg: 'bg-amber-50 text-amber-700 border-amber-200' }; case 'ANSWERED': return { label: 'پاسخ داده شد', bg: 'bg-emerald-50 text-emerald-700 border-emerald-200' }; case 'IN_PROGRESS': return { label: 'در حال بررسی', bg: 'bg-blue-50 text-blue-700 border-blue-200' }; case 'CLOSED': return { label: 'بسته شده', bg: 'bg-gray-100 text-gray-600 border-gray-200' }; default: return { label: status, bg: 'bg-gray-50 text-gray-600 border-gray-200' }; } }; return (
{/* Header */}

پشتیبانی، تیکتینگ و مشاوره دامپزشکی

مشاهده و پاسخ‌دهی به تیکت‌های پزشکی، مشاوره‌ای و پشتیبانی مشتریان کنینا

{/* Filters & Search */}
setSearchQuery(e.target.value)} className="w-full pl-4 pr-10 py-2.5 rounded-xl border border-gray-200 text-xs font-medium focus:border-canina-blue outline-none" />
{/* Tickets Table */}
{loading && tickets.length === 0 ? ( ) : tickets.length === 0 ? ( ) : ( tickets.map((t) => { const cat = getCategoryBadge(t.category); const st = getStatusBadge(t.status); const userName = `${t.user?.firstName || ''} ${t.user?.lastName || ''}`.trim() || 'بدون نام'; return ( ); }) )}
شماره تیکت کاربر / صاحب پت پت مرتبط موضوع و دسته‌بندی وضعیت آخرین بروزرسانی عملیات
در حال دریافت تیکت‌ها...
تیکتی مطابق با فیلترهای انتخابی یافت نشد.
{t.ticketNumber}
{userName}
{t.user?.mobile}
{t.pet ? ( 🐾 {t.pet.name} ({t.pet.breed || t.pet.type}) ) : ( - )}
{t.subject}
{cat.label}
{st.label} {new Date(t.updatedAt).toLocaleDateString('fa-IR')}
{/* Pagination */}
تعداد کل تیکت‌ها: {total}
صفحه {page}
{/* Ticket Details & Admin Reply Modal */} {selectedTicket && ( setSelectedTicket(null)} title={`تیکت ${selectedTicket.ticketNumber}: ${selectedTicket.subject}`} icon={MessageSquare} maxWidth="3xl" footer={
} >
{/* Header meta */}
کاربر: {selectedTicket.user?.firstName} {selectedTicket.user?.lastName} ({selectedTicket.user?.mobile}) {selectedTicket.pet && ( 🐾 {selectedTicket.pet.name} ({selectedTicket.pet.breed || selectedTicket.pet.type}) )}
تغییر وضعیت:
{/* Messages Chat Scroll */}
{(selectedTicket.messages || []).map((msg) => { const isAdminOrVet = msg.senderRole === 'ADMIN' || msg.senderRole === 'VET_DOCTOR'; return (
{isAdminOrVet ? ( <> {msg.senderName} (تیم پزشکی / ادمین) ) : ( <> {msg.senderName} (مشتری) )} {new Date(msg.createdAt).toLocaleString('fa-IR')}

{msg.message}

); })}
{/* Admin Response Composer */}
تغییر وضعیت به: