import { useState, useEffect, useCallback } from 'react' import { Helmet } from 'react-helmet' import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui' import { FaFolder, FaCloudUploadAlt, FaSearch, FaTh, FaList, FaArrowUp } from 'react-icons/fa' import Container from '@/components/shared/Container' import { useLocalization } from '@/utils/hooks/useLocalization' import fileManagementService from '@/services/fileManagement.service' import Breadcrumb from './components/Breadcrumb' import FileItem from './components/FileItem' import FileUploadModal from './components/FileUploadModal' import { CreateFolderModal, RenameItemModal, DeleteConfirmModal } from './components/FileModals' import type { FileItem as FileItemType, BreadcrumbItem, ViewMode, SortBy, SortOrder, FileManagerFilters, } from '@/types/fileManagement' import classNames from 'classnames' // Select options for sorting const FileManager = () => { const { translate } = useLocalization() // State const [loading, setLoading] = useState(true) const [items, setItems] = useState([]) const [filteredItems, setFilteredItems] = useState([]) const [currentFolderId, setCurrentFolderId] = useState() const [breadcrumbItems, setBreadcrumbItems] = useState([ { name: 'Files', path: '', id: undefined }, ]) const [selectedItems, setSelectedItems] = useState([]) const [viewMode, setViewMode] = useState('grid') const [filters, setFilters] = useState({ searchTerm: '', sortBy: 'name', sortOrder: 'asc', }) // Modal states const [uploadModalOpen, setUploadModalOpen] = useState(false) const [createFolderModalOpen, setCreateFolderModalOpen] = useState(false) const [renameModalOpen, setRenameModalOpen] = useState(false) const [deleteModalOpen, setDeleteModalOpen] = useState(false) const [itemToRename, setItemToRename] = useState() const [itemsToDelete, setItemsToDelete] = useState([]) // Loading states const [uploading, setUploading] = useState(false) const [creating, setCreating] = useState(false) const [renaming, setRenaming] = useState(false) const [deleting, setDeleting] = useState(false) // Fetch items from API const fetchItems = useCallback(async (folderId?: string) => { try { setLoading(true) const response = await fileManagementService.getItems(folderId) // Backend returns GetFilesDto which has Items property setItems(response.data.items || []) } catch (error) { console.error('Failed to fetch items:', error) toast.push(Failed to load files and folders) } finally { setLoading(false) } }, []) // Fetch breadcrumb path const fetchBreadcrumb = useCallback(async (folderId?: string) => { try { if (!folderId) { setBreadcrumbItems([{ name: 'Files', path: '', id: undefined }]) return } const response = await fileManagementService.getFolderPath(folderId) const pathItems: BreadcrumbItem[] = [ { name: 'Files', path: '', id: undefined }, ...response.data.path.map((item) => ({ name: item.name, path: item.id, id: item.id, })), ] setBreadcrumbItems(pathItems) } catch (error) { console.error('Failed to fetch breadcrumb:', error) } }, []) // Initial load useEffect(() => { fetchItems(currentFolderId) fetchBreadcrumb(currentFolderId) }, [currentFolderId, fetchItems, fetchBreadcrumb]) // Filter and sort items useEffect(() => { let filtered = [...items] // Apply search filter if (filters.searchTerm) { filtered = filtered.filter((item) => item.name.toLowerCase().includes(filters.searchTerm!.toLowerCase()), ) } // Apply sorting filtered.sort((a, b) => { let comparison = 0 switch (filters.sortBy) { case 'name': comparison = a.name.localeCompare(b.name) break case 'size': comparison = (a.size || 0) - (b.size || 0) break case 'type': comparison = a.type.localeCompare(b.type) break case 'modified': comparison = new Date(a.modifiedAt).getTime() - new Date(b.modifiedAt).getTime() break } return filters.sortOrder === 'desc' ? -comparison : comparison }) // Folders first filtered.sort((a, b) => { if (a.type === 'folder' && b.type === 'file') return -1 if (a.type === 'file' && b.type === 'folder') return 1 return 0 }) setFilteredItems(filtered) }, [items, filters]) // Navigation handlers const handleBreadcrumbNavigate = (breadcrumb: BreadcrumbItem) => { setCurrentFolderId(breadcrumb.id) setSelectedItems([]) } const handleItemSelect = (item: FileItemType) => { setSelectedItems((prev) => { if (prev.includes(item.id)) { return prev.filter((id) => id !== item.id) } else { return [...prev, item.id] } }) } const handleItemDoubleClick = (item: FileItemType) => { if (item.type === 'folder') { setCurrentFolderId(item.id) setSelectedItems([]) } } // File operations const handleUploadFiles = async (files: File[]) => { try { setUploading(true) for (const file of files) { await fileManagementService.uploadFile({ file, parentId: currentFolderId, }) } await fetchItems(currentFolderId) toast.push(Files uploaded successfully) } catch (error) { console.error('Upload failed:', error) toast.push(Failed to upload files) throw error } finally { setUploading(false) } } const handleCreateFolder = async (name: string) => { try { setCreating(true) await fileManagementService.createFolder({ name, parentId: currentFolderId, }) await fetchItems(currentFolderId) toast.push(Folder created successfully) } catch (error) { console.error('Create folder failed:', error) toast.push(Failed to create folder) throw error } finally { setCreating(false) } } const handleRenameItem = async (newName: string) => { if (!itemToRename) return try { setRenaming(true) await fileManagementService.renameItem({ id: itemToRename.id, newName, }) await fetchItems(currentFolderId) toast.push(Item renamed successfully) } catch (error) { console.error('Rename failed:', error) toast.push(Failed to rename item) throw error } finally { setRenaming(false) } } const handleDeleteItems = async () => { try { setDeleting(true) for (const item of itemsToDelete) { await fileManagementService.deleteItem({ id: item.id }) } await fetchItems(currentFolderId) setSelectedItems([]) toast.push(Items deleted successfully) } catch (error) { console.error('Delete failed:', error) toast.push(Failed to delete items) throw error } finally { setDeleting(false) } } const handleDownload = async (item: FileItemType) => { try { const blob = await fileManagementService.downloadFile(item.id) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = item.name document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } catch (error) { console.error('Download failed:', error) toast.push(Failed to download file) } } // Action handlers const openRenameModal = (item: FileItemType) => { setItemToRename(item) setRenameModalOpen(true) } const openDeleteModal = (items: FileItemType[]) => { setItemsToDelete(items) setDeleteModalOpen(true) } const goUpOneLevel = () => { if (breadcrumbItems.length > 1) { const parentBreadcrumb = breadcrumbItems[breadcrumbItems.length - 2] handleBreadcrumbNavigate(parentBreadcrumb) } } return ( {/* Toolbar */}
{breadcrumbItems.length > 1 && ( )}
{/* Search */}
setFilters((prev) => ({ ...prev, searchTerm: e.target.value }))} prefix={} className="w-64" />
{/* Sort */}