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.
This commit is contained in:
Mika Kuns
2026-08-12 09:39:42 +02:00
parent d3abd4b88b
commit bbd6151ba6
9 changed files with 342 additions and 21 deletions
@@ -9,24 +9,32 @@ public sealed partial class WeeklyReportModalViewModel : ViewModelBase
{
private readonly IWorkerClient _worker;
public WeeklyReportModalViewModel(IWorkerClient worker) => _worker = 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]
[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 bool EmptyStateVisible => !HasReport && !GenerateOperation.IsRunning;
public Action? CloseAction { get; set; }
[RelayCommand] private void Close() => CloseAction?.Invoke();
@@ -68,21 +76,19 @@ public sealed partial class WeeklyReportModalViewModel : ViewModelBase
catch (Exception ex) { StatusMessage = ex.Message; }
}
private bool CanGenerate() => !IsBusy;
private bool CanGenerate() => !GenerateOperation.IsRunning;
[RelayCommand(CanExecute = nameof(CanGenerate))]
private async Task Generate()
{
if (!RangeValid) { StatusMessage = Loc.T("vm.weeklyReport.invalidRange"); return; }
IsBusy = true;
StatusMessage = Loc.T("vm.weeklyReport.generating");
using var op = GenerateOperation.Begin(Loc.T("ops.reports.generatingWeekly"));
StatusMessage = "";
try
{
ReportMarkdown = await _worker.GenerateWeekReportAsync(
DateOnly.FromDateTime(StartDate!.Value), DateOnly.FromDateTime(EndDate!.Value));
StatusMessage = "";
}
catch (Exception ex) { StatusMessage = Loc.T("vm.weeklyReport.error", ex.Message); }
finally { IsBusy = false; }
}
}