Files
ClaudeDo/src/ClaudeDo.Ui/ViewModels/Modals/Settings/TicketSettingsTabViewModel.cs
T
mika kuns 30d0611b8e fix(worker): fix Bandel ticket sync and reactivate the integration
The status PATCH needs statusName alongside statusId or the API 400s,
so every write-back had been silently failing. The board's flat
assigneeName is null on every live ticket; the real assignee list is
ticketAssignees[]. Import now also picks up In Bearbeitung tickets,
not just Offen ones.

Both issues are live-verified against the real API, so the feature
flag flips back on.
2026-09-02 09:59:06 +02:00

106 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.Settings;
public sealed partial class TicketSettingsTabViewModel : ViewModelBase
{
/// <summary>
/// Der Aus-Schalter der gesamten Ticketsystem-Anbindung in der UI, wie bei
/// <c>SettingsModalViewModel.ShowOnlineInbox</c>. Er versteckt drei Einstiegspunkte: diesen
/// Settings-Tab, das Ticket-Projekt-Dropdown in den Listen-Einstellungen und den
/// Kontextmenü-Eintrag zum Importieren.
///
/// Pausiert am 2026-08-27 (API unter dem Hostnamen nicht erreichbar), reaktiviert am
/// 2026-09-01: die API läuft unter <c>http://192.168.45.161</c> und alle vier Routen sind
/// live verifiziert. Base-URL ist deshalb eine IP, kein Hostname — <c>bandelapis.fb-tuning.local</c>
/// lehnt die Verbindung weiterhin ab.
/// </summary>
public const bool FeatureEnabled = true;
private readonly IWorkerClient _worker;
[ObservableProperty] private string _apiBaseUrl = "";
[ObservableProperty] private string _token = "";
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(TokenPlaceholder))]
private bool _tokenSet;
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private string _statusMessage = "";
// 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 async Task LoadAsync()
{
var dto = await _worker.GetTicketSettingsAsync();
if (dto is null)
{
StatusMessage = Loc.T("vm.tickets.workerOffline");
return;
}
ApiBaseUrl = dto.ApiBaseUrl ?? "";
TokenSet = dto.TokenSet;
}
public async Task SaveAsync()
{
await _worker.SetTicketApiBaseUrlAsync(ApiBaseUrl);
if (!string.IsNullOrWhiteSpace(Token))
{
await _worker.SetTicketTokenAsync(Token);
Token = "";
TokenSet = true;
}
}
[RelayCommand]
private async Task TestConnection()
{
IsBusy = true;
StatusMessage = "";
try
{
await SaveAsync();
var result = await _worker.TestTicketConnectionAsync();
if (result is null || !result.Ok)
{
StatusMessage = result?.Error ?? Loc.T("vm.tickets.workerOffline");
return;
}
var missing = RequiredScopes.Where(s => !result.Scopes.Contains(s)).ToList();
StatusMessage = missing.Count == 0
? Loc.T("vm.tickets.connectionOk", result.UserName!)
: Loc.T("vm.tickets.connectionMissingScopes", result.UserName!, string.Join(", ", missing));
}
finally
{
IsBusy = false;
}
}
[RelayCommand]
private async Task ClearToken()
{
await _worker.ClearTicketTokenAsync();
Token = "";
TokenSet = false;
StatusMessage = Loc.T("vm.tickets.tokenCleared");
}
// Ohne diese drei Scopes scheitert entweder der Import oder die Statusrueckmeldung —
// der mit Abstand haeufigste Konfigurationsfehler, deshalb wird er beim Testen benannt
// statt nur geloggt.
private static readonly string[] RequiredScopes =
[
"pat:projects:read",
"pat:board:read",
"pat:tickets:write",
];
}