Files
C4IT-F4SD-Client/FasdDesktopUi/Pages/SearchPage/SearchPageView.xaml.cs
2026-02-04 13:27:03 +01:00

1973 lines
78 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using C4IT.FASD.Base;
using C4IT.FASD.Cockpit.Communication;
using C4IT.Logging;
using C4IT.MultiLanguage;
using FasdDesktopUi.Basics;
using FasdDesktopUi.Basics.Enums;
using FasdDesktopUi.Basics.Helper;
using FasdDesktopUi.Basics.Models;
using FasdDesktopUi.Basics.Services;
using FasdDesktopUi.Basics.Services.Models;
using FasdDesktopUi.Basics.Services.RelationService;
using FasdDesktopUi.Basics.Services.SupportCaseSearchService;
using FasdDesktopUi.Basics.UiActions;
using FasdDesktopUi.Basics.UserControls;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using static C4IT.Logging.cLogManager;
using static FasdDesktopUi.Basics.UserControls.TicketOverview;
namespace FasdDesktopUi.Pages.SearchPage
{
public partial class SearchPageView : Window, ISearchUiProvider
{
private static SearchPageView _instance = null;
private const int WM_NCHITTEST = 0x0084;
private const int HTTRANSPARENT = -1;
#region Ticketübersicht
private readonly HashSet<TileScope> _ticketOverviewNotificationScopesPrimed = new HashSet<TileScope>();
private bool _ticketOverviewFirstEventHandled;
private readonly HashSet<TileScope> _ticketOverviewInitWasEmptyScopes = new HashSet<TileScope>();
private bool _ticketOverviewAutoContinueActive;
private DispatcherTimer _ticketOverviewAutoContinueFallbackTimer;
private EventHandler _ticketOverviewAutoContinueCaseChangedHandler;
private bool _renderTicketOverviewUserNames = false;
private readonly HashSet<cSearchHistorySearchResultEntry> _ticketOverviewHistoryEntries = new HashSet<cSearchHistorySearchResultEntry>();
private const string DemoTicketHasDetailsInfoKey = "Demo.HasTicketDetails";
// Event zum auslösen wenn Toggle geändert wird
public event EventHandler<bool> FilterToggleCheckedChanged;
// Aktueller Zustand der Checkbox
public bool IsFilterChecked => FilterCheckbox.IsChecked == true;
#endregion
public static SearchPageView Instance
{
get
{
return _instance ?? (_instance = new SearchPageView());
}
}
private cF4sdPipeServer _pipeServer;
private cHotKeyManager _hotKeyManager;
private CancellationTokenSource _searchCancellationTokenSource = new CancellationTokenSource();
private bool _isActivating = false;
private cF4sdApiSearchResultRelation preSelectedRelation = null;
private readonly IRelationService _relationService = new RelationService();
public SupportCaseSearchService SearchService { get; }
private SearchPageView()
{
try
{
InitializeComponent();
SearchService = new SupportCaseSearchService(_relationService);
Visibility = Visibility.Visible;
_instance = this;
GetPrimaryScreenSize();
// FilterToggleCheckBox-Events registrieren
FilterCheckbox.Checked += (s, e) => FilterToggleCheckedChanged?.Invoke(this, true);
FilterCheckbox.Unchecked += (s, e) => FilterToggleCheckedChanged?.Invoke(this, false);
Loaded += (s, e) =>
{
Hide();
SearchBarUc.ActivateManualSearch();
ScheduleSearchResultMaxHeightUpdate();
};
SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
SearchBarUc.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
BodyStack_TicketOverview.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
SearchHistoryBorder.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
AddCustomEventHandlers();
if (TicketOverviewUpdateService.Instance != null)
{
TicketOverviewUpdateService.Instance.OverviewCountsChanged += TicketOverviewUpdateService_OverviewCountsChanged;
}
UiSettingsChanged(null, null);
}
catch (Exception E)
{
LogException(E);
}
}
public void GetPrimaryScreenSize()
{
UpdateSearchResultMaxHeight();
}
private void ScheduleSearchResultMaxHeightUpdate()
{
if (Dispatcher == null || Dispatcher.HasShutdownStarted)
return;
Dispatcher.BeginInvoke((Action)UpdateSearchResultMaxHeight, DispatcherPriority.Loaded);
}
private void UpdateSearchResultMaxHeight()
{
if (SearchResultBorder == null)
return;
var workAreaHeight = ActualHeight > 0 ? ActualHeight : SystemParameters.WorkArea.Height;
var reservedHeight = 0.0;
reservedHeight += GetVisibleHeightWithMargin(SearchBarUc);
reservedHeight += GetVisibleHeightWithMargin(BodyStack_TicketOverview);
reservedHeight += GetVisibleHeightWithMargin(SearchHistoryBorder);
if (MainBorder != null)
{
reservedHeight += MainBorder.Padding.Top + MainBorder.Padding.Bottom;
}
var searchResultMargin = SearchResultBorder.Margin;
var availableHeight = workAreaHeight - reservedHeight - searchResultMargin.Top - searchResultMargin.Bottom;
availableHeight = Math.Max(0, availableHeight);
SearchResultBorder.MaxHeight = availableHeight;
}
private static double GetVisibleHeightWithMargin(FrameworkElement element)
{
if (element == null || element.Visibility != Visibility.Visible)
return 0;
var margin = element.Margin;
return element.ActualHeight + margin.Top + margin.Bottom;
}
private void SetSearchResultVisibility(bool isVisible)
{
SearchResultBorder.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
BodyStack_SearchResults.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed;
ScheduleSearchResultMaxHeightUpdate();
}
public void SetSearchHistoryVisibility(bool isVisible)
{
SearchHistoryBorder.Visibility = isVisible && !SearchHistory.IsEmpty() ? Visibility.Visible : Visibility.Collapsed;
BodyStack_SearchResults.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed;
ScheduleSearchResultMaxHeightUpdate();
}
public void ShowLoadingTextItem(string itemText)
{
SetSearchHistoryVisibility(false);
ResultMenu.ShowLoadingTextItem(itemText);
}
public void ShowSearchRelations(cSearchHistorySearchResultEntry searchHistoryEntry, IRelationService relationService, ISearchUiProvider searchUiProvider)
{
try
{
_renderTicketOverviewUserNames = searchHistoryEntry != null && _ticketOverviewHistoryEntries.Contains(searchHistoryEntry);
Dispatcher.Invoke(() => ResultMenu.SetHeaderText(searchHistoryEntry.HeaderText, hideDetailsCheckbox: _renderTicketOverviewUserNames));
cSearchManager.ResolveRelations(searchHistoryEntry.Relations);
ILookup<enumFasdInformationClass, cMenuDataBase> relationsLookup = searchHistoryEntry.Relations
.OrderBy(r => r.UsingLevel)
.ThenBy(r => r.LastUsed)
.ToLookup(GetInformationClass, r => GetMenuData(r, relationService));
Dispatcher.Invoke(() =>
{
ResultMenu.UpdateSearchRelations(relationsLookup);
ResultMenu.SetHeaderText(searchHistoryEntry.HeaderText, hideDetailsCheckbox: _renderTicketOverviewUserNames);
});
}
catch (Exception ex)
{
LogException(ex);
}
enumFasdInformationClass GetInformationClass(cF4sdApiSearchResultRelation relation) => cF4sdIdentityEntry.GetFromSearchResult(relation.Type);
cMenuDataBase GetMenuData(cF4sdApiSearchResultRelation relation, IRelationService subRelationService)
{
try
{
var requiredInformationClasses = new List<enumFasdInformationClass>() { cF4sdIdentityEntry.GetFromSearchResult(relation.Type) };
bool isEnabled = cHealthCardDataHelper.HasAvailableHealthCard(requiredInformationClasses);
string disabledReason = null;
if (!isEnabled)
{
disabledReason = cMultiLanguageSupport.GetItem("Searchbar.NoValidHealthcard") ?? string.Empty;
}
else if (ShouldDisableTicketRelationForDemo(relation))
{
isEnabled = false;
disabledReason = cMultiLanguageSupport.GetItem("Searchbar.Demo.NoTicketDetails") ?? string.Empty;
}
string trailingText = null;
if (_renderTicketOverviewUserNames && relation.Infos != null && relation.Infos.TryGetValue("UserDisplayName", out var relationUserDisplayName))
{
trailingText = relationUserDisplayName;
}
string summaryText = null;
if (_renderTicketOverviewUserNames && relation.Infos != null && relation.Infos.TryGetValue("Summary", out var relationSummary))
{
summaryText = relationSummary;
}
return new cMenuDataSearchRelation(relation)
{
MenuText = relation.DisplayName,
SubMenuText = summaryText,
TrailingText = trailingText,
UiAction = new cUiProcessSearchRelationAction(searchHistoryEntry, relation, relationService, searchUiProvider)
{
DisplayType = isEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled,
Description = isEnabled ? string.Empty : disabledReason,
AlternativeDescription = isEnabled ? string.Empty : disabledReason
}
};
}
catch (Exception ex)
{
LogException(ex);
// >>> Fallback NICHT null: einfacher, aktivierter Eintrag <<<
string fallbackTrailingText = null;
if (_renderTicketOverviewUserNames && relation.Infos != null && relation.Infos.TryGetValue("UserDisplayName", out var relationUserDisplayNameFallback))
{
fallbackTrailingText = relationUserDisplayNameFallback;
}
string fallbackSummaryText = null;
if (_renderTicketOverviewUserNames && relation.Infos != null && relation.Infos.TryGetValue("Summary", out var relationSummaryFallback))
{
fallbackSummaryText = relationSummaryFallback;
}
string fallbackDisabledReason = null;
bool fallbackIsEnabled = true;
try
{
var required = new List<enumFasdInformationClass> { cF4sdIdentityEntry.GetFromSearchResult(relation.Type) };
fallbackIsEnabled = cHealthCardDataHelper.HasAvailableHealthCard(required);
if (!fallbackIsEnabled)
{
fallbackDisabledReason = cMultiLanguageSupport.GetItem("Searchbar.NoValidHealthcard") ?? string.Empty;
}
else if (ShouldDisableTicketRelationForDemo(relation))
{
fallbackIsEnabled = false;
fallbackDisabledReason = cMultiLanguageSupport.GetItem("Searchbar.Demo.NoTicketDetails") ?? string.Empty;
}
}
catch
{
fallbackIsEnabled = true;
fallbackDisabledReason = string.Empty;
}
return new cMenuDataSearchRelation(relation)
{
MenuText = relation.DisplayName,
SubMenuText = fallbackSummaryText,
TrailingText = fallbackTrailingText,
UiAction = new cUiProcessSearchRelationAction(searchHistoryEntry, relation, relationService, searchUiProvider)
{
DisplayType = fallbackIsEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled,
Description = fallbackIsEnabled ? string.Empty : fallbackDisabledReason,
AlternativeDescription = fallbackIsEnabled ? string.Empty : fallbackDisabledReason
}
};
}
}
}
private void TriggeredSearch(string searchValue)
{
try
{
var visibleWindows = Application.Current.Windows.Cast<Window>().Where(window => window.IsVisible && (window is SearchPageView) == false).ToList();
visibleWindows.ForEach(window => window.Visibility = Visibility.Hidden);
SearchBarUc.SetSearchText(searchValue);
Show();
Activate();
}
catch (Exception E)
{
LogException(E);
}
}
private void ShowExternalSearchInfo(string strInfo, cFasdApiSearchResultCollection resultEntry, enumF4sdSearchResultClass Class)
{
var filteredResults = new cFilteredResults(resultEntry) { AutoContinue = true };
var _t = ShowExternalSearchInfoAsync(strInfo, filteredResults, Class, suppressUi: false);
}
private async Task ShowExternalSearchInfoAsync(string strInfo, cFilteredResults result, enumF4sdSearchResultClass Class, bool suppressUi)
{
try
{
await this.Dispatcher.InvokeAsync(async () =>
{
try
{
await SearchBarUc.SetFixedSearchResultAsync(Class, strInfo, result);
if (result.AutoContinue && result.Results?.Count == 1)
{
ResultMenu.IndexOfSelectedResultItem = 0;
ResultMenu.SelectCurrentResultItem();
}
if (!suppressUi && !_ticketOverviewAutoContinueActive)
{
Show();
Activate();
}
}
catch (Exception E)
{
LogException(E);
}
});
}
catch (Exception E)
{
LogException(E);
}
}
private void ActivateManualSearch()
{
CancledSearchAction();
SearchBarUc.ActivateManualSearch();
Show();
Activate();
if (cSearchManager.Instance.HistoryList.Count > 0)
{
SearchHistory.ShowSearchHistory();
SetSearchHistoryVisibility(true);
}
else
SetSearchHistoryVisibility(false);
}
public void PhoneCallSearch(cPhoneSearchParameters searchInfo)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
var _h = Task.Run(async () =>
{
try
{
var _result = await cFasdCockpitCommunicationBase.Instance.GetPhoneSearchResults(searchInfo);
if (_result != null && _result.Count > 0 || cFasdCockpitConfig.Instance?.PhoneSupport?.ShowUnresolvedPhoneNumbers is true)
{
string strTxt;
if (string.IsNullOrEmpty(searchInfo.name))
strTxt = searchInfo.phone;
else
strTxt = $"{searchInfo.name} ({searchInfo.phone})";
var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.Phonecall.Info"), strTxt);
ShowExternalSearchInfo(strInfo, _result, enumF4sdSearchResultClass.Phone);
}
}
catch (Exception E)
{
LogException(E);
}
});
}
catch (Exception E)
{
LogException(E);
}
finally
{
LogMethodEnd(CM);
}
}
private void ComputerDomainSearch(cComputerDomainSearchParameters searchInfo)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
var _h = Task.Run(async () =>
{
try
{
var _result = await cFasdCockpitCommunicationBase.Instance.GetComputerSearchResults(searchInfo.name, searchInfo.domain);
var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.ComputerSearch.Info"), searchInfo.name);
ShowExternalSearchInfo(strInfo, _result, enumF4sdSearchResultClass.Computer);
}
catch (Exception E)
{
LogException(E);
}
});
}
catch (Exception E)
{
LogException(E);
}
finally
{
LogMethodEnd(CM);
}
}
private void UserSidSearch(cUserSidSearchParameters searchInfo)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
var _h = Task.Run(async () =>
{
try
{
var lstSids = searchInfo.sids.Split(',').Select((v) => v.Trim()).ToList();
var _result = await cFasdCockpitCommunicationBase.Instance.GetUserSearchResults(searchInfo.name, lstSids);
var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.UserSearch.Info"), searchInfo.name);
ShowExternalSearchInfo(strInfo, _result, enumF4sdSearchResultClass.User);
}
catch (Exception E)
{
LogException(E);
}
});
}
catch (Exception E)
{
LogException(E);
}
finally
{
LogMethodEnd(CM);
}
}
private void TicketSearch(cTicketSearchParameters searchInfo)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
if (!Guid.TryParse(searchInfo.ticketId, out var _ticketId))
return;
_ = RunTicketSearchAsync(searchInfo.ticketName, _ticketId, searchInfo.userName, searchInfo.sids);
}
catch (Exception E)
{
LogException(E);
}
finally
{
LogMethodEnd(CM);
}
}
private void PipeServer_PipeMessage(string reply)
{
try
{
var type = reply;
if (type.Contains(':'))
type = reply.Substring(0, Math.Max(reply.IndexOf(':'), 0));
type = type.ToLowerInvariant();
if (string.IsNullOrWhiteSpace(type))
{
LogEntry("No valid pipe reply", LogLevels.Warning);
return;
}
if (type == "hello")
return;
if (DefaultLogger.IsDebug)
{
var _msg = new List<string>() { "pipe message received:", reply };
DefaultLogger.LogList(LogLevels.Debug, _msg);
}
if (cConnectionStatusHelper.Instance?.ApiConnectionStatus is cConnectionStatusHelper.enumOnlineStatus.offline)
return;
var value = reply.Remove(0, type.Length).TrimStart(':');
switch (type)
{
case "search":
try
{
Dispatcher.Invoke(() => TriggeredSearch(value));
}
catch (Exception E)
{
LogException(E);
}
break;
case "phonesearch":
try
{
var searchInfo = JsonConvert.DeserializeObject<cPhoneSearchParameters>(value);
PhoneCallSearch(searchInfo);
}
catch (Exception E)
{
LogException(E);
}
break;
case "computerdomainsearch":
try
{
var searchInfo = JsonConvert.DeserializeObject<cComputerDomainSearchParameters>(value);
ComputerDomainSearch(searchInfo);
}
catch (Exception E)
{
LogException(E);
}
break;
case "usersidsearch":
try
{
var searchInfo = JsonConvert.DeserializeObject<cUserSidSearchParameters>(value);
UserSidSearch(searchInfo);
}
catch (Exception E)
{
LogException(E);
}
break;
case "ticketsearch":
try
{
var searchInfo = JsonConvert.DeserializeObject<cTicketSearchParameters>(value);
TicketSearch(searchInfo);
}
catch (Exception E)
{
LogException(E);
}
break;
default:
break;
}
}
catch (Exception E)
{
LogException(E);
}
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
_pipeServer = new cF4sdPipeServer();
_pipeServer.PipeMessage += PipeServer_PipeMessage;
_pipeServer.Listen(App.PipeName, MaxSize: 4096, LowerIntegrity: true);
LogEntry($"Start listening on named pipe '{App.PipeName}'...", LogLevels.Debug);
IntPtr handle = new WindowInteropHelper(this).Handle;
var hwndSource = HwndSource.FromHwnd(handle);
hwndSource.AddHook(SearchViewWindowProc);
hwndSource.AddHook(new HwndSourceHook(cUtility.WindowProc));
var hotKeyDefinitions = new List<cHotKeyManager.cHotKeyDefinition>
{
new cHotKeyManager.cHotKeyDefinition { Id = 1, Modifier = new List<ModifierKeys> { ModifierKeys.Control }, Key = Key.F3, HotKeyAction = HotKeyManager_ActivateSearch },
new cHotKeyManager.cHotKeyDefinition { Id = 2, Modifier = new List<ModifierKeys> { ModifierKeys.Control, ModifierKeys.Alt }, Key = Key.F3, HotKeyAction = HotKeyManager_CopyAndSearch }
};
#if isDemo
if (cFasdCockpitCommunicationBase.Instance?.IsDemo() == true)
{
hotKeyDefinitions.Add(new cHotKeyManager.cHotKeyDefinition
{
Id = 3,
Modifier = new List<ModifierKeys> { ModifierKeys.Control, ModifierKeys.Alt },
Key = Key.T,
HotKeyAction = () => TicketOverviewUpdateService.Instance.SimulateDemoTicket()
});
}
#endif
_hotKeyManager = new cHotKeyManager(handle, hotKeyDefinitions);
}
catch (Exception E)
{
LogException(E);
}
finally
{
LogMethodEnd(CM);
}
}
public void ActivateSearchView()
{
if (_isActivating)
return;
_isActivating = true;
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
bool doShow = true;
if (doShow)
{
ActivateManualSearch();
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
_isActivating = false;
LogMethodEnd(CM);
}
}
private void HotKeyManager_ActivateSearch()
{
if (!IsVisible)
ActivateSearchView();
}
public void BringToFrontPreserveState()
{
Dispatcher.Invoke(() =>
{
if (!IsVisible)
Show();
if (WindowState == WindowState.Minimized)
WindowState = WindowState.Normal;
Activate();
Topmost = true;
Topmost = false;
Focus();
});
}
private async void HotKeyManager_CopyAndSearch()
{
try
{
bool isHotKeyDown = true;
while (isHotKeyDown)
{
isHotKeyDown = _hotKeyManager.IsKeyDown(cHotKeyManager.VirtualKeyStates.VK_F3) || _hotKeyManager.IsKeyDown(cHotKeyManager.VirtualKeyStates.VK_CONTROL) || _hotKeyManager.IsKeyDown(cHotKeyManager.VirtualKeyStates.VK_MENU);
await Task.Delay(50);
}
cUtility.SendCtrlCToCurrentWindow();
await Task.Delay(150);
var copiedText = Clipboard.GetText();
copiedText = cUtility.CleanPhoneString(copiedText);
copiedText = copiedText.Trim().Substring(0, Math.Min(copiedText.Trim().Length, 75));
ActivateSearchView();
if (copiedText != null)
{
await Dispatcher.BeginInvoke((Action)delegate
{
Activate();
SearchBarUc.SetSearchText(copiedText);
}, DispatcherPriority.Render);
}
}
catch (Exception E)
{
LogException(E);
}
}
protected override void OnClosed(EventArgs e)
{
try
{
if (TicketOverviewUpdateService.Instance != null)
TicketOverviewUpdateService.Instance.OverviewCountsChanged -= TicketOverviewUpdateService_OverviewCountsChanged;
_pipeServer?.Dispose();
}
catch { }
_pipeServer = null;
_hotKeyManager?.UnRegisterHotKeys();
RemoveHandler(TicketOverview.SelectionRequestedEvent,
new TicketOverviewSelectionRequestedEventHandler(TicketOverview_SelectionRequested));
base.OnClosed(e);
}
private void AddCustomEventHandlers()
{
AddHandler(cUiActionBase.UiActionClickedEvent, new cUiActionBase.UiActionEventHandlerDelegate(UiActionWasTriggered));
SearchBarUc.CancledSearchAction = CancledSearchAction;
AddHandler(TicketOverview.SelectionRequestedEvent,
new TicketOverviewSelectionRequestedEventHandler(TicketOverview_SelectionRequested), true);
cFasdCockpitConfig.Instance.UiSettingsChanged += UiSettingsChanged;
SearchBarUc.ChangedSearchValue = filteredResults =>
{
UpdateSearchResults(filteredResults);
return Task.CompletedTask;
};
SearchBarUc.SearchValueChanged += HandleSearchValueChanged;
SearchService.RelationsFound += HandleRelationsFound;
}
private async void HandleSearchValueChanged(object sender, string searchValue)
{
const int minSearchLength = 2;
try
{
_searchCancellationTokenSource?.Cancel();
_searchCancellationTokenSource = new CancellationTokenSource();
if (string.IsNullOrWhiteSpace(searchValue))
{
SearchBarUc.SetSpinnerVisibility(Visibility.Hidden);
UpdateSearchResults(new cFilteredResults());
return;
}
if (searchValue.Length < minSearchLength)
return;
SearchBarUc.SetSpinnerVisibility(Visibility.Visible);
cFasdApiSearchResultCollection searchResults = await SupportCaseSearchService.GetSearchResultsAsync(searchValue, _searchCancellationTokenSource.Token);
SearchBarUc.SetSpinnerVisibility(Visibility.Hidden);
UpdateSearchResults(new cFilteredResults(searchResults));
}
catch (Exception ex)
{
LogException(ex);
}
}
private void HandleRelationsFound(object sender, StagedSearchResultRelationsEventArgs e)
{
try
{
Dispatcher.Invoke(() =>
{
var first = e.RelatedTo.FirstOrDefault();
var relationSearchResult = new cSearchHistorySearchResultEntry(first.DisplayName, first.DisplayName, e.RelatedTo.ToList(), e.StagedResultRelations.Relations.ToList(), this);
ShowSearchRelations(relationSearchResult, e.RelationService, this);
UpdatePendingInformationClasses(e.StagedResultRelations.PendingInformationClasses);
});
}
catch (Exception ex)
{
LogException(ex);
}
}
#region Ticketübersicht
private bool CheckTicketOverviewAvailability()
{
return cFasdCockpitConfig.Instance?.Global?.TicketConfiguration?.ShowOverview == true
&& IsTicketIntegrationActive();
}
private bool IsTicketIntegrationActive()
{
try
{
var healthCards = cF4SDCockpitXmlConfig.Instance?.HealthCardConfig?.HealthCards?.Values;
if (healthCards == null)
return false;
return healthCards.Any(card =>
card?.InformationClasses?.Contains(enumFasdInformationClass.Ticket) == true);
}
catch (Exception E)
{
LogException(E);
return false;
}
}
private void UpdateTicketOverviewAvailability()
{
var enabled = CheckTicketOverviewAvailability();
var service = TicketOverviewUpdateService.Instance;
service?.UpdateAvailability(enabled);
if (enabled)
_ = service?.FetchAsync();
if (!enabled)
{
if (Dispatcher.CheckAccess())
{
ApplyTicketOverviewDisabledState();
}
else
{
Dispatcher.Invoke(ApplyTicketOverviewDisabledState);
}
}
}
private void ApplyTicketOverviewDisabledState()
{
_renderTicketOverviewUserNames = false;
_ticketOverviewNotificationScopesPrimed.Clear();
_ticketOverviewFirstEventHandled = false;
_ticketOverviewInitWasEmptyScopes.Clear();
SetTicketOverviewVisibility(false);
(Application.Current as App)?.ClearTicketOverviewTrayNotification();
}
private void SetTicketOverviewVisibility(bool isVisible)
{
var b = isVisible;
if (!CheckTicketOverviewAvailability())
b = false;
BodyStack_TicketOverview.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
TicketOverviewBorder.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
FilterCheckbox.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
RoleLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
OwnTicketsLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
TicketOverviewLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
ScheduleSearchResultMaxHeightUpdate();
}
public void ShowTicketOverviewPane()
{
Dispatcher.Invoke(() =>
{
bool overviewAlreadyVisible = TicketOverviewBorder.Visibility == Visibility.Visible;
SetTicketOverviewVisibility(true);
if (!overviewAlreadyVisible)
{
SetSearchResultVisibility(false);
SetSearchHistoryVisibility(false);
TicketOverviewUc?.ResetSelection();
}
TicketOverviewUc?.RefreshHighlightState(IsFilterChecked);
var app = Application.Current as FasdDesktopUi.App;
app?.ClearTicketOverviewTrayNotification();
});
}
public void ShowTicketOverviewPaneForScope(TileScope? scope)
{
if (scope.HasValue)
{
Dispatcher.Invoke(() =>
{
if (TicketOverviewBorder.Visibility == Visibility.Visible)
return;
var useRoleScope = scope.Value == TileScope.Role;
if (FilterCheckbox != null && FilterCheckbox.IsChecked != useRoleScope)
FilterCheckbox.IsChecked = useRoleScope;
});
}
ShowTicketOverviewPane();
}
internal void CloseTicketOverviewResults()
{
_renderTicketOverviewUserNames = false;
ResultMenu.ShowSearchResults(new cFilteredResults(), null, this);
ResultMenu.SetHeaderText(string.Empty);
SetSearchResultVisibility(false);
SetTicketOverviewVisibility(true);
}
private void TicketOverviewUpdateService_OverviewCountsChanged(object sender, TicketOverviewCountsChangedEventArgs e)
{
try
{
ApplyLatestCounts();
var positiveChanges = e.Changes?.Where(change => change.Delta > 0).ToList();
var app = Application.Current as FasdDesktopUi.App;
var service = TicketOverviewUpdateService.Instance;
if (!_ticketOverviewFirstEventHandled)
{
_ticketOverviewFirstEventHandled = true;
foreach (var scope in GetTicketOverviewEventScopes(e))
{
PrimeTicketOverviewScope(scope);
TrackEmptyInitScope(scope, e?.CurrentCounts);
}
app?.ClearTicketOverviewTrayNotification();
return;
}
if (e.InitializedScope.HasValue)
{
PrimeTicketOverviewScope(e.InitializedScope.Value);
TrackEmptyInitScope(e.InitializedScope.Value, e?.CurrentCounts);
app?.ClearTicketOverviewTrayNotification();
return;
}
if (positiveChanges == null || positiveChanges.Count == 0)
{
app?.ClearTicketOverviewTrayNotification();
return;
}
if (service != null && !service.AreAllScopesInitialized)
{
app?.ClearTicketOverviewTrayNotification();
return;
}
var filteredChanges = FilterChangesForPrimedScopes(positiveChanges);
if (filteredChanges.Count == 0)
{
app?.ClearTicketOverviewTrayNotification();
return;
}
TicketOverviewUc?.SetHighlights(filteredChanges, IsFilterChecked);
var pendingScope = ResolveSingleScope(filteredChanges);
app?.SetTicketOverviewNotificationScope(pendingScope);
var message = BuildNotificationMessage(filteredChanges);
if (string.IsNullOrWhiteSpace(message))
{
app?.ClearTicketOverviewTrayNotification();
return;
}
app?.ShowTicketOverviewTrayNotification(message);
ShowTicketOverviewNotification(message);
}
catch (Exception ex)
{
LogException(ex);
}
}
private Task TicketSearchFromOverviewRelationAsync(cF4sdApiSearchResultRelation relation)
{
if (relation == null || relation.Type != enumF4sdSearchResultClass.Ticket)
return Task.CompletedTask;
var ticketName = string.IsNullOrWhiteSpace(relation.DisplayName) ? relation.Name : relation.DisplayName;
var ticketId = relation.id;
if (ticketId == Guid.Empty)
return Task.CompletedTask;
string userName = null;
string sids = null;
if (relation.Infos != null)
{
if (!relation.Infos.TryGetValue("UserDisplayName", out userName))
relation.Infos.TryGetValue("UserAccount", out userName);
if (!relation.Infos.TryGetValue("Sids", out sids))
relation.Infos.TryGetValue("UserSid", out sids);
}
return RunTicketSearchAsync(ticketName, ticketId, userName, sids, suppressUi: true);
}
private Task RunTicketSearchAsync(string ticketName, Guid ticketId, string userName, string sids, bool suppressUi = false)
{
if (ticketId == Guid.Empty)
return Task.CompletedTask;
if (suppressUi)
BeginTicketOverviewAutoContinue(TimeSpan.FromSeconds(6));
return Task.Run(async () =>
{
try
{
var lstSids = sids?.Split(',').Select((v) => v.Trim()).ToList();
var _result = await cFasdCockpitCommunicationBase.Instance.GetUserSearchResults(userName, lstSids);
if (_result is null || _result.Count == 0 || _result.First().Value.Count == 0)
{
LogEntry($"No corresponding user could be found for ticket '{ticketName}'", LogLevels.Warning);
if (suppressUi)
EndTicketOverviewAutoContinue(showSearch: true);
return;
}
var userId = _result.Values.First().First().id;
if (userId == Guid.Empty)
{
LogEntry($"No valid user id could be found for ticket '{ticketName}'", LogLevels.Warning);
if (suppressUi)
EndTicketOverviewAutoContinue(showSearch: true);
return;
}
var _ticketRelation = new cF4sdApiSearchResultRelation()
{
Type = enumF4sdSearchResultClass.Ticket,
Name = ticketName,
DisplayName = ticketName,
id = ticketId,
Status = enumF4sdSearchResultStatus.Active,
Identities = new cF4sdIdentityList
{
new cF4sdIdentityEntry()
{
Class = enumFasdInformationClass.User,
Id = userId
},
new cF4sdIdentityEntry()
{
Class = enumFasdInformationClass.Ticket,
Id = ticketId
},
}
};
var filteredResults = new cFilteredResults(_result) { AutoContinue = true, PreSelectedRelation = _ticketRelation };
var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.TicketSearch.Info"), ticketName);
await ShowExternalSearchInfoAsync(strInfo, filteredResults, enumF4sdSearchResultClass.Ticket, suppressUi);
}
catch (Exception E)
{
LogException(E);
if (suppressUi)
EndTicketOverviewAutoContinue(showSearch: true);
}
});
}
private void BeginTicketOverviewAutoContinue(TimeSpan fallbackTimeout)
{
if (Dispatcher.CheckAccess())
BeginTicketOverviewAutoContinueCore(fallbackTimeout);
else
Dispatcher.Invoke(() => BeginTicketOverviewAutoContinueCore(fallbackTimeout));
}
private void BeginTicketOverviewAutoContinueCore(TimeSpan fallbackTimeout)
{
if (_ticketOverviewAutoContinueActive)
return;
_ticketOverviewAutoContinueActive = true;
SearchBarUc.IsHitTestVisible = false;
ResultMenu.IsHitTestVisible = false;
SearchHistory.IsHitTestVisible = false;
BodyStack_TicketOverview.IsHitTestVisible = false;
FilterCheckbox.IsHitTestVisible = false;
SetSearchHistoryVisibility(false);
SetSearchResultVisibility(false);
_ticketOverviewAutoContinueCaseChangedHandler = (sender, args) => EndTicketOverviewAutoContinue(showSearch: false);
cSupportCaseDataProvider.CaseChanged += _ticketOverviewAutoContinueCaseChangedHandler;
_ticketOverviewAutoContinueFallbackTimer = new DispatcherTimer { Interval = fallbackTimeout };
_ticketOverviewAutoContinueFallbackTimer.Tick += TicketOverviewAutoContinueFallbackTimer_Tick;
_ticketOverviewAutoContinueFallbackTimer.Start();
}
private void TicketOverviewAutoContinueFallbackTimer_Tick(object sender, EventArgs e)
{
EndTicketOverviewAutoContinue(showSearch: true);
}
private void EndTicketOverviewAutoContinue(bool showSearch)
{
if (Dispatcher.CheckAccess())
EndTicketOverviewAutoContinueCore(showSearch);
else
Dispatcher.Invoke(() => EndTicketOverviewAutoContinueCore(showSearch));
}
private void EndTicketOverviewAutoContinueCore(bool showSearch)
{
if (!_ticketOverviewAutoContinueActive)
return;
_ticketOverviewAutoContinueActive = false;
if (_ticketOverviewAutoContinueFallbackTimer != null)
{
_ticketOverviewAutoContinueFallbackTimer.Stop();
_ticketOverviewAutoContinueFallbackTimer.Tick -= TicketOverviewAutoContinueFallbackTimer_Tick;
_ticketOverviewAutoContinueFallbackTimer = null;
}
if (_ticketOverviewAutoContinueCaseChangedHandler != null)
{
cSupportCaseDataProvider.CaseChanged -= _ticketOverviewAutoContinueCaseChangedHandler;
_ticketOverviewAutoContinueCaseChangedHandler = null;
}
SearchBarUc.IsHitTestVisible = true;
ResultMenu.IsHitTestVisible = true;
SearchHistory.IsHitTestVisible = true;
BodyStack_TicketOverview.IsHitTestVisible = true;
FilterCheckbox.IsHitTestVisible = true;
if (showSearch)
{
SetSearchResultVisibility(true);
Show();
Activate();
}
}
private void PrimeTicketOverviewScope(TileScope scope)
{
if (_ticketOverviewNotificationScopesPrimed.Add(scope))
{
TicketOverviewUc?.ClearHighlightsForScope(scope);
}
}
private IReadOnlyList<TileCountChange> FilterChangesForPrimedScopes(IReadOnlyList<TileCountChange> changes)
{
if (changes == null || changes.Count == 0)
return Array.Empty<TileCountChange>();
var filteredChanges = new List<TileCountChange>(changes.Count);
var unprimedScopes = new HashSet<TileScope>();
var silentInitScopes = new HashSet<TileScope>();
foreach (var change in changes)
{
if (_ticketOverviewInitWasEmptyScopes.Contains(change.Scope))
{
silentInitScopes.Add(change.Scope);
continue;
}
if (_ticketOverviewNotificationScopesPrimed.Contains(change.Scope))
{
filteredChanges.Add(change);
}
else
{
unprimedScopes.Add(change.Scope);
}
}
foreach (var scope in unprimedScopes)
{
PrimeTicketOverviewScope(scope);
}
if (silentInitScopes.Count > 0)
{
foreach (var scope in silentInitScopes)
{
_ticketOverviewInitWasEmptyScopes.Remove(scope);
PrimeTicketOverviewScope(scope);
}
}
return filteredChanges;
}
private void TrackEmptyInitScope(TileScope scope, IReadOnlyDictionary<string, TileCounts> counts)
{
if (IsScopeCountsEmpty(scope, counts))
{
_ticketOverviewInitWasEmptyScopes.Add(scope);
}
}
private static bool IsScopeCountsEmpty(TileScope scope, IReadOnlyDictionary<string, TileCounts> counts)
{
if (counts == null || counts.Count == 0)
return true;
foreach (var kvp in counts)
{
var value = scope == TileScope.Role ? kvp.Value.Role : kvp.Value.Personal;
if (value != 0)
return false;
}
return true;
}
private static IEnumerable<TileScope> GetTicketOverviewEventScopes(TicketOverviewCountsChangedEventArgs e)
{
if (e == null)
return Enumerable.Empty<TileScope>();
if (e.InitializedScope.HasValue)
return new[] { e.InitializedScope.Value };
if (e.Changes == null || e.Changes.Count == 0)
return Enumerable.Empty<TileScope>();
return e.Changes.Select(change => change.Scope).Distinct();
}
private static TileScope? ResolveSingleScope(IReadOnlyList<TileCountChange> changes)
{
if (changes == null || changes.Count == 0)
return null;
var scope = changes[0].Scope;
for (int i = 1; i < changes.Count; i++)
{
if (changes[i].Scope != scope)
return null;
}
return scope;
}
private void ApplyLatestCounts()
{
var service = TicketOverviewUpdateService.Instance;
if (service == null)
return;
var counts = service.GetCountsForScope(IsFilterChecked);
if (counts == null || counts.Count == 0)
return;
TicketOverviewUc?.UpdateCounts(counts, IsFilterChecked);
TicketOverviewUc?.RefreshHighlightState(IsFilterChecked);
}
private string BuildNotificationMessage(IReadOnlyList<TileCountChange> changes)
{
if (changes == null || changes.Count == 0)
return string.Empty;
var aggregates = new List<(string RowKey, string ColumnKey, TileScope Scope, int Delta)>();
var indexMap = new Dictionary<(string RowKey, string ColumnKey, TileScope Scope), int>();
foreach (var change in changes)
{
if (!TryResolveTicketOverviewLabels(change.Key, out var rowKey, out var columnKey))
continue;
var delta = change.NewCount - change.OldCount;
if (delta == 0)
continue;
var aggregateKey = (rowKey, columnKey, change.Scope);
if (indexMap.TryGetValue(aggregateKey, out var aggregateIndex))
{
var existing = aggregates[aggregateIndex];
aggregates[aggregateIndex] = (existing.RowKey, existing.ColumnKey, existing.Scope, existing.Delta + delta);
}
else
{
indexMap[aggregateKey] = aggregates.Count;
aggregates.Add((rowKey, columnKey, change.Scope, delta));
}
}
if (aggregates.Count == 0)
return string.Empty;
var pieces = new List<string>(aggregates.Count);
foreach (var aggregate in aggregates)
{
if (aggregate.Delta == 0)
continue;
var row = cMultiLanguageSupport.GetItem(aggregate.RowKey) ?? aggregate.RowKey;
var column = cMultiLanguageSupport.GetItem(aggregate.ColumnKey) ?? aggregate.ColumnKey;
var scopeRowLabel = GetScopeRowLabel(aggregate.Scope, aggregate.RowKey, row);
var deltaText = aggregate.Delta >= 0 ? $"+{aggregate.Delta}" : aggregate.Delta.ToString();
pieces.Add($"{scopeRowLabel} {column} ({deltaText})");
}
return string.Join(Environment.NewLine, pieces);
}
private static bool TryResolveTicketOverviewLabels(string key, out string rowKey, out string columnKey)
{
rowKey = null;
columnKey = null;
if (string.IsNullOrWhiteSpace(key))
return false;
var normalized = key.ToLowerInvariant();
if (normalized.StartsWith("tickets"))
rowKey = "TicketOverview.Row.Heading.Tickets";
else if (normalized.StartsWith("incident"))
rowKey = "TicketOverview.Row.Heading.Incidents";
else if (normalized.StartsWith("unassigned"))
rowKey = "TicketOverview.Row.Heading.UnassignedTickets";
else
return false;
columnKey = ResolveColumnTranslationKey(normalized);
return columnKey != null;
}
private static string ResolveColumnTranslationKey(string normalizedKey)
{
if (normalizedKey.EndsWith("newinfo"))
return "TicketOverview.Column.Heading.NewInfo";
if (normalizedKey.EndsWith("critical"))
return "TicketOverview.Column.Heading.Critical";
if (normalizedKey.EndsWith("active"))
return "TicketOverview.Column.Heading.Active";
return "TicketOverview.Column.Heading.New";
}
private static string GetScopeRowLabel(TileScope scope, string rowKey, string rowLabel)
{
string suffix = null;
if (string.Equals(rowKey, "TicketOverview.Row.Heading.Tickets", StringComparison.OrdinalIgnoreCase))
suffix = "Tickets";
else if (string.Equals(rowKey, "TicketOverview.Row.Heading.Incidents", StringComparison.OrdinalIgnoreCase))
suffix = "Incidents";
else if (string.Equals(rowKey, "TicketOverview.Row.Heading.UnassignedTickets", StringComparison.OrdinalIgnoreCase))
suffix = "UnassignedTickets";
var translationKey = scope == TileScope.Role
? $"TicketOverview.ScopeRow.Role.{suffix}"
: $"TicketOverview.ScopeRow.Personal.{suffix}";
return cMultiLanguageSupport.GetItem(translationKey) ?? rowLabel;
}
private void ShowTicketOverviewNotification(string message)
{
if (string.IsNullOrWhiteSpace(message))
return;
try
{
var notificationTitle = cMultiLanguageSupport.GetItem("TicketOverview.Notification.Title") ?? "Ticketübersicht";
var app = Application.Current as App;
app?.notifyIcon?.ShowBalloonTip(3000, notificationTitle, message, System.Windows.Forms.ToolTipIcon.Info);
}
catch (Exception ex)
{
LogException(ex);
}
}
private async void TicketOverview_SelectionRequested(object sender, TicketOverviewSelectionRequestedEventArgs e)
{
try
{
var app = Application.Current as FasdDesktopUi.App;
app?.ClearTicketOverviewTrayNotification();
_renderTicketOverviewUserNames = true;
SetSearchHistoryVisibility(false);
SetTicketOverviewVisibility(true);
SetSearchResultVisibility(true);
var header = BuildHeaderText(e.Key, e.UseRoleScope, e.Count);
ResultMenu.SetHeaderText(header, hideDetailsCheckbox: true);
ShowLoadingTextItem(cMultiLanguageSupport.GetItem("Searchbar.Loading.CaseData"));
SetPendingInformationClasses(new HashSet<enumFasdInformationClass> { enumFasdInformationClass.Ticket });
var relations = await LoadRelationsForTileAsync(e.Key, e.UseRoleScope, Math.Max(0, e.Count));
await PopulateTicketOverviewRelationUsersAsync(relations);
Debug.WriteLine($"[TicketOverview] Relations loaded: {relations?.Count ?? 0}");
var firstRelation = relations.FirstOrDefault();
string displayText = header;
if (firstRelation != null)
{
string firstSummary = null;
if (firstRelation.Infos != null && firstRelation.Infos.TryGetValue("Summary", out var summaryValue))
{
firstSummary = summaryValue;
}
displayText = string.IsNullOrWhiteSpace(firstSummary)
? $"{header} → {firstRelation.DisplayName}"
: $"{header} → {firstRelation.DisplayName} {firstSummary}";
}
var entry = new cSearchHistorySearchResultEntry(
displayText,
header,
new List<cFasdApiSearchResultEntry>(),
relations,
this
)
{ isSeen = true };
_ticketOverviewHistoryEntries.Add(entry);
var lookup = relations.ToLookup(
r => cF4sdIdentityEntry.GetFromSearchResult(r.Type),
r =>
{
var required = new List<enumFasdInformationClass> { cF4sdIdentityEntry.GetFromSearchResult(r.Type) };
bool isEnabled = cHealthCardDataHelper.HasAvailableHealthCard(required);
string disabledReason = null;
if (!isEnabled)
{
disabledReason = cMultiLanguageSupport.GetItem("Searchbar.NoValidHealthcard") ?? string.Empty;
}
else if (ShouldDisableTicketRelationForDemo(r))
{
isEnabled = false;
disabledReason = cMultiLanguageSupport.GetItem("Searchbar.Demo.NoTicketDetails") ?? string.Empty;
}
string trailingUser = null;
if (_renderTicketOverviewUserNames && r.Infos != null && r.Infos.TryGetValue("UserDisplayName", out var userDisplayName))
{
trailingUser = userDisplayName;
}
return (cMenuDataBase)new cMenuDataSearchRelation(r)
{
MenuText = r.DisplayName,
TrailingText = trailingUser,
UiAction = new cUiProcessTicketOverviewRelationAction(r.DisplayName, () => TicketSearchFromOverviewRelationAsync(r))
{
DisplayType = isEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled,
Description = isEnabled ? string.Empty : disabledReason,
AlternativeDescription = isEnabled ? string.Empty : disabledReason
}
};
}
);
ResultMenu.UpdateSearchRelations(lookup);
ResultMenu.SetHeaderText(header, hideDetailsCheckbox: true);
UpdatePendingInformationClasses(new HashSet<enumFasdInformationClass>());
}
catch (Exception ex)
{
LogException(ex);
CancledSearchAction();
}
System.Diagnostics.Debug.WriteLine(
$"[TicketOverview] Key={e.Key}, UseRoleScope={e.UseRoleScope}, Count={e.Count}");
}
private string BuildHeaderText(string key, bool useRoleScope, int? count = null)
{
string scopeKey = useRoleScope ? "Searchbar.Header.Scope.Role" : "Searchbar.Header.Scope.Personal";
string entityKey = DetermineFallbackEntityKey(key);
string filterKey = DetermineFallbackFilterKey(key);
string scope = cMultiLanguageSupport.GetItem(scopeKey) ?? scopeKey;
string entity = cMultiLanguageSupport.GetItem(entityKey) ?? entityKey;
string filter = cMultiLanguageSupport.GetItem(filterKey) ?? filterKey;
string template = cMultiLanguageSupport.GetItem("Searchbar.Header.Template") ?? "{0} - {1} ({2})";
bool expectsCount = template.IndexOf("{3}", StringComparison.Ordinal) >= 0;
try
{
if (expectsCount)
{
int countValue = count ?? 0;
return string.Format(template, entity, filter, scope, countValue);
}
return string.Format(template, entity, filter, scope);
}
catch (FormatException)
{
return string.Format("{0} - {1} ({2})", entity, filter, scope);
}
}
private string DetermineFallbackEntityKey(string key)
{
if (string.IsNullOrWhiteSpace(key))
return "Searchbar.Header.Entity.Tickets";
var keyLower = key.ToLowerInvariant();
return keyLower.Contains("incident")
? "Searchbar.Header.Entity.Incidents"
: "Searchbar.Header.Entity.Tickets";
}
private string DetermineFallbackFilterKey(string key)
{
if (string.IsNullOrWhiteSpace(key))
return "Searchbar.Header.Filter.Overview";
var keyLower = key.ToLowerInvariant();
if (keyLower.Contains("newinfo"))
return "Searchbar.Header.Filter.NewInfo";
if (keyLower.Contains("new"))
return "Searchbar.Header.Filter.New";
if (keyLower.Contains("active"))
return "Searchbar.Header.Filter.Active";
if (keyLower.Contains("critical"))
return "Searchbar.Header.Filter.Critical";
if (keyLower.Contains("unassigned"))
return "Searchbar.Header.Filter.Unassigned";
if (keyLower.Contains("hold") || keyLower.Contains("onhold") || keyLower.Contains("wait"))
return "Searchbar.Header.Filter.OnHold";
if (keyLower.Contains("closed"))
return "Searchbar.Header.Filter.Closed";
return "Searchbar.Header.Filter.Overview";
}
private bool ShouldDisableTicketRelationForDemo(cF4sdApiSearchResultRelation relation)
{
if (relation == null)
return false;
var communication = cFasdCockpitCommunicationBase.Instance;
if (communication?.IsDemo() != true)
return false;
if (relation.Type != enumF4sdSearchResultClass.Ticket)
return false;
if (relation.Infos == null)
return false;
if (!relation.Infos.TryGetValue(DemoTicketHasDetailsInfoKey, out var hasDetailsValue))
return false;
if (bool.TryParse(hasDetailsValue, out var hasDetails))
return !hasDetails;
return true;
}
private async Task<List<cF4sdApiSearchResultRelation>> LoadRelationsForTileAsync(string key, bool useRoleScope, int count)
{
var communication = cFasdCockpitCommunicationBase.Instance;
if (communication == null)
return new List<cF4sdApiSearchResultRelation>();
try
{
var relations = await communication.GetTicketOverviewRelations(key, useRoleScope, Math.Max(0, count)).ConfigureAwait(false);
var list = relations?.ToList() ?? new List<cF4sdApiSearchResultRelation>();
if (TicketOverviewUpdateService.Instance != null)
foreach (var demoRelation in TicketOverviewUpdateService.Instance.GetDemoRelations(key, useRoleScope))
{
if (!list.Any(r => r.id == demoRelation.id))
list.Add(demoRelation);
}
return list;
}
catch (Exception ex)
{
LogException(ex);
return new List<cF4sdApiSearchResultRelation>();
}
}
private async Task PopulateTicketOverviewRelationUsersAsync(List<cF4sdApiSearchResultRelation> relations)
{
if (relations == null || relations.Count == 0)
return;
foreach (var relation in relations)
{
if (relation == null || relation.Type != enumF4sdSearchResultClass.Ticket)
continue;
if (relation.Identities == null)
relation.Identities = new cF4sdIdentityList();
var existingUsers = relation.Identities
.Where(identity => identity.Class == enumFasdInformationClass.User)
.ToList();
Guid userId = Guid.Empty;
if (relation.Infos != null)
{
if (relation.Infos.TryGetValue("Sids", out var sidsValue) ||
relation.Infos.TryGetValue("UserSid", out sidsValue))
{
var sids = sidsValue?.Split(',')
.Select(v => v.Trim())
.Where(v => !string.IsNullOrWhiteSpace(v))
.ToList();
if (sids != null && sids.Count > 0)
{
var communication = cFasdCockpitCommunicationBase.Instance;
if (communication != null)
{
relation.Infos.TryGetValue("UserDisplayName", out var userDisplayName);
var result = await communication.GetUserSearchResults(userDisplayName, sids);
var user = result?.Values?.FirstOrDefault()?.FirstOrDefault();
if (user != null)
userId = user.id;
}
}
}
if (userId == Guid.Empty && relation.Infos.TryGetValue("UserAccount", out var userAccount))
{
relation.Infos.TryGetValue("UserDomain", out var userDomain);
if (!string.IsNullOrWhiteSpace(userAccount))
{
var communication = cFasdCockpitCommunicationBase.Instance;
if (communication != null)
{
userId = await communication.GetUserIdByAccount(userAccount, userDomain ?? string.Empty);
}
}
}
if (userId == Guid.Empty)
{
if (relation.Infos.TryGetValue("UserId", out var userIdString) ||
relation.Infos.TryGetValue("UserGuid", out userIdString) ||
relation.Infos.TryGetValue("UserIdentityId", out userIdString))
{
Guid.TryParse(userIdString, out userId);
}
}
}
if (userId == Guid.Empty)
{
if (existingUsers.Count == 0)
continue;
if (relation.Infos != null)
{
if (!relation.Infos.ContainsKey("UserId"))
relation.Infos["UserId"] = existingUsers[0].Id.ToString();
if (!relation.Infos.ContainsKey("UserGuid"))
relation.Infos["UserGuid"] = existingUsers[0].Id.ToString();
}
continue;
}
if (existingUsers.Count == 0 || existingUsers.Any(identity => identity.Id != userId))
{
relation.Identities.RemoveAll(identity => identity.Class == enumFasdInformationClass.User);
relation.Identities.Add(new cF4sdIdentityEntry
{
Class = enumFasdInformationClass.User,
Id = userId
});
}
if (relation.Infos == null)
relation.Infos = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
relation.Infos["UserId"] = userId.ToString();
relation.Infos["UserGuid"] = userId.ToString();
}
}
#endregion
private IntPtr SearchViewWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_NCHITTEST)
{
if (!IsPointWithinInteractiveBounds(lParam))
{
handled = true;
return new IntPtr(HTTRANSPARENT);
}
}
return IntPtr.Zero;
}
private bool IsPointWithinInteractiveBounds(IntPtr lParam)
{
if (!IsLoaded || MainBorder == null || !MainBorder.IsLoaded)
return true;
if (MainBorder.ActualWidth <= 0 || MainBorder.ActualHeight <= 0)
return true;
var screenPoint = ExtractScreenPoint(lParam);
var windowPoint = PointFromScreen(screenPoint);
if (!(InputHitTest(windowPoint) is DependencyObject hitElement))
return false;
return IsDescendantOf(hitElement, MainBorder);
}
private static Point ExtractScreenPoint(IntPtr lParam)
{
int value = unchecked((int)(long)lParam);
short x = (short)(value & 0xFFFF);
short y = (short)((value >> 16) & 0xFFFF);
return new Point(x, y);
}
private static bool IsDescendantOf(DependencyObject element, DependencyObject ancestor)
{
while (element != null)
{
if (element == ancestor)
return true;
element = GetParent(element);
}
return false;
}
private static DependencyObject GetParent(DependencyObject current)
{
if (current == null)
return null;
if (current is Visual || current is System.Windows.Media.Media3D.Visual3D)
return VisualTreeHelper.GetParent(current);
if (current is System.Windows.ContentElement contentElement)
return System.Windows.ContentOperations.GetParent(contentElement);
return LogicalTreeHelper.GetParent(current);
}
private void UpdateSearchResults(cFilteredResults filteredResults)
{
this.Dispatcher.Invoke(new Action(() =>
{
try
{
_renderTicketOverviewUserNames = false;
if (filteredResults?.Results is null)
{
SetSearchResultVisibility(false);
SetTicketOverviewVisibility(true);
return;
}
string menuHeaderText = filteredResults?.PreSelectedRelation?.DisplayName;
if (filteredResults?.PreSelectedRelation != null)
{
List<cFasdApiSearchResultEntry> selectedResult = filteredResults.Results.Values.FirstOrDefault();
var processSearchResult = new cUiProcessSearchResultAction(selectedResult.FirstOrDefault()?.DisplayName, this, selectedResult) { PreSelectedSearchRelation = filteredResults.PreSelectedRelation };
Dispatcher.Invoke(async () =>
{
bool isSearchOngoing = await processSearchResult.RunUiActionAsync(this, this, false, null);
if (!isSearchOngoing)
Hide();
});
return;
}
ResultMenu.ShowSearchResults(filteredResults, menuHeaderText, this);
preSelectedRelation = filteredResults.PreSelectedRelation;
SetSearchResultVisibility(true);
SetTicketOverviewVisibility(false);
}
catch (Exception E)
{
LogException(E);
}
}));
}
private void CancledSearchAction()
{
_renderTicketOverviewUserNames = false;
SearchBarUc.Clear();
ResultMenu.ShowSearchResults(new cFilteredResults(), null, this);
SetSearchResultVisibility(false);
SetSearchHistoryVisibility(false);
SetTicketOverviewVisibility(true);
Visibility = Visibility.Hidden;
}
private void SlimPageWindowStateBar_ClickedClose(object sender, RoutedEventArgs e)
{
CancledSearchAction();
}
private async void UiActionWasTriggered(object sender, UiActionEventArgs e)
{
try
{
if (e.UiAction == null)
return;
Mouse.OverrideCursor = Cursors.Wait;
switch (e.UiAction)
{
case cUiProcessSearchResultAction searchResultAction:
searchResultAction.PreSelectedSearchRelation = preSelectedRelation;
break;
case cUiProcessSearchRelationAction _:
case cUiProcessSearchHistoryEntry _:
case cUiProcessTicketOverviewRelationAction _:
break;
default:
var _t = e.UiAction?.GetType().Name;
Debug.Assert(true, $"The UI action '{_t}' is not supported in detailed page");
CancledSearchAction();
return;
}
SetSearchResultVisibility(true);
var actionResult = await e.UiAction.RunUiActionAsync(sender, null, false, null);
if (!(e.UiAction is cUiProcessTicketOverviewRelationAction) && !actionResult)
CancledSearchAction();
}
catch (Exception E)
{
LogException(E);
}
finally
{
Mouse.OverrideCursor = null;
}
}
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
if (ResultMenu.IndexOfSelectedResultItem == int.MinValue)
ResultMenu.IndexOfSelectedResultItem = ResultMenu.GetSearchResultCount() - 1;
else
ResultMenu.IndexOfSelectedResultItem--;
break;
case Key.Down:
if (ResultMenu.IndexOfSelectedResultItem == int.MinValue)
ResultMenu.IndexOfSelectedResultItem = 0;
else
ResultMenu.IndexOfSelectedResultItem++;
break;
case Key.Enter:
ResultMenu.SelectCurrentResultItem();
e.Handled = true;
break;
case Key.Escape:
CancledSearchAction();
break;
}
}
private void UiSettingsChanged(object sender, EventArgs e)
{
try
{
var positionAlignement = cFasdCockpitConfig.Instance.Global.SmallViewAlignment;
switch (positionAlignement)
{
case enumF4sdHorizontalAlignment.Center:
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Center);
break;
case enumF4sdHorizontalAlignment.Right:
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Right);
break;
default:
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Left);
break;
}
UpdateTicketOverviewAvailability();
}
catch (Exception E)
{
LogException(E);
}
}
public void SetPendingInformationClasses(HashSet<enumFasdInformationClass> informationClasses) => Dispatcher.Invoke(() => ResultMenu.SetPendingInformationClasses(informationClasses));
public void UpdatePendingInformationClasses(HashSet<enumFasdInformationClass> informationClasses) => Dispatcher.Invoke(() => ResultMenu.UpdatePendingInformationClasses(informationClasses));
private void ResultMenu_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollViewer = FindParent<ScrollViewer>((DependencyObject)sender);
if (scrollViewer != null)
{
if (e.Delta > 0)
scrollViewer.LineUp();
else
scrollViewer.LineDown();
e.Handled = true;
}
}
private T FindParent<T>(DependencyObject child) where T : DependencyObject
{
DependencyObject parent = VisualTreeHelper.GetParent(child);
while (parent != null)
{
if (parent is T typed)
return typed;
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
}
}