1210 lines
49 KiB
C#
1210 lines
49 KiB
C#
// Copyright (c) 2023 schick Informatik
|
|
// Description: Bremen calling main window
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
|
|
using log4net;
|
|
|
|
using BreCalClient.misc.Api;
|
|
using BreCalClient.misc.Client;
|
|
using BreCalClient.misc.Model;
|
|
|
|
using static BreCalClient.Extensions;
|
|
using System.Collections.Concurrent;
|
|
using Newtonsoft.Json;
|
|
|
|
using Polly;
|
|
using System.Net.Http;
|
|
using System.Net;
|
|
using System.Windows.Input;
|
|
using System.Text.RegularExpressions;
|
|
using Newtonsoft.Json.Linq;
|
|
using System.Linq;
|
|
|
|
|
|
namespace BreCalClient
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for MainWindow.xaml
|
|
/// </summary>
|
|
public partial class MainWindow : Window
|
|
{
|
|
private readonly ILog _log = LogManager.GetLogger(typeof(MainWindow));
|
|
private readonly ToastViewModel _vm;
|
|
|
|
private const int SHIPCALL_UPDATE_INTERVAL_SECONDS = 30;
|
|
private const int SHIPS_UPDATE_INTERVAL_SECONDS = 120;
|
|
private const int CHECK_NOTIFICATIONS_INTERVAL_SECONDS = 5;
|
|
private const int PROGRESS_STEPS = 50;
|
|
|
|
#region Fields
|
|
|
|
//private static int _uiUpdateRunning = 0;
|
|
private static readonly SemaphoreSlim uiLock = new(1);
|
|
|
|
private Credentials? _credentials;
|
|
|
|
private readonly ConcurrentDictionary<int, ShipcallControlModel> _allShipcallsDict = new();
|
|
private readonly ConcurrentDictionary<int, ShipcallControl> _allShipCallsControlDict = new();
|
|
private readonly List<ShipcallControlModel> _visibleControlModels = new();
|
|
|
|
private readonly ShipcallApi _shipcallApi;
|
|
private readonly UserApi _userApi;
|
|
private readonly TimesApi _timesApi;
|
|
private readonly StaticApi _staticApi;
|
|
private readonly ShipApi _shipApi;
|
|
|
|
private CancellationTokenSource _tokenSource = new();
|
|
private LoginResult? _loginResult;
|
|
private bool _refreshImmediately = false;
|
|
|
|
private bool? _showCanceled = null;
|
|
private SortOrder _sortOrder = SortOrder.ETA_ETD;
|
|
private int searchPastDays = 0;
|
|
|
|
// private bool _filterChanged = false;
|
|
// private bool _sequenceChanged = false;
|
|
private HistoryDialog? _historyDialog;
|
|
|
|
#endregion
|
|
|
|
#region Enums
|
|
|
|
private enum ConnectionStatus
|
|
{
|
|
UNDEFINED,
|
|
SUCCESSFUL,
|
|
FAILED
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Construction
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
_userApi = new UserApi(Properties.Settings.Default.API_URL);
|
|
_userApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
|
|
_shipcallApi = new ShipcallApi(Properties.Settings.Default.API_URL);
|
|
_shipcallApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
|
|
_timesApi = new TimesApi(Properties.Settings.Default.API_URL);
|
|
_timesApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
|
|
_staticApi = new StaticApi(Properties.Settings.Default.API_URL);
|
|
_staticApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
|
|
_shipApi = new ShipApi(Properties.Settings.Default.API_URL);
|
|
_shipApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
|
|
|
|
const int maxDelayInMilliseconds = 32 * 1000;
|
|
var jitterer = new Random();
|
|
|
|
var retryPolicy =
|
|
// Policy.Handle<HttpRequestException>()
|
|
Policy.HandleResult<RestSharp.RestResponse>(resp => resp.StatusCode == HttpStatusCode.Unauthorized)
|
|
//.OrResult<RestSharp.RestResponse>
|
|
.WaitAndRetryAsync(1,
|
|
retryAttempt =>
|
|
{
|
|
var calculatedDelayInMilliseconds = Math.Pow(2, retryAttempt) * 1000;
|
|
var jitterInMilliseconds = jitterer.Next(0, 1000);
|
|
|
|
var actualDelay = Math.Min(calculatedDelayInMilliseconds + jitterInMilliseconds, maxDelayInMilliseconds);
|
|
return TimeSpan.FromMilliseconds(actualDelay);
|
|
},
|
|
onRetry: (resp, timespan, context) =>
|
|
{
|
|
this.RefreshToken();
|
|
Trace.WriteLine("token refreshed");
|
|
});
|
|
RetryConfiguration.AsyncRetryPolicy = retryPolicy;
|
|
|
|
this.generalProgressStatus.Maximum = PROGRESS_STEPS;
|
|
_vm = new ToastViewModel();
|
|
this.Unloaded += MainWindow_Unloaded;
|
|
}
|
|
|
|
private void MainWindow_Unloaded(object sender, RoutedEventArgs e)
|
|
{
|
|
_vm.OnUnloaded();
|
|
}
|
|
|
|
#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;
|
|
if (!string.IsNullOrEmpty(Properties.Settings.Default.APP_TITLE))
|
|
this.Title = Properties.Settings.Default.APP_TITLE;
|
|
searchFilterControl.SearchFilterChanged += SearchFilterControl_SearchFilterChanged;
|
|
searchFilterControl.LogoImageClicked += () =>
|
|
{
|
|
Process.Start("explorer", Properties.Settings.Default.LOGO_IMAGE_URL);
|
|
};
|
|
this.comboBoxSortOrder.ItemsSource = Enum.GetValues(typeof(Extensions.SortOrder));
|
|
this.comboBoxSortOrder.SelectedIndex = (int)_sortOrder;
|
|
}
|
|
|
|
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
// serialize filter settings
|
|
Properties.Settings.Default.FilterCriteriaMap = SearchFilterModel.Serialize();
|
|
Properties.Settings.Default.Save();
|
|
_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 = BreCalClient.Resources.Resources.textUserNamePasswordEmpty;
|
|
return;
|
|
}
|
|
|
|
_credentials = new(username: textUsername.Text.Trim(), password: textPassword.Password.Trim());
|
|
|
|
try
|
|
{
|
|
_loginResult = await _userApi.LoginAsync(_credentials);
|
|
if (_loginResult != null)
|
|
{
|
|
if (_loginResult.Id > 0)
|
|
{
|
|
Mouse.OverrideCursor = Cursors.Wait;
|
|
this.busyIndicator.IsBusy = false;
|
|
this._userApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this._shipcallApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this._timesApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this._staticApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this._shipApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this.LoadStaticLists();
|
|
this.labelUsername.Text = $"{_loginResult.FirstName} {_loginResult.LastName}";
|
|
}
|
|
}
|
|
labelGeneralStatus.Text = $"Connection {ConnectionStatus.SUCCESSFUL}";
|
|
}
|
|
catch (ApiException ex)
|
|
{
|
|
if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith("{"))) {
|
|
Error? anError = JsonConvert.DeserializeObject<Error>((string)ex.ErrorContent);
|
|
if ((anError != null) && anError.ErrorField.Equals("invalid credentials"))
|
|
this.labelLoginResult.Content = BreCalClient.Resources.Resources.textWrongCredentials;
|
|
else
|
|
this.labelLoginResult.Content = anError?.ErrorField ?? ex.Message;
|
|
}
|
|
else {
|
|
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 bool RefreshToken()
|
|
{
|
|
bool result = false;
|
|
try
|
|
{
|
|
_loginResult = _userApi.Login(_credentials);
|
|
if (_loginResult != null)
|
|
{
|
|
if (_loginResult.Id > 0)
|
|
{
|
|
this._userApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this._timesApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this._shipcallApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this._staticApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
this._shipApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
|
|
result = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_log.Error("Token refresh: Renewed login returned empty login result");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_log.ErrorFormat("Error refreshing token: {0}", ex.Message);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void buttonExit_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
this.Close();
|
|
}
|
|
|
|
private void buttonNew_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
NewWithModel(null);
|
|
}
|
|
|
|
private async void NewWithModel(ShipcallControlModel? model)
|
|
{
|
|
EditShipcallControl esc = new()
|
|
{
|
|
ShipEditingEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD),
|
|
ShipApi = _shipApi,
|
|
IsCreate = true
|
|
};
|
|
if (model != null)
|
|
esc.ShipcallModel = model;
|
|
|
|
if (esc.ShowDialog() ?? false)
|
|
{
|
|
// create UI & save new dialog model
|
|
if (esc.ShipcallModel.Shipcall != null)
|
|
{
|
|
await uiLock.WaitAsync();
|
|
this.UpdateUI();
|
|
uiLock.Release();
|
|
|
|
esc.ShipcallModel.Shipcall?.Participants.Clear();
|
|
foreach (ParticipantAssignment pa in esc.ShipcallModel.AssignedParticipants.Values)
|
|
esc.ShipcallModel.Shipcall?.Participants.Add(pa);
|
|
try
|
|
{
|
|
this._shipcallApi.ShipcallCreate(esc.ShipcallModel.Shipcall); // save new ship call
|
|
this.AddShipcall(esc.ShipcallModel);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.ShowErrorDialog(ex.ToString(), ex.Message);
|
|
}
|
|
|
|
_refreshImmediately = true; // set flag to avoid timer loop termination
|
|
_tokenSource.Cancel(); // force timer loop end
|
|
|
|
// if this was an arrival, create the matching departure call and open it
|
|
if (esc.ShipcallModel.Shipcall?.Type == ShipcallType.Arrival)
|
|
{
|
|
ShipcallControlModel scmOut = new()
|
|
{
|
|
Shipcall = new()
|
|
{
|
|
Type = ShipcallType.Departure
|
|
}
|
|
};
|
|
scmOut.Shipcall.ShipId = esc.ShipcallModel.Shipcall.ShipId;
|
|
scmOut.Shipcall.PortId = esc.ShipcallModel.Shipcall.PortId;
|
|
scmOut.Ship = esc.ShipcallModel.Ship;
|
|
scmOut.AllowPortChange = false;
|
|
DateTime eta = esc.ShipcallModel.Shipcall?.Eta ?? DateTime.Now;
|
|
scmOut.Shipcall.Etd = eta.AddDays(2);
|
|
scmOut.Shipcall.DepartureBerthId = esc.ShipcallModel.Shipcall?.ArrivalBerthId;
|
|
if (esc.ShipcallModel.Shipcall != null)
|
|
{
|
|
scmOut.Shipcall.Participants = new();
|
|
scmOut.Shipcall.Participants.AddRange(esc.ShipcallModel.Shipcall.Participants);
|
|
foreach(ParticipantType pType in esc.ShipcallModel.AssignedParticipants.Keys)
|
|
scmOut.AssignedParticipants[pType] = esc.ShipcallModel.AssignedParticipants[pType];
|
|
}
|
|
|
|
this.Dispatcher.Invoke(() =>
|
|
{
|
|
NewWithModel(scmOut);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void buttonInfo_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
AboutDialog ad = new()
|
|
{
|
|
LoginResult = this._loginResult
|
|
};
|
|
ad.ChangePasswordRequested += async (oldPw, newPw) =>
|
|
{
|
|
if (_loginResult != null)
|
|
{
|
|
UserDetails ud = new()
|
|
{
|
|
Id = _loginResult.Id,
|
|
OldPassword = oldPw,
|
|
NewPassword = newPw
|
|
};
|
|
try
|
|
{
|
|
await _userApi.UserUpdateAsync(ud);
|
|
MessageBox.Show(BreCalClient.Resources.Resources.textPasswordChanged, BreCalClient.Resources.Resources.textConfirmation, MessageBoxButton.OK, MessageBoxImage.Information);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.Dispatcher.Invoke(new Action(() =>
|
|
{
|
|
ShowErrorDialog(ex.Message, "Error saving user information");
|
|
}));
|
|
}
|
|
}
|
|
};
|
|
ad.ChangeUserSettingsRequested += async () =>
|
|
{
|
|
if (_loginResult != null)
|
|
{
|
|
UserDetails ud = new()
|
|
{
|
|
Id = _loginResult.Id,
|
|
FirstName = _loginResult.FirstName,
|
|
LastName = _loginResult.LastName,
|
|
UserPhone = _loginResult.UserPhone,
|
|
UserEmail = _loginResult.UserEmail,
|
|
NotifyEmail = _loginResult.NotifyEmail,
|
|
NotifyPopup = _loginResult.NotifyPopup,
|
|
NotifySignal = _loginResult.NotifySignal,
|
|
NotifyWhatsapp = _loginResult.NotifyWhatsapp
|
|
};
|
|
try
|
|
{
|
|
await _userApi.UserUpdateAsync(ud);
|
|
MessageBox.Show(BreCalClient.Resources.Resources.textInformationUpdated, BreCalClient.Resources.Resources.textConfirmation, MessageBoxButton.OK, MessageBoxImage.Information);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.Dispatcher.Invoke(new Action(() =>
|
|
{
|
|
ShowErrorDialog(ex.Message, "Error saving user information");
|
|
}));
|
|
}
|
|
}
|
|
};
|
|
ad.ShowDialog();
|
|
}
|
|
|
|
private void buttonClearFilter_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
this.searchFilterControl.ClearFilters();
|
|
this.checkboxShowCancelledCalls.IsChecked = false;
|
|
this.comboBoxPorts.UnSelectAll();
|
|
this.FilterShipcalls();
|
|
}
|
|
|
|
private async void SearchFilterControl_SearchFilterChanged()
|
|
{
|
|
this.FilterShipcalls();
|
|
await uiLock.WaitAsync();
|
|
this.UpdateUI();
|
|
uiLock.Release();
|
|
}
|
|
|
|
private void checkboxShowCancelledCalls_Checked(object sender, RoutedEventArgs e)
|
|
{
|
|
this._showCanceled = this.checkboxShowCancelledCalls.IsChecked;
|
|
this.SearchFilterControl_SearchFilterChanged();
|
|
}
|
|
|
|
private void comboBoxPorts_ItemSelectionChanged(object sender, Xceed.Wpf.Toolkit.Primitives.ItemSelectionChangedEventArgs e)
|
|
{
|
|
this.searchFilterControl.SearchFilter.Ports.Clear();
|
|
|
|
List<Berth> berths = new();
|
|
foreach (Port port in comboBoxPorts.SelectedItems)
|
|
{
|
|
this.searchFilterControl.SearchFilter.Ports.Add(port.Id);
|
|
berths.AddRange(BreCalLists.GetBerthsByPort(port.Id));
|
|
}
|
|
// create list of berths from selected port(s) or return all berths
|
|
if (berths.Count == 0)
|
|
berths = BreCalLists.AllBerths;
|
|
this.searchFilterControl.SetBerths(berths);
|
|
|
|
this.SearchFilterControl_SearchFilterChanged();
|
|
}
|
|
|
|
private async void comboBoxSortOrder_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
|
{
|
|
_sortOrder = (Extensions.SortOrder) this.comboBoxSortOrder.SelectedIndex;
|
|
this.FilterShipcalls();
|
|
await uiLock.WaitAsync();
|
|
this.UpdateUI();
|
|
uiLock.Release();
|
|
}
|
|
|
|
private void buttonHistory_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if(_historyDialog == null)
|
|
{
|
|
_historyDialog = new HistoryDialog(_allShipcallsDict, _staticApi);
|
|
_historyDialog.Closed += (sender, e) => { this._historyDialog = null; };
|
|
_historyDialog.HistoryItemSelected += (x) =>
|
|
{
|
|
if(_allShipCallsControlDict.ContainsKey(x))
|
|
_allShipCallsControlDict[x].BringIntoView();
|
|
};
|
|
_historyDialog.Show();
|
|
}
|
|
else
|
|
{
|
|
_historyDialog.Activate();
|
|
}
|
|
}
|
|
|
|
private void buttonManualRefresh_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_refreshImmediately = true; // set flag to avoid timer loop termination
|
|
_tokenSource.Cancel(); // force timer loop end
|
|
Mouse.OverrideCursor = Cursors.Wait;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region network operations
|
|
|
|
private async void LoadStaticLists()
|
|
{
|
|
if (_loginResult == null) return;
|
|
|
|
BreCalLists.InitializePorts(await _staticApi.GetPortsAsync());
|
|
BreCalLists.InitializeBerths(await _staticApi.BerthsGetAsync());
|
|
BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync());
|
|
BreCalLists.InitializeParticipants(await _staticApi.ParticipantsGetAsync());
|
|
|
|
this.searchFilterControl.SetBerths(BreCalLists.Berths);
|
|
|
|
foreach (Participant participant in BreCalLists.Participants)
|
|
{
|
|
if (_loginResult?.ParticipantId == participant.Id)
|
|
{
|
|
App.Participant = participant;
|
|
EnableControlsForParticipant();
|
|
}
|
|
}
|
|
|
|
this.searchFilterControl.SetAgencies(BreCalLists.Participants_Agent);
|
|
|
|
if (!string.IsNullOrEmpty(Properties.Settings.Default.FilterCriteriaMap))
|
|
{
|
|
SearchFilterModel.Deserialize(Properties.Settings.Default.FilterCriteriaMap);
|
|
SearchFilterModel? currentFilter = null;
|
|
if (SearchFilterModel.filterMap != null)
|
|
{
|
|
if((_loginResult != null) && SearchFilterModel.filterMap.ContainsKey(_loginResult.Id))
|
|
{
|
|
currentFilter = SearchFilterModel.filterMap[_loginResult.Id];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SearchFilterModel.filterMap = new();
|
|
}
|
|
if (currentFilter == null)
|
|
{
|
|
currentFilter = new();
|
|
if(_loginResult != null)
|
|
SearchFilterModel.filterMap[_loginResult.Id] = currentFilter;
|
|
}
|
|
this.searchFilterControl.SetFilterFromModel(currentFilter);
|
|
|
|
if (currentFilter.Ports != null)
|
|
{
|
|
foreach (Port p in this.comboBoxPorts.ItemsSource)
|
|
{
|
|
if (currentFilter.Ports.Contains(p.Id)) this.comboBoxPorts.SelectedItems.Add(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
_ = Task.Run(() => RefreshShipcalls());
|
|
_ = Task.Run(() => RefreshShips());
|
|
_ = Task.Run(() => CheckNotifications());
|
|
|
|
}
|
|
|
|
|
|
public async Task RefreshShips()
|
|
{
|
|
while (true)
|
|
{
|
|
Thread.Sleep(SHIPS_UPDATE_INTERVAL_SECONDS * 1000);
|
|
BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync());
|
|
}
|
|
}
|
|
|
|
public async Task RefreshShipcalls()
|
|
{
|
|
while (!_tokenSource.Token.IsCancellationRequested || _refreshImmediately)
|
|
{
|
|
|
|
if (_refreshImmediately)
|
|
{
|
|
_refreshImmediately = false;
|
|
_tokenSource = new CancellationTokenSource();
|
|
}
|
|
|
|
List<Shipcall>? shipcalls = null;
|
|
try
|
|
{
|
|
if(this.searchPastDays != 0)
|
|
shipcalls = await _shipcallApi.ShipcallsGetAsync(this.searchPastDays);
|
|
else
|
|
shipcalls = await _shipcallApi.ShipcallsGetAsync();
|
|
|
|
this.Dispatcher.Invoke(new Action(() =>
|
|
{
|
|
labelGeneralStatus.Text = $"Connection {ConnectionStatus.SUCCESSFUL}";
|
|
labelLatestUpdate.Text = $"Last update: {DateTime.Now.ToLongTimeString()}";
|
|
labelStatusBar.Text = "";
|
|
generalProgressStatus.Value = 0;
|
|
}));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.Dispatcher.Invoke(new Action(() =>
|
|
{
|
|
labelGeneralStatus.Text = $"Connection {ConnectionStatus.FAILED}";
|
|
labelStatusBar.Text = ex.Message;
|
|
}));
|
|
|
|
if (ex.Message.Contains("access", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
this.RefreshToken();
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
|
|
if (shipcalls != null)
|
|
{
|
|
foreach (Shipcall shipcall in shipcalls)
|
|
{
|
|
// load times for each shipcall
|
|
List<Times> currentTimes = await _timesApi.TimesGetAsync(shipcall.Id);
|
|
|
|
if (!_allShipcallsDict.ContainsKey(shipcall.Id))
|
|
{
|
|
// add entry
|
|
ShipcallControlModel scm = new()
|
|
{
|
|
Shipcall = shipcall,
|
|
Times = currentTimes
|
|
};
|
|
this.AddShipcall(scm);
|
|
}
|
|
else
|
|
{
|
|
// update entry
|
|
_allShipcallsDict[shipcall.Id].Shipcall = shipcall;
|
|
_allShipcallsDict[shipcall.Id].Times = currentTimes;
|
|
UpdateShipcall(_allShipcallsDict[shipcall.Id]);
|
|
}
|
|
}
|
|
|
|
List<int> existingIds = new(this._allShipcallsDict.Keys);
|
|
|
|
foreach (int existingId in existingIds)
|
|
{
|
|
if (shipcalls.Find(s => s.Id == existingId) == null) // the model is no longer in the search result
|
|
{
|
|
this.RemoveShipcall(existingId);
|
|
}
|
|
}
|
|
|
|
this.FilterShipcalls();
|
|
await uiLock.WaitAsync();
|
|
this.UpdateUI();
|
|
}
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
_log.Error(ex);
|
|
}
|
|
finally
|
|
{
|
|
uiLock.Release();
|
|
}
|
|
|
|
try
|
|
{
|
|
double interval = (double) SHIPCALL_UPDATE_INTERVAL_SECONDS / PROGRESS_STEPS;
|
|
for (int i = 0; i < PROGRESS_STEPS; i++)
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(interval), _tokenSource.Token);
|
|
this.Dispatcher.Invoke(new Action(() =>
|
|
{
|
|
this.generalProgressStatus.Value = i;
|
|
}));
|
|
}
|
|
}
|
|
catch(TaskCanceledException) { }
|
|
}
|
|
}
|
|
|
|
public async Task CheckNotifications()
|
|
{
|
|
while (true)
|
|
{
|
|
Thread.Sleep(CHECK_NOTIFICATIONS_INTERVAL_SECONDS * 1000);
|
|
List<Notification> notifications = await _staticApi.NotificationsGetAsync();
|
|
AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region basic operations
|
|
|
|
private void AddShipcall(ShipcallControlModel scm)
|
|
{
|
|
if (scm.Shipcall == null) return;
|
|
_allShipcallsDict[scm.Shipcall.Id] = scm;
|
|
|
|
Shipcall shipcall = scm.Shipcall;
|
|
if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
|
|
scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship;
|
|
|
|
if (shipcall.Type == ShipcallType.Arrival)
|
|
{
|
|
if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0))
|
|
scm.Berth = BreCalLists.BerthLookupDict[shipcall.ArrivalBerthId ?? 0].Name;
|
|
}
|
|
else
|
|
{
|
|
if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.DepartureBerthId ?? 0))
|
|
scm.Berth = BreCalLists.BerthLookupDict[shipcall.DepartureBerthId ?? 0].Name;
|
|
}
|
|
scm.AssignParticipants();
|
|
|
|
this.Dispatcher.Invoke(() =>
|
|
{
|
|
ShipcallControl sc = new()
|
|
{
|
|
Height = 145,
|
|
ShipcallControlModel = scm
|
|
};
|
|
sc.EditTimesRequested += Sc_EditTimesRequested;
|
|
sc.EditRequested += Sc_EditRequested;
|
|
sc.EditAgencyRequested += Sc_EditAgencyRequested;
|
|
sc.RefreshData();
|
|
this._allShipCallsControlDict[scm.Shipcall.Id] = sc;
|
|
});
|
|
}
|
|
|
|
private static void UpdateShipcall(ShipcallControlModel scm)
|
|
{
|
|
if(scm.Shipcall == null) return;
|
|
Shipcall shipcall = scm.Shipcall;
|
|
if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
|
|
scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship;
|
|
|
|
if (shipcall.Type == ShipcallType.Arrival)
|
|
{
|
|
if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0))
|
|
scm.Berth = BreCalLists.BerthLookupDict[shipcall.ArrivalBerthId ?? 0].Name;
|
|
}
|
|
else
|
|
{
|
|
if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.DepartureBerthId ?? 0))
|
|
scm.Berth = BreCalLists.BerthLookupDict[shipcall.DepartureBerthId ?? 0].Name;
|
|
}
|
|
scm.AssignParticipants();
|
|
}
|
|
|
|
private void RemoveShipcall(int shipcallId)
|
|
{
|
|
this.Dispatcher.Invoke(() =>
|
|
{
|
|
this.stackPanel.Children.Remove(this._allShipCallsControlDict[shipcallId]);
|
|
});
|
|
|
|
ShipcallControlModel removeModel = this._allShipcallsDict[shipcallId];
|
|
_visibleControlModels.Remove(removeModel);
|
|
|
|
this._allShipCallsControlDict.Remove(shipcallId, out _);
|
|
this._allShipcallsDict.Remove(shipcallId, out _);
|
|
}
|
|
|
|
private void FilterShipcalls()
|
|
{
|
|
SearchFilterModel sfm = this.searchFilterControl.SearchFilter;
|
|
|
|
if( sfm.EtaFrom.HasValue && sfm.EtaFrom < DateTime.Now.AddDays(-2))
|
|
{
|
|
int daysInThePast = (int)Math.Ceiling((DateTime.Now - sfm.EtaFrom.Value).TotalDays);
|
|
if (this.searchPastDays != daysInThePast)
|
|
{
|
|
this.searchPastDays = daysInThePast;
|
|
_refreshImmediately = true; // set flag to avoid timer loop termination
|
|
_tokenSource.Cancel(); // force timer loop end
|
|
}
|
|
}
|
|
else
|
|
{
|
|
searchPastDays = 0;
|
|
if (sfm.EtaFrom == null)
|
|
sfm.EtaFrom = DateTime.Now.AddDays(-2);
|
|
}
|
|
|
|
this._visibleControlModels.Clear();
|
|
// first add everything
|
|
this._visibleControlModels.AddRange(_allShipcallsDict.Values);
|
|
|
|
// now remove elements whose filter criteria are met
|
|
|
|
if(sfm.Berths.Count > 0 )
|
|
{
|
|
this._visibleControlModels.RemoveAll(x => (!sfm.Berths.Contains((x.Shipcall?.ArrivalBerthId) ?? -1) && (x.Shipcall?.Type == ShipcallType.Arrival)) ||
|
|
(!sfm.Berths.Contains((x.Shipcall?.DepartureBerthId) ?? -1) && (x.Shipcall?.Type != ShipcallType.Arrival)));
|
|
}
|
|
|
|
if(sfm.Agencies.Count > 0 )
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll((x) =>
|
|
{
|
|
Participant? agency = x.GetParticipantForType(ParticipantType.AGENCY);
|
|
if(agency != null)
|
|
{
|
|
return !sfm.Agencies.Contains(agency.Id);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
if(sfm.Categories.Count > 0 )
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x => { if (x.Shipcall == null) return false; else return !sfm.Categories.Contains(x.Shipcall.Type); });
|
|
}
|
|
|
|
if(sfm.Ports.Count > 0 )
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x => { if(x.Shipcall == null) return false; else return !sfm.Ports.Contains(x.Shipcall.PortId); });
|
|
}
|
|
|
|
if(!string.IsNullOrEmpty(sfm.SearchString))
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x => !(x.ContainsRemarkText(sfm.SearchString) || (x.Ship?.Name.Contains(sfm.SearchString, StringComparison.InvariantCultureIgnoreCase) ?? false)));
|
|
}
|
|
|
|
if(sfm.ShipLengthTo != null)
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x => x.Ship?.Length > sfm.ShipLengthTo);
|
|
}
|
|
|
|
if(sfm.ShipLengthFrom != null)
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x => x.Ship?.Length < sfm.ShipLengthFrom);
|
|
}
|
|
|
|
if(sfm.EtaFrom != null)
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x =>
|
|
{
|
|
Times? t = x.GetTimesForParticipantType(ParticipantType.AGENCY);
|
|
switch (x.Shipcall?.Type)
|
|
{
|
|
case ShipcallType.Arrival:
|
|
{
|
|
if ((t != null) && t.EtaBerth.HasValue) return t.EtaBerth.Value < sfm.EtaFrom;
|
|
return x.Shipcall?.Eta < sfm.EtaFrom;
|
|
}
|
|
default: // Shifting / Departing
|
|
{
|
|
if ((t != null) && t.EtdBerth.HasValue) return t.EtdBerth.Value < sfm.EtaFrom;
|
|
return x.Shipcall?.Etd < sfm.EtaFrom;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if(sfm.EtaTo != null)
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x =>
|
|
{
|
|
Times? t = x.GetTimesForParticipantType(ParticipantType.AGENCY);
|
|
DateTime refValue = sfm.EtaTo.Value.AddMinutes(1440); // including 23:59
|
|
switch (x.Shipcall?.Type)
|
|
{
|
|
case ShipcallType.Arrival:
|
|
{
|
|
if ((t != null) && t.EtaBerth.HasValue) return t.EtaBerth.Value > refValue;
|
|
return x.Shipcall?.Eta > refValue;
|
|
}
|
|
default: // Shifting / Departing
|
|
{
|
|
if ((t != null) && t.EtdBerth.HasValue) return t.EtdBerth.Value > refValue;
|
|
return x.Shipcall?.Etd > refValue;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if(sfm.MineOnly ?? false)
|
|
{
|
|
_ = _visibleControlModels.RemoveAll(x =>
|
|
{
|
|
bool contained = false;
|
|
foreach(ParticipantAssignment p in x.AssignedParticipants.Values)
|
|
{
|
|
if(p.ParticipantId.Equals(App.Participant.Id))
|
|
{
|
|
contained = true; break;
|
|
}
|
|
}
|
|
return !contained;
|
|
});
|
|
}
|
|
|
|
if(!_showCanceled ?? true) // canceled calls are filtered by default
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x => x.Shipcall?.Canceled ?? false);
|
|
}
|
|
|
|
// special Basti case: Wenn das ATA / ATD eingetragen ist und schon 2 Stunden in der Vergangenheit liegt
|
|
if (searchPastDays <= 3)
|
|
{
|
|
_ = this._visibleControlModels.RemoveAll(x =>
|
|
{
|
|
Times? mooringTimes = x.GetTimesForParticipantType(ParticipantType.MOORING);
|
|
if (mooringTimes != null)
|
|
{
|
|
switch (x.Shipcall?.Type)
|
|
{
|
|
case ShipcallType.Arrival:
|
|
if (mooringTimes.Ata.HasValue && ((DateTime.Now - mooringTimes.Ata.Value).TotalHours > 2))
|
|
return true;
|
|
break;
|
|
|
|
default:
|
|
if (mooringTimes.Atd.HasValue && ((DateTime.Now - mooringTimes.Atd.Value).TotalHours > 2))
|
|
return true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
|
|
switch (this._sortOrder)
|
|
{
|
|
case Extensions.SortOrder.SHIP_NAME:
|
|
this._visibleControlModels.Sort((x, y) => { if (x.Ship == null) return 0; if (y.Ship == null) return 0; return x.Ship.Name.CompareTo(y.Ship.Name); });
|
|
break;
|
|
case Extensions.SortOrder.MODIFIED:
|
|
this._visibleControlModels.Sort((x, y) => { if (x.Shipcall == null) return 0; if (y.Shipcall == null) return 0; return DateTime.Compare(x.Shipcall.Modified ?? x.Shipcall.Created, y.Shipcall.Modified ?? x.Shipcall.Created); });
|
|
break;
|
|
case Extensions.SortOrder.ETA_ETD:
|
|
this._visibleControlModels.Sort((x, y) =>
|
|
{
|
|
if (x.Shipcall == null) return 0;
|
|
if (y.Shipcall == null) return 0;
|
|
DateTime now = DateTime.Now;
|
|
|
|
DateTime xDate = (x.Shipcall.Type == ShipcallType.Arrival) ? (x.Eta ?? now) : (x.Etd ?? now);
|
|
|
|
Times? xTimes = x.GetTimesForParticipantType(ParticipantType.AGENCY);
|
|
if(xTimes != null)
|
|
xDate = (x.Shipcall.Type == ShipcallType.Arrival) ? (xTimes.EtaBerth ?? now) : (xTimes.EtdBerth ?? now);
|
|
|
|
DateTime yDate = (y.Shipcall.Type == ShipcallType.Arrival) ? (y.Eta ?? now) : (y.Etd ?? now);
|
|
|
|
Times? yTimes = y.GetTimesForParticipantType(ParticipantType.AGENCY);
|
|
if (yTimes != null)
|
|
yDate = (y.Shipcall.Type == ShipcallType.Arrival) ? (yTimes.EtaBerth ?? now) : (yTimes.EtdBerth ?? now);
|
|
|
|
return DateTime.Compare(xDate, yDate);
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
#region UpdateUI func
|
|
|
|
private void UpdateUI()
|
|
{
|
|
|
|
this.Dispatcher.Invoke(new Action(() =>
|
|
{
|
|
//if (Interlocked.CompareExchange(ref _uiUpdateRunning, 1, 0) == 1) return;
|
|
|
|
try
|
|
{
|
|
this.stackPanel.Children.Clear();
|
|
foreach (ShipcallControlModel visibleModel in this._visibleControlModels)
|
|
{
|
|
if (visibleModel.Shipcall == null) continue; // should not happen
|
|
if (this._allShipCallsControlDict.ContainsKey(visibleModel.Shipcall.Id))
|
|
{
|
|
this._allShipCallsControlDict[visibleModel.Shipcall.Id].RefreshData();
|
|
this.stackPanel.Children.Add(this._allShipCallsControlDict[visibleModel.Shipcall.Id]);
|
|
}
|
|
}
|
|
}
|
|
catch(Exception e) {
|
|
_log.ErrorFormat("Exception running ui update: {0}", e.ToString());
|
|
}
|
|
finally
|
|
{
|
|
// _uiUpdateRunning = 0;
|
|
}
|
|
|
|
Mouse.OverrideCursor = null;
|
|
}));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region control event handler
|
|
|
|
private async void Sc_EditRequested(ShipcallControl obj)
|
|
{
|
|
if (obj.ShipcallControlModel != null)
|
|
{
|
|
EditShipcallControl esc = new()
|
|
{
|
|
ShipcallModel = obj.ShipcallControlModel,
|
|
ShipApi = _shipApi,
|
|
ShipEditingEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD)
|
|
};
|
|
|
|
if(esc.ShowDialog() ?? false)
|
|
{
|
|
try
|
|
{
|
|
obj.ShipcallControlModel.Shipcall?.Participants.Clear();
|
|
if(! await obj.ShipcallControlModel.UpdateTimesAssignments(this._timesApi))
|
|
{
|
|
ShowErrorDialog(obj.ShipcallControlModel.LastErrorMessage, "Update assignments error");
|
|
}
|
|
foreach(ParticipantAssignment pa in obj.ShipcallControlModel.AssignedParticipants.Values)
|
|
obj.ShipcallControlModel.Shipcall?.Participants.Add(pa);
|
|
await _shipcallApi.ShipcallUpdateAsync(obj.ShipcallControlModel.Shipcall);
|
|
obj.RefreshData();
|
|
_refreshImmediately = true;
|
|
_tokenSource.Cancel();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ShowErrorDialog(ex.Message, "Error saving edited shipcall");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void Sc_EditTimesRequested(ShipcallControl obj, Times? times, ParticipantType participantType)
|
|
{
|
|
|
|
if( obj.ShipcallControlModel == null) { return; }
|
|
if (!obj.ShipcallControlModel.AssignedParticipants.ContainsKey(participantType)) return; // no assigment means no dialog my friend
|
|
|
|
Times? agencyTimes = obj.ShipcallControlModel.GetTimesForParticipantType(ParticipantType.AGENCY);
|
|
|
|
// show a dialog that lets the user create / update times for the given shipcall
|
|
IEditTimesControl etc = (participantType == ParticipantType.TERMINAL) ? new EditTimesTerminalControl() : new EditTimesControl();
|
|
etc.Title = obj.ShipcallControlModel.Title;
|
|
etc.ShipcallModel = obj.ShipcallControlModel;
|
|
if (etc is EditTimesControl control)
|
|
control.AgencyTimes = agencyTimes;
|
|
|
|
bool wasEdit = false;
|
|
if (times != null)
|
|
{
|
|
etc.Times = times;
|
|
wasEdit = true;
|
|
}
|
|
else
|
|
{
|
|
if(obj.ShipcallControlModel.AssignedParticipants[participantType].ParticipantId == App.Participant.Id)
|
|
{
|
|
etc.Times.ParticipantId = App.Participant.Id; // this is my record, so the Participant Id is set that allows editing
|
|
}
|
|
}
|
|
|
|
// actually we should only do this on create but we have existing data
|
|
etc.Times.ParticipantType = (int) participantType;
|
|
|
|
if(etc.ShowDialog() ?? false)
|
|
{
|
|
try
|
|
{
|
|
if (wasEdit)
|
|
{
|
|
await _timesApi.TimesUpdateAsync(etc.Times);
|
|
}
|
|
else
|
|
{
|
|
etc.Times.ParticipantId = App.Participant.Id;
|
|
if ((obj.ShipcallControlModel != null) && (obj.ShipcallControlModel.Shipcall != null))
|
|
{
|
|
etc.Times.ShipcallId = obj.ShipcallControlModel.Shipcall.Id;
|
|
}
|
|
Id apiResultId = await _timesApi.TimesCreateAsync(etc.Times);
|
|
etc.Times.Id = apiResultId.VarId;
|
|
obj.ShipcallControlModel?.Times.Add(etc.Times);
|
|
}
|
|
_refreshImmediately = true;
|
|
_tokenSource.Cancel();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ShowErrorDialog(ex.Message, "Error saving times");
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void Sc_EditAgencyRequested(ShipcallControl sc, Times? times)
|
|
{
|
|
IEditTimesControl? editControl = null;
|
|
switch(sc.ShipcallControlModel?.Shipcall?.Type)
|
|
{
|
|
case ShipcallType.Arrival:
|
|
editControl = new EditTimesAgencyIncomingControl();
|
|
break;
|
|
case ShipcallType.Departure:
|
|
editControl = new EditTimesAgencyOutgoingControl();
|
|
break;
|
|
case ShipcallType.Shifting:
|
|
editControl = new EditTimesAgencyShiftingControl();
|
|
break;
|
|
}
|
|
|
|
if (editControl != null)
|
|
{
|
|
editControl.ShipcallModel = sc.ShipcallControlModel ?? new ShipcallControlModel();
|
|
bool wasEdit = false;
|
|
if (times != null)
|
|
{
|
|
editControl.Times = times;
|
|
wasEdit = true;
|
|
}
|
|
else
|
|
{
|
|
if(editControl.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
|
|
editControl.Times.ParticipantId = editControl.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
|
|
}
|
|
editControl.Times.ParticipantType = (int)ParticipantType.AGENCY;
|
|
if(editControl.ShowDialog() ?? false)
|
|
{
|
|
try
|
|
{
|
|
if (sc.ShipcallControlModel != null)
|
|
{
|
|
if (!await sc.ShipcallControlModel.UpdateTimesAssignments(_timesApi)) // if the agent changed the assignment of the participant to another
|
|
{
|
|
ShowErrorDialog(sc.ShipcallControlModel.LastErrorMessage, "Error updating times assignment");
|
|
}
|
|
}
|
|
|
|
// always try to be the agent, even if we are BSMD
|
|
if (editControl.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
|
|
{
|
|
editControl.Times.ParticipantId = editControl.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
|
|
}
|
|
else
|
|
{
|
|
editControl.Times.ParticipantId = App.Participant.Id;
|
|
}
|
|
|
|
if (wasEdit)
|
|
{
|
|
await _timesApi.TimesUpdateAsync(editControl.Times);
|
|
}
|
|
else
|
|
{
|
|
if ((sc.ShipcallControlModel != null) && (sc.ShipcallControlModel.Shipcall != null))
|
|
{
|
|
editControl.Times.ShipcallId = sc.ShipcallControlModel.Shipcall.Id;
|
|
}
|
|
Id resultAPI_Id = await _timesApi.TimesCreateAsync(editControl.Times);
|
|
editControl.Times.Id = resultAPI_Id.VarId;
|
|
|
|
sc.ShipcallControlModel?.Times.Add(editControl.Times);
|
|
}
|
|
editControl.ShipcallModel.Shipcall?.Participants.Clear();
|
|
foreach (ParticipantAssignment pa in editControl.ShipcallModel.AssignedParticipants.Values)
|
|
editControl.ShipcallModel.Shipcall?.Participants.Add(pa);
|
|
await _shipcallApi.ShipcallUpdateAsync(editControl.ShipcallModel.Shipcall);
|
|
_refreshImmediately = true;
|
|
_tokenSource.Cancel();
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
ShowErrorDialog(ex.Message, "Error saving agency information");
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region helper
|
|
|
|
private void ShowErrorDialog(string message, string caption)
|
|
{
|
|
// Example:
|
|
// Error calling ShipcallUpdate: {\"message\": \"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users
|
|
// (if the special-flag is enabled). Assigned Agency: ShipcallParticipantMap(id=628, shipcall_id=115, participant_id=10,
|
|
// type=8, created=datetime.datetime(2024, 8, 28, 15, 13, 14), modified=None) with Flags: 42\"}
|
|
|
|
Match m = Regex.Match(message, "\\{(.*)\\}");
|
|
if ((m != null) && m.Success)
|
|
{
|
|
try
|
|
{
|
|
dynamic? msg = JsonConvert.DeserializeObject(m.Value);
|
|
if (msg != null)
|
|
{
|
|
if (msg.error_field != null)
|
|
{
|
|
caption = $"{caption}: {msg.error_field}";
|
|
}
|
|
|
|
if(msg.error_description != null)
|
|
{
|
|
message = msg.error_description;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception) { }
|
|
}
|
|
|
|
_log.ErrorFormat("{0} - {1}", caption, message);
|
|
|
|
Dispatcher.Invoke(new Action(() =>
|
|
{
|
|
MessageBox.Show(message, caption, MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}));
|
|
|
|
}
|
|
|
|
private void EnableControlsForParticipant()
|
|
{
|
|
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD))
|
|
this.buttonNew.Visibility = Visibility.Visible;
|
|
|
|
this.comboBoxPorts.ItemsSource = BreCalLists.AllPorts.Where(x => App.Participant.Ports.Contains(x.Id));
|
|
}
|
|
|
|
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
|
|
{
|
|
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });
|
|
e.Handled = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|