feat(ui): drive prime schedule rows from weekday toggles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-02 16:40:41 +02:00
parent 81fd186fb2
commit 37738e3c8f
4 changed files with 66 additions and 26 deletions

View File

@@ -2,10 +2,8 @@ namespace ClaudeDo.Ui.Services;
public sealed record PrimeScheduleDto( public sealed record PrimeScheduleDto(
Guid Id, Guid Id,
DateOnly StartDate, int Days,
DateOnly EndDate,
TimeSpan TimeOfDay, TimeSpan TimeOfDay,
bool WorkdaysOnly,
bool Enabled, bool Enabled,
DateTimeOffset? LastRunAt, DateTimeOffset? LastRunAt,
string? PromptOverride); string? PromptOverride);

View File

@@ -30,8 +30,8 @@ public sealed partial class PrimeClaudeTabViewModel : ViewModelBase
{ {
foreach (var r in Rows) foreach (var r in Rows)
{ {
if (r.StartDate > r.EndDate) if (r.DaysMask() == 0)
return $"Schedule {r.TimeOfDay:hh\\:mm}: start date is after end date."; return $"Schedule {r.TimeOfDay:hh\\:mm}: select at least one day.";
if (r.TimeOfDay < TimeSpan.Zero || r.TimeOfDay >= TimeSpan.FromDays(1)) if (r.TimeOfDay < TimeSpan.Zero || r.TimeOfDay >= TimeSpan.FromDays(1))
return "Time must be between 00:00 and 23:59."; return "Time must be between 00:00 and 23:59.";
} }
@@ -52,13 +52,10 @@ public sealed partial class PrimeClaudeTabViewModel : ViewModelBase
[RelayCommand] [RelayCommand]
private void AddSchedule() private void AddSchedule()
{ {
var today = DateOnly.FromDateTime(DateTime.Today);
var dto = new PrimeScheduleDto( var dto = new PrimeScheduleDto(
Id: Guid.NewGuid(), Id: Guid.NewGuid(),
StartDate: today, Days: 31, // MonFri
EndDate: today.AddDays(30),
TimeOfDay: new TimeSpan(7, 0, 0), TimeOfDay: new TimeSpan(7, 0, 0),
WorkdaysOnly: true,
Enabled: true, Enabled: true,
LastRunAt: null, LastRunAt: null,
PromptOverride: null); PromptOverride: null);

View File

@@ -5,14 +5,20 @@ namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
public sealed partial class PrimeScheduleRowViewModel : ViewModelBase public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
{ {
private const int Mon = 1, Tue = 2, Wed = 4, Thu = 8, Fri = 16, Sat = 32, Sun = 64;
public Guid Id { get; } public Guid Id { get; }
public bool IsExisting { get; } public bool IsExisting { get; }
[ObservableProperty] private bool _enabled; [ObservableProperty] private bool _enabled;
[ObservableProperty] private DateOnly _startDate; [ObservableProperty] private bool _monday;
[ObservableProperty] private DateOnly _endDate; [ObservableProperty] private bool _tuesday;
[ObservableProperty] private bool _wednesday;
[ObservableProperty] private bool _thursday;
[ObservableProperty] private bool _friday;
[ObservableProperty] private bool _saturday;
[ObservableProperty] private bool _sunday;
[ObservableProperty] private TimeSpan _timeOfDay; [ObservableProperty] private TimeSpan _timeOfDay;
[ObservableProperty] private bool _workdaysOnly;
[ObservableProperty] private DateTimeOffset? _lastRunAt; [ObservableProperty] private DateTimeOffset? _lastRunAt;
public string LastRunLabel => LastRunAt is { } v ? v.LocalDateTime.ToString("g") : "—"; public string LastRunLabel => LastRunAt is { } v ? v.LocalDateTime.ToString("g") : "—";
@@ -24,13 +30,30 @@ public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
Id = dto.Id; Id = dto.Id;
IsExisting = isExisting; IsExisting = isExisting;
Enabled = dto.Enabled; Enabled = dto.Enabled;
StartDate = dto.StartDate; Monday = (dto.Days & Mon) != 0;
EndDate = dto.EndDate; Tuesday = (dto.Days & Tue) != 0;
Wednesday = (dto.Days & Wed) != 0;
Thursday = (dto.Days & Thu) != 0;
Friday = (dto.Days & Fri) != 0;
Saturday = (dto.Days & Sat) != 0;
Sunday = (dto.Days & Sun) != 0;
TimeOfDay = dto.TimeOfDay; TimeOfDay = dto.TimeOfDay;
WorkdaysOnly = dto.WorkdaysOnly;
LastRunAt = dto.LastRunAt; LastRunAt = dto.LastRunAt;
} }
public int DaysMask()
{
int m = 0;
if (Monday) m |= Mon;
if (Tuesday) m |= Tue;
if (Wednesday) m |= Wed;
if (Thursday) m |= Thu;
if (Friday) m |= Fri;
if (Saturday) m |= Sat;
if (Sunday) m |= Sun;
return m;
}
public PrimeScheduleDto ToDto() => public PrimeScheduleDto ToDto() =>
new(Id, StartDate, EndDate, TimeOfDay, WorkdaysOnly, Enabled, LastRunAt, null); new(Id, DaysMask(), TimeOfDay, Enabled, LastRunAt, null);
} }

View File

@@ -19,13 +19,14 @@ public class PrimeClaudeTabViewModelTests
public Task DeleteAsync(Guid id) { Deletes.Add(id); return Task.CompletedTask; } public Task DeleteAsync(Guid id) { Deletes.Add(id); return Task.CompletedTask; }
} }
private static PrimeScheduleDto Dto(Guid id, int days, TimeSpan time) =>
new(id, days, time, true, null, null);
[Fact] [Fact]
public async Task Load_Populates_Rows() public async Task Load_Populates_Rows()
{ {
var api = new FakeApi(); var api = new FakeApi();
api.Stored.Add(new PrimeScheduleDto( api.Stored.Add(Dto(Guid.NewGuid(), 31, new TimeSpan(7, 0, 0)));
Guid.NewGuid(), new DateOnly(2026,5,1), new DateOnly(2026,5,31),
new TimeSpan(7,0,0), true, true, null, null));
var vm = new PrimeClaudeTabViewModel(api); var vm = new PrimeClaudeTabViewModel(api);
await vm.LoadAsync(); await vm.LoadAsync();
Assert.Single(vm.Rows); Assert.Single(vm.Rows);
@@ -38,8 +39,21 @@ public class PrimeClaudeTabViewModelTests
vm.AddScheduleCommand.Execute(null); vm.AddScheduleCommand.Execute(null);
Assert.Single(vm.Rows); Assert.Single(vm.Rows);
Assert.True(vm.Rows[0].Enabled); Assert.True(vm.Rows[0].Enabled);
Assert.True(vm.Rows[0].WorkdaysOnly); Assert.True(vm.Rows[0].Monday);
Assert.Equal(new TimeSpan(7,0,0), vm.Rows[0].TimeOfDay); Assert.True(vm.Rows[0].Friday);
Assert.False(vm.Rows[0].Saturday);
Assert.Equal(new TimeSpan(7, 0, 0), vm.Rows[0].TimeOfDay);
}
[Fact]
public void Row_Decomposes_And_Recomposes_Days()
{
var vm = new PrimeClaudeTabViewModel(new FakeApi());
vm.AddScheduleCommand.Execute(null);
var row = vm.Rows[0];
Assert.Equal(31, row.DaysMask());
row.Saturday = true;
Assert.Equal(63, row.DaysMask());
} }
[Fact] [Fact]
@@ -48,8 +62,8 @@ public class PrimeClaudeTabViewModelTests
var api = new FakeApi(); var api = new FakeApi();
var keptId = Guid.NewGuid(); var keptId = Guid.NewGuid();
var deletedId = Guid.NewGuid(); var deletedId = Guid.NewGuid();
api.Stored.Add(new PrimeScheduleDto(keptId, new(2026,5,1), new(2026,5,31), new(7,0,0), true, true, null, null)); api.Stored.Add(Dto(keptId, 31, new TimeSpan(7, 0, 0)));
api.Stored.Add(new PrimeScheduleDto(deletedId, new(2026,5,1), new(2026,5,31), new(8,0,0), true, true, null, null)); api.Stored.Add(Dto(deletedId, 31, new TimeSpan(8, 0, 0)));
var vm = new PrimeClaudeTabViewModel(api); var vm = new PrimeClaudeTabViewModel(api);
await vm.LoadAsync(); await vm.LoadAsync();
@@ -63,12 +77,20 @@ public class PrimeClaudeTabViewModelTests
} }
[Fact] [Fact]
public void Validate_Reports_StartAfterEnd() public void Validate_Reports_No_Days_Selected()
{ {
var vm = new PrimeClaudeTabViewModel(new FakeApi()); var vm = new PrimeClaudeTabViewModel(new FakeApi());
vm.AddScheduleCommand.Execute(null); vm.AddScheduleCommand.Execute(null);
vm.Rows[0].StartDate = new DateOnly(2026, 6, 1); var row = vm.Rows[0];
vm.Rows[0].EndDate = new DateOnly(2026, 5, 1); row.Monday = row.Tuesday = row.Wednesday = row.Thursday = row.Friday = false;
Assert.NotNull(vm.Validate()); Assert.NotNull(vm.Validate());
} }
[Fact]
public void Validate_Passes_With_One_Day()
{
var vm = new PrimeClaudeTabViewModel(new FakeApi());
vm.AddScheduleCommand.Execute(null);
Assert.Null(vm.Validate());
}
} }