erp-platform/ui/src/views/admin/files/FileManager.tsx
Sedat Öztürk 697c7c1d65 Dosya Yöneticisi
File exists, Toolbar ve style güncellemeleri
2025-10-26 19:27:19 +03:00

999 lines
33 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<FileItemType[]>([])
const [filteredItems, setFilteredItems] = useState<FileItemType[]>([])
const [currentFolderId, setCurrentFolderId] = useState<string | undefined>()
const [breadcrumbItems, setBreadcrumbItems] = useState<BreadcrumbItem[]>([
{ name: 'Files', path: '', id: undefined },
])
const [selectedItems, setSelectedItems] = useState<string[]>([])
const [viewMode, setViewMode] = useState<ViewMode>('grid')
const [filters, setFilters] = useState<FileManagerFilters>({
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<FileItemType | undefined>()
const [itemsToDelete, setItemsToDelete] = useState<FileItemType[]>([])
// 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(<Notification type="danger">Failed to load files and folders</Notification>)
} 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(<Notification type="success">Files uploaded successfully</Notification>)
} catch (error) {
console.error('Upload failed:', error)
toast.push(<Notification type="danger">Failed to upload files</Notification>)
throw error
} finally {
setUploading(false)
}
}
const handleCreateFolder = async (name: string) => {
try {
setCreating(true)
await fileManagementService.createFolder({
name,
parentId: currentFolderId,
})
await fetchItems(currentFolderId)
toast.push(<Notification type="success">Folder created successfully</Notification>)
} catch (error) {
console.error('Create folder failed:', error)
toast.push(<Notification type="danger">Failed to create folder</Notification>)
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(<Notification type="success">Item renamed successfully</Notification>)
} catch (error) {
console.error('Rename failed:', error)
toast.push(<Notification type="danger">Failed to rename item</Notification>)
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(<Notification type="success">Items deleted successfully</Notification>)
} catch (error) {
console.error('Delete failed:', error)
toast.push(<Notification type="danger">Failed to delete items</Notification>)
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(<Notification type="danger">Failed to download file</Notification>)
}
}
// 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(
<Notification title="Warning" type="warning">
{protectedItems.length} protected system folder(s) cannot be deleted:{' '}
{protectedItems.map((i) => i.name).join(', ')}
</Notification>,
)
}
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(
<Notification title="Warning" type="warning">
{protectedItems.length} protected system folder(s) cannot be copied:{' '}
{protectedItems.map((i) => i.name).join(', ')}
</Notification>,
)
}
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(
<Notification title="Copied" type="success">
{copyableItems.length} item(s) copied to clipboard
</Notification>,
)
}
}
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(
<Notification title="Warning" type="warning">
{protectedItems.length} protected system folder(s) cannot be moved:{' '}
{protectedItems.map((i) => i.name).join(', ')}
</Notification>,
)
}
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(
<Notification title="Cut" type="success">
{cuttableItems.length} item(s) cut to clipboard
</Notification>,
)
}
}
const pasteItems = async () => {
const clipboardData = localStorage.getItem('fileManager_clipboard')
if (!clipboardData) {
toast.push(
<Notification title="Clipboard Empty" type="info">
No items in clipboard
</Notification>,
)
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(
<Notification title="Success" type="success">
{itemIds.length} item(s) copied successfully
</Notification>,
)
} catch (error) {
console.error('Copy failed:', error)
toast.push(
<Notification title="Error" type="danger">
Failed to copy items
</Notification>,
)
} finally {
setLoading(false)
}
} else if (clipboard.operation === 'cut') {
// Aynı klasörde move yapmaya çalışırsa engelleyelim
if (clipboard.sourceFolder === currentFolderId) {
toast.push(
<Notification title="Warning" type="warning">
Cannot move items to the same folder
</Notification>,
)
return
}
setLoading(true)
try {
await fileManagementService.moveItems(itemIds, currentFolderId)
await fetchItems(currentFolderId)
// Clipboard'ı temizle
localStorage.removeItem('fileManager_clipboard')
setHasClipboardData(false)
toast.push(
<Notification title="Success" type="success">
{itemIds.length} item(s) moved successfully
</Notification>,
)
} catch (error) {
console.error('Move failed:', error)
toast.push(
<Notification title="Error" type="danger">
Failed to move items
</Notification>,
)
} finally {
setLoading(false)
}
}
} catch (error) {
toast.push(
<Notification title="Error" type="danger">
Invalid clipboard data
</Notification>,
)
}
}
return (
<Container className="px-3 sm:px-4 md:px-6">
<Helmet
titleTemplate="%s | Sözsoft Kurs Platform"
title={translate('::' + 'App.Files')}
defaultTitle="Sözsoft Kurs Platform"
></Helmet>
{/* Enhanced Unified Toolbar */}
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-3 sm:p-4 mb-4 mt-2">
{/* Main Toolbar Row */}
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-3 sm:gap-4">
{/* Left Section - Primary Actions */}
<div className="flex items-center gap-2 flex-wrap min-w-0">
{/* File Operations */}
{/* Navigation */}
<Button
variant="plain"
icon={<FaArrowUp />}
onClick={goUpOneLevel}
size="sm"
disabled={breadcrumbItems.length <= 1}
className="text-gray-600 hover:text-blue-600 flex-shrink-0"
title="Go up one level"
/>
<Button
variant="solid"
icon={<FaCloudUploadAlt />}
onClick={() => setUploadModalOpen(true)}
size="sm"
className="flex-shrink-0"
>
<span className="hidden sm:inline">Upload Files</span>
<span className="sm:hidden">Upload</span>
</Button>
<Button
variant="default"
icon={<FaFolder />}
onClick={() => setCreateFolderModalOpen(true)}
size="sm"
className="flex-shrink-0"
>
<span className="hidden sm:inline">Create Folder</span>
<span className="sm:hidden">Create</span>
</Button>
{/* Clipboard Operations */}
<Button
variant="plain"
icon={<FaCopy />}
onClick={copySelectedItems}
disabled={selectedItems.length === 0}
size="sm"
className="text-gray-600 hover:text-blue-600 disabled:opacity-50 flex-shrink-0"
title="Copy selected items"
/>
<Button
variant="plain"
icon={<FaCut />}
onClick={cutSelectedItems}
disabled={selectedItems.length === 0}
size="sm"
className="text-gray-600 hover:text-orange-600 disabled:opacity-50 flex-shrink-0"
title="Cut selected items"
/>
<Button
variant="plain"
icon={<FaPaste />}
onClick={pasteItems}
disabled={!hasClipboardData}
size="sm"
className="text-gray-600 hover:text-green-600 disabled:opacity-50 flex-shrink-0"
title="Paste items"
/>
<Button
variant="plain"
icon={<FaEdit />}
onClick={() => {
if (selectedItems.length === 1) {
const itemToRename = filteredItems.find((item) => item.id === selectedItems[0])
if (itemToRename && !itemToRename.isReadOnly) {
openRenameModal(itemToRename)
} else {
toast.push(
<Notification title="Warning" type="warning">
Protected system folders cannot be renamed
</Notification>,
)
}
}
}}
size="sm"
disabled={selectedItems.length !== 1}
className="text-gray-600 hover:text-blue-600 flex-shrink-0"
title="Rename selected item"
>
Rename
</Button>
<Button
variant="plain"
icon={<FaDownload />}
onClick={() => {
if (selectedItems.length === 1) {
const itemToDownload = filteredItems.find((item) => item.id === selectedItems[0])
if (itemToDownload && itemToDownload.type === 'file') {
handleDownload(itemToDownload)
} else {
toast.push(
<Notification title="Warning" type="warning">
Only files can be downloaded
</Notification>,
)
}
}
}}
size="sm"
disabled={
selectedItems.length !== 1 ||
(() => {
const selectedItem = filteredItems.find((item) => item.id === selectedItems[0])
return selectedItem?.type !== 'file'
})()
}
className="text-gray-600 hover:text-green-600 flex-shrink-0"
title="Download selected file"
>
Download
</Button>
<Button
variant="plain"
icon={<FaTrash />}
onClick={deleteSelectedItems}
size="sm"
disabled={selectedItems.length === 0}
className="text-gray-600 hover:text-red-600 flex-shrink-0"
title="Delete selected items"
>
<span>Delete {selectedItems.length > 0 && `(${selectedItems.length})`}</span>
</Button>
{/* Selection Actions */}
{filteredItems.length > 0 && (
<Button
variant="plain"
icon={
selectedItems.length ===
filteredItems.filter((item) => !item.isReadOnly).length ? (
<FaCheckSquare />
) : (
<FaSquare />
)
}
onClick={
selectedItems.length === filteredItems.filter((item) => !item.isReadOnly).length
? deselectAllItems
: selectAllItems
}
size="sm"
className="text-gray-600 hover:text-blue-600 flex-shrink-0"
title={
selectedItems.length === filteredItems.filter((item) => !item.isReadOnly).length
? 'Deselect all selectable items'
: 'Select all selectable items'
}
>
<span className="hidden lg:inline">
{selectedItems.length === filteredItems.filter((item) => !item.isReadOnly).length
? 'Deselect All'
: 'Select All'}
</span>
<span className="lg:hidden">
{selectedItems.length === filteredItems.filter((item) => !item.isReadOnly).length
? 'Deselect'
: 'Select'}
</span>
</Button>
)}
</div>
{/* Right Section - Search, Sort, View */}
<div className="flex items-center gap-2 sm:gap-3 flex-wrap justify-start lg:justify-end min-w-0 w-full lg:w-auto">
{/* Search */}
<div className="flex items-center w-full sm:w-auto">
<Input
size="sm"
placeholder="Search files..."
value={filters.searchTerm}
onChange={(e) => setFilters((prev) => ({ ...prev, searchTerm: e.target.value }))}
prefix={<FaSearch className="text-gray-400" />}
className="w-full sm:w-36 md:w-48"
/>
</div>
{/* Sort */}
<Select
size="sm"
options={[
{ value: 'name-asc', label: 'Name (A-Z)' },
{ value: 'name-desc', label: 'Name (Z-A)' },
{ value: 'size-asc', label: 'Size (Small to Large)' },
{ value: 'size-desc', label: 'Size (Large to Small)' },
{ value: 'modified-desc', label: 'Modified (Newest)' },
{ value: 'modified-asc', label: 'Modified (Oldest)' },
]}
value={{
value: `${filters.sortBy}-${filters.sortOrder}`,
label: (() => {
const sortOptions = {
'name-asc': 'Name (A-Z)',
'name-desc': 'Name (Z-A)',
'size-asc': 'Size (Small to Large)',
'size-desc': 'Size (Large to Small)',
'modified-desc': 'Modified (Newest)',
'modified-asc': 'Modified (Oldest)',
}
return sortOptions[
`${filters.sortBy}-${filters.sortOrder}` as keyof typeof sortOptions
]
})(),
}}
onChange={(option) => {
if (option && 'value' in option) {
const [sortBy, sortOrder] = (option.value as string).split('-') as [
SortBy,
SortOrder,
]
setFilters((prev) => ({ ...prev, sortBy, sortOrder }))
}
}}
className="min-w-32 sm:min-w-36 flex-shrink-0"
/>
{/* View Mode */}
<div className="flex border border-gray-300 dark:border-gray-600 rounded flex-shrink-0">
<Button
variant="plain"
size="sm"
icon={<FaTh />}
className={classNames(
'rounded-r-none border-r px-2 sm:px-3',
viewMode === 'grid' && 'bg-blue-50 dark:bg-blue-900/20 text-blue-600',
)}
onClick={() => setViewMode('grid')}
title="Grid view"
/>
<Button
variant="plain"
size="sm"
icon={<FaList />}
className={classNames(
'rounded-l-none px-2 sm:px-3',
viewMode === 'list' && 'bg-blue-50 dark:bg-blue-900/20 text-blue-600',
)}
onClick={() => setViewMode('list')}
title="List view"
/>
</div>
</div>
</div>
</div>
{/* Breadcrumb */}
<div className="mb-4 sm:mb-6 overflow-x-auto">
<div className="min-w-max">
<Breadcrumb items={breadcrumbItems} onNavigate={handleBreadcrumbNavigate} />
</div>
</div>
{/* Files Grid/List */}
{loading ? (
<div className="flex justify-center items-center py-20">
<Spinner size="lg" />
</div>
) : (
<>
{/* List View Header */}
{viewMode === 'list' && (
<div className="hidden sm:grid grid-cols-12 gap-4 px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 border-b dark:border-gray-700 mb-2">
<div className="col-span-5 lg:col-span-5">İsim</div>
<div className="col-span-2 lg:col-span-2">Tür</div>
<div className="col-span-2 lg:col-span-2">Boyut</div>
<div className="col-span-2 lg:col-span-2">Değiştirilme</div>
<div className="col-span-1"></div> {/* Actions column */}
</div>
)}
<div
className={classNames(
viewMode === 'grid'
? 'grid grid-cols-2 xs:grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10 gap-3 sm:gap-4'
: 'space-y-1',
)}
>
{filteredItems.length === 0 ? (
<div className="col-span-full text-center py-12 sm:py-20">
<FaFolder className="mx-auto h-12 w-12 sm:h-16 sm:w-16 text-gray-400 mb-4" />
<p className="text-gray-500 dark:text-gray-400 text-sm sm:text-base px-4">
{filters.searchTerm ? 'No files match your search' : 'This folder is empty'}
</p>
</div>
) : (
filteredItems.map((item) => (
<FileItem
key={item.id}
item={item}
viewMode={viewMode}
selected={selectedItems.includes(item.id)}
onSelect={handleItemSelect}
onDoubleClick={handleItemDoubleClick}
onToggleSelect={handleItemSelect}
onCreateFolder={(parentItem) => {
// Klasör içinde yeni klasör oluşturmak için parent klasörü set et
setCurrentFolderId(parentItem.id)
setCreateFolderModalOpen(true)
}}
onRename={openRenameModal}
onMove={(item) => {
// Move işlemi için öğeyi cut olarak clipboard'a koy
cutSelectedItems()
if (!selectedItems.includes(item.id)) {
setSelectedItems([item.id])
localStorage.setItem(
'fileManager_clipboard',
JSON.stringify({
operation: 'cut',
items: [item],
sourceFolder: currentFolderId,
}),
)
setHasClipboardData(true)
toast.push(
<Notification title="Cut" type="success">
Item ready to move. Navigate to target folder and paste.
</Notification>,
)
}
}}
onDelete={(item) => openDeleteModal([item])}
onDownload={item.type === 'file' ? handleDownload : undefined}
onPreview={
item.type === 'file'
? (item) => {
// Preview işlevi - resimler için modal açabiliriz
if (item.mimeType?.startsWith('image/')) {
// Resim preview modal'ıılabilir
toast.push(
<Notification type="info">
Resim önizleme özelliği yakında eklenecek
</Notification>,
)
} else {
// Diğer dosya tipleri için download
handleDownload(item)
}
}
: undefined
}
/>
))
)}
</div>
</>
)}
{/* Modals */}
<FileUploadModal
isOpen={uploadModalOpen}
onClose={() => setUploadModalOpen(false)}
onUpload={handleUploadFiles}
currentFolderId={currentFolderId}
loading={uploading}
/>
<CreateFolderModal
isOpen={createFolderModalOpen}
onClose={() => setCreateFolderModalOpen(false)}
onCreate={handleCreateFolder}
loading={creating}
currentFolderId={currentFolderId}
/>
<RenameItemModal
isOpen={renameModalOpen}
onClose={() => {
setRenameModalOpen(false)
setItemToRename(undefined)
}}
onRename={handleRenameItem}
item={itemToRename}
loading={renaming}
/>
<DeleteConfirmModal
isOpen={deleteModalOpen}
onClose={() => {
setDeleteModalOpen(false)
setItemsToDelete([])
}}
onDelete={handleDeleteItems}
items={itemsToDelete}
loading={deleting}
/>
</Container>
)
}
export default FileManager