393 lines
16 KiB
C#
393 lines
16 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;
|
|
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; }))
|
|
};
|
|
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; }))
|
|
};
|
|
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; }))
|
|
};
|
|
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)
|
|
{
|
|
Dictionary<string, int> counts = null;
|
|
var service = TicketOverviewUpdateService.Instance;
|
|
|
|
if (service != null)
|
|
{
|
|
try
|
|
{
|
|
var scope = useRoleTickets ? TileScope.Role : TileScope.Personal;
|
|
await service.FetchAsync(scope).ConfigureAwait(false);
|
|
counts = service.GetCountsForScope(useRoleTickets);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"[TicketOverview] Service refresh failed: {ex}");
|
|
counts = null;
|
|
}
|
|
}
|
|
|
|
if (counts == null || counts.Count == 0)
|
|
counts = await LoadCountsFallbackAsync(useRoleTickets).ConfigureAwait(false);
|
|
|
|
await Dispatcher.InvokeAsync(() =>
|
|
{
|
|
foreach (var (key, setter) in CategorySetters)
|
|
{
|
|
counts.TryGetValue(key, out var value);
|
|
setter(_viewModel, value);
|
|
}
|
|
|
|
_viewModel.ResetSelection();
|
|
SearchPageView.Instance?.CloseTicketOverviewResults();
|
|
UpdateHighlightState(useRoleTickets);
|
|
}, DispatcherPriority.Background);
|
|
}
|
|
|
|
private async Task<Dictionary<string, int>> LoadCountsFallbackAsync(bool useRoleTickets)
|
|
{
|
|
var counts = CategorySetters.ToDictionary(item => item.Key, _ => 0);
|
|
var communication = cFasdCockpitCommunicationBase.Instance;
|
|
if (communication == null)
|
|
return counts;
|
|
|
|
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] Fallback load failed for '{kvp.Key}': {ex}");
|
|
counts[kvp.Key] = 0;
|
|
}
|
|
}
|
|
|
|
return counts;
|
|
}
|
|
|
|
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 ClearHighlightsForScope(TileScope scope)
|
|
{
|
|
var highlightStates = scope == TileScope.Role ? _roleHighlightStates : _personalHighlightStates;
|
|
if (highlightStates.Count == 0)
|
|
return;
|
|
|
|
highlightStates.Clear();
|
|
UpdateHighlightState(scope == TileScope.Role);
|
|
}
|
|
|
|
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; }
|
|
}
|
|
}
|
|
}
|