diff --git a/src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs b/src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs index ce09b6d..c6959f6 100644 --- a/src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs @@ -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 LiveLines { get; } = new(); + public ObservableCollection Tags { get; } = new(); + [ObservableProperty] private string _newTagInput = ""; private string? _taskId; private string? _listId; + private bool _isLoading; private const int MaxLiveLines = 500; + public event Action? 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(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)