Klasörlerin altında öğe adedi gösterilecek

This commit is contained in:
Sedat Öztürk 2025-10-26 19:46:50 +03:00
parent 697c7c1d65
commit 78cce3d4fb
6 changed files with 123 additions and 16 deletions

View file

@ -16,6 +16,7 @@ public class FileItemDto
public string Path { get; set; } = string.Empty;
public string ParentId { get; set; } = string.Empty;
public bool IsReadOnly { get; set; }
public int? ChildCount { get; set; } // Klasörler için öğe sayısı
}
public class GetFilesDto

View file

@ -138,6 +138,20 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
var dirInfo = new DirectoryInfo(dir);
var relativePath = string.IsNullOrEmpty(folderPath) ? dirInfo.Name : $"{folderPath}/{dirInfo.Name}";
// Klasör içindeki öğe sayısını hesapla
var childCount = 0;
try
{
var childFiles = Directory.GetFiles(dir, "*", SearchOption.TopDirectoryOnly);
var childDirectories = Directory.GetDirectories(dir, "*", SearchOption.TopDirectoryOnly);
childCount = childFiles.Length + childDirectories.Length;
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error counting items in folder: {FolderPath}", dir);
childCount = 0;
}
items.Add(new FileMetadata
{
Id = EncodePathAsId(relativePath),
@ -148,7 +162,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
Path = relativePath,
ParentId = folderPath ?? "",
IsReadOnly = false,
TenantId = _currentTenant.Id?.ToString()
TenantId = _currentTenant.Id?.ToString(),
ChildCount = childCount
});
}
@ -248,7 +263,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
ModifiedAt = metadata.ModifiedAt,
Path = metadata.Path,
ParentId = parentId ?? string.Empty,
IsReadOnly = finalIsReadOnly
IsReadOnly = finalIsReadOnly,
ChildCount = metadata.ChildCount
};
}).OrderBy(x => x.Type == "folder" ? 0 : 1).ThenBy(x => x.Name).ToList();

View file

@ -16,4 +16,5 @@ public class FileMetadata
public string ParentId { get; set; } = string.Empty;
public bool IsReadOnly { get; set; }
public string? TenantId { get; set; }
public int? ChildCount { get; set; } // Klasörler için öğe sayısı
}

View file

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

View file

@ -90,6 +90,15 @@ const FileManager = () => {
'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)
@ -338,6 +347,34 @@ const FileManager = () => {
setDeleteModalOpen(true)
}
const handleSingleItemDelete = async (item: FileItemType) => {
// Check if it's a protected item
if (item.isReadOnly) {
toast.push(
<Notification title="Warning" type="warning">
Protected system folders cannot be deleted.
</Notification>,
)
return
}
// Check if it's a folder containing files
if (item.type === 'folder') {
const hasFiles = await checkFolderHasFiles(item.id)
if (hasFiles) {
toast.push(
<Notification title="Security Warning" type="warning">
Folder '{item.name}' contains files and cannot be deleted for security reasons.
</Notification>,
)
return
}
}
// If all checks pass, open delete modal
openDeleteModal([item])
}
const goUpOneLevel = () => {
if (breadcrumbItems.length > 1) {
const parentBreadcrumb = breadcrumbItems[breadcrumbItems.length - 2]
@ -422,11 +459,44 @@ const FileManager = () => {
setSelectedItems([])
}
const deleteSelectedItems = () => {
// Check if a folder contains files (for security purposes)
const checkFolderHasFiles = async (folderId: string): Promise<boolean> => {
try {
const response = await fileManagementService.getItems(folderId)
const items = response.data.items || []
// Check if folder contains any files (not just other folders)
return items.some(item => item.type === 'file')
} catch (error) {
console.error('Error checking folder contents:', error)
return false
}
}
const deleteSelectedItems = async () => {
const itemsToDelete = filteredItems.filter((item) => selectedItems.includes(item.id))
const deletableItems = itemsToDelete.filter((item) => !item.isReadOnly)
const protectedItems = itemsToDelete.filter((item) => item.isReadOnly)
// Check for folders containing files
const foldersWithFiles: string[] = []
for (const item of deletableItems) {
if (item.type === 'folder') {
const hasFiles = await checkFolderHasFiles(item.id)
if (hasFiles) {
foldersWithFiles.push(item.name)
}
}
}
// Filter out folders that contain files
const finalDeletableItems = deletableItems.filter(item => {
if (item.type === 'folder') {
return !foldersWithFiles.includes(item.name)
}
return true
})
// Show warnings
if (protectedItems.length > 0) {
toast.push(
<Notification title="Warning" type="warning">
@ -436,11 +506,27 @@ const FileManager = () => {
)
}
if (deletableItems.length > 0) {
openDeleteModal(deletableItems)
// Remove protected items from selection
const deletableIds = deletableItems.map((item) => item.id)
if (foldersWithFiles.length > 0) {
toast.push(
<Notification title="Security Warning" type="warning">
{foldersWithFiles.length} folder(s) containing files cannot be deleted for security reasons:{' '}
{foldersWithFiles.join(', ')}
</Notification>,
)
}
if (finalDeletableItems.length > 0) {
openDeleteModal(finalDeletableItems)
// Remove protected items and folders with files from selection
const deletableIds = finalDeletableItems.map((item) => item.id)
setSelectedItems((prev) => prev.filter((id) => deletableIds.includes(id)))
} else if (itemsToDelete.length > 0) {
// If no items can be deleted, show info message
toast.push(
<Notification title="Info" type="info">
No items can be deleted. Selected items are either protected or folders containing files.
</Notification>,
)
}
}
@ -927,7 +1013,7 @@ const FileManager = () => {
)
}
}}
onDelete={(item) => openDeleteModal([item])}
onDelete={handleSingleItemDelete}
onDownload={item.type === 'file' ? handleDownload : undefined}
onPreview={
item.type === 'file'

View file

@ -338,12 +338,16 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
<span className="text-sm text-gray-500 dark:text-gray-400">{getFileTypeLabel(item)}</span>
</div>
{/* File Size */}
{/* File Size / Folder Item Count */}
<div className="col-span-2 sm:col-span-2 flex items-center">
{item.type === 'file' && item.size ? (
<span className="text-sm text-gray-500 dark:text-gray-400">
{formatFileSize(item.size)}
</span>
) : item.type === 'folder' && typeof item.childCount === 'number' ? (
<span className="text-sm text-gray-500 dark:text-gray-400">
{item.childCount} öğe
</span>
) : (
<span className="text-sm text-gray-500 dark:text-gray-400">-</span>
)}
@ -431,11 +435,9 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
)}
{/* Folder Child Count */}
{item.type === 'folder' &&
'childCount' in item &&
typeof (item as any).childCount === 'number' && (
{item.type === 'folder' && (
<p className="text-xs text-gray-500 dark:text-gray-400">
{(item as any).childCount} öğe
{typeof item.childCount === 'number' ? `${item.childCount} öğe` : 'Sayılıyor...'}
</p>
)}
</div>