feat(ui): editable task status and tags from details panel
Adds a status ComboBox in the Details header (no transition guards) and a Tags section with chips + AutoCompleteBox. TaskRowViewModel.Tags becomes an ObservableCollection so chip lists stay live. TasksIsland caches AllTags for the row context menu and exposes Set/Toggle helpers. Test fakes updated for the new IWorkerClient methods. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
public ObservableCollection<TaskRowViewModel> OverdueItems { get; } = new();
|
||||
public ObservableCollection<TaskRowViewModel> OpenItems { get; } = new();
|
||||
public ObservableCollection<TaskRowViewModel> CompletedItems { get; } = new();
|
||||
public ObservableCollection<string> AllTags { get; } = new();
|
||||
|
||||
[ObservableProperty] private string _newTaskTitle = "";
|
||||
[ObservableProperty] private TaskRowViewModel? _selectedTask;
|
||||
@@ -54,9 +55,22 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
_worker.TaskUpdatedEvent += OnWorkerTaskUpdated;
|
||||
_worker.WorktreeUpdatedEvent += OnWorkerTaskUpdated;
|
||||
_worker.TaskMessageEvent += OnWorkerTaskMessage;
|
||||
_ = RefreshAllTagsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshAllTagsAsync()
|
||||
{
|
||||
if (_worker is null) return;
|
||||
try
|
||||
{
|
||||
var tags = await _worker.GetAllTagsAsync();
|
||||
AllTags.Clear();
|
||||
foreach (var t in tags) AllTags.Add(t);
|
||||
}
|
||||
catch { /* offline */ }
|
||||
}
|
||||
|
||||
private void OnWorkerTaskMessage(string taskId, string line)
|
||||
{
|
||||
var row = Items.FirstOrDefault(r => r.Id == taskId);
|
||||
@@ -83,6 +97,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
var entity = await db.Tasks
|
||||
.Include(t => t.List)
|
||||
.Include(t => t.Worktree)
|
||||
.Include(t => t.Tags)
|
||||
.FirstOrDefaultAsync(t => t.Id == taskId);
|
||||
|
||||
var existing = Items.FirstOrDefault(r => r.Id == taskId);
|
||||
@@ -171,6 +186,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
var all = await db.Tasks
|
||||
.Include(t => t.List)
|
||||
.Include(t => t.Worktree)
|
||||
.Include(t => t.Tags)
|
||||
.OrderBy(t => t.SortOrder).ThenBy(t => t.CreatedAt)
|
||||
.ToListAsync(ct);
|
||||
|
||||
@@ -462,6 +478,30 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
TasksChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public async Task SetStatusOnRowAsync(TaskRowViewModel row, TaskStatus status)
|
||||
{
|
||||
if (_worker is null) return;
|
||||
try { await _worker.SetTaskStatusAsync(row.Id, status); }
|
||||
catch { /* offline; broadcast won't fire */ }
|
||||
}
|
||||
|
||||
public async Task ToggleTagOnRowAsync(TaskRowViewModel row, string tagName)
|
||||
{
|
||||
if (_worker is null) return;
|
||||
var name = tagName.Trim().ToLowerInvariant();
|
||||
if (name.Length == 0) return;
|
||||
var current = row.Tags.ToList();
|
||||
var next = current.Contains(name)
|
||||
? current.Where(t => t != name).ToList()
|
||||
: current.Append(name).ToList();
|
||||
try
|
||||
{
|
||||
await _worker.SetTaskTagsAsync(row.Id, next);
|
||||
await RefreshAllTagsAsync();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SendToQueueAsync(TaskRowViewModel? row)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user