feat(ui): tickets + plan day button, fallback list and prime action
This commit is contained in:
@@ -581,6 +581,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
Bind(null, "prep");
|
||||
IsPrepMode = true;
|
||||
_ = Prep.LoadLastPrepLogIfEmptyAsync();
|
||||
_ = Prep.RefreshTicketsConfiguredAsync();
|
||||
}
|
||||
|
||||
/// <param name="source">What triggered the selection (<see cref="TasksIslandViewModel.SelectionSource"/>);
|
||||
|
||||
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Ui.Helpers;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Islands;
|
||||
|
||||
@@ -20,6 +21,9 @@ public sealed partial class PrepPanelViewModel : ViewModelBase, IDisposable
|
||||
|
||||
[ObservableProperty] private bool _isPrepRunning;
|
||||
|
||||
// The button only makes sense with a configured ticket system — otherwise it would just plan the day.
|
||||
[ObservableProperty] private bool _ticketsConfigured;
|
||||
|
||||
// Covers the gap between the click and the worker's PrepStartedEvent broadcast — IsPrepRunning
|
||||
// stays false until that event arrives, so without this the button click looks like a no-op.
|
||||
public OperationStatus PrepOperation { get; } = new();
|
||||
@@ -57,6 +61,7 @@ public sealed partial class PrepPanelViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
if (e.PropertyName != nameof(OperationStatus.IsRunning)) return;
|
||||
PlanDayCommand.NotifyCanExecuteChanged();
|
||||
ImportTicketsAndPlanDayCommand.NotifyCanExecuteChanged();
|
||||
OnPropertyChanged(nameof(IsPlanDayEnabled));
|
||||
};
|
||||
}
|
||||
@@ -78,6 +83,25 @@ public sealed partial class PrepPanelViewModel : ViewModelBase, IDisposable
|
||||
catch { }
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanPlanDay))]
|
||||
private async System.Threading.Tasks.Task ImportTicketsAndPlanDayAsync()
|
||||
{
|
||||
using var op = PrepOperation.Begin(Loc.T("ops.reports.runningTicketImport"));
|
||||
try { await _worker.RunTicketImportNowAsync(); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <summary>Reads whether the ticket system has a base URL and a token; keeps the last value when the worker is unreachable.</summary>
|
||||
public async System.Threading.Tasks.Task RefreshTicketsConfiguredAsync()
|
||||
{
|
||||
if (!TicketSettingsTabViewModel.FeatureEnabled) { TicketsConfigured = false; return; }
|
||||
TicketSettingsDto? dto;
|
||||
try { dto = await _worker.GetTicketSettingsAsync(); }
|
||||
catch { return; }
|
||||
if (dto is null) return;
|
||||
TicketsConfigured = !string.IsNullOrWhiteSpace(dto.ApiBaseUrl) && dto.TokenSet;
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task LoadLastPrepLogIfEmptyAsync()
|
||||
{
|
||||
if (IsPrepRunning || PrepLog.Count > 0) return;
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed partial class PrimeClaudeTabViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private void UpdateAnyFillMyDay() =>
|
||||
AnyFillMyDay = Rows.Any(r => r.Kind == PrimeActionKind.FillMyDay);
|
||||
AnyFillMyDay = Rows.Any(r => r.Kind is PrimeActionKind.FillMyDay or PrimeActionKind.ImportTickets);
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
|
||||
/// ItemsControl share one selection.</summary>
|
||||
public string RadioGroup => $"prime-kind-{Id}";
|
||||
|
||||
// Three booleans rather than an enum-to-bool converter: RadioButton.IsChecked binds
|
||||
// One boolean per kind rather than an enum-to-bool converter: RadioButton.IsChecked binds
|
||||
// TwoWay to a bool, and compiled bindings stay simple.
|
||||
public bool IsPing
|
||||
{
|
||||
@@ -68,6 +68,12 @@ public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
|
||||
set { if (value) Kind = PrimeActionKind.Custom; }
|
||||
}
|
||||
|
||||
public bool IsImportTickets
|
||||
{
|
||||
get => Kind == PrimeActionKind.ImportTickets;
|
||||
set { if (value) Kind = PrimeActionKind.ImportTickets; }
|
||||
}
|
||||
|
||||
public bool ShowPrompt => Kind != PrimeActionKind.Ping;
|
||||
|
||||
public string PromptLabel => Kind == PrimeActionKind.Custom
|
||||
@@ -83,6 +89,7 @@ public sealed partial class PrimeScheduleRowViewModel : ViewModelBase
|
||||
OnPropertyChanged(nameof(IsPing));
|
||||
OnPropertyChanged(nameof(IsFillMyDay));
|
||||
OnPropertyChanged(nameof(IsCustom));
|
||||
OnPropertyChanged(nameof(IsImportTickets));
|
||||
OnPropertyChanged(nameof(ShowPrompt));
|
||||
OnPropertyChanged(nameof(PromptLabel));
|
||||
OnPropertyChanged(nameof(PromptPlaceholder));
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||
|
||||
/// <summary>One fallback-list choice; Id null = no fallback.</summary>
|
||||
public sealed record FallbackListOption(string? Id, string Name);
|
||||
|
||||
public sealed partial class TicketSettingsTabViewModel : ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
@@ -21,6 +27,7 @@ public sealed partial class TicketSettingsTabViewModel : ViewModelBase
|
||||
public const bool FeatureEnabled = true;
|
||||
|
||||
private readonly IWorkerClient _worker;
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
|
||||
[ObservableProperty] private string _apiBaseUrl = "";
|
||||
[ObservableProperty] private string _token = "";
|
||||
@@ -33,7 +40,17 @@ public sealed partial class TicketSettingsTabViewModel : ViewModelBase
|
||||
// Never shows the stored token — TicketSettingsDto only ever carries the bool.
|
||||
public string TokenPlaceholder => TokenSet ? Loc.T("settings.tickets.tokenSet") : "";
|
||||
|
||||
public TicketSettingsTabViewModel(IWorkerClient worker) => _worker = worker;
|
||||
public ObservableCollection<FallbackListOption> FallbackLists { get; } = new();
|
||||
[ObservableProperty] private FallbackListOption? _selectedFallbackList;
|
||||
|
||||
// Only a successful load may write the fallback back — otherwise an offline load would clear it.
|
||||
private bool _fallbackLoaded;
|
||||
|
||||
public TicketSettingsTabViewModel(IWorkerClient worker, IDbContextFactory<ClaudeDoDbContext> dbFactory)
|
||||
{
|
||||
_worker = worker;
|
||||
_dbFactory = dbFactory;
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
@@ -45,6 +62,17 @@ public sealed partial class TicketSettingsTabViewModel : ViewModelBase
|
||||
}
|
||||
ApiBaseUrl = dto.ApiBaseUrl ?? "";
|
||||
TokenSet = dto.TokenSet;
|
||||
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync();
|
||||
var lists = await ctx.Lists.AsNoTracking()
|
||||
.OrderBy(l => l.SortOrder).ThenBy(l => l.Name)
|
||||
.Select(l => new FallbackListOption(l.Id, l.Name))
|
||||
.ToListAsync();
|
||||
FallbackLists.Clear();
|
||||
FallbackLists.Add(new FallbackListOption(null, Loc.T("settings.tickets.fallbackNone")));
|
||||
foreach (var option in lists) FallbackLists.Add(option);
|
||||
SelectedFallbackList = FallbackLists.FirstOrDefault(o => o.Id == dto.FallbackListId) ?? FallbackLists[0];
|
||||
_fallbackLoaded = true;
|
||||
}
|
||||
|
||||
public async Task SaveAsync()
|
||||
@@ -56,6 +84,8 @@ public sealed partial class TicketSettingsTabViewModel : ViewModelBase
|
||||
Token = "";
|
||||
TokenSet = true;
|
||||
}
|
||||
if (_fallbackLoaded)
|
||||
await _worker.SetTicketFallbackListAsync(SelectedFallbackList?.Id);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -108,7 +108,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
||||
Prime = prime;
|
||||
OnlineInbox = new OnlineInboxSettingsViewModel(worker, onlineLoginService);
|
||||
SessionSkills = new SessionSkillsSettingsTabViewModel(worker);
|
||||
Tickets = new TicketSettingsTabViewModel(worker);
|
||||
Tickets = new TicketSettingsTabViewModel(worker, dbFactory);
|
||||
|
||||
_categories = new List<SettingsCategory>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user