@using MudBlazor
@using Tasks.Module.Models
@using Tasks.Module.Services
TASKS SHEETS
@if (_loading)
{
}
else if (_treeItems.Any())
{
@foreach (var item in _treeItems)
{
}
}
else
{
No tasks sheets yet
}
@if (_contextMenuOpen)
{
New Subfolder
New Task List
Delete
}
@code {
private bool _loading = true;
private List _treeItems = new();
private bool _contextMenuOpen = false;
private string? _contextItemId = null;
private TreeItemType _contextItemType = TreeItemType.Folder;
private double _contextMenuX = 0;
private double _contextMenuY = 0;
private bool _rootDragOver = false;
private TreeItemDto? _draggingItem = null;
private bool _isMoving = false;
private int _dragSessionId = 0;
[Inject] private IFolderService FolderService { get; set; } = null!;
[Inject] private ISnackbar Snackbar { get; set; } = null!;
[Inject] private IDialogService DialogService { get; set; } = null!;
[Inject] private NavigationManager NavigationManager { get; set; } = null!;
protected override async Task OnInitializedAsync()
{
await RefreshTree();
}
private async Task RefreshTree()
{
_loading = true;
try
{
var newTreeItems = new List();
var rootFolders = await FolderService.GetAllRootFoldersAsync();
foreach (var folder in rootFolders)
{
var treeItem = await BuildTreeItem(folder);
newTreeItems.Add(treeItem);
}
var rootTasksSheets = await FolderService.GetRootTasksSheetsAsync();
foreach (var ts in rootTasksSheets)
{
newTreeItems.Add(new TreeItemDto
{
Id = ts.Id,
Name = ts.Title,
Type = TreeItemType.TasksSheet,
ParentId = null,
IsExpanded = false,
Children = new List()
});
}
_treeItems = newTreeItems;
}
catch (Exception ex)
{
Snackbar.Add($"Error: {ex.Message}", Severity.Error);
}
finally
{
_loading = false;
}
}
private async Task BuildTreeItem(FolderDto folder)
{
var item = new TreeItemDto
{
Id = folder.Id,
Name = folder.Name,
Type = TreeItemType.Folder,
ParentId = folder.ParentId,
IsExpanded = false,
Children = new List()
};
foreach (var child in folder.Children)
{
var childItem = await BuildTreeItem(child);
item.Children.Add(childItem);
}
try
{
var tasksSheets = await FolderService.GetTasksSheetsInFolderAsync(folder.Id);
foreach (var ts in tasksSheets)
{
item.Children.Add(new TreeItemDto
{
Id = ts.Id,
Name = ts.Title,
Type = TreeItemType.TasksSheet,
ParentId = folder.Id,
IsExpanded = false,
Children = new List()
});
}
}
catch
{
}
return item;
}
private async Task OnItemClick(TreeItemDto item)
{
if (item.Type == TreeItemType.Folder)
{
item.IsExpanded = !item.IsExpanded;
if (item.IsExpanded && !item.Children.Any())
{
await LoadFolderChildren(item);
}
}
else if (item.Type == TreeItemType.TasksSheet)
{
NavigationManager.NavigateTo($"/tasks/taskssheet/{item.Id}");
}
}
private async Task OnItemExpand(TreeItemDto item)
{
if (item.Type != TreeItemType.Folder) return;
item.IsExpanded = !item.IsExpanded;
if (item.IsExpanded && !item.Children.Any())
{
await LoadFolderChildren(item);
}
}
private async Task LoadFolderChildren(TreeItemDto item)
{
try
{
var folder = await FolderService.GetFolderByIdAsync(item.Id);
if (folder != null)
{
item.Children.Clear();
foreach (var child in folder.Children)
{
var childItem = await BuildTreeItem(child);
item.Children.Add(childItem);
}
var tasksSheets = await FolderService.GetTasksSheetsInFolderAsync(item.Id);
foreach (var ts in tasksSheets)
{
item.Children.Add(new TreeItemDto
{
Id = ts.Id,
Name = ts.Title,
Type = TreeItemType.TasksSheet,
ParentId = item.Id,
IsExpanded = false,
Children = new List()
});
}
}
}
catch
{
}
}
private void OnContextMenuWithArgs((TreeItemDto, MouseEventArgs) args)
{
_contextItemId = args.Item1.Id;
_contextItemType = args.Item1.Type;
_contextMenuX = args.Item2.ClientX;
_contextMenuY = args.Item2.ClientY;
_contextMenuOpen = true;
}
private void CloseContextMenu()
{
_contextMenuOpen = false;
_contextItemId = null;
}
private string GetContextMenuStyle()
{
return $"position: fixed; left: {_contextMenuX}px; top: {_contextMenuY}px; z-index: 1100;";
}
private async Task OnCreateFolder()
{
var parameters = new DialogParameters
{
{ "Title", "New Folder" },
{ "Label", "Folder Name" }
};
var dialog = await DialogService.ShowAsync("New Folder", parameters);
var result = await dialog.Result;
if (!result.Canceled && result.Data is string name && !string.IsNullOrWhiteSpace(name))
{
try
{
await FolderService.CreateFolderAsync(new CreateFolderDto { Name = name });
Snackbar.Add($"Created folder '{name}'", Severity.Success);
await RefreshTree();
}
catch (Exception ex)
{
Snackbar.Add($"Error creating folder: {ex.Message}", Severity.Error);
}
}
}
private async Task OnCreateFile()
{
var parameters = new DialogParameters
{
{ "Title", "New Tasks Sheet" },
{ "Label", "Tasks Sheet Name" }
};
var dialog = await DialogService.ShowAsync("New Tasks Sheet", parameters);
var result = await dialog.Result;
if (!result.Canceled && result.Data is string name && !string.IsNullOrWhiteSpace(name))
{
try
{
await FolderService.CreateTasksSheetAsync(new CreateTasksSheetDto { Title = name });
Snackbar.Add($"Created tasks sheet '{name}'", Severity.Success);
await RefreshTree();
}
catch (Exception ex)
{
Snackbar.Add($"Error creating tasks sheet: {ex.Message}", Severity.Error);
}
}
}
private async Task CreateSubFolder()
{
if (_contextItemId == null) return;
var parentId = _contextItemId;
CloseContextMenu();
var parameters = new DialogParameters
{
{ "Title", "New Subfolder" },
{ "Label", "Folder Name" }
};
var dialog = await DialogService.ShowAsync("New Subfolder", parameters);
var result = await dialog.Result;
if (!result.Canceled && result.Data is string name && !string.IsNullOrWhiteSpace(name))
{
try
{
await FolderService.CreateFolderAsync(new CreateFolderDto { Name = name, ParentId = parentId });
Snackbar.Add($"Created subfolder '{name}'", Severity.Success);
await RefreshTree();
}
catch (Exception ex)
{
Snackbar.Add($"Error: {ex.Message}", Severity.Error);
}
}
}
private async Task CreateSubFile()
{
if (_contextItemId == null) return;
var parentId = _contextItemId;
CloseContextMenu();
var parameters = new DialogParameters
{
{ "Title", "New Tasks Sheet" },
{ "Label", "Tasks Sheet Name" }
};
var dialog = await DialogService.ShowAsync("New Tasks Sheet", parameters);
var result = await dialog.Result;
if (!result.Canceled && result.Data is string name && !string.IsNullOrWhiteSpace(name))
{
try
{
await FolderService.CreateTasksSheetAsync(new CreateTasksSheetDto { Title = name, ParentFolderId = parentId });
Snackbar.Add($"Created tasks sheet '{name}'", Severity.Success);
await RefreshTree();
}
catch (Exception ex)
{
Snackbar.Add($"Error: {ex.Message}", Severity.Error);
}
}
}
private async Task DeleteSelected()
{
if (_contextItemId == null) return;
var itemId = _contextItemId;
var itemType = _contextItemType;
CloseContextMenu();
try
{
if (itemType == TreeItemType.Folder)
{
await FolderService.DeleteFolderAsync(itemId);
Snackbar.Add("Folder deleted", Severity.Success);
}
await RefreshTree();
}
catch (Exception ex)
{
Snackbar.Add($"Error: {ex.Message}", Severity.Error);
}
}
private void OnDragStartHandler(TreeItemDto item)
{
_dragSessionId++;
_draggingItem = item;
item.IsDragSource = true;
}
private void OnDragEndHandler(TreeItemDto item)
{
_dragSessionId++;
_isMoving = false;
ClearDragState(_treeItems);
_rootDragOver = false;
_draggingItem = null;
}
private void ClearDragState(List items)
{
foreach (var item in items)
{
item.IsDragSource = false;
item.IsDragOver = false;
if (item.Children.Any())
ClearDragState(item.Children);
}
}
private async Task OnItemDropHandler(TreeItemDto target)
{
var source = _draggingItem;
if (source == null) return;
var sessionId = _dragSessionId;
await MoveItem(source, target.Id, sessionId);
_draggingItem = null;
}
private void OnRootDragEnter()
{
if (_draggingItem != null) _rootDragOver = true;
}
private void OnRootDragOver()
{
if (_draggingItem != null) _rootDragOver = true;
}
private void OnRootDragLeave()
{
_rootDragOver = false;
}
private async Task OnRootDrop()
{
_rootDragOver = false;
var source = _draggingItem;
if (source == null) return;
var sessionId = _dragSessionId;
await MoveItem(source, null, sessionId);
_draggingItem = null;
}
private async Task MoveItem(TreeItemDto source, string? targetParentId, int sessionId)
{
if (_isMoving || sessionId != _dragSessionId) return;
_isMoving = true;
if (source.Id == targetParentId) { _isMoving = false; return; }
if (source.ParentId == targetParentId && targetParentId != null) { _isMoving = false; return; }
if (source.Type == TreeItemType.Folder && targetParentId != null)
{
if (await IsDescendantOf(source.Id, targetParentId))
{
Snackbar.Add("Cannot move a folder into one of its children.", Severity.Error);
_isMoving = false;
return;
}
}
try
{
bool success;
if (source.Type == TreeItemType.Folder)
{
success = await FolderService.MoveFolderAsync(source.Id, targetParentId);
}
else
{
success = await FolderService.MoveTasksSheetAsync(source.Id, targetParentId);
}
if (success)
{
Snackbar.Add($"Moved '{source.Name}'", Severity.Success);
await RefreshTree();
}
else
{
Snackbar.Add($"Could not move '{source.Name}' — item not found.", Severity.Warning);
}
}
catch (Exception ex)
{
Snackbar.Add($"Error moving item: {ex.Message}", Severity.Error);
}
finally
{
_isMoving = false;
}
}
private async Task IsDescendantOf(string ancestorId, string candidateId)
{
var queue = new Queue();
queue.Enqueue(ancestorId);
var visited = new HashSet();
while (queue.Count > 0)
{
var current = queue.Dequeue();
if (!visited.Add(current)) continue;
if (current == candidateId) return true;
var children = await FolderService.GetFolderByIdAsync(current);
if (children == null) continue;
foreach (var child in children.Children)
{
queue.Enqueue(child.Id);
}
}
return false;
}
}