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