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 Setter)[] CategorySetters = new[] { ("TicketsNew", new Action((vm, value) => { vm.TicketsNew = value; })), ("TicketsActive", new Action((vm, value) => { vm.TicketsActive = value; })), ("TicketsCritical", new Action((vm, value) => { vm.TicketsCritical = value; })), ("TicketsNewInfo", new Action((vm, value) => { vm.TicketsNewInfo = value; })), ("IncidentNew", new Action((vm, value) => { vm.IncidentNew = value; })), ("IncidentActive", new Action((vm, value) => { vm.IncidentActive = value; })), ("IncidentCritical", new Action((vm, value) => { vm.IncidentCritical = value; })), ("IncidentNewInfo", new Action((vm, value) => { vm.IncidentNewInfo = value; })), ("UnassignedTickets", new Action((vm, value) => { vm.UnassignedTickets = value; })), ("UnassignedTicketsCritical", new Action((vm, value) => { vm.UnassignedTicketsCritical = value; })) }; private static readonly (string Key, Action Setter)[] HighlightSetters = new[] { ("TicketsNew", new Action((vm, value) => { vm.TicketsNewHighlighted = value; })), ("TicketsActive", new Action((vm, value) => { vm.TicketsActiveHighlighted = value; })), ("TicketsCritical", new Action((vm, value) => { vm.TicketsCriticalHighlighted = value; })), ("TicketsNewInfo", new Action((vm, value) => { vm.TicketsNewInfoHighlighted = value; })), ("IncidentNew", new Action((vm, value) => { vm.IncidentNewHighlighted = value; })), ("IncidentActive", new Action((vm, value) => { vm.IncidentActiveHighlighted = value; })), ("IncidentCritical", new Action((vm, value) => { vm.IncidentCriticalHighlighted = value; })), ("IncidentNewInfo", new Action((vm, value) => { vm.IncidentNewInfoHighlighted = value; })), ("UnassignedTickets", new Action((vm, value) => { vm.UnassignedTicketsHighlighted = value; })), ("UnassignedTicketsCritical", new Action((vm, value) => { vm.UnassignedTicketsCriticalHighlighted = value; })) }; private static readonly (string Key, Action Setter)[] ChangeHintSetters = new[] { ("TicketsNew", new Action((vm, value) => { vm.TicketsNewChangeHint = value; })), ("TicketsActive", new Action((vm, value) => { vm.TicketsActiveChangeHint = value; })), ("TicketsCritical", new Action((vm, value) => { vm.TicketsCriticalChangeHint = value; })), ("TicketsNewInfo", new Action((vm, value) => { vm.TicketsNewInfoChangeHint = value; })), ("IncidentNew", new Action((vm, value) => { vm.IncidentNewChangeHint = value; })), ("IncidentActive", new Action((vm, value) => { vm.IncidentActiveChangeHint = value; })), ("IncidentCritical", new Action((vm, value) => { vm.IncidentCriticalChangeHint = value; })), ("IncidentNewInfo", new Action((vm, value) => { vm.IncidentNewInfoChangeHint = value; })), ("UnassignedTickets", new Action((vm, value) => { vm.UnassignedTicketsChangeHint = value; })), ("UnassignedTicketsCritical", new Action((vm, value) => { vm.UnassignedTicketsCriticalChangeHint = value; })) }; private readonly Dictionary _personalHighlightStates = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _roleHighlightStates = new Dictionary(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(); } /// /// 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 /// /// 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 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 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; } } } }