import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Plus, User, Clock, AlertCircle, CheckCircle, XCircle, Circle, Loader2 } from 'lucide-react'; import { useTickets } from '../hooks/useTickets'; import { useTicketWebSocket } from '../hooks/useTicketWebSocket'; import { Ticket, TicketStatus } from '../types'; import TicketModal from '../components/TicketModal'; import { useCurrentUser } from '../hooks/useAuth'; const TicketStatusBadge: React.FC<{ status: Ticket['status'] }> = ({ status }) => { const { t } = useTranslation(); const statusConfig = { OPEN: { color: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300', icon: Circle, label: t('tickets.status.open', 'Open'), }, IN_PROGRESS: { color: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300', icon: Loader2, label: t('tickets.status.in_progress', 'In Progress'), }, RESOLVED: { color: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300', icon: CheckCircle, label: t('tickets.status.resolved', 'Resolved'), }, CLOSED: { color: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300', icon: XCircle, label: t('tickets.status.closed', 'Closed'), }, }; const config = statusConfig[status] || statusConfig.OPEN; const Icon = config.icon; return ( {config.label} ); }; const TicketPriorityBadge: React.FC<{ priority: Ticket['priority'] }> = ({ priority }) => { const { t } = useTranslation(); const priorityConfig = { LOW: { color: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300', label: t('tickets.priority.low', 'Low'), }, MEDIUM: { color: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300', label: t('tickets.priority.medium', 'Medium'), }, HIGH: { color: 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300', label: t('tickets.priority.high', 'High'), }, URGENT: { color: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300', label: t('tickets.priority.urgent', 'Urgent'), }, }; const config = priorityConfig[priority] || priorityConfig.MEDIUM; return ( {config.label} ); }; const Tickets: React.FC = () => { const { t } = useTranslation(); const { data: currentUser } = useCurrentUser(); const [statusFilter, setStatusFilter] = useState('ALL'); const [isTicketModalOpen, setIsTicketModalOpen] = useState(false); const [selectedTicket, setSelectedTicket] = useState(null); // Enable real-time ticket updates via WebSocket useTicketWebSocket({ showToasts: true }); // Fetch all tickets (backend will filter based on user role) const { data: tickets = [], isLoading, error } = useTickets(); // Filter tickets by status const filteredTickets = useMemo(() => { if (statusFilter === 'ALL') return tickets; return tickets.filter(ticket => ticket.status === statusFilter); }, [tickets, statusFilter]); // Count tickets by status for tabs const statusCounts = useMemo(() => { const counts = { ALL: tickets.length, OPEN: 0, IN_PROGRESS: 0, RESOLVED: 0, CLOSED: 0, }; tickets.forEach(ticket => { counts[ticket.status]++; }); return counts; }, [tickets]); const openTicketModal = (ticket: Ticket | null = null) => { setSelectedTicket(ticket); setIsTicketModalOpen(true); }; const closeTicketModal = () => { setSelectedTicket(null); setIsTicketModalOpen(false); }; const isOwnerOrManager = currentUser?.role === 'owner' || currentUser?.role === 'manager'; if (isLoading) { return (
); } if (error) { return (

{t('tickets.errorLoading', 'Error loading tickets')}: {(error as Error).message}

); } const tabs: Array<{ id: TicketStatus | 'ALL'; label: string }> = [ { id: 'ALL', label: t('tickets.tabs.all', 'All') }, { id: 'OPEN', label: t('tickets.tabs.open', 'Open') }, { id: 'IN_PROGRESS', label: t('tickets.tabs.inProgress', 'In Progress') }, { id: 'RESOLVED', label: t('tickets.tabs.resolved', 'Resolved') }, { id: 'CLOSED', label: t('tickets.tabs.closed', 'Closed') }, ]; return (
{/* Header */}

{t('tickets.title', 'Support Tickets')}

{isOwnerOrManager ? t('tickets.descriptionOwner', 'Manage support tickets for your business') : t('tickets.descriptionStaff', 'View and create support tickets')}

{/* Status Filter Tabs */}
{/* Tickets Grid */}
{filteredTickets.length === 0 ? (

{statusFilter === 'ALL' ? t('tickets.noTicketsFound', 'No tickets found') : t('tickets.noTicketsInStatus', `No ${statusFilter.toLowerCase().replace('_', ' ')} tickets`)}

) : (
{filteredTickets.map(ticket => (
openTicketModal(ticket)} className="bg-white dark:bg-gray-700/50 border border-gray-200 dark:border-gray-600 rounded-lg p-4 hover:border-brand-300 dark:hover:border-brand-600 hover:shadow-md transition-all cursor-pointer group" style={{ borderLeft: ticket.source_email_address ? `4px solid ${ticket.source_email_address.color}` : undefined }} >

{ticket.subject}

{ticket.description}

{ticket.category && ( {ticket.category} )} {ticket.source_email_address && ( {ticket.source_email_address.display_name} )} {ticket.creatorFullName || ticket.creatorEmail} {new Date(ticket.createdAt).toLocaleDateString()}
{ticket.assigneeFullName ? (
{t('tickets.assignedTo', 'Assigned to')}
{ticket.assigneeFullName}
) : ( {t('tickets.unassigned', 'Unassigned')} )}
))}
)}
{/* Ticket Create/Detail Modal */} {isTicketModalOpen && ( )}
); }; export default Tickets;