bookwiz.io / components / ExplorerTab.tsx
ExplorerTab.tsx
Raw
import { useState } from 'react'
import { FileSystemItem, CreateFileSystemItemRequest } from '@/lib/types/database'
import FileExplorer from './FileExplorer'
import { CreateFileDialog, RenameDialog, DeleteConfirmDialog } from './FileDialogs'
import { IoAddCircleOutline, IoDocumentTextOutline, IoFolderOutline } from 'react-icons/io5'

interface ExplorerTabProps {
  files: (FileSystemItem & { children?: FileSystemItem[] })[]
  onFileSelect: (file: FileSystemItem) => void
  selectedFile: FileSystemItem | null
  projectName: string
  bookId: string
  createItem: (bookId: string, data: CreateFileSystemItemRequest) => Promise<FileSystemItem | null>
  updateItem: (bookId: string, fileId: string, data: any) => Promise<FileSystemItem | null>
  deleteItem: (bookId: string, fileId: string) => Promise<boolean>
  loading: boolean
}

interface DialogState {
  type: 'create-file' | 'create-folder' | 'rename' | 'delete' | null
  parentId?: string
  parentName?: string
  item?: FileSystemItem
}

export default function ExplorerTab({ 
  files, 
  onFileSelect, 
  selectedFile, 
  projectName, 
  bookId,
  createItem,
  updateItem,
  deleteItem,
  loading
}: ExplorerTabProps) {
  const [dialog, setDialog] = useState<DialogState>({ type: null })

  const handleCreateFile = (parentId?: string) => {
    const parent = parentId ? findItemById(files, parentId) : null
    setDialog({
      type: 'create-file',
      parentId,
      parentName: parent?.name || projectName
    })
  }

  const handleCreateFolder = (parentId?: string) => {
    const parent = parentId ? findItemById(files, parentId) : null
    setDialog({
      type: 'create-folder',
      parentId,
      parentName: parent?.name || projectName
    })
  }

  const handleRename = (item: FileSystemItem) => {
    setDialog({
      type: 'rename',
      item
    })
  }

  const handleDelete = (item: FileSystemItem) => {
    setDialog({
      type: 'delete',
      item
    })
  }

  const handleDeleteConfirm = async () => {
    if (dialog.item) {
      const success = await deleteItem(bookId, dialog.item.id)
      if (success && selectedFile?.id === dialog.item.id) {
        onFileSelect(null as any)
      }
      
      // Dispatch event to notify other components
      if (typeof window !== 'undefined') {
        window.dispatchEvent(new CustomEvent('file-operation-completed', { 
          detail: { type: 'delete', fileId: dialog.item.id, fileName: dialog.item.name } 
        }))
      }
    }
  }

  const handleMove = async (item: FileSystemItem, newParentId?: string) => {
    try {
      const success = await updateItem(bookId, item.id, { 
        parent_id: newParentId || null 
      })
      
      if (success && selectedFile?.id === item.id) {
        onFileSelect(success)
      }
    } catch (error) {
      console.error('Error moving item:', error)
    }
  }

  const handleCreateSubmit = async (name: string, content?: string) => {
    const type = dialog.type === 'create-file' ? 'file' : 'folder'
    
    const data: CreateFileSystemItemRequest = {
      book_id: bookId,
      parent_id: dialog.parentId || null,
      name,
      type: type as 'file' | 'folder',
      ...(type === 'file' && {
        content: content || '',
        file_extension: name.includes('.') ? name.split('.').pop() : 'md',
        mime_type: 'text/markdown'
      })
    }

    const newItem = await createItem(bookId, data)
    if (newItem) {
      if (type === 'file') {
        onFileSelect(newItem)
      }
      
      // Dispatch event to notify other components
      if (typeof window !== 'undefined') {
        window.dispatchEvent(new CustomEvent('file-operation-completed', { 
          detail: { type: 'create', itemType: type, fileId: newItem.id, fileName: newItem.name } 
        }))
      }
    }
  }

  const handleRenameSubmit = async (newName: string) => {
    if (dialog.item) {
      const updatedItem = await updateItem(bookId, dialog.item.id, { name: newName })
      if (updatedItem && selectedFile?.id === dialog.item.id) {
        onFileSelect(updatedItem)
      }
    }
  }

  const closeDialog = () => {
    setDialog({ type: null })
  }

  return (
    <>
      <div className="flex-1 overflow-y-auto min-h-0">
        <div className="px-4 py-3">
          <div className="flex items-center justify-between group">
            <div className="flex items-center text-sm text-slate-200 font-medium">
              <div className="w-8 h-8 rounded-xl bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center mr-3">
                <IoFolderOutline className="w-4 h-4 text-white" />
              </div>
              {projectName}
            </div>
            <div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-all duration-200">
              <button 
                onClick={() => handleCreateFolder()}
                className="p-1.5 text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-lg transition-all duration-200"
                title="New Folder"
                disabled={loading}
              >
                <IoAddCircleOutline className="w-4 h-4" />
              </button>
              <button 
                onClick={() => handleCreateFile()}
                className="p-1.5 text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-lg transition-all duration-200"
                title="New File"
                disabled={loading}
              >
                <IoDocumentTextOutline className="w-4 h-4" />
              </button>
            </div>
          </div>
        </div>
        
        <FileExplorer 
          files={files} 
          onFileSelect={onFileSelect}
          selectedFile={selectedFile}
          onCreateFile={handleCreateFile}
          onCreateFolder={handleCreateFolder}
          onRename={handleRename}
          onDelete={handleDelete}
          onMove={handleMove}
          bookId={bookId}
          loading={loading}
        />
      </div>

      <CreateFileDialog
        isOpen={dialog.type === 'create-file'}
        onClose={closeDialog}
        onSubmit={handleCreateSubmit}
        type="file"
        parentName={dialog.parentName}
        loading={loading}
      />

      <CreateFileDialog
        isOpen={dialog.type === 'create-folder'}
        onClose={closeDialog}
        onSubmit={handleCreateSubmit}
        type="folder"
        parentName={dialog.parentName}
        loading={loading}
      />

      {dialog.item && (
        <RenameDialog
          isOpen={dialog.type === 'rename'}
          onClose={closeDialog}
          onSubmit={handleRenameSubmit}
          currentName={dialog.item.name}
          type={dialog.item.type}
          loading={loading}
        />
      )}

      {dialog.item && (
        <DeleteConfirmDialog
          isOpen={dialog.type === 'delete'}
          onClose={closeDialog}
          onConfirm={handleDeleteConfirm}
          itemName={dialog.item.name}
          itemType={dialog.item.type}
          loading={loading}
        />
      )}
    </>
  )
}

function findItemById(
  items: (FileSystemItem & { children?: FileSystemItem[] })[], 
  id: string
): FileSystemItem | null {
  for (const item of items) {
    if (item.id === id) return item
    if (item.children) {
      const found = findItemById(item.children, id)
      if (found) return found
    }
  }
  return null
}