'use client'; import { Archive, ChevronRight, Folder as FolderIcon, FolderOpen, Inbox, MoreVertical, Pencil, Trash2, } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { toast } from 'sonner'; import { deleteFolderApiV1FolderFolderIdDelete, renameFolderApiV1FolderFolderIdPut, } from '@/client/sdk.gen'; import type { FolderResponse, WorkflowListResponse } from '@/client/types.gen'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import logger from '@/lib/logger'; import { cn } from '@/lib/utils'; import { WorkflowTable } from '../WorkflowTable'; import { FolderFormDialog } from './FolderFormDialog'; /** * - `folder` — a real, renameable/deletable folder of active agents * - `uncategorized` — active agents with no folder * - `archived` — archived agents (restore-only; not a move target) */ type SectionKind = 'folder' | 'uncategorized' | 'archived'; interface FolderSectionProps { kind: SectionKind; /** Required when kind === 'folder'; ignored otherwise. */ folder?: FolderResponse | null; workflows: WorkflowListResponse[]; /** All folders, passed through so each row's "Move to folder" menu has targets. */ allFolders?: FolderResponse[]; /** Defaults to open only for Uncategorized; folders and Archived start collapsed. */ defaultOpen?: boolean; } export function FolderSection({ kind, folder = null, workflows, allFolders = [], defaultOpen, }: FolderSectionProps) { const router = useRouter(); const [open, setOpen] = useState(defaultOpen ?? kind === 'uncategorized'); const [isRenaming, setIsRenaming] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const isFolder = kind === 'folder'; const isArchived = kind === 'archived'; const count = workflows.length; const title = isFolder ? (folder?.name ?? '') : isArchived ? 'Archived' : 'Uncategorized'; const handleRename = async (name: string) => { if (!folder) return; const response = await renameFolderApiV1FolderFolderIdPut({ path: { folder_id: folder.id }, body: { name }, }); if (response.error) { const detail = (response.error as { detail?: string })?.detail ?? 'Failed to rename folder'; toast.error(detail); throw new Error(detail); } toast.success('Folder renamed'); router.refresh(); }; const handleDelete = async () => { if (!folder) return; setIsDeleting(true); try { const response = await deleteFolderApiV1FolderFolderIdDelete({ path: { folder_id: folder.id }, }); if (response.error) { throw new Error('Failed to delete folder'); } toast.success(`Folder "${folder.name}" deleted`); setConfirmDelete(false); router.refresh(); } catch (err) { logger.error(`Error deleting folder: ${err}`); toast.error('Failed to delete folder'); } finally { setIsDeleting(false); } }; return (
{isFolder && ( setIsRenaming(true)}> Rename setConfirmDelete(true)} className="text-destructive focus:text-destructive" > Delete )}
{count > 0 ? ( ) : (
{isArchived ? 'No archived agents.' : isFolder ? 'This folder is empty. Use “Move to folder” on an agent to add it here.' : 'No uncategorized agents.'}
)}
{isFolder && folder && ( <> Delete “{folder.name}”? The {count} agent{count === 1 ? '' : 's'} in this folder won’t be deleted - they’ll move to Uncategorized. Cancel { e.preventDefault(); handleDelete(); }} disabled={isDeleting} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > {isDeleting ? 'Deleting...' : 'Delete folder'} )}
); }