Files
C4IT-F4SD-Client/FasdDesktopUi/Basics/UserControls/TicketOverview.xaml.cs
2025-11-11 11:03:42 +01:00

363 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using C4IT.FASD.Cockpit.Communication;
using FasdDesktopUi.Basics.Models;
using FasdDesktopUi.Basics.Services.Models;
using FasdDesktopUi.Pages.SearchPage;
namespace FasdDesktopUi.Basics.UserControls
{
public partial class TicketOverview : UserControl
{
private readonly TicketOverviewModel _viewModel = new TicketOverviewModel();
private bool _isInitialized;
private static readonly (string Key, Action<TicketOverviewModel, int> Setter)[] CategorySetters = new[]
{
("TicketsNew", new Action<TicketOverviewModel, int>((vm, value) => { vm.TicketsNew = value; })),
("TicketsActive", new Action<TicketOverviewModel, int>((vm, value) => { vm.TicketsActive = value; })),
("TicketsCritical", new Action<TicketOverviewModel, int>((vm, value) => { vm.TicketsCritical = value; })),
("TicketsNewInfo", new Action<TicketOverviewModel, int>((vm, value) => { vm.TicketsNewInfo = value; })),
("IncidentNew", new Action<TicketOverviewModel, int>((vm, value) => { vm.IncidentNew = value; })),
("IncidentActive", new Action<TicketOverviewModel, int>((vm, value) => { vm.IncidentActive = value; })),
("IncidentCritical", new Action<TicketOverviewModel, int>((vm, value) => { vm.IncidentCritical = value; })),
("IncidentNewInfo", new Action<TicketOverviewModel, int>((vm, value) => { vm.IncidentNewInfo = value; })),
("UnassignedTickets", new Action<TicketOverviewModel, int>((vm, value) => { vm.UnassignedTickets = value; })),
("UnassignedTicketsCritical", new Action<TicketOverviewModel, int>((vm, value) => { vm.UnassignedTicketsCritical = value; }))
};
private static readonly (string Key, Action<TicketOverviewModel, bool> Setter)[] HighlightSetters = new[]
{
("TicketsNew", new Action<TicketOverviewModel, bool>((vm, value) => { vm.TicketsNewHighlighted = value; })),
("TicketsActive", new Action<TicketOverviewModel, bool>((vm, value) => { vm.TicketsActiveHighlighted = value; })),
("TicketsCritical", new Action<TicketOverviewModel, bool>((vm, value) => { vm.TicketsCriticalHighlighted = value; })),
("TicketsNewInfo", new Action<TicketOverviewModel, bool>((vm, value) => { vm.TicketsNewInfoHighlighted = value; })),
("IncidentNew", new Action<TicketOverviewModel, bool>((vm, value) => { vm.IncidentNewHighlighted = value; })),
("IncidentActive", new Action<TicketOverviewModel, bool>((vm, value) => { vm.IncidentActiveHighlighted = value; })),
("IncidentCritical", new Action<TicketOverviewModel, bool>((vm, value) => { vm.IncidentCriticalHighlighted = value; })),
("IncidentNewInfo", new Action<TicketOverviewModel, bool>((vm, value) => { vm.IncidentNewInfoHighlighted = value; })),
("UnassignedTickets", new Action<TicketOverviewModel, bool>((vm, value) => { vm.UnassignedTicketsHighlighted = value; })),
("UnassignedTicketsCritical", new Action<TicketOverviewModel, bool>((vm, value) => { vm.UnassignedTicketsCriticalHighlighted = value; }))
};
private static readonly (string Key, Action<TicketOverviewModel, string> Setter)[] ChangeHintSetters = new[]
{
("TicketsNew", new Action<TicketOverviewModel, string>((vm, value) => { vm.TicketsNewChangeHint = value; })),
("TicketsActive", new Action<TicketOverviewModel, string>((vm, value) => { vm.TicketsActiveChangeHint = value; })),
("TicketsCritical", new Action<TicketOverviewModel, string>((vm, value) => { vm.TicketsCriticalChangeHint = value; })),
("TicketsNewInfo", new Action<TicketOverviewModel, string>((vm, value) => { vm.TicketsNewInfoChangeHint = value; })),
("IncidentNew", new Action<TicketOverviewModel, string>((vm, value) => { vm.IncidentNewChangeHint = value; })),
("IncidentActive", new Action<TicketOverviewModel, string>((vm, value) => { vm.IncidentActiveChangeHint = value; })),
("IncidentCritical", new Action<TicketOverviewModel, string>((vm, value) => { vm.IncidentCriticalChangeHint = value; })),
("IncidentNewInfo", new Action<TicketOverviewModel, string>((vm, value) => { vm.IncidentNewInfoChangeHint = value; })),
("UnassignedTickets", new Action<TicketOverviewModel, string>((vm, value) => { vm.UnassignedTicketsChangeHint = value; })),
("UnassignedTicketsCritical", new Action<TicketOverviewModel, string>((vm, value) => { vm.UnassignedTicketsCriticalChangeHint = value; }))
};
private readonly Dictionary<string, HighlightInfo> _personalHighlightStates = new Dictionary<string, HighlightInfo>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, HighlightInfo> _roleHighlightStates = new Dictionary<string, HighlightInfo>(StringComparer.OrdinalIgnoreCase);
#region Properties
public static TicketOverview Instance { get; private set; }
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.RegisterAttached(
"IsSelected",
typeof(bool),
typeof(TicketOverview),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static void SetIsSelected(UIElement element, bool value) =>
element.SetValue(IsSelectedProperty, value);
public static bool GetIsSelected(UIElement element) =>
(bool)element.GetValue(IsSelectedProperty);
public static readonly DependencyProperty IsHighlightedProperty =
DependencyProperty.RegisterAttached(
"IsHighlighted",
typeof(bool),
typeof(TicketOverview),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static void SetIsHighlighted(UIElement element, bool value) =>
element.SetValue(IsHighlightedProperty, value);
public static bool GetIsHighlighted(UIElement element) =>
(bool)element.GetValue(IsHighlightedProperty);
#endregion
public delegate void TicketOverviewSelectionRequestedEventHandler(object sender, TicketOverviewSelectionRequestedEventArgs e);
public static readonly RoutedEvent SelectionRequestedEvent =
EventManager.RegisterRoutedEvent(
"SelectionRequested",
RoutingStrategy.Bubble,
typeof(TicketOverviewSelectionRequestedEventHandler),
typeof(TicketOverview));
public event TicketOverviewSelectionRequestedEventHandler SelectionRequested
{
add { AddHandler(SelectionRequestedEvent, value); }
remove { RemoveHandler(SelectionRequestedEvent, value); }
}
#region Initial
public TicketOverview()
{
InitializeComponent();
DataContext = _viewModel;
Loaded += TicketOverview_Loaded;
Instance = this;
}
#endregion
#region Click-Events
private void Label_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!(sender is Label lbl) || !(lbl.Tag is string propName) || !(DataContext is TicketOverviewModel vm))
return;
var vmType = vm.GetType();
var boolProp = vmType.GetProperty(propName);
if (boolProp?.PropertyType != typeof(bool))
return;
var wasSelected = (bool)(boolProp.GetValue(vm) ?? false);
var keyBase = propName.EndsWith("Selected", StringComparison.Ordinal)
? propName.Substring(0, propName.Length - "Selected".Length)
: propName;
int count = 0;
var countProp = vmType.GetProperty(keyBase) ?? vmType.GetProperty(keyBase + "Count");
if (countProp?.PropertyType == typeof(int))
count = (int)(countProp.GetValue(vm) ?? 0);
if (count <= 0)
{
boolProp.SetValue(vm, false);
ClearHighlight(keyBase, SearchPageView.Instance?.IsFilterChecked == true);
SearchPageView.Instance?.CloseTicketOverviewResults();
return;
}
if (wasSelected)
{
boolProp.SetValue(vm, false);
ClearHighlight(keyBase, SearchPageView.Instance?.IsFilterChecked == true);
SearchPageView.Instance?.CloseTicketOverviewResults();
return;
}
vm.ResetSelection(); // setzt alle ...Selected auf false
boolProp.SetValue(vm, true);
bool useRoleScope = false;
try { useRoleScope = SearchPageView.Instance?.IsFilterChecked == true; } catch { }
ClearHighlight(keyBase, useRoleScope);
var args = new TicketOverviewSelectionRequestedEventArgs(
SelectionRequestedEvent,
this,
keyBase,
useRoleScope,
count
);
RaiseEvent(args);
}
#endregion
#region Methods
private async void TicketOverview_Loaded(object sender, RoutedEventArgs e)
{
if (_isInitialized)
return;
_isInitialized = true;
bool filter = SearchPageView.Instance?.IsFilterChecked == true;
await UpdateDataContextAsync(filter);
if (SearchPageView.Instance != null)
{
SearchPageView.Instance.FilterToggleCheckedChanged += async (s, isChecked) =>
{
try
{
await UpdateDataContextAsync(isChecked);
}
catch (Exception ex)
{
Debug.WriteLine($"[TicketOverview] Refresh after toggle failed: {ex}");
}
};
}
}
public void ResetSelection()
{
if (DataContext is TicketOverviewModel vm)
vm.ResetSelection();
}
/// <summary>
/// Prüft welche Daten für die Ansicht geladen werden sollen
/// Wenn UseRoledTickets = true dann Rolebased Tickets und Incidents laden
/// Wenn UseRoledTickets = false dann meine Eigenen Tickets und Incidents laden
/// </summary>
/// <param name="useRoleTickets"></param>
private Task UpdateDataContextAsync(bool useRoleTickets)
{
return RefreshCountsAsync(useRoleTickets);
}
private async Task RefreshCountsAsync(bool useRoleTickets)
{
var communication = cFasdCockpitCommunicationBase.Instance;
var counts = CategorySetters.ToDictionary(item => item.Key, _ => 0);
if (communication != null)
{
var tasks = CategorySetters.ToDictionary(
item => item.Key,
item => communication.GetTicketOverviewRelations(item.Key, useRoleTickets, 0));
foreach (var kvp in tasks)
{
try
{
var relations = await kvp.Value.ConfigureAwait(false);
counts[kvp.Key] = relations?.Count ?? 0;
}
catch (Exception ex)
{
Debug.WriteLine($"[TicketOverview] Failed to load count for '{kvp.Key}': {ex}");
counts[kvp.Key] = 0;
}
}
}
await Dispatcher.InvokeAsync(() =>
{
foreach (var (key, setter) in CategorySetters)
{
setter(_viewModel, counts[key]);
}
_viewModel.ResetSelection();
SearchPageView.Instance?.CloseTicketOverviewResults();
UpdateHighlightState(useRoleTickets);
}, DispatcherPriority.Background);
}
public void UpdateCounts(IDictionary<string, int> counts, bool useRoleTickets)
{
if (counts == null)
return;
Dispatcher.Invoke(() =>
{
foreach (var (key, setter) in CategorySetters)
{
counts.TryGetValue(key, out var value);
setter(_viewModel, value);
}
UpdateHighlightState(useRoleTickets);
}, DispatcherPriority.Background);
}
public void SetHighlights(IEnumerable<TileCountChange> changes, bool useRoleTickets)
{
if (changes != null)
{
foreach (var change in changes)
{
if (change.Delta <= 0)
continue;
var target = change.Scope == TileScope.Personal ? _personalHighlightStates : _roleHighlightStates;
if (!target.TryGetValue(change.Key, out var info))
{
info = new HighlightInfo(change.OldCount);
target[change.Key] = info;
}
info.Current = change.NewCount;
if (info.Current == info.Baseline)
target.Remove(change.Key);
}
}
UpdateHighlightState(useRoleTickets);
}
public void RefreshHighlightState(bool useRoleTickets)
{
UpdateHighlightState(useRoleTickets);
}
private void UpdateHighlightState(bool useRoleTickets)
{
Dispatcher.Invoke(() =>
{
var highlightStates = useRoleTickets ? _roleHighlightStates : _personalHighlightStates;
foreach (var (key, setter) in HighlightSetters)
{
setter(_viewModel, highlightStates.ContainsKey(key));
}
foreach (var (key, setter) in ChangeHintSetters)
{
if (highlightStates.TryGetValue(key, out var info))
setter(_viewModel, FormatDelta(info.Current - info.Baseline));
else
setter(_viewModel, null);
}
}, DispatcherPriority.Background);
}
private void ClearHighlight(string key, bool useRoleScope)
{
var highlightStates = useRoleScope ? _roleHighlightStates : _personalHighlightStates;
if (!highlightStates.Remove(key))
return;
UpdateHighlightState(useRoleScope);
}
#endregion
private static string FormatDelta(int delta)
{
if (delta == 0)
return null;
var prefix = delta > 0 ? "+" : string.Empty;
return prefix + delta.ToString();
}
private sealed class HighlightInfo
{
public HighlightInfo(int baseline)
{
Baseline = baseline;
Current = baseline;
}
public int Baseline { get; }
public int Current { get; set; }
}
}
}