oxios 1.5.2

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { Clock, Trash2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { type Column, DataTable } from '@/components/shared/data-table'
import { EmptyState } from '@/components/shared/empty-state'
import { ErrorState } from '@/components/shared/error-state'
import { LoadingTable } from '@/components/shared/loading'
import { RefreshButton } from '@/components/shared/refresh-button'
import { Button } from '@/components/ui/button'
import { api } from '@/lib/api-client'
import type { Session } from '@/types'

export const Route = createFileRoute('/sessions/')({
  component: SessionsListPage,
})

function SessionsListPage() {
  const { t } = useTranslation()
  const navigate = useNavigate()
  const queryClient = useQueryClient()

  const { data, isLoading, isError, refetch, isFetching } = useQuery({
    queryKey: ['sessions'],
    queryFn: () => api.get<{ items: Session[]; total: number }>('/api/sessions'),
    refetchInterval: 10000,
  })

  const deleteMutation = useMutation({
    mutationFn: (id: string) => api.delete(`/api/sessions/${id}`),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['sessions'] }),
  })

  if (isLoading) return <LoadingTable rows={5} />
  if (isError) return <ErrorState onRetry={() => refetch()} />

  const sessions = Array.isArray(data?.items) ? data.items : []

  const columns: Column<Session>[] = [
    {
      header: t('sessions.id'),
      mobilePriority: 'hidden',
      accessor: (row: Session) => (
        <span className="font-mono text-xs">{row.id.slice(0, 12)}...</span>
      ),
    },
    {
      header: t('sessions.title', 'Title'),
      mobilePriority: 'primary',
      accessor: (row: Session) => row.title ?? '—',
    },
    {
      header: t('sessions.agent'),
      mobilePriority: 'secondary',
      accessor: (row: Session) => (row.user_id ? `${row.user_id.slice(0, 8)}...` : '—'),
    },
    {
      header: t('sessions.messages'),
      mobilePriority: 'secondary',
      accessor: (row: Session) => row.message_count ?? 0,
    },
    {
      header: t('sessions.createdAt'),
      mobilePriority: 'hidden',
      accessor: (row: Session) => new Date(row.created_at).toLocaleString(),
    },
    {
      header: t('sessions.updatedAt'),
      mobilePriority: 'hidden',
      accessor: (row: Session) =>
        row.updated_at ? new Date(row.updated_at).toLocaleString() : '—',
    },
    {
      header: '',
      mobilePriority: 'hidden',
      accessor: (row: Session) => (
        <Button
          variant="ghost"
          size="icon"
          onClick={(e) => {
            e.stopPropagation()
            deleteMutation.mutate(row.id)
          }}
          aria-label={t('sessions.deleteSession')}
          disabled={deleteMutation.isPending}
        >
          <Trash2 className="h-4 w-4 text-destructive" />
        </Button>
      ),
    },
  ]

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold">{t('sessions.title')}</h1>
          <p className="text-muted-foreground">
            {t('sessions.registered', { count: data?.total ?? 0 })}
          </p>
        </div>
        <RefreshButton onClick={() => refetch()} isFetching={isFetching} />
      </div>

      {sessions.length === 0 ? (
        <EmptyState
          icon={<Clock className="h-10 w-10" />}
          title={t('sessions.noSessions')}
          description={t('sessions.noSessionsDescription')}
        />
      ) : (
        <DataTable
          columns={columns}
          data={sessions}
          keyExtractor={(row) => row.id}
          onRowClick={(row) =>
            navigate({ to: '/sessions/$sessionId', params: { sessionId: row.id } })
          }
        />
      )}
    </div>
  )
}