Files
Tasks.Module/Components/Pages/TasksPage.razor
T
2026-08-07 02:17:32 +03:00

389 lines
14 KiB
Plaintext

@page "/tasks"
@using Tasks.Module.Models
@using Tasks.Module.Services
@using Tasks.Module.Components.Dialogs
@inject ITodoService TodoService
@inject IDialogService Dialog
@inject IViewService ViewService
<div class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
<div class="max-w-4xl mx-auto py-8 px-4">
@* Header *@
<div class="mb-8">
<h1 class="text-3xl font-bold text-slate-800">My Tasks</h1>
<p class="text-slate-500 mt-1">Manage your tasks and sub-tasks</p>
</div>
@* Search and Filters *@
<div class="bg-white rounded-2xl shadow-md p-4 mb-4">
<div class="flex flex-wrap items-center gap-3">
<MudTextField T="string"
@bind-Value:get="searchText"
@bind-Value:set="OnSearchChanged"
Placeholder="Search tasks..."
DisableUnderLine="true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
Class="flex-grow"
Style="max-width: 400px;" />
<MudButton Variant="Variant.Text"
Color="Color.Default"
StartIcon="@(_showFilters ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)"
OnClick="ToggleFilters"
Class="rounded">
@(_showFilters ? "Hide Filters" : "Show Filters")
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Default"
StartIcon="@Icons.Material.Filled.Add"
OnClick="CreateView"
Class="rounded">
Add View
</MudButton>
</div>
@if (_showFilters)
{
<div class="flex flex-wrap items-center gap-3 mt-3">
<MudSelect T="int?" Label="Priority" Value="selectedPriority" ValueChanged="OnPriorityChanged" Clearable="true" Class="min-w-[140px]">
<MudSelectItem T="int?" Value="null">All</MudSelectItem>
<MudSelectItem T="int?" Value="0">
<div class="flex items-center gap-2">
<span class="w-2 h-2 bg-green-500 rounded-full"></span> Low
</div>
</MudSelectItem>
<MudSelectItem T="int?" Value="1">
<div class="flex items-center gap-2">
<span class="w-2 h-2 bg-yellow-500 rounded-full"></span> Medium
</div>
</MudSelectItem>
<MudSelectItem T="int?" Value="2">
<div class="flex items-center gap-2">
<span class="w-2 h-2 bg-red-500 rounded-full"></span> High
</div>
</MudSelectItem>
</MudSelect>
<MudSelect T="bool?" Label="Status" Value="selectedCompleted" ValueChanged="OnCompletedChanged" Clearable="true" Class="min-w-[140px]">
<MudSelectItem T="bool?" Value="null">All</MudSelectItem>
<MudSelectItem T="bool?" Value="false">
<div class="flex items-center gap-2">
<MudIcon Icon="@Icons.Material.Filled.Pending" Size="Size.Small" Color="Color.Warning" />
Pending
</div>
</MudSelectItem>
<MudSelectItem T="bool?" Value="true">
<div class="flex items-center gap-2">
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Size="Size.Small" Color="Color.Success" />
Completed
</div>
</MudSelectItem>
</MudSelect>
</div>
}
</div>
@* Quick Add Input *@
<div class="bg-white rounded-2xl shadow-lg p-4 mb-6">
<div class="flex items-center gap-3">
<div class="flex-shrink-0 w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
<MudIcon Icon="@Icons.Material.Filled.Add" Color="Color.Primary" Size="Size.Small" />
</div>
<MudTextField T="string"
@bind-Value:get="quickAddTitle"
@bind-Value:set="(v) => { quickAddTitle = v; StateHasChanged(); }"
Placeholder="Add a task quickly... (press Enter)"
DisableUnderLine="true"
Class="flex-grow"
Adornment="Adornment.None"
OnKeyDown="HandleQuickAdd" />
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="QuickAddTask"
Class="rounded-xl">
Add
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenCreateDialog"
Class="rounded-xl">
Add Task
</MudButton>
</div>
</div>
@* Stats *@
@if (!isLoading && tasks.Any())
{
<div class="flex items-center gap-4 mb-4 text-sm">
<span class="text-slate-500">
@tasks.Count task@(tasks.Count != 1 ? "s" : "")
</span>
@{
var completed = tasks.Count(t => t.IsCompleted);
var pending = tasks.Count - completed;
}
@if (pending > 0)
{
<span class="px-2 py-1 bg-yellow-100 text-yellow-700 rounded-full text-xs">@pending pending</span>
}
@if (completed > 0)
{
<span class="px-2 py-1 bg-green-100 text-green-700 rounded-full text-xs">@completed completed</span>
}
</div>
}
@* Tasks List *@
@if (isLoading)
{
<div class="flex justify-center py-12">
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
</div>
}
else if (tasks.Count == 0)
{
<div class="bg-white rounded-2xl shadow-lg p-12 text-center border border-slate-100">
<div class="w-20 h-20 bg-slate-100 rounded-full flex items-center justify-center mx-auto mb-4">
<MudIcon Icon="@Icons.Material.Filled.Assignment" Size="Size.Large" Color="Color.Default" />
</div>
<h3 class="text-lg font-semibold text-slate-700">No tasks yet</h3>
<p class="text-slate-500 mt-2">Type a task above and press Enter to get started</p>
</div>
}
else
{
<div class="space-y-3">
@foreach (var task in tasks)
{
<TaskCard Task="task"
OnToggle="ToggleTask"
OnDelete="DeleteTask"
OnUpdate="OpenUpdateDialog"
OnAddChild="OpenAddChildDialog"
CollapsedTasks="collapsedTasks"
ToggleCollapse="ToggleCollapse" />
}
</div>
}
</div>
</div>
@code {
private List<TodoItemDto> tasks = new();
private List<TodoItemDto> allTasks = new();
private bool isLoading = true;
private int? selectedPriority;
private bool? selectedCompleted;
private CreateTodoItemDto editTask = new();
private string currentTaskId = string.Empty;
private string quickAddTitle = string.Empty;
private HashSet<string> collapsedTasks = new();
private string searchText = string.Empty;
private bool _showFilters = false;
private void ToggleFilters()
{
_showFilters = !_showFilters;
}
protected override async Task OnInitializedAsync()
{
await LoadTasks();
}
private async Task LoadTasks()
{
isLoading = true;
try
{
var filter = new TodoFilterDto
{
Priority = selectedPriority,
IsCompleted = selectedCompleted
};
allTasks = await TodoService.GetAllTodosAsync(filter);
ApplyFilters();
}
finally
{
isLoading = false;
}
}
private void ApplyFilters()
{
var filtered = allTasks.AsEnumerable();
if (!string.IsNullOrWhiteSpace(searchText))
{
filtered = filtered.Where(t =>
t.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
(t.Description?.Contains(searchText, StringComparison.OrdinalIgnoreCase) ?? false));
}
tasks = filtered.ToList();
}
private void OnSearchChanged(string value)
{
searchText = value;
ApplyFilters();
}
private async Task HandleQuickAdd(KeyboardEventArgs e)
{
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(quickAddTitle))
{
await QuickAddTask();
}
}
private async Task QuickAddTask()
{
if (string.IsNullOrWhiteSpace(quickAddTitle))
return;
var newTask = new CreateTodoItemDto
{
Title = quickAddTitle.Trim(),
Priority = 0
};
await TodoService.CreateTodoAsync(newTask);
quickAddTitle = string.Empty;
await LoadTasks();
}
private void ToggleCollapse(string taskId)
{
if (collapsedTasks.Contains(taskId))
collapsedTasks.Remove(taskId);
else
collapsedTasks.Add(taskId);
}
private async Task OnPriorityChanged(int? value)
{
selectedPriority = value;
await LoadTasks();
}
private async Task OnCompletedChanged(bool? value)
{
selectedCompleted = value;
await LoadTasks();
}
private async Task ToggleTask(string id)
{
await TodoService.ToggleCompleteAsync(id);
await LoadTasks();
}
private async Task DeleteTask(string id)
{
await TodoService.DeleteTodoAsync(id);
await LoadTasks();
}
private async Task OpenCreateDialog()
{
editTask = new CreateTodoItemDto();
var parameters = new DialogParameters<CreateTaskDialog>
{
{ x => x.Task, editTask },
{ x => x.IsUpdate, false }
};
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
var dialog = await Dialog.ShowAsync<CreateTaskDialog>("Create Task", parameters, options);
var result = await dialog.Result;
if (result is { Canceled: false })
{
await TodoService.CreateTodoAsync(editTask);
await LoadTasks();
}
}
private async Task OpenAddChildDialog(TodoItemDto parentTask)
{
editTask = new CreateTodoItemDto { ParentId = parentTask.Id };
var parameters = new DialogParameters<CreateTaskDialog>
{
{ x => x.Task, editTask },
{ x => x.IsUpdate, false },
{ x => x.IsChild, true },
{ x => x.ParentTitle, parentTask.Title }
};
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
var dialog = await Dialog.ShowAsync<CreateTaskDialog>("Add Sub-task", parameters, options);
var result = await dialog.Result;
if (result is { Canceled: false })
{
await TodoService.CreateTodoAsync(editTask);
await LoadTasks();
}
}
private async Task OpenUpdateDialog(TodoItemDto task)
{
currentTaskId = task.Id;
editTask = new CreateTodoItemDto
{
Title = task.Title,
Description = task.Description,
Priority = task.Priority,
DueDate = task.DueDate,
ParentId = task.ParentId,
CategoryId = task.CategoryId,
TagIds = task.TagIds.ToList(),
FileId = task.FileId
};
var parameters = new DialogParameters<CreateTaskDialog>
{
{ x => x.Task, editTask },
{ x => x.IsUpdate, true }
};
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
var dialog = await Dialog.ShowAsync<CreateTaskDialog>("Update Task", parameters, options);
var result = await dialog.Result;
if (result is { Canceled: false })
{
await TodoService.UpdateTodoAsync(new UpdateTodoItemDto
{
Id = currentTaskId,
Title = editTask.Title,
Description = editTask.Description,
Priority = editTask.Priority,
DueDate = editTask.DueDate,
IsCompleted = task.IsCompleted,
ParentId = editTask.ParentId,
CategoryId = editTask.CategoryId,
TagIds = editTask.TagIds,
FileId = editTask.FileId
});
await LoadTasks();
}
}
private async Task CreateView()
{
var parameters = new DialogParameters<CreateViewDialog>();
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
var dialog = await Dialog.ShowAsync<CreateViewDialog>("Create View", parameters, options);
var result = await dialog.Result;
if (result is { Canceled: false } && result.Data is CreateTaskViewDto createDto)
{
await ViewService.CreateViewAsync(createDto);
}
}
}