Files
ClaudeDo/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs
mika kuns e7d595244e fix(ui): Planned status uses blue badge style
Previously both Planning and Planned rendered the same amber badge because a
single <Border class="badge planning"> was used. Split into two borders gated
by IsPlanning / IsPlanned so Planned picks up the blue badge.planned style.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:46:39 +02:00

143 lines
6.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel;
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Ui.ViewModels.Islands;
public sealed partial class TaskRowViewModel : ViewModelBase
{
public required string Id { get; init; }
[ObservableProperty] private string _title = "";
[ObservableProperty] private string _listName = "";
[ObservableProperty] private bool _done;
[ObservableProperty] private bool _isStarred;
[ObservableProperty] private bool _isMyDay;
[ObservableProperty] private bool _isSelected;
[ObservableProperty] private TaskStatus _status;
[ObservableProperty] private string? _branch;
[ObservableProperty] private string? _diffStat;
[ObservableProperty] private string? _liveTail;
[ObservableProperty] private DateTime? _scheduledFor;
[ObservableProperty] private int _diffAdditions;
[ObservableProperty] private int _diffDeletions;
[ObservableProperty] private bool _dropHintAbove;
[ObservableProperty] private bool _dropHintBelow;
[ObservableProperty] private string? _parentTaskId;
[ObservableProperty] private bool _isExpanded = true;
public DateTime CreatedAt { get; init; }
public string CreatedAtFormatted => CreatedAt == default ? "—" : $"Created {CreatedAt:MMM d}";
public IReadOnlyList<string> Tags { get; init; } = Array.Empty<string>();
public int StepsCount { get; init; }
public int StepsCompleted { get; init; }
public bool IsChild => !string.IsNullOrEmpty(ParentTaskId);
public bool IsPlanningParent => Status == TaskStatus.Planning || Status == TaskStatus.Planned;
public bool IsPlanning => Status == TaskStatus.Planning;
public bool IsPlanned => Status == TaskStatus.Planned;
public bool IsDraft => Status == TaskStatus.Draft;
public bool CanOpenPlanningSession => Status == TaskStatus.Manual && !IsChild;
public bool CanResumeOrDiscardPlanning => Status == TaskStatus.Planning;
public string? PlanningBadge => Status switch
{
TaskStatus.Planning => "PLANNING",
TaskStatus.Planned => "PLANNED",
_ => null,
};
public bool HasBranch => !string.IsNullOrWhiteSpace(Branch);
public bool HasDiff => DiffAdditions > 0 || DiffDeletions > 0;
public bool HasTags => Tags.Count > 0;
public bool HasSteps => StepsCount > 0;
public bool IsOverdue => ScheduledFor is { } d && d.Date < DateTime.Today && !Done;
public bool IsRunning => Status == TaskStatus.Running;
public bool IsQueued => Status == TaskStatus.Queued;
public bool HasSchedule => ScheduledFor.HasValue;
public bool HasLiveTail => IsRunning && !string.IsNullOrEmpty(LiveTail);
public string DiffAdditionsText => $"+{DiffAdditions}";
public string DiffDeletionsText => $"{DiffDeletions}";
public string StepsText => $"{StepsCompleted}/{StepsCount} steps";
public string StatusChipClass => Status switch
{
TaskStatus.Running => "running",
TaskStatus.Failed => "error",
TaskStatus.Done => "review",
TaskStatus.Queued => "queued",
_ => "idle",
};
partial void OnStatusChanged(TaskStatus value)
{
OnPropertyChanged(nameof(StatusChipClass));
OnPropertyChanged(nameof(IsRunning));
OnPropertyChanged(nameof(IsQueued));
OnPropertyChanged(nameof(HasLiveTail));
OnPropertyChanged(nameof(IsPlanningParent));
OnPropertyChanged(nameof(IsPlanning));
OnPropertyChanged(nameof(IsPlanned));
OnPropertyChanged(nameof(PlanningBadge));
OnPropertyChanged(nameof(IsDraft));
OnPropertyChanged(nameof(CanOpenPlanningSession));
OnPropertyChanged(nameof(CanResumeOrDiscardPlanning));
}
partial void OnParentTaskIdChanged(string? value)
{
OnPropertyChanged(nameof(IsChild));
OnPropertyChanged(nameof(CanOpenPlanningSession));
}
partial void OnBranchChanged(string? value) => OnPropertyChanged(nameof(HasBranch));
partial void OnLiveTailChanged(string? value) => OnPropertyChanged(nameof(HasLiveTail));
partial void OnDoneChanged(bool value) => OnPropertyChanged(nameof(IsOverdue));
partial void OnScheduledForChanged(DateTime? value)
{
OnPropertyChanged(nameof(IsOverdue));
OnPropertyChanged(nameof(HasSchedule));
}
partial void OnDiffAdditionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffAdditionsText)); }
partial void OnDiffDeletionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffDeletionsText)); }
public static TaskRowViewModel FromEntity(TaskEntity t)
{
var (add, del) = ParseDiffStat(t.Worktree?.DiffStat);
return new TaskRowViewModel
{
Id = t.Id,
Title = t.Title,
ListName = t.List?.Name ?? "",
Done = t.Status == TaskStatus.Done,
IsStarred = t.IsStarred,
IsMyDay = t.IsMyDay,
Status = t.Status,
Branch = t.Worktree?.BranchName,
DiffStat = t.Worktree?.DiffStat,
ScheduledFor = t.ScheduledFor,
DiffAdditions = add,
DiffDeletions = del,
CreatedAt = t.CreatedAt,
ParentTaskId = t.ParentTaskId,
};
}
// Best-effort parse of diff stat strings like "+12 -3" or "12 additions, 3 deletions".
private static (int add, int del) ParseDiffStat(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return (0, 0);
int add = 0, del = 0;
var parts = s.Split(new[] { ' ', ',', '\t' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var p in parts)
{
if (p.Length > 1 && p[0] == '+' && int.TryParse(p.AsSpan(1), out var a)) add = a;
else if (p.Length > 1 && (p[0] == '-' || p[0] == '\u2212') && int.TryParse(p.AsSpan(1), out var d)) del = d;
}
return (add, del);
}
}