feat(ui): drive prime schedule rows from weekday toggles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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, // Mon–Fri
|
||||||
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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user