import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { DataTablePagination } from '@/components/ui/data-table-pagination'; import { DetailEmptyView } from '@/components/ui/detail-empty-view'; import { DropdownMenuActions } from '@/components/ui/dropdown-menu-actions'; import { QueryErrorView } from '@/components/ui/query-error-view'; import { Skeleton } from '@/components/ui/skeleton'; import { DELETE_TASKS, GET_TASKS, RETRY_TASKS, type TaskDto, } from '@/domains/recorder/schema/tasks'; import { type DeleteTasksMutation, type DeleteTasksMutationVariables, type GetTasksQuery, type RetryTasksMutation, type RetryTasksMutationVariables, SubscriberTaskStatusEnum, } from '@/infra/graphql/gql/graphql'; import type { RouteStateDataOption } from '@/infra/routes/traits'; import { useDebouncedSkeleton } from '@/presentation/hooks/use-debounded-skeleton'; import { useMutation, useQuery } from '@apollo/client'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { type ColumnDef, type PaginationState, type SortingState, type VisibilityState, getCoreRowModel, getPaginationRowModel, useReactTable, } from '@tanstack/react-table'; import { format } from 'date-fns'; import { RefreshCw } from 'lucide-react'; import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; import { apolloErrorToMessage, getApolloQueryError, } from '@/infra/errors/apollo'; import { useMemo, useState } from 'react'; import { toast } from 'sonner'; import { getStatusBadge } from './-status-badge'; export const Route = createFileRoute('/_app/tasks/manage')({ component: TaskManageRouteComponent, staticData: { breadcrumb: { label: 'Manage' }, } satisfies RouteStateDataOption, }); function TaskManageRouteComponent() { const navigate = useNavigate(); const [columnVisibility, setColumnVisibility] = useState({ lockAt: false, lockBy: false, attempts: false, }); const [sorting, setSorting] = useState([]); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); const { loading, error, data, refetch } = useQuery(GET_TASKS, { variables: { pagination: { page: { page: pagination.pageIndex, limit: pagination.pageSize, }, }, filters: {}, orderBy: { runAt: 'DESC', }, }, pollInterval: 5000, // Auto-refresh every 5 seconds }); const { showSkeleton } = useDebouncedSkeleton({ loading }); const tasks = data?.subscriberTasks; const [deleteTasks] = useMutation< DeleteTasksMutation, DeleteTasksMutationVariables >(DELETE_TASKS, { onCompleted: async () => { const refetchResult = await refetch(); const error = getApolloQueryError(refetchResult); if (error) { toast.error('Failed to delete tasks', { description: apolloErrorToMessage(error), }); return; } toast.success('Tasks deleted'); }, onError: (error) => { toast.error('Failed to delete tasks', { description: error.message, }); }, }); const [retryTasks] = useMutation< RetryTasksMutation, RetryTasksMutationVariables >(RETRY_TASKS, { onCompleted: () => { toast.success('Tasks retried'); }, onError: (error) => { toast.error('Failed to retry tasks', { description: error.message, }); }, }); const columns = useMemo(() => { const cs: ColumnDef[] = [ { header: 'ID', accessorKey: 'id', cell: ({ row }) => { return (
{row.original.id}
); }, }, ]; return cs; }, []); const table = useReactTable({ data: useMemo(() => (tasks?.nodes ?? []) as TaskDto[], [tasks]), columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), onPaginationChange: setPagination, onSortingChange: setSorting, onColumnVisibilityChange: setColumnVisibility, pageCount: tasks?.paginationInfo?.pages, rowCount: tasks?.paginationInfo?.total, enableColumnPinning: true, autoResetPageIndex: true, manualPagination: true, state: { pagination, sorting, columnVisibility, }, initialState: { columnPinning: { right: ['actions'], }, }, }); if (error) { return ; } return (

Tasks Management

Manage your tasks

{showSkeleton && Array.from(new Array(10)).map((_, index) => ( ))} {!showSkeleton && table.getRowModel().rows?.length > 0 ? ( table.getRowModel().rows.map((row, index) => { const task = row.original; return (
{/* Header with status and priority */}
# {task.id}
{task.taskType}
{getStatusBadge(task.status)} Priority: {task.priority}
{ navigate({ to: '/tasks/detail/$id', params: { id: task.id }, }); }} showDelete onDelete={() => deleteTasks({ variables: { filters: { id: { eq: task.id, }, }, }, }) } > {task.status === (SubscriberTaskStatusEnum.Killed || SubscriberTaskStatusEnum.Failed) && ( retryTasks({ variables: { filters: { id: { eq: task.id, }, }, }, }) } > Retry )}
{/* Time info */}
Run at: {format(new Date(task.runAt), 'MM/dd HH:mm')}
Done: {task.doneAt ? format(new Date(task.doneAt), 'MM/dd HH:mm') : '-'}
{/* Attempts */}
Attempts: {task.attempts} / {task.maxAttempts}
{/* Lock at */}
Lock at: {task.lockAt ? format(new Date(task.lockAt), 'MM/dd HH:mm') : '-'}
{task.job && (
Job:
)} {/* Error if exists */} {(task.status === SubscriberTaskStatusEnum.Failed || task.status === SubscriberTaskStatusEnum.Killed) && task.lastError && (
{task.lastError}
)}
); }) ) : ( )}
); }