Klasörlerin altında öğe adedi gösterilecek
This commit is contained in:
parent
697c7c1d65
commit
78cce3d4fb
6 changed files with 123 additions and 16 deletions
|
|
@ -16,6 +16,7 @@ public class FileItemDto
|
||||||
public string Path { get; set; } = string.Empty;
|
public string Path { get; set; } = string.Empty;
|
||||||
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; } // Klasörler için öğe sayısı
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetFilesDto
|
public class GetFilesDto
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,20 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
var dirInfo = new DirectoryInfo(dir);
|
var dirInfo = new DirectoryInfo(dir);
|
||||||
var relativePath = string.IsNullOrEmpty(folderPath) ? dirInfo.Name : $"{folderPath}/{dirInfo.Name}";
|
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
|
items.Add(new FileMetadata
|
||||||
{
|
{
|
||||||
Id = EncodePathAsId(relativePath),
|
Id = EncodePathAsId(relativePath),
|
||||||
|
|
@ -148,7 +162,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
Path = relativePath,
|
Path = relativePath,
|
||||||
ParentId = folderPath ?? "",
|
ParentId = folderPath ?? "",
|
||||||
IsReadOnly = false,
|
IsReadOnly = false,
|
||||||
TenantId = _currentTenant.Id?.ToString()
|
TenantId = _currentTenant.Id?.ToString(),
|
||||||
|
ChildCount = childCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,7 +263,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
ModifiedAt = metadata.ModifiedAt,
|
ModifiedAt = metadata.ModifiedAt,
|
||||||
Path = metadata.Path,
|
Path = metadata.Path,
|
||||||
ParentId = parentId ?? string.Empty,
|
ParentId = parentId ?? string.Empty,
|
||||||
IsReadOnly = finalIsReadOnly
|
IsReadOnly = finalIsReadOnly,
|
||||||
|
ChildCount = metadata.ChildCount
|
||||||
};
|
};
|
||||||
}).OrderBy(x => x.Type == "folder" ? 0 : 1).ThenBy(x => x.Name).ToList();
|
}).OrderBy(x => x.Type == "folder" ? 0 : 1).ThenBy(x => x.Name).ToList();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,5 @@ public class FileMetadata
|
||||||
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 string? TenantId { get; set; }
|
public string? TenantId { get; set; }
|
||||||
|
public int? ChildCount { get; set; } // Klasörler için öğe sayısı
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ export interface FileItem {
|
||||||
path: string
|
path: string
|
||||||
extension: string
|
extension: string
|
||||||
isReadOnly: boolean
|
isReadOnly: boolean
|
||||||
|
childCount?: number // Klasörler için öğe sayısı
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FolderItem extends Omit<FileItem, 'type' | 'size' | 'mimeType' | 'extension'> {
|
export interface FolderItem extends Omit<FileItem, 'type' | 'size' | 'mimeType' | 'extension'> {
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,15 @@ const FileManager = () => {
|
||||||
'Protected folders check:',
|
'Protected folders check:',
|
||||||
protectedItems.filter((item) => item.isReadOnly),
|
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)
|
setItems(protectedItems)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch items:', error)
|
console.error('Failed to fetch items:', error)
|
||||||
|
|
@ -338,6 +347,34 @@ const FileManager = () => {
|
||||||
setDeleteModalOpen(true)
|
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 = () => {
|
const goUpOneLevel = () => {
|
||||||
if (breadcrumbItems.length > 1) {
|
if (breadcrumbItems.length > 1) {
|
||||||
const parentBreadcrumb = breadcrumbItems[breadcrumbItems.length - 2]
|
const parentBreadcrumb = breadcrumbItems[breadcrumbItems.length - 2]
|
||||||
|
|
@ -422,11 +459,44 @@ const FileManager = () => {
|
||||||
setSelectedItems([])
|
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 itemsToDelete = filteredItems.filter((item) => selectedItems.includes(item.id))
|
||||||
const deletableItems = itemsToDelete.filter((item) => !item.isReadOnly)
|
const deletableItems = itemsToDelete.filter((item) => !item.isReadOnly)
|
||||||
const protectedItems = 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) {
|
if (protectedItems.length > 0) {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Warning" type="warning">
|
<Notification title="Warning" type="warning">
|
||||||
|
|
@ -436,11 +506,27 @@ const FileManager = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deletableItems.length > 0) {
|
if (foldersWithFiles.length > 0) {
|
||||||
openDeleteModal(deletableItems)
|
toast.push(
|
||||||
// Remove protected items from selection
|
<Notification title="Security Warning" type="warning">
|
||||||
const deletableIds = deletableItems.map((item) => item.id)
|
{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)))
|
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}
|
onDownload={item.type === 'file' ? handleDownload : undefined}
|
||||||
onPreview={
|
onPreview={
|
||||||
item.type === 'file'
|
item.type === '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>
|
<span className="text-sm text-gray-500 dark:text-gray-400">{getFileTypeLabel(item)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* File Size */}
|
{/* File Size / Folder Item Count */}
|
||||||
<div className="col-span-2 sm:col-span-2 flex items-center">
|
<div className="col-span-2 sm:col-span-2 flex items-center">
|
||||||
{item.type === 'file' && item.size ? (
|
{item.type === 'file' && item.size ? (
|
||||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
{formatFileSize(item.size)}
|
{formatFileSize(item.size)}
|
||||||
</span>
|
</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>
|
<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 */}
|
{/* Folder Child Count */}
|
||||||
{item.type === 'folder' &&
|
{item.type === 'folder' && (
|
||||||
'childCount' in item &&
|
|
||||||
typeof (item as any).childCount === 'number' && (
|
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
<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>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue