Files
ClaudeDo/src/ClaudeDo.Ui/ViewModels/Modals/WeeklyReportModalViewModel.cs
T
Mika Kuns bbd6151ba6 feat(claude-do): [A4] WeeklyReport, DailyPrep und Planning-Aktionen auf OperationStatus umstellen
WeeklyReportModalViewModel.Generate, PrepPanelViewModel.PlanDayAsync und die beiden
Planning-Aktionen (QueuePlanningSubtasksAsync, FinalizePlanningSessionAsync) laufen jetzt
durch je eine OperationStatus + OperationIndicator statt handgebauter Spinner. Die beiden
Planning-Aktionen teilen sich eine Instanz, angezeigt im Tasks-Island-Header, weil sie aus
einem sofort schliessenden Kontextmenue ausgeloest werden und es keine dauerhafte
Pro-Zeilen-Flaeche gibt, an die ein Indikator gehaengt werden koennte.
2026-08-12 09:39:42 +02:00

95 lines
3.5 KiB
C#

using ClaudeDo.Ui.Localization;
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;
// OperationStatus is a nested ObservableObject — [NotifyCanExecuteChangedFor] can't see
// into it, so IsRunning changes are relayed by hand.
GenerateOperation.PropertyChanged += (_, e) =>
{
if (e.PropertyName != nameof(OperationStatus.IsRunning)) return;
GenerateCommand.NotifyCanExecuteChanged();
OnPropertyChanged(nameof(EmptyStateVisible));
};
}
public OperationStatus GenerateOperation { get; } = new();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasReport))]
[NotifyPropertyChangedFor(nameof(EmptyStateVisible))]
private string? _reportMarkdown;
[ObservableProperty] private DateTime? _startDate;
[ObservableProperty] private DateTime? _endDate;
[ObservableProperty] private string _statusMessage = "";
public bool HasReport => !string.IsNullOrWhiteSpace(ReportMarkdown);
public bool EmptyStateVisible => !HasReport && !GenerateOperation.IsRunning;
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() => !GenerateOperation.IsRunning;
[RelayCommand(CanExecute = nameof(CanGenerate))]
private async Task Generate()
{
if (!RangeValid) { StatusMessage = Loc.T("vm.weeklyReport.invalidRange"); return; }
using var op = GenerateOperation.Begin(Loc.T("ops.reports.generatingWeekly"));
StatusMessage = "";
try
{
ReportMarkdown = await _worker.GenerateWeekReportAsync(
DateOnly.FromDateTime(StartDate!.Value), DateOnly.FromDateTime(EndDate!.Value));
}
catch (Exception ex) { StatusMessage = Loc.T("vm.weeklyReport.error", ex.Message); }
}
}