// Copyright (c) 2023 schick Informatik // Description: Bremen calling main window // using BreCalClient.misc.Api; using BreCalClient.misc.Client; using BreCalClient.misc.Model; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace BreCalClient { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { #region Fields private const int SHIPCALL_UPDATE_INTERVAL_SECONDS = 30; private readonly DefaultApi _api; private readonly ObservableCollection _controlModels = new(); private List _ships = new(); private readonly ConcurrentDictionary _shipLookupDict = new(); private List _berths = new(); private readonly ConcurrentDictionary _berthLookupDict = new(); private List _participants = new(); private readonly Dictionary _participantLookupDict = new(); private readonly Dictionary _shipCallControlDict = new(); private readonly CancellationTokenSource _tokenSource = new(); private LoginResult? _loginResult; #endregion #region Enums private enum ConnectionStatus { UNDEFINED, SUCCESSFUL, FAILED } #endregion #region Construction public MainWindow() { InitializeComponent(); _api = new DefaultApi(); _api.Configuration.ApiKeyPrefix["Authorization"] = "Bearer"; } #endregion #region event handler private void Window_Loaded(object sender, RoutedEventArgs e) { labelGeneralStatus.Text = $"Connection {ConnectionStatus.UNDEFINED}"; labelVersion.Text = "V. " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { _tokenSource.Cancel(); } private async void buttonLogin_Click(object sender, RoutedEventArgs e) { if (string.IsNullOrEmpty(this.textPassword.Password) || string.IsNullOrEmpty(this.textUsername.Text)) { this.labelLoginResult.Content = Application.Current.FindResource("textUserNamePasswordEmpty").ToString(); return; } Credentials credentials = new(username: textUsername.Text.Trim(), password: textPassword.Password.Trim()); try { _loginResult = await _api.LoginPostAsync(credentials); if(_loginResult != null) { if(_loginResult.Id > 0) { this.busyIndicator.IsBusy = false; this._api.Configuration.ApiKey["Authorization"] = _loginResult.Token; this.LoadStaticLists(); this.labelUsername.Text = $"{_loginResult.FirstName} {_loginResult.LastName}"; } } labelGeneralStatus.Text = $"Connection {ConnectionStatus.SUCCESSFUL}"; } catch (ApiException ex) { this.labelLoginResult.Content = ex.Message; labelGeneralStatus.Text = $"Connection {ConnectionStatus.FAILED}"; } catch(Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error); labelGeneralStatus.Text = $"Connection {ConnectionStatus.FAILED}"; } } private void buttonExit_Click(object sender, RoutedEventArgs e) { this.Close(); } private void buttonNew_Click(object sender, RoutedEventArgs e) { EditShipcallControl esc = new() { Participants = this._participants, Ships = this._ships, Berths = this._berths }; if (esc.ShowDialog() ?? false) { // save new dialog model // add dialog model to list } } private void buttonInfo_Click(object sender, RoutedEventArgs e) { AboutDialog ad = new(); ad.ChangePasswordRequested += async (oldPw, newPw) => { if (_loginResult != null) { UserDetails ud = new() { Id = _loginResult.Id, FirstName = _loginResult.FirstName, LastName = _loginResult.LastName, UserPhone = _loginResult.UserPhone, OldPassword = oldPw, NewPassword = newPw }; try { await _api.UserPutAsync(ud); } catch (Exception ex) { this.Dispatcher.Invoke(new Action(() => { MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error); })); } } }; ad.ShowDialog(); } #endregion #region private methods private async void LoadStaticLists() { this._berths = await _api.BerthsGetAsync(); foreach(var berth in this._berths) _berthLookupDict[berth.Id] = berth; this.searchFilterControl.SetBerths(this._berths); this._ships = await _api.ShipsGetAsync(); foreach(var ship in this._ships) _shipLookupDict[ship.Id] = ship; this._participants = await _api.ParticipantsGetAsync(); List agencies = new(); foreach (Participant participant in this._participants) { this._participantLookupDict[participant.Id] = participant; if (_loginResult?.ParticipantId == participant.Id) { App.Participant = participant; EnableControlsForParticipant(); } if(participant.IsFlagSet(Extensions.ParticipantType.AGENCY)) agencies.Add(participant); } this.searchFilterControl.SetAgencies(agencies); _ = Task.Run(() => RefreshShipcalls()); } private void EnableControlsForParticipant() { if (App.Participant.IsFlagSet(Extensions.ParticipantType.BSMD)) this.buttonNew.Visibility = Visibility.Visible; } public async Task RefreshShipcalls() { while (!_tokenSource.Token.IsCancellationRequested) { List? shipcalls = null; try { shipcalls = await _api.ShipcallsGetAsync(); this.Dispatcher.Invoke(new Action(() => { labelGeneralStatus.Text = $"Connection {ConnectionStatus.SUCCESSFUL}"; labelGeneralStatus.Text = $"Ok"; })); } catch (Exception ex) { this.Dispatcher.Invoke(new Action(() => { labelGeneralStatus.Text = $"Connection {ConnectionStatus.FAILED}"; labelStatusBar.Text = ex.Message; })); } if (shipcalls != null) { foreach (Shipcall shipcall in shipcalls) { ShipcallControlModel? selectedSCMModel = null; foreach (ShipcallControlModel scm in this._controlModels) { if (scm.Shipcall?.Id == shipcall.Id) { selectedSCMModel = scm; break; } } if (selectedSCMModel != null) { selectedSCMModel.Shipcall = shipcall; } else { // no: create new entry selectedSCMModel = new() { Shipcall = shipcall }; if (this._shipLookupDict.ContainsKey(shipcall.ShipId)) selectedSCMModel.Ship = this._shipLookupDict[shipcall.ShipId]; if (this._berthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0)) selectedSCMModel.Berth = this._berthLookupDict[shipcall.ArrivalBerthId ?? 0].Name; _controlModels.Add(selectedSCMModel); this.Dispatcher.Invoke(new Action(() => { ShipcallControl sc = new() { Height = 120, ShipcallControlModel = selectedSCMModel }; sc.TimesRequested += Sc_TimesRequested; sc.EditRequested += Sc_EditRequested; this.stackPanel.Children.Add(sc); this._shipCallControlDict[shipcall.Id] = sc; })); } selectedSCMModel.AssignParticipants(this._participants); this.Dispatcher.Invoke((Action)(() => { this._shipCallControlDict[shipcall.Id].RefreshData(); })); } List removeList = new(); foreach (ShipcallControlModel scm in this._controlModels) { if (shipcalls.Find(s => s.Id == scm.Shipcall?.Id) == null) // the model is no longer in the search result { if((scm.Shipcall != null) && this._shipCallControlDict.ContainsKey(scm.Shipcall.Id)) { this.Dispatcher.Invoke((Action)(() => { this.stackPanel.Children.Remove(this._shipCallControlDict[scm.Shipcall.Id]); })); this._shipCallControlDict.Remove(scm.Shipcall.Id); } } } } await Task.Delay(TimeSpan.FromSeconds(SHIPCALL_UPDATE_INTERVAL_SECONDS), _tokenSource.Token); } } private async void Sc_EditRequested(ShipcallControl obj) { Shipcall? sc = obj.ShipcallControlModel?.Shipcall; if (sc != null) { EditShipcallControl esc = new() { Shipcall = sc, Ships = _ships, Participants = _participants, Berths = _berths }; if(esc.ShowDialog() ?? false) { try { await _api.ShipcallsPutAsync(sc); obj.RefreshData(); } catch (Exception ex) { ShowErrorDialog(ex.Message, "Error saving edited shipcall"); } } } } private void Sc_TimesRequested(ShipcallControl obj) { // TODO: get the shipcall, load and show a list of the times } private void ShowErrorDialog(string message, string caption) { Dispatcher.Invoke(new Action(() => { MessageBox.Show(message, caption, MessageBoxButton.OK, MessageBoxImage.Error); })); } #endregion } }