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, FaCheckSquare, FaSquare, FaTrash, FaCut, FaCopy, FaEdit, FaDownload, FaPaste, } 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 const items = response.data.items || [] // Manual protection for system folders const protectedItems = items.map((item) => { const isSystemFolder = ['avatar', 'import', 'activity'].includes(item.name.toLowerCase()) return { ...item, isReadOnly: item.isReadOnly || isSystemFolder, } }) console.log('Fetched items:', protectedItems) console.log( 'Protected folders check:', protectedItems.filter((item) => item.isReadOnly), ) setItems(protectedItems) } 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) console.log('Breadcrumb response for folderId:', folderId, response) 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) => { // Protected öğeler seçilemez if (item.isReadOnly) { return } setSelectedItems((prev) => { if (prev.includes(item.id)) { return prev.filter((id) => id !== item.id) } else { return [...prev, item.id] } }) } const handleItemDoubleClick = (item: FileItemType, event?: React.MouseEvent) => { // Prevent text selection and other default behaviors if (event) { event.preventDefault() event.stopPropagation() } // Clear any text selection that might have occurred if (window.getSelection) { const selection = window.getSelection() if (selection) { selection.removeAllRanges() } } if (item.type === 'folder') { setCurrentFolderId(item.id) setSelectedItems([]) } } // File operations const handleUploadFiles = async (files: File[]) => { try { setUploading(true) for (const file of files) { // ActivityModal pattern'ini kullan - Files array ile FormData const formData = new FormData() formData.append('fileName', file.name) formData.append('Files', file) // ActivityModal pattern - Files array if (currentFolderId) { formData.append('parentId', currentFolderId) } console.log('FileManager uploading:', { fileName: file.name, fileSize: file.size, fileType: file.type, parentId: currentFolderId, }) await fileManagementService.uploadFileDirectly(formData) } 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) if (itemsToDelete.length === 1) { // Single item delete - use existing API await fileManagementService.deleteItem({ id: itemsToDelete[0].id }) } else { // Multiple items - use bulk delete API const itemIds = itemsToDelete.map((item) => item.id) await fileManagementService.bulkDeleteItems(itemIds) } 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) } } // Clipboard state for paste button const [hasClipboardData, setHasClipboardData] = useState(false) useEffect(() => { const checkClipboard = () => { setHasClipboardData(!!localStorage.getItem('fileManager_clipboard')) } // Check initially checkClipboard() // Check periodically (in case another tab changes it) const interval = setInterval(checkClipboard, 1000) return () => clearInterval(interval) }, []) // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.ctrlKey || e.metaKey) { switch (e.key) { case 'a': e.preventDefault() selectAllItems() break case 'c': e.preventDefault() if (selectedItems.length > 0) { copySelectedItems() setHasClipboardData(true) } break case 'x': e.preventDefault() if (selectedItems.length > 0) { cutSelectedItems() setHasClipboardData(true) } break case 'v': e.preventDefault() pasteItems() break case 'Delete': case 'Backspace': e.preventDefault() if (selectedItems.length > 0) { deleteSelectedItems() } break } } else if (e.key === 'Delete') { e.preventDefault() if (selectedItems.length > 0) { deleteSelectedItems() } } } document.addEventListener('keydown', handleKeyDown) return () => { document.removeEventListener('keydown', handleKeyDown) } }, [selectedItems, filteredItems]) // Bulk operations const selectAllItems = () => { // Sadece protected olmayan öğeleri seç const selectableItems = filteredItems.filter((item) => !item.isReadOnly) setSelectedItems(selectableItems.map((item) => item.id)) } const deselectAllItems = () => { setSelectedItems([]) } const deleteSelectedItems = () => { const itemsToDelete = filteredItems.filter((item) => selectedItems.includes(item.id)) const deletableItems = itemsToDelete.filter((item) => !item.isReadOnly) const protectedItems = itemsToDelete.filter((item) => item.isReadOnly) if (protectedItems.length > 0) { toast.push( {protectedItems.length} protected system folder(s) cannot be deleted:{' '} {protectedItems.map((i) => i.name).join(', ')} , ) } if (deletableItems.length > 0) { openDeleteModal(deletableItems) // Remove protected items from selection const deletableIds = deletableItems.map((item) => item.id) setSelectedItems((prev) => prev.filter((id) => deletableIds.includes(id))) } } const copySelectedItems = () => { const itemsToCopy = filteredItems.filter((item) => selectedItems.includes(item.id)) const copyableItems = itemsToCopy.filter((item) => !item.isReadOnly) const protectedItems = itemsToCopy.filter((item) => item.isReadOnly) if (protectedItems.length > 0) { toast.push( {protectedItems.length} protected system folder(s) cannot be copied:{' '} {protectedItems.map((i) => i.name).join(', ')} , ) } if (copyableItems.length > 0) { // Store in local storage or context for paste operation localStorage.setItem( 'fileManager_clipboard', JSON.stringify({ operation: 'copy', items: copyableItems, sourceFolder: currentFolderId, }), ) setHasClipboardData(true) toast.push( {copyableItems.length} item(s) copied to clipboard , ) } } const cutSelectedItems = () => { const itemsToCut = filteredItems.filter((item) => selectedItems.includes(item.id)) const cuttableItems = itemsToCut.filter((item) => !item.isReadOnly) const protectedItems = itemsToCut.filter((item) => item.isReadOnly) if (protectedItems.length > 0) { toast.push( {protectedItems.length} protected system folder(s) cannot be moved:{' '} {protectedItems.map((i) => i.name).join(', ')} , ) } if (cuttableItems.length > 0) { // Store in local storage or context for paste operation localStorage.setItem( 'fileManager_clipboard', JSON.stringify({ operation: 'cut', items: cuttableItems, sourceFolder: currentFolderId, }), ) setHasClipboardData(true) toast.push( {cuttableItems.length} item(s) cut to clipboard , ) } } const pasteItems = async () => { const clipboardData = localStorage.getItem('fileManager_clipboard') if (!clipboardData) { toast.push( No items in clipboard , ) return } try { const clipboard = JSON.parse(clipboardData) const itemIds = clipboard.items.map((item: FileItemType) => item.id) if (clipboard.operation === 'copy') { setLoading(true) try { await fileManagementService.copyItems(itemIds, currentFolderId) await fetchItems(currentFolderId) toast.push( {itemIds.length} item(s) copied successfully , ) } catch (error) { console.error('Copy failed:', error) toast.push( Failed to copy items , ) } finally { setLoading(false) } } else if (clipboard.operation === 'cut') { // Aynı klasörde move yapmaya çalışırsa engelleyelim if (clipboard.sourceFolder === currentFolderId) { toast.push( Cannot move items to the same folder , ) return } setLoading(true) try { await fileManagementService.moveItems(itemIds, currentFolderId) await fetchItems(currentFolderId) // Clipboard'ı temizle localStorage.removeItem('fileManager_clipboard') setHasClipboardData(false) toast.push( {itemIds.length} item(s) moved successfully , ) } catch (error) { console.error('Move failed:', error) toast.push( Failed to move items , ) } finally { setLoading(false) } } } catch (error) { toast.push( Invalid clipboard data , ) } } return ( {/* Enhanced Unified Toolbar */}
{/* Main Toolbar Row */}
{/* Left Section - Primary Actions */}
{/* File Operations */} {/* Navigation */} {/* Clipboard Operations */} {/* Selection Actions */} {filteredItems.length > 0 && ( )}
{/* Right Section - Search, Sort, View */}
{/* Search */}
setFilters((prev) => ({ ...prev, searchTerm: e.target.value }))} prefix={} className="w-full sm:w-36 md:w-48" />
{/* Sort */}