FileManagement düzenlemesi

This commit is contained in:
Sedat Öztürk 2026-01-10 17:30:53 +03:00
parent 8bc2e4250b
commit b436f53480
7 changed files with 152 additions and 65 deletions

View file

@ -17,6 +17,7 @@ public class FileItemDto
public string ParentId { get; set; } = string.Empty;
public bool IsReadOnly { get; set; }
public int? ChildCount { get; set; }
public string TenantId { get; set; } = string.Empty;
}
public class GetFilesDto

View file

@ -50,7 +50,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
private string GetTenantPrefix()
{
var tenantId = _currentTenant.Id?.ToString() ?? "host";
return $"files/{tenantId}/";
return $"tenants/{tenantId}/";
}
private string EncodePathAsId(string path)
@ -241,7 +241,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
Path = metadata.Path,
ParentId = parentId ?? string.Empty,
IsReadOnly = finalIsReadOnly,
ChildCount = metadata.ChildCount
ChildCount = metadata.ChildCount,
TenantId = metadata.TenantId
};
}).OrderBy(x => x.Type == "folder" ? 0 : 1).ThenBy(x => x.Name).ToList();
@ -309,7 +310,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = metadata.ModifiedAt,
Path = metadata.Path,
ParentId = input.ParentId ?? string.Empty,
IsReadOnly = metadata.IsReadOnly
IsReadOnly = metadata.IsReadOnly,
TenantId = _currentTenant.Id?.ToString()
};
}
@ -401,7 +403,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = metadata.ModifiedAt,
Path = metadata.Path,
ParentId = input.ParentId ?? string.Empty,
IsReadOnly = metadata.IsReadOnly
IsReadOnly = metadata.IsReadOnly,
TenantId = _currentTenant.Id?.ToString()
};
}
@ -468,7 +471,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = metadata.ModifiedAt,
Path = metadata.Path,
ParentId = metadata.ParentId,
IsReadOnly = metadata.IsReadOnly
IsReadOnly = metadata.IsReadOnly,
TenantId = _currentTenant.Id?.ToString()
};
}
@ -530,7 +534,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = metadata.ModifiedAt,
Path = metadata.Path,
ParentId = metadata.ParentId,
IsReadOnly = metadata.IsReadOnly
IsReadOnly = metadata.IsReadOnly,
TenantId = _currentTenant.Id?.ToString()
};
}
@ -675,7 +680,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = dirInfo.LastWriteTime,
Path = finalTargetPath,
ParentId = input.TargetFolderId ?? string.Empty,
IsReadOnly = false
IsReadOnly = false,
TenantId = _currentTenant.Id?.ToString()
});
}
else if (File.Exists(sourceFullPath))
@ -704,7 +710,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = fileInfo.LastWriteTime,
Path = finalTargetPath,
ParentId = input.TargetFolderId ?? string.Empty,
IsReadOnly = false
IsReadOnly = false,
TenantId = _currentTenant.Id?.ToString()
});
}
else
@ -800,7 +807,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = dirInfo.LastWriteTime,
Path = finalTargetPath,
ParentId = input.TargetFolderId ?? string.Empty,
IsReadOnly = false
IsReadOnly = false,
TenantId = _currentTenant.Id?.ToString()
});
}
else if (File.Exists(sourceFullPath))
@ -829,7 +837,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = fileInfo.LastWriteTime,
Path = finalTargetPath,
ParentId = input.TargetFolderId ?? string.Empty,
IsReadOnly = false
IsReadOnly = false,
TenantId = _currentTenant.Id?.ToString()
});
}
else
@ -895,7 +904,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = metadata.ModifiedAt,
Path = metadata.Path,
ParentId = metadata.ParentId,
IsReadOnly = metadata.IsReadOnly
IsReadOnly = metadata.IsReadOnly,
TenantId = _currentTenant.Id?.ToString()
})
.OrderBy(x => x.Type == "folder" ? 0 : 1)
.ThenBy(x => x.Name)

View file

@ -462,7 +462,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
Path = relativePath,
ParentId = string.Empty,
IsReadOnly = false,
ChildCount = 0
ChildCount = 0,
TenantId = _currentTenant.Id?.ToString()
});
}

View file

@ -6,7 +6,9 @@ export const REDIRECT_URL_KEY = 'redirectUrl'
export const DEFAULT_API_NAME = 'Default'
export const AUTH_API_NAME = 'AuthApi'
export const AVATAR_URL = (id?: string, tenantId?: string) =>
`${VITE_CDN_URL}/${tenantId ? 'tenants/' + tenantId : 'host'}/Avatar/${id ?? 'default'}.jpg`
`${VITE_CDN_URL}/${tenantId ? 'tenants/' + tenantId : 'host'}/avatar/${id ?? 'default'}.jpg`
export const MULTIVALUE_DELIMITER = '|'
export const DX_CLASSNAMES =
'dx-viewport dx-device-desktop dx-device-generic dx-theme-generic dx-theme-generic-typography dx-color-scheme-light'
export const FILE_URL = (fileName?: string, tenantId?: string) =>
`${VITE_CDN_URL}/${tenantId ? 'tenants/' + tenantId : 'host'}/${fileName}`

View file

@ -11,6 +11,7 @@ export interface FileItem {
extension: string
isReadOnly: boolean
childCount?: number // Klasörler için öğe sayısı
tenantId?: string
}
export interface FolderItem extends Omit<FileItem, 'type' | 'size' | 'mimeType' | 'extension'> {

View file

@ -85,20 +85,20 @@ const FileManager = () => {
}
})
console.log('Fetched items:', protectedItems)
console.log(
'Protected folders check:',
protectedItems.filter((item) => item.isReadOnly),
)
console.log(
'Folders with childCount:',
protectedItems.filter((item) => item.type === 'folder').map(item => ({
name: item.name,
childCount: item.childCount,
hasChildCount: 'childCount' in item,
type: typeof item.childCount
}))
)
// console.log('Fetched items:', protectedItems)
// console.log(
// 'Protected folders check:',
// protectedItems.filter((item) => item.isReadOnly),
// )
// console.log(
// 'Folders with childCount:',
// protectedItems.filter((item) => item.type === 'folder').map(item => ({
// name: item.name,
// childCount: item.childCount,
// hasChildCount: 'childCount' in item,
// type: typeof item.childCount
// }))
// )
setItems(protectedItems)
} catch (error) {
console.error('Failed to fetch items:', error)
@ -117,7 +117,7 @@ const FileManager = () => {
}
const response = await fileManagementService.getFolderPath(folderId)
console.log('Breadcrumb response for folderId:', folderId, response)
// console.log('Breadcrumb response for folderId:', folderId, response)
const pathItems: BreadcrumbItem[] = [
{ name: 'Files', path: '', id: undefined },
...response.data.path.map((item) => ({
@ -236,13 +236,6 @@ const FileManager = () => {
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)

View file

@ -8,15 +8,17 @@ import {
FaFilm,
FaMusic,
FaArchive,
FaEllipsisV,
FaPencilAlt,
FaSignOutAlt,
FaTrash,
FaDownload,
FaEye,
FaLink,
} from 'react-icons/fa'
import { toast, Notification } from '@/components/ui'
// import { Dropdown } from '@/components/ui' // Artık kullanmıyoruz
import type { FileItem as FileItemType, FileActionMenuItem } from '@/types/fileManagement'
import { FILE_URL } from '@/constants/app.constant'
export interface FileItemProps {
item: FileItemType
@ -132,6 +134,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
} = props
const [dropdownOpen, setDropdownOpen] = useState(false)
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null)
const handleClick = () => {
onSelect?.(item)
@ -141,6 +144,32 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
onDoubleClick?.(item, e)
}
const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
// Only show context menu if there are actions available
if (actionMenuItems.length > 0) {
setContextMenuPosition({ x: e.clientX, y: e.clientY })
setDropdownOpen(true)
}
}
// Close context menu when clicking outside
useEffect(() => {
const handleClickOutside = () => {
if (dropdownOpen) {
setDropdownOpen(false)
setContextMenuPosition(null)
}
}
if (dropdownOpen) {
document.addEventListener('click', handleClickOutside)
return () => document.removeEventListener('click', handleClickOutside)
}
}, [dropdownOpen])
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation() // Prevent item selection when clicking checkbox
onToggleSelect?.(item)
@ -173,6 +202,35 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
},
]
: []),
// Copy File URL - sadece dosyalar için
...(item.type === 'file'
? [
{
key: 'copyUrl',
label: 'URL Kopyala',
icon: 'HiLink',
onClick: () => {
const fileUrl = FILE_URL(item.path, item.tenantId)
navigator.clipboard.writeText(fileUrl).then(
() => {
toast.push(
<Notification type="success" title="Başarılı">
Dosya URL'si panoya kopyalandı
</Notification>,
)
},
() => {
toast.push(
<Notification type="danger" title="Hata">
URL kopyalanamadı
</Notification>,
)
},
)
},
},
]
: []),
// Create Folder - sadece klasörler için
...(item.type === 'folder' && onCreateFolder
? [
@ -221,18 +279,26 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
]
const dropdownList = (
<div className="py-1 min-w-36">
<div
className="fixed z-50 py-1 min-w-36 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg"
style={contextMenuPosition ? {
left: `${contextMenuPosition.x}px`,
top: `${contextMenuPosition.y}px`,
} : undefined}
>
{actionMenuItems.map((menuItem) => (
<div
key={menuItem.key}
className={classNames(
'flex items-center px-2 py-1 text-xs cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',
'flex items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',
menuItem.dangerous &&
'text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20',
)}
onClick={() => {
onClick={(e) => {
e.stopPropagation()
menuItem.onClick()
setDropdownOpen(false)
setContextMenuPosition(null)
}}
>
{menuItem.icon === 'HiFolderPlus' && (
@ -242,6 +308,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
{menuItem.icon === 'HiArrowDownTray' && (
<FaDownload className="h-4 w-4 mr-3 text-green-500" />
)}
{menuItem.icon === 'HiLink' && <FaLink className="h-4 w-4 mr-3 text-indigo-500" />}
{menuItem.icon === 'HiPencil' && <FaPencilAlt className="h-4 w-4 mr-3 text-orange-500" />}
{menuItem.icon === 'HiArrowRightOnRectangle' && (
<FaSignOutAlt className="h-4 w-4 mr-3 text-purple-500" />
@ -277,19 +344,21 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
if (viewMode === 'list') {
return (
<div
ref={ref}
className={classNames(
'relative group grid grid-cols-12 gap-4 p-2 border rounded-lg cursor-pointer transition-all duration-200 select-none',
'hover:border-blue-300 hover:bg-gray-50 dark:hover:bg-gray-700/50',
selected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800',
className,
)}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
>
<>
<div
ref={ref}
className={classNames(
'relative group grid grid-cols-12 gap-4 p-2 border rounded-lg cursor-pointer transition-all duration-200 select-none',
'hover:border-blue-300 hover:bg-gray-50 dark:hover:bg-gray-700/50',
selected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800',
className,
)}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
onContextMenu={handleContextMenu}
>
{/* File Name */}
<div className="col-span-5 flex items-center min-w-0 gap-2">
{/* Checkbox */}
@ -364,24 +433,30 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
<div className="col-span-1 sm:col-span-1 flex items-center justify-end">
</div>
</div>
{/* Context Menu */}
{dropdownOpen && contextMenuPosition && dropdownList}
</>
)
}
// Grid view (varsayılan)
return (
<div
ref={ref}
className={classNames(
'relative group p-4 border rounded-xl cursor-pointer transition-all duration-300 select-none',
'hover:border-blue-400 hover:shadow-lg hover:-translate-y-1',
selected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 shadow-lg'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm',
className,
)}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
>
<>
<div
ref={ref}
className={classNames(
'relative group p-4 border rounded-xl cursor-pointer transition-all duration-300 select-none',
'hover:border-blue-400 hover:shadow-lg hover:-translate-y-1',
selected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 shadow-lg'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm',
className,
)}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
onContextMenu={handleContextMenu}
>
{/* Checkbox */}
{!item.isReadOnly && (
<div className="absolute top-3 left-3 z-10">
@ -442,6 +517,10 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
)}
</div>
</div>
{/* Context Menu */}
{dropdownOpen && contextMenuPosition && dropdownList}
</>
)
})