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.
95 lines
3.5 KiB
C#
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); }
|
|
}
|
|
}
|