style(ui): tasks header toolbar and add-task row

- Reformat subtitle to "{Weekday}, {Month} {Day} · {open} open".
- Add right-aligned running/review status pill (kbd style).
- Add header icon toolbar: Sort, Eye (toggle completed), MoreHorizontal.
- Wire Eye to IsShowingCompleted [ObservableProperty] on the VM.
- Style add-task row as rounded Surface2 border with dashed Plus circle,
  borderless TextBox, and ENTER kbd chip visible on focus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-04-20 11:30:32 +02:00
parent 287e098c3a
commit 940b72f8dd
3 changed files with 364 additions and 29 deletions

View File

@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using System.Globalization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Data;
@@ -19,12 +20,21 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
public void RequestFocusAddTask() => FocusAddTaskRequested?.Invoke(this, EventArgs.Empty);
public ObservableCollection<TaskRowViewModel> Items { get; } = new();
public ObservableCollection<TaskRowViewModel> OverdueItems { get; } = new();
public ObservableCollection<TaskRowViewModel> OpenItems { get; } = new();
public ObservableCollection<TaskRowViewModel> CompletedItems { get; } = new();
[ObservableProperty] private string _newTaskTitle = "";
[ObservableProperty] private TaskRowViewModel? _selectedTask;
[ObservableProperty] private string _headerTitle = "";
[ObservableProperty] private string _headerEyebrow = "";
[ObservableProperty] private string _subtitle = "";
[ObservableProperty] private string _statusPill = "";
[ObservableProperty] private bool _hasStatusPill;
[ObservableProperty] private bool _isShowingCompleted = true;
[ObservableProperty] private bool _hasOverdue;
[ObservableProperty] private bool _hasCompleted;
[ObservableProperty] private string _completedHeader = "COMPLETED";
public TasksIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory)
{
@@ -40,10 +50,15 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
_currentList = list;
Items.Clear();
OverdueItems.Clear();
OpenItems.Clear();
CompletedItems.Clear();
HasOverdue = false;
HasCompleted = false;
if (list is null) return;
HeaderTitle = list.Name;
HeaderEyebrow = DateTime.Now.ToString("dddd · MMM dd").ToUpperInvariant();
HeaderEyebrow = DateTime.Now.ToString("dddd · MMM dd", CultureInfo.InvariantCulture).ToUpperInvariant();
_ = LoadForListAsync(list, ct);
}
@@ -74,17 +89,56 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
foreach (var t in filtered)
Items.Add(TaskRowViewModel.FromEntity(t));
Regroup();
UpdateSubtitle();
}
catch (OperationCanceledException) { }
}
private void Regroup()
{
OverdueItems.Clear();
OpenItems.Clear();
CompletedItems.Clear();
var today = DateTime.Today;
foreach (var r in Items)
{
if (r.Done)
CompletedItems.Add(r);
else if (r.ScheduledFor is { } d && d.Date < today)
OverdueItems.Add(r);
else
OpenItems.Add(r);
}
HasOverdue = OverdueItems.Count > 0;
HasCompleted = CompletedItems.Count > 0;
CompletedHeader = $"COMPLETED · {CompletedItems.Count}";
}
private void UpdateSubtitle()
{
var now = DateTime.Now;
var open = Items.Count(i => !i.Done);
var running = Items.Count(i => i.Status == TaskStatus.Running);
var review = Items.Count(i => i.Status == TaskStatus.Done && i.Branch != null);
Subtitle = $"{open} open · {running} running · {review} in review";
var weekday = now.ToString("dddd", CultureInfo.CurrentCulture);
var month = now.ToString("MMM", CultureInfo.CurrentCulture);
var day = now.Day;
Subtitle = $"{weekday}, {month} {day} · {open} open";
if (running > 0 || review > 0)
{
StatusPill = $"{running} running · {review} review";
HasStatusPill = true;
}
else
{
StatusPill = "";
HasStatusPill = false;
}
}
[RelayCommand]
@@ -102,7 +156,9 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
await using var db = await _dbFactory.CreateDbContextAsync();
db.Tasks.Add(entity);
await db.SaveChangesAsync();
Items.Insert(0, TaskRowViewModel.FromEntity(entity));
var row = TaskRowViewModel.FromEntity(entity);
Items.Insert(0, row);
Regroup();
NewTaskTitle = "";
UpdateSubtitle();
}
@@ -119,6 +175,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
row.Status = entity.Status;
await db.SaveChangesAsync();
}
Regroup();
UpdateSubtitle();
}
@@ -138,6 +195,15 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
[RelayCommand]
private void Select(TaskRowViewModel row) => SelectedTask = row;
[RelayCommand]
private void ToggleShowCompleted() => IsShowingCompleted = !IsShowingCompleted;
[RelayCommand]
private void Sort() { /* placeholder — UI-only */ }
[RelayCommand]
private void More() { /* placeholder — UI-only */ }
partial void OnSelectedTaskChanged(TaskRowViewModel? value)
{
foreach (var i in Items) i.IsSelected = ReferenceEquals(i, value);