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' && (