feat(ui): make TaskDetailViewModel editable with auto-save and tag CRUD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-04-14 10:24:10 +02:00
parent a4da2e23a1
commit 28a0d9b11f

View File

@@ -16,12 +16,18 @@ public partial class TaskDetailViewModel : ViewModelBase
private readonly ListRepository _listRepo;
private readonly GitService _git;
private readonly WorkerClient _worker;
private readonly TagRepository _tagRepo;
[ObservableProperty] private string _title = "";
[ObservableProperty] private string? _description;
[ObservableProperty] private string? _result;
[ObservableProperty] private string? _logPath;
[ObservableProperty] private string _statusText = "";
[ObservableProperty] private string _statusChoice = "Manual";
[ObservableProperty] private string _commitType = "chore";
public static string[] StatusChoices { get; } = ["Manual", "Queued", "Running", "Done", "Failed"];
public static string[] CommitTypes { get; } = ["feat", "fix", "refactor", "docs", "test", "chore", "ci", "perf", "style", "build"];
// Worktree
[ObservableProperty] private bool _hasWorktree;
@@ -32,19 +38,25 @@ public partial class TaskDetailViewModel : ViewModelBase
// Live stream
public ObservableCollection<string> LiveLines { get; } = new();
public ObservableCollection<TagEntity> Tags { get; } = new();
[ObservableProperty] private string _newTagInput = "";
private string? _taskId;
private string? _listId;
private bool _isLoading;
private const int MaxLiveLines = 500;
public event Action<string>? TaskChanged;
public TaskDetailViewModel(TaskRepository taskRepo, WorktreeRepository worktreeRepo,
ListRepository listRepo, GitService git, WorkerClient worker)
ListRepository listRepo, GitService git, WorkerClient worker, TagRepository tagRepo)
{
_taskRepo = taskRepo;
_worktreeRepo = worktreeRepo;
_listRepo = listRepo;
_git = git;
_worker = worker;
_tagRepo = tagRepo;
worker.TaskMessageEvent += OnTaskMessage;
worker.WorktreeUpdatedEvent += OnWorktreeUpdated;
@@ -59,16 +71,77 @@ public partial class TaskDetailViewModel : ViewModelBase
var task = await _taskRepo.GetByIdAsync(taskId);
if (task is null) return;
_listId = task.ListId;
Title = task.Title;
Description = task.Description;
Result = task.Result;
LogPath = task.LogPath;
StatusText = task.Status.ToString().ToLowerInvariant();
_isLoading = true;
try
{
_listId = task.ListId;
Title = task.Title;
Description = task.Description;
Result = task.Result;
LogPath = task.LogPath;
StatusText = task.Status.ToString().ToLowerInvariant();
StatusChoice = task.Status.ToString();
CommitType = task.CommitType;
Tags.Clear();
var tags = await _taskRepo.GetTagsAsync(taskId);
foreach (var tag in tags)
Tags.Add(tag);
}
finally
{
_isLoading = false;
}
await LoadWorktreeAsync(taskId);
}
public async Task SaveAsync()
{
if (_isLoading || _taskId is null) return;
var entity = await _taskRepo.GetByIdAsync(_taskId);
if (entity is null) return;
entity.Title = Title;
entity.Description = Description;
entity.CommitType = CommitType;
if (Enum.TryParse<Data.Models.TaskStatus>(StatusChoice, true, out var status))
entity.Status = status;
await _taskRepo.UpdateAsync(entity);
StatusText = entity.Status.ToString().ToLowerInvariant();
TaskChanged?.Invoke(_taskId);
}
[RelayCommand]
private async Task AddTag()
{
var name = NewTagInput.Trim();
if (string.IsNullOrEmpty(name) || _taskId is null) return;
var tagId = await _tagRepo.GetOrCreateAsync(name);
await _taskRepo.AddTagAsync(_taskId, tagId);
Tags.Clear();
var tags = await _taskRepo.GetTagsAsync(_taskId);
foreach (var tag in tags)
Tags.Add(tag);
NewTagInput = "";
TaskChanged?.Invoke(_taskId);
}
[RelayCommand]
private async Task RemoveTag(TagEntity tag)
{
if (_taskId is null) return;
await _taskRepo.RemoveTagAsync(_taskId, tag.Id);
Tags.Remove(tag);
TaskChanged?.Invoke(_taskId);
}
public void Clear()
{
_taskId = null;
@@ -80,6 +153,10 @@ public partial class TaskDetailViewModel : ViewModelBase
StatusText = "";
HasWorktree = false;
LiveLines.Clear();
Tags.Clear();
NewTagInput = "";
StatusChoice = "Manual";
CommitType = "chore";
}
private async Task LoadWorktreeAsync(string taskId)