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 string ParentId { get; set; } = string.Empty;
public bool IsReadOnly { get; set; } public bool IsReadOnly { get; set; }
public int? ChildCount { get; set; } public int? ChildCount { get; set; }
public string TenantId { get; set; } = string.Empty;
} }
public class GetFilesDto public class GetFilesDto

View file

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

View file

@ -462,7 +462,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
Path = relativePath, Path = relativePath,
ParentId = string.Empty, ParentId = string.Empty,
IsReadOnly = false, 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 DEFAULT_API_NAME = 'Default'
export const AUTH_API_NAME = 'AuthApi' export const AUTH_API_NAME = 'AuthApi'
export const AVATAR_URL = (id?: string, tenantId?: string) => 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 MULTIVALUE_DELIMITER = '|'
export const DX_CLASSNAMES = export const DX_CLASSNAMES =
'dx-viewport dx-device-desktop dx-device-generic dx-theme-generic dx-theme-generic-typography dx-color-scheme-light' '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 extension: string
isReadOnly: boolean isReadOnly: boolean
childCount?: number // Klasörler için öğe sayısı childCount?: number // Klasörler için öğe sayısı
tenantId?: string
} }
export interface FolderItem extends Omit<FileItem, 'type' | 'size' | 'mimeType' | 'extension'> { 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('Fetched items:', protectedItems)
console.log( // console.log(
'Protected folders check:', // 'Protected folders check:',
protectedItems.filter((item) => item.isReadOnly), // protectedItems.filter((item) => item.isReadOnly),
) // )
console.log( // console.log(
'Folders with childCount:', // 'Folders with childCount:',
protectedItems.filter((item) => item.type === 'folder').map(item => ({ // protectedItems.filter((item) => item.type === 'folder').map(item => ({
name: item.name, // name: item.name,
childCount: item.childCount, // childCount: item.childCount,
hasChildCount: 'childCount' in item, // hasChildCount: 'childCount' in item,
type: typeof item.childCount // type: typeof item.childCount
})) // }))
) // )
setItems(protectedItems) setItems(protectedItems)
} catch (error) { } catch (error) {
console.error('Failed to fetch items:', error) console.error('Failed to fetch items:', error)
@ -117,7 +117,7 @@ const FileManager = () => {
} }
const response = await fileManagementService.getFolderPath(folderId) 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[] = [ const pathItems: BreadcrumbItem[] = [
{ name: 'Files', path: '', id: undefined }, { name: 'Files', path: '', id: undefined },
...response.data.path.map((item) => ({ ...response.data.path.map((item) => ({
@ -236,13 +236,6 @@ const FileManager = () => {
formData.append('parentId', 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 fileManagementService.uploadFileDirectly(formData)
} }
await fetchItems(currentFolderId) await fetchItems(currentFolderId)

View file

@ -8,15 +8,17 @@ import {
FaFilm, FaFilm,
FaMusic, FaMusic,
FaArchive, FaArchive,
FaEllipsisV,
FaPencilAlt, FaPencilAlt,
FaSignOutAlt, FaSignOutAlt,
FaTrash, FaTrash,
FaDownload, FaDownload,
FaEye, FaEye,
FaLink,
} from 'react-icons/fa' } from 'react-icons/fa'
import { toast, Notification } from '@/components/ui'
// import { Dropdown } from '@/components/ui' // Artık kullanmıyoruz // import { Dropdown } from '@/components/ui' // Artık kullanmıyoruz
import type { FileItem as FileItemType, FileActionMenuItem } from '@/types/fileManagement' import type { FileItem as FileItemType, FileActionMenuItem } from '@/types/fileManagement'
import { FILE_URL } from '@/constants/app.constant'
export interface FileItemProps { export interface FileItemProps {
item: FileItemType item: FileItemType
@ -132,6 +134,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
} = props } = props
const [dropdownOpen, setDropdownOpen] = useState(false) const [dropdownOpen, setDropdownOpen] = useState(false)
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null)
const handleClick = () => { const handleClick = () => {
onSelect?.(item) onSelect?.(item)
@ -141,6 +144,32 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
onDoubleClick?.(item, e) 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>) => { const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation() // Prevent item selection when clicking checkbox e.stopPropagation() // Prevent item selection when clicking checkbox
onToggleSelect?.(item) 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 // Create Folder - sadece klasörler için
...(item.type === 'folder' && onCreateFolder ...(item.type === 'folder' && onCreateFolder
? [ ? [
@ -221,18 +279,26 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
] ]
const dropdownList = ( 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) => ( {actionMenuItems.map((menuItem) => (
<div <div
key={menuItem.key} key={menuItem.key}
className={classNames( 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 && menuItem.dangerous &&
'text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20', 'text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20',
)} )}
onClick={() => { onClick={(e) => {
e.stopPropagation()
menuItem.onClick() menuItem.onClick()
setDropdownOpen(false) setDropdownOpen(false)
setContextMenuPosition(null)
}} }}
> >
{menuItem.icon === 'HiFolderPlus' && ( {menuItem.icon === 'HiFolderPlus' && (
@ -242,6 +308,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
{menuItem.icon === 'HiArrowDownTray' && ( {menuItem.icon === 'HiArrowDownTray' && (
<FaDownload className="h-4 w-4 mr-3 text-green-500" /> <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 === 'HiPencil' && <FaPencilAlt className="h-4 w-4 mr-3 text-orange-500" />}
{menuItem.icon === 'HiArrowRightOnRectangle' && ( {menuItem.icon === 'HiArrowRightOnRectangle' && (
<FaSignOutAlt className="h-4 w-4 mr-3 text-purple-500" /> <FaSignOutAlt className="h-4 w-4 mr-3 text-purple-500" />
@ -277,6 +344,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
if (viewMode === 'list') { if (viewMode === 'list') {
return ( return (
<>
<div <div
ref={ref} ref={ref}
className={classNames( className={classNames(
@ -289,6 +357,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
)} )}
onClick={handleClick} onClick={handleClick}
onDoubleClick={handleDoubleClick} onDoubleClick={handleDoubleClick}
onContextMenu={handleContextMenu}
> >
{/* File Name */} {/* File Name */}
<div className="col-span-5 flex items-center min-w-0 gap-2"> <div className="col-span-5 flex items-center min-w-0 gap-2">
@ -364,11 +433,16 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
<div className="col-span-1 sm:col-span-1 flex items-center justify-end"> <div className="col-span-1 sm:col-span-1 flex items-center justify-end">
</div> </div>
</div> </div>
{/* Context Menu */}
{dropdownOpen && contextMenuPosition && dropdownList}
</>
) )
} }
// Grid view (varsayılan) // Grid view (varsayılan)
return ( return (
<>
<div <div
ref={ref} ref={ref}
className={classNames( className={classNames(
@ -381,6 +455,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
)} )}
onClick={handleClick} onClick={handleClick}
onDoubleClick={handleDoubleClick} onDoubleClick={handleDoubleClick}
onContextMenu={handleContextMenu}
> >
{/* Checkbox */} {/* Checkbox */}
{!item.isReadOnly && ( {!item.isReadOnly && (
@ -442,6 +517,10 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
)} )}
</div> </div>
</div> </div>
{/* Context Menu */}
{dropdownOpen && contextMenuPosition && dropdownList}
</>
) )
}) })