feat(ui): WeeklyReportModalViewModel with default-range logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ public interface IWorkerClient : INotifyPropertyChanged
|
||||
Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default);
|
||||
Task<string?> GetWeekReportAsync(DateOnly start, DateOnly end);
|
||||
Task<string> GenerateWeekReportAsync(DateOnly start, DateOnly end);
|
||||
Task<AppSettingsDto?> GetAppSettingsAsync();
|
||||
Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day);
|
||||
Task<DailyNoteDto?> AddDailyNoteAsync(DateOnly day, string text);
|
||||
Task UpdateDailyNoteAsync(string id, string text);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
public sealed partial class WeeklyReportModalViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IWorkerClient _worker;
|
||||
|
||||
public WeeklyReportModalViewModel(IWorkerClient worker) => _worker = worker;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(HasReport))]
|
||||
[NotifyPropertyChangedFor(nameof(EmptyStateVisible))]
|
||||
private string? _reportMarkdown;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(EmptyStateVisible))]
|
||||
[NotifyCanExecuteChangedFor(nameof(GenerateCommand))]
|
||||
private bool _isBusy;
|
||||
|
||||
[ObservableProperty] private DateTime? _startDate;
|
||||
[ObservableProperty] private DateTime? _endDate;
|
||||
[ObservableProperty] private string _statusMessage = "";
|
||||
|
||||
public bool HasReport => !string.IsNullOrWhiteSpace(ReportMarkdown);
|
||||
public bool EmptyStateVisible => !HasReport && !IsBusy;
|
||||
|
||||
public Action? CloseAction { get; set; }
|
||||
[RelayCommand] private void Close() => CloseAction?.Invoke();
|
||||
|
||||
public static (DateOnly Start, DateOnly End) DefaultRange(DayOfWeek standup, DateOnly today)
|
||||
{
|
||||
int diff = ((int)today.DayOfWeek - (int)standup + 7) % 7;
|
||||
if (diff == 0) diff = 7;
|
||||
return (today.AddDays(-diff), today);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var standup = DayOfWeek.Wednesday;
|
||||
var settings = await _worker.GetAppSettingsAsync();
|
||||
if (settings is not null && settings.StandupWeekday is >= 0 and <= 6)
|
||||
standup = (DayOfWeek)settings.StandupWeekday;
|
||||
|
||||
var (start, end) = DefaultRange(standup, DateOnly.FromDateTime(DateTime.Today));
|
||||
StartDate = start.ToDateTime(TimeOnly.MinValue);
|
||||
EndDate = end.ToDateTime(TimeOnly.MinValue);
|
||||
await LoadStoredAsync();
|
||||
}
|
||||
|
||||
partial void OnStartDateChanged(DateTime? value) => _ = LoadStoredAsync();
|
||||
partial void OnEndDateChanged(DateTime? value) => _ = LoadStoredAsync();
|
||||
|
||||
private bool RangeValid => StartDate is not null && EndDate is not null && StartDate <= EndDate;
|
||||
|
||||
private async Task LoadStoredAsync()
|
||||
{
|
||||
if (!RangeValid) return;
|
||||
StatusMessage = "";
|
||||
try
|
||||
{
|
||||
ReportMarkdown = await _worker.GetWeekReportAsync(
|
||||
DateOnly.FromDateTime(StartDate!.Value), DateOnly.FromDateTime(EndDate!.Value));
|
||||
}
|
||||
catch (Exception ex) { StatusMessage = ex.Message; }
|
||||
}
|
||||
|
||||
private bool CanGenerate() => !IsBusy;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanGenerate))]
|
||||
private async Task Generate()
|
||||
{
|
||||
if (!RangeValid) { StatusMessage = "Ungültiger Zeitraum."; return; }
|
||||
IsBusy = true;
|
||||
StatusMessage = "Bericht wird erstellt…";
|
||||
try
|
||||
{
|
||||
ReportMarkdown = await _worker.GenerateWeekReportAsync(
|
||||
DateOnly.FromDateTime(StartDate!.Value), DateOnly.FromDateTime(EndDate!.Value));
|
||||
StatusMessage = "";
|
||||
}
|
||||
catch (Exception ex) { StatusMessage = $"Fehler: {ex.Message}"; }
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,13 @@ public abstract class StubWorkerClient : IWorkerClient
|
||||
public virtual Task ContinuePlanningMergeAsync(string planningTaskId) => Task.CompletedTask;
|
||||
public virtual Task AbortPlanningMergeAsync(string planningTaskId) => Task.CompletedTask;
|
||||
public virtual Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask;
|
||||
public virtual Task<string?> GetWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult<string?>(null);
|
||||
public virtual Task<string> GenerateWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult("");
|
||||
public virtual Task<AppSettingsDto?> GetAppSettingsAsync() => Task.FromResult<AppSettingsDto?>(null);
|
||||
public virtual Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day) => Task.FromResult(new List<DailyNoteDto>());
|
||||
public virtual Task<DailyNoteDto?> AddDailyNoteAsync(DateOnly day, string text) => Task.FromResult<DailyNoteDto?>(null);
|
||||
public virtual Task UpdateDailyNoteAsync(string id, string text) => Task.CompletedTask;
|
||||
public virtual Task DeleteDailyNoteAsync(string id) => Task.CompletedTask;
|
||||
|
||||
protected void RaisePropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
|
||||
24
tests/ClaudeDo.Ui.Tests/ViewModels/WeeklyReportRangeTests.cs
Normal file
24
tests/ClaudeDo.Ui.Tests/ViewModels/WeeklyReportRangeTests.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
namespace ClaudeDo.Ui.Tests.ViewModels;
|
||||
|
||||
public class WeeklyReportRangeTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultRange_TodayIsStandupDay_GoesBackToPreviousStandup()
|
||||
{
|
||||
var (start, end) = WeeklyReportModalViewModel.DefaultRange(
|
||||
DayOfWeek.Wednesday, new DateOnly(2026, 6, 3));
|
||||
Assert.Equal(new DateOnly(2026, 5, 27), start);
|
||||
Assert.Equal(new DateOnly(2026, 6, 3), end);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultRange_MidWeek_StartsAtMostRecentStandup()
|
||||
{
|
||||
var (start, end) = WeeklyReportModalViewModel.DefaultRange(
|
||||
DayOfWeek.Wednesday, new DateOnly(2026, 6, 5));
|
||||
Assert.Equal(new DateOnly(2026, 6, 3), start);
|
||||
Assert.Equal(new DateOnly(2026, 6, 5), end);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user