import { useState, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { adminApi, AdminTicket, AdminTicketDetail, AdminTicketMessage } from '../api/admin'; import { ticketsApi } from '../api/tickets'; const BackIcon = () => ( ); function AdminMessageMedia({ message, t, }: { message: AdminTicketMessage; t: (key: string) => string; }) { const [imageLoaded, setImageLoaded] = useState(false); const [imageError, setImageError] = useState(false); const [showFullImage, setShowFullImage] = useState(false); if (!message.has_media || !message.media_file_id) { return null; } const mediaUrl = ticketsApi.getMediaUrl(message.media_file_id); if (message.media_type === 'photo') { return (
{!imageLoaded && !imageError && (
)} {imageError ? (
{t('support.imageLoadFailed')}
) : ( {message.media_caption setImageLoaded(true)} onError={() => setImageError(true)} onClick={() => setShowFullImage(true)} /> )} {message.media_caption && (

{message.media_caption}

)} {showFullImage && (
setShowFullImage(false)} > {message.media_caption
)}
); } return (
{message.media_caption || `Download ${message.media_type}`}
); } export default function AdminTickets() { const { t } = useTranslation(); const queryClient = useQueryClient(); const [selectedTicketId, setSelectedTicketId] = useState(null); const [statusFilter, setStatusFilter] = useState(''); const [replyText, setReplyText] = useState(''); const [page, setPage] = useState(1); const [showSettings, setShowSettings] = useState(false); const { data: stats } = useQuery({ queryKey: ['admin-ticket-stats'], queryFn: adminApi.getTicketStats, }); const { data: ticketsData, isLoading: ticketsLoading } = useQuery({ queryKey: ['admin-tickets', page, statusFilter], queryFn: () => adminApi.getTickets({ page, per_page: 20, status: statusFilter || undefined, }), }); const { data: selectedTicket, isLoading: ticketLoading } = useQuery({ queryKey: ['admin-ticket', selectedTicketId], queryFn: () => adminApi.getTicket(selectedTicketId!), enabled: !!selectedTicketId, }); const replyMutation = useMutation({ mutationFn: ({ ticketId, message }: { ticketId: number; message: string }) => adminApi.replyToTicket(ticketId, message), onSuccess: () => { setReplyText(''); queryClient.invalidateQueries({ queryKey: ['admin-ticket', selectedTicketId] }); queryClient.invalidateQueries({ queryKey: ['admin-tickets'] }); queryClient.invalidateQueries({ queryKey: ['admin-ticket-stats'] }); }, }); const statusMutation = useMutation({ mutationFn: ({ ticketId, status }: { ticketId: number; status: string }) => adminApi.updateTicketStatus(ticketId, status), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-ticket', selectedTicketId] }); queryClient.invalidateQueries({ queryKey: ['admin-tickets'] }); queryClient.invalidateQueries({ queryKey: ['admin-ticket-stats'] }); }, }); const handleReply = (e: React.FormEvent) => { e.preventDefault(); if (!selectedTicketId || !replyText.trim()) return; replyMutation.mutate({ ticketId: selectedTicketId, message: replyText }); }; const getStatusBadge = (status: string) => { switch (status) { case 'open': return 'badge-info'; case 'pending': return 'badge-warning'; case 'answered': return 'badge-success'; case 'closed': return 'badge-neutral'; default: return 'badge-neutral'; } }; const getPriorityBadge = (priority: string) => { switch (priority) { case 'urgent': return 'badge-error'; case 'high': return 'badge-warning'; default: return 'badge-neutral'; } }; const formatUser = (ticket: AdminTicket | AdminTicketDetail) => { if (!ticket.user) return 'Unknown'; const { first_name, last_name, username } = ticket.user; if (first_name || last_name) return `${first_name || ''} ${last_name || ''}`.trim(); if (username) return `@${username}`; return 'User'; }; const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text).catch(() => { const textarea = document.createElement('textarea'); textarea.value = text; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); }); }; return (

{t('admin.tickets.title')}

{/* Stats */} {stats && (
{stats.total}
{t('admin.tickets.total')}
{stats.open}
{t('admin.tickets.statusOpen')}
{stats.pending}
{t('admin.tickets.statusPending')}
{stats.answered}
{t('admin.tickets.statusAnswered')}
{stats.closed}
{t('admin.tickets.statusClosed')}
)}
{/* Ticket List */}

{t('admin.tickets.list')}

{ticketsLoading ? (
) : ticketsData?.items.length === 0 ? (
{t('admin.tickets.noTickets')}
) : (
{ticketsData?.items.map((ticket) => ( )}{' '} | {new Date(ticket.updated_at).toLocaleDateString()}
{ticket.last_message && (
{ticket.last_message.is_from_admin ? t('admin.tickets.you') : t('admin.tickets.user')} : {ticket.last_message.message_text.substring(0, 50)}...
)} ))}
)} {ticketsData && ticketsData.pages > 1 && (
{page} / {ticketsData.pages}
)}
{/* Ticket Detail */}
{!selectedTicketId ? (
{t('admin.tickets.selectTicket')}
) : ticketLoading ? (
) : selectedTicket ? (
{/* Header */}

#{selectedTicket.id} {selectedTicket.title}

{t( `admin.tickets.status${selectedTicket.status.charAt(0).toUpperCase() + selectedTicket.status.slice(1)}`, )} {selectedTicket.priority}
{t('admin.tickets.from')}: {formatUser(selectedTicket)} {selectedTicket.user?.telegram_id && ( )}{' '} | {t('admin.tickets.created')}:{' '} {new Date(selectedTicket.created_at).toLocaleString()}
{['open', 'pending', 'answered', 'closed'].map((s) => ( ))}
{/* Messages */}
{selectedTicket.messages.map((msg) => (
{msg.is_from_admin ? t('admin.tickets.adminLabel') : t('admin.tickets.userLabel')} {new Date(msg.created_at).toLocaleString()}

{msg.message_text}

))}
{/* Reply form */} {selectedTicket.status !== 'closed' && (