import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import type { TFunction } from 'i18next'; import { ColumnDef } from '@tanstack/react-table'; import { Info, Trash2 } from 'lucide-react'; import { Link, useNavigate } from 'react-router-dom'; import { useTickets, useDeleteTicket, useTicketStats } from '@/hooks/useTickets'; import { useExploreView } from '@/hooks/useExploreView'; import { useAdmin } from '@/hooks/useAdmins'; import { Ticket, TicketListParams } from '@/types/api'; import { getSavedPageSize } from '@/utils/tablePagination'; import { formatStatusLabel } from '@/utils/statusLabels'; import { ExploreListShell } from '@/components/explore/ExploreListShell'; import { ExploreDataView } from '@/components/explore/ExploreDataView'; import { RowActionsMenu, type RowAction } from '@/components/explore/RowActionsMenu'; import type { DenseRowModel } from '@/components/explore/DenseRowList'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Skeleton } from '@/components/ui/skeleton'; const TABLE_KEY = 'tickets'; const isBadValue = (val: unknown) => val === '-' || val === 'undefined' || val === '' || val === null || val === undefined; const ticketStatusVariant = (status: string) => { if (status === 'open') return 'destructive' as const; if (status === 'in_progress') return 'default' as const; if (status === 'resolved') return 'secondary' as const; return 'outline' as const; }; function ticketActions( handlers: { onOpen: () => void; onDelete: () => void }, t: TFunction ): RowAction[] { return [ { label: t('common.open'), icon: , onClick: handlers.onOpen }, { label: t('common.delete'), icon: , onClick: handlers.onDelete, destructive: true, separatorBefore: true, }, ]; } const AssignedCell: React.FC<{ adminId: string | null }> = ({ adminId }) => { const { data: admin, isLoading: loading } = useAdmin(adminId || ''); if (!adminId || adminId === '-' || adminId === 'undefined') return -; if (loading) return ; if (!admin) return {adminId}; const name = !isBadValue(admin.nickname) ? admin.nickname : admin.email; return {!isBadValue(name) ? name : admin.id}; }; const TicketListPage: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); const { viewMode, setViewMode } = useExploreView(); const [params, setParams] = useState({ limit: getSavedPageSize(TABLE_KEY), offset: 0, sort: 'last_seen', order: 'desc', }); const { data, isLoading } = useTickets(params); const deleteTicket = useDeleteTicket(); const { data: stats } = useTicketStats(); const [deleteConfirm, setDeleteConfirm] = useState<{ open: boolean; ticketId: string | null }>({ open: false, ticketId: null, }); const handleDelete = (id: string) => setDeleteConfirm({ open: true, ticketId: id }); const confirmDelete = () => { if (!deleteConfirm.ticketId) return; deleteTicket.mutate(deleteConfirm.ticketId, { onSuccess: () => setDeleteConfirm({ open: false, ticketId: null }), }); }; const exploreStats = useMemo(() => { if (!stats) return []; return [ { label: t('common.total'), value: stats.total_tickets }, { label: formatStatusLabel('open'), value: stats.open }, { label: formatStatusLabel('in_progress'), value: stats.in_progress }, { label: formatStatusLabel('resolved'), value: stats.resolved }, ]; }, [stats, t]); const columns = useMemo[]>( () => [ { accessorKey: 'error_message', header: t('common.error'), enableSorting: true, cell: ({ getValue }) => { const text = getValue(); return isBadValue(text) ? '-' : text; }, }, { accessorKey: 'status', header: t('common.status'), size: 100, enableSorting: true, cell: ({ getValue }) => { const status = getValue(); return {formatStatusLabel(status)}; }, }, { accessorKey: 'assigned_to', header: t('common.assigned'), enableSorting: false, cell: ({ getValue }) => ()} />, }, { accessorKey: 'count', header: t('common.count'), size: 80, enableSorting: true }, { accessorKey: 'first_seen', header: t('common.firstSeen'), enableSorting: true }, { accessorKey: 'last_seen', header: t('common.lastSeen'), enableSorting: true }, { id: 'actions', header: '', size: 48, enableSorting: false, cell: ({ row }) => { const record = row.original; return ( navigate(`/tickets/${record.id}`), onDelete: () => handleDelete(record.id), }, t)} /> ); }, }, ], [navigate, t] ); const denseRows = useMemo(() => { return (data?.data ?? []).map((record) => { const errorText = isBadValue(record.error_message) ? record.id : record.error_message; const title = typeof errorText === 'string' && errorText.length > 80 ? `${errorText.slice(0, 80)}…` : errorText; return { id: record.id, title, subtitle: record.assigned_to && record.assigned_to !== '-' ? record.assigned_to : undefined, badges: ( {formatStatusLabel(record.status)} ), meta: `×${record.count} · ${record.last_seen}`, onActivate: () => navigate(`/tickets/${record.id}`), actions: ticketActions({ onOpen: () => navigate(`/tickets/${record.id}`), onDelete: () => handleDelete(record.id), }, t), }; }); }, [data?.data, navigate, t]); return ( <> !open && setDeleteConfirm({ open: false, ticketId: null })}> {t('tickets.deleteTitle')} {t('common.irreversible')} setDeleteConfirm({ open: false, ticketId: null })}> {t('common.cancel')} {t('common.delete')} > ); }; export default TicketListPage;
{t('common.irreversible')}