oxios 1.5.2

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { createFileRoute } from '@tanstack/react-router'
import { Package, Plus } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { CreateProjectDialog } from '@/components/project/create-project-dialog'
import { DeleteProjectDialog } from '@/components/project/delete-project-dialog'
import { EditProjectDialog } from '@/components/project/edit-project-dialog'
import { ProjectCard } from '@/components/project/project-card'
import { EmptyState } from '@/components/shared/empty-state'
import { ErrorState } from '@/components/shared/error-state'
import { LoadingCards } from '@/components/shared/loading'
import { RefreshButton } from '@/components/shared/refresh-button'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useProjects } from '@/hooks/use-projects'
import type { Project } from '@/types'

export const Route = createFileRoute('/projects/')({ component: ProjectsPage })

function ProjectsPage() {
  const { t } = useTranslation()
  const [search, setSearch] = useState('')
  const [editTarget, setEditTarget] = useState<Project | null>(null)
  const [deleteTarget, setDeleteTarget] = useState<Project | null>(null)
  const [showCreate, setShowCreate] = useState(false)

  const { data, isLoading, isError, refetch, isFetching } = useProjects(search || undefined)

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

  return (
    <div className="space-y-4">
      {/* Header */}
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold">{t('projects.title', 'Projects')}</h1>
          <p className="text-muted-foreground text-sm">
            {t('projects.desc', 'Registered work contexts with paths and associated memories')}
          </p>
        </div>
        <div className="flex items-center gap-2">
          <Button onClick={() => setShowCreate(true)} size="sm">
            <Plus className="h-4 w-4 mr-1" />
            {t('projects.new', 'New Project')}
          </Button>
          <RefreshButton onClick={() => refetch()} isFetching={isFetching} />
        </div>
      </div>

      {/* Search */}
      <div className="flex items-center gap-2">
        <Input
          placeholder={t('projects.search', 'Search by name, description, or tag...')}
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          className="max-w-sm"
        />
        {search && (
          <span className="text-xs text-muted-foreground">
            {data?.total ?? 0} result{data?.total !== 1 ? 's' : ''}
          </span>
        )}
      </div>

      {/* Content */}
      {isLoading ? (
        <LoadingCards count={6} />
      ) : isError ? (
        <ErrorState onRetry={() => refetch()} />
      ) : projects.length === 0 ? (
        <EmptyState
          icon={<Package className="h-10 w-10" />}
          title={
            search
              ? t('projects.noResults', 'No projects found')
              : t('projects.empty', 'No projects yet')
          }
          description={
            search
              ? t('projects.noResultsDesc', 'Try a different search term')
              : t('projects.emptyDesc', 'Create your first project to get started')
          }
          action={
            !search ? (
              <Button onClick={() => setShowCreate(true)}>
                <Plus className="h-4 w-4 mr-1" />
                {t('projects.new', 'New Project')}
              </Button>
            ) : undefined
          }
        />
      ) : (
        <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
          {projects.map((project) => (
            <ProjectCard
              key={project.id}
              project={project}
              onEdit={setEditTarget}
              onDelete={setDeleteTarget}
            />
          ))}
        </div>
      )}

      {/* Dialogs */}
      <CreateProjectDialog open={showCreate} onOpenChange={setShowCreate} />
      <EditProjectDialog
        project={editTarget}
        open={!!editTarget}
        onOpenChange={(open) => !open && setEditTarget(null)}
        onSuccess={() => setEditTarget(null)}
      />
      <DeleteProjectDialog
        project={deleteTarget}
        open={!!deleteTarget}
        onOpenChange={(open) => !open && setDeleteTarget(null)}
      />
    </div>
  )
}