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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user