feat(web): add task detail panel with inline editing, subtasks, and notes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 22:22:45 -05:00
parent e34c5d561f
commit 1d1b2a153e
5 changed files with 573 additions and 0 deletions

View File

@@ -167,6 +167,113 @@ public class BoardModel : PageModel
return await ReturnBoardContentAsync();
}
// Load task detail panel
public async Task<IActionResult> OnGetTaskDetailAsync(int id)
{
var task = await _taskRepo.GetByIdAsync(id);
if (task is null) return NotFound();
return Partial("Partials/_TaskDetail", task);
}
// Update task fields (inline edit)
public async Task<IActionResult> OnPutUpdateTaskAsync(int id, string? title, string? description, string? category, int? estimatedMinutes)
{
var task = await _taskRepo.GetByIdAsync(id);
if (task is null) return NotFound();
if (title is not null) task.Title = title;
if (description is not null) task.Description = description;
if (category is not null) task.Category = category;
if (estimatedMinutes.HasValue) task.EstimatedMinutes = estimatedMinutes;
await _taskRepo.UpdateAsync(task);
return Partial("Partials/_TaskDetail", task);
}
// Add subtask
public async Task<IActionResult> OnPostAddSubtaskAsync(int id, string title)
{
var parent = await _taskRepo.GetByIdAsync(id);
if (parent is null) return NotFound();
var subtask = new WorkTask
{
Title = title,
ParentTaskId = id,
Status = WorkTaskStatus.Pending
};
await _taskRepo.CreateAsync(subtask);
// Reload parent to get updated subtask list
parent = await _taskRepo.GetByIdAsync(id);
return Partial("Partials/_SubtaskList", parent);
}
// Complete subtask
public async Task<IActionResult> OnPutCompleteSubtaskAsync(int id)
{
var task = await _taskRepo.GetByIdAsync(id);
if (task is null) return NotFound();
task.Status = WorkTaskStatus.Completed;
task.CompletedAt = DateTime.UtcNow;
await _taskRepo.UpdateAsync(task);
// Return parent's subtask list
if (task.ParentTaskId.HasValue)
{
var parent = await _taskRepo.GetByIdAsync(task.ParentTaskId.Value);
if (parent is not null)
return Partial("Partials/_SubtaskList", parent);
}
return Content("");
}
// Add note
public async Task<IActionResult> OnPostAddNoteAsync(int id, string content)
{
var task = await _taskRepo.GetByIdAsync(id);
if (task is null) return NotFound();
task.Notes.Add(new TaskNote
{
Content = content,
Type = NoteType.General,
CreatedAt = DateTime.UtcNow
});
await _taskRepo.UpdateAsync(task);
return Partial("Partials/_NotesList", task);
}
// Search tasks (for Ctrl+K modal)
public async Task<IActionResult> OnGetSearchAsync(string? q)
{
var allTasks = await _taskRepo.GetAllAsync();
List<WorkTask> results;
if (string.IsNullOrWhiteSpace(q))
{
results = allTasks
.Where(t => t.Status is WorkTaskStatus.Active or WorkTaskStatus.Paused or WorkTaskStatus.Pending)
.OrderByDescending(t => t.Status == WorkTaskStatus.Active)
.Take(8)
.ToList();
}
else
{
results = allTasks
.Where(t =>
t.Title.Contains(q, StringComparison.OrdinalIgnoreCase) ||
(t.Description?.Contains(q, StringComparison.OrdinalIgnoreCase) ?? false) ||
(t.Category?.Contains(q, StringComparison.OrdinalIgnoreCase) ?? false))
.Take(10)
.ToList();
}
return Partial("Partials/_SearchResults", results);
}
private async Task<IActionResult> ReturnBoardContentAsync()
{
await LoadBoardDataAsync();