using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Xamarin.Forms; using InABox.Core; using InABox.Clients; using InABox.Mobile; using Comal.Classes; using InABox.Configuration; using JetBrains.Annotations; using XF.Material.Forms.UI.Dialogs; using Comal.Classes.SecurityDescriptors; namespace PRS.Mobile { public class MainPageSettings : ILocalConfigurationSettings { public bool ClockOnDisabled { get; set; } public MainPageSettings() { ClockOnDisabled = true; } } public partial class MainPage { #region Fields private readonly MainPageSettings _settings; private readonly Dictionary _profiles; private TimeSheet _currenttimesheet; #endregion #region Constructor private readonly Task[] _setuptasks; public MainPage() { _settings = new LocalConfiguration().Load(); _setuptasks = new Task[] { Task.Run(() => App.Data.GPSTrackers.Refresh(false)), Task.Run(() => App.Data.BluetoothGates.Refresh(false)), Task.Run(() => CheckTimeSheet()) }; App.Data.GPSLocationUpdated += OnGPSLocationUpdated; App.Data.BluetoothScanFinished += OnBluetothScanFinished; InitializeComponent(); ProgressVisible = true; var logo = Path.Combine(CoreRepository.CacheFolder(), "logo.png"); if (File.Exists(logo)) _splash.Source = FileImageSource.FromFile(logo); else _splash.Source = ImageSource.FromFile("splash"); _profiles = new LocalConfiguration().LoadAll(); if (_profiles.Count > 1) { _menu.Items.Clear(); _menu.Clicked -= SettingsTapped; foreach (var profile in _profiles) { var item = new MobileMenuItem() { Image = profile.Value.IsDefault ? ImageSource.FromFile("tick") : null, Text = profile.Key, BindingContext = profile.Value }; item.Clicked += ProfileTapped; _menu.Items.Add(item); } _menu.Items.Add(new MobileMenuSeparator()); var settings = new MobileMenuItem() { Text = "Settings" }; settings.Clicked += SettingsTapped; _menu.Items.Add(settings); } BackButtonEnabled = App.IsSharedDevice; SetupModules(); // LocalNotificationCenter.Current.NotificationActionTapped += (Plugin.LocalNotification.EventArgs.NotificationActionEventArgs e) => // { // if (MainPageUtils.DetermineCorrectPage(e) != null) // { // Device.BeginInvokeOnMainThread(() => // { // Navigation.PushAsync(MainPageUtils.DetermineCorrectPage(e)); // }); // } // }; // // MainPageUtils.OnMainPageNotificationsChanged += RefreshOnNotificationsChange; MessagingCenter.Subscribe(this, App.MessageOnResume, (o) => { if (!App.GPS.RecentlyLocated) App.GPS.GetLocation(); RefreshScreen(); } ); } private async void ProfileTapped(object sender, EventArgs e) { if (sender is MobileMenuItem item && item.BindingContext is MobileDatabaseSettings settings) { foreach (var profile in _profiles) profile.Value.IsDefault = profile.Value == settings; new LocalConfiguration().SaveAll(_profiles); ClientFactory.InvalidateUser(); if (App.Current.Properties.ContainsKey("SessionID")) App.Current.Properties.Remove("SessionID"); App.Data.Reset(); ClientFactory.InvalidateUser(); if (App.Current.Properties.ContainsKey("SessionID")) App.Current.Properties.Remove("SessionID"); App.Data.Reset(); TransportStatus connection = TransportStatus.None; using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Connecting")) { await Task.Run(() => { CoreRepository.CacheID = settings.CacheID; connection = App.ConnectTransport(settings.URLs); var info = App.Transport.Info(); if (info?.Logo?.Any() == true) File.WriteAllBytes(Path.Combine(CoreRepository.CacheFolder(),"logo.png"), info.Logo); }); } if (connection != TransportStatus.OK) { await DisplayAlert("Connection Error", $"Unable to establish a connection!\n\nERR: {connection}", "OK"); return; } Navigation.PopAsync(); } } #endregion #region OnAppearing and Display private SelectionPage _selectionpage; protected override void OnAppearing() { _pollingToken = new CancellationTokenSource(); StartMonitoringNotifications(); StartMonitoringTasks(); StartMonitoringVersion(); StartMonitoringEmpoyeeDetails(); //ProgressVisible = false; if (_selectionpage == null) { CheckTimeSheet(); } _selectionpage = null; Task.WaitAll(_setuptasks); RefreshScreen(); base.OnAppearing(); } protected override void OnDisappearing() { EnableModules(false); _pollingToken.Cancel(); base.OnDisappearing(); } private void OnBluetothScanFinished(BluetoothEventArgs args) { Dispatcher.BeginInvokeOnMainThread(RefreshScreen); } private void OnGPSLocationUpdated(GPSEventArgs args) { Dispatcher.BeginInvokeOnMainThread(RefreshScreen); } void SetupClockOnButton(bool disable) { if (disable != _settings.ClockOnDisabled) { _settings.ClockOnDisabled = disable; new LocalConfiguration().Save(_settings); } SplashCard.IsVisible = disable; ClockOnButton.IsVisible = !disable; jobBtn.IsVisible = !disable; addNoteBtn.IsVisible = !disable; taskBtn.IsVisible = !disable; } private void SetupModules() { SetupClockOnButton(Security.IsAllowed()); Tools.BeginUpdate(); Assignments.IsVisible = Security.IsAllowed(); Deliveries.IsVisible = Security.IsAllowed(); Forms.IsVisible = Security.IsAllowed(); Equipment.IsVisible = Security.IsAllowed(); MyHR.IsVisible = Security.IsAllowed() || Security.IsAllowed() || Security.IsAllowed() || Security.IsAllowed() || Security.IsAllowed(); InOut.IsVisible = Security.IsAllowed(); Manufacturing.IsVisible = Security.IsAllowed(); Meetings.IsVisible = Security.IsAllowed(); Products.IsVisible = Security.IsAllowed(); PurchaseOrders.IsVisible = Security.IsAllowed(); Scans.IsVisible = Security.IsAllowed(); MyTasks.IsVisible = Security.IsAllowed(); Site.IsVisible = Security.IsAllowed(); Warehousing.IsVisible = Security.IsAllowed(); Tools.EndUpdate(); } protected override void UpdateTransportStatus() { base.UpdateTransportStatus(); CheckTimeSheet(); EnableModules(App.Data.IsConnected()); } private void EnableModules(bool enabled) { Tools.BeginUpdate(); foreach (var item in Tools.Items) item.IsEnabled = enabled; Tools.EndUpdate(); } private CancellationTokenSource _pollingToken = new CancellationTokenSource(); private void StartMonitoringNotifications() { //notifications are allowed to upload once every 30 seconds // This will eventually be replaced with websocket pushes // but we're not ready for that yet :-( var token = _pollingToken.Token; Task.Run( () => { while (!_pollingToken.Token.IsCancellationRequested) { App.Data.Notifications.Refresh(true); int _notificationcount = App.Data.Notifications.Items.Where(x=>x.Closed.IsEmpty()).ToList().Count; Dispatcher.BeginInvokeOnMainThread(() => { Notifications.Alert = _notificationcount > 0 ? _notificationcount.ToString() : ""; }); Task.Delay(TimeSpan.FromSeconds(30), token) .Wait(token); } }, token ); } private void StartMonitoringTasks() { var token = _pollingToken.Token; Task.Run( async () => { while (!_pollingToken.Token.IsCancellationRequested) { App.Data.Kanbans.Refresh(true); int _taskcount = App.Data.Kanbans.Items.Where(x=>x.Status == KanbanStatus.Open).ToList().Count; Dispatcher.BeginInvokeOnMainThread(() => { MyTasks.Alert = _taskcount > 0 ? _taskcount.ToString() : ""; }); await Task.Delay(TimeSpan.FromSeconds(30), token); } }, token ); } private void StartMonitoringVersion() { var token = _pollingToken.Token; Task.Run( async () => { while (!_pollingToken.Token.IsCancellationRequested) { bool isLatest = true; try { isLatest = await MobileUtils.AppVersion.IsUsingLatestVersion(); } catch (Exception e) { InABox.Mobile.MobileLogging.Log(e); } //bool isLatest = MobileUtils.AppVersion.IsUsingLatestVersion().Result; Dispatcher.BeginInvokeOnMainThread(() => { if (Update.IsVisible == isLatest) { Update.IsVisible = !isLatest; Tools.Refresh(); Update.Alert = isLatest ? "" : "!"; } }); Task.Delay(TimeSpan.FromSeconds(30), token) .Wait(token); } }, token ); } private void StartMonitoringEmpoyeeDetails() { var token = _pollingToken.Token; Task.Run(() => { while (!_pollingToken.Token.IsCancellationRequested) { App.Data.EmployeeQualifications.Refresh(true); bool bQualificationIssues = App.Data.EmployeeQualifications.NeedsAttention > 0; App.Data.EmployeeForms.Refresh(true); bool bFormIssues = App.Data.EmployeeForms.NeedsAttention > 0; Dispatcher.BeginInvokeOnMainThread(() => { MyHR.Alert = (bQualificationIssues || bFormIssues) ? "!" : ""; }); Task.Delay(TimeSpan.FromSeconds(30), token) .Wait(token); } }, token ); } private void RefreshScreen() { SetupClockOnButton(_settings.ClockOnDisabled); bool GateReady = CheckLocation(); var location = GetAddress(); Title = App.Data.Me != null ? App.Data.Me.Name : "(Not Logged In)"; ClockOnButton.IsEnabled = App.Data.IsConnected() && GateReady; ClockOnButton.IsClickable = ClockOnButton.IsEnabled; ClockOnOffLabel.Text = GateReady ? _currenttimesheet == null ? "CLOCK ON" : "CLOCK OFF" : "PLEASE WAIT"; CurrentLocation.Text = location.ToUpper().Contains("ERROR") ? "Unknown Address" : location; ClockOnButton.BackgroundColor = App.Data.IsConnected() && GateReady ? _currenttimesheet == null ? Color.FromHex("#e6e6fa") : Color.FromHex("#15C7C1") : Color.Silver; ClockOnButton.BorderColor = App.Data.IsConnected() && GateReady ? _currenttimesheet == null ? Color.FromHex("#96969a") : Color.FromHex("#059791") : Color.Gray; addNoteBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null; jobBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null; taskBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null; jobBtn.Text = ((_currenttimesheet?.JobID ?? Guid.Empty) != Guid.Empty) ? $"{_currenttimesheet?.JobLink.JobNumber}: {_currenttimesheet?.JobLink.Name}" : "Select Job"; } #endregion #region Clock on/off private void CheckTimeSheet() { if (App.Data.IsConnected()) { var client = new Client(); var filter = new Filter(x => x.EmployeeLink.ID).IsEqualTo(App.Data.Me.ID) .And(x => x.Date).IsEqualTo(DateTime.Today) .And(x => x.Finish).IsEqualTo(TimeSpan.Zero); var table = client.Query( filter, Columns.None() .Add(x=>x.ID) .Add(x=>x.Date) .Add(x=>x.Start) .Add(x=>x.StartLocation.Longitude) .Add(x=>x.StartLocation.Latitude) .Add(x=>x.StartLocation.Address) .Add(x=>x.StartLocation.Timestamp) .Add(x=>x.Finish) .Add(x=>x.FinishLocation.Longitude) .Add(x=>x.FinishLocation.Latitude) .Add(x=>x.FinishLocation.Address) .Add(x=>x.FinishLocation.Timestamp) .Add(x=>x.JobLink.ID) .Add(x=>x.JobLink.JobNumber) .Add(x=>x.JobLink.Name) .Add(x=>x.Notes) ); _currenttimesheet = table.Rows.FirstOrDefault()?.ToObject(); var token = _pollingToken.Token; Task.Run( () => { while (!token.IsCancellationRequested) { if (_currenttimesheet != null) { if (_currenttimesheet.Date != DateTime.Today) { var here = GPSLocation(); _currenttimesheet.FinishLocation.CopyFrom(here); _currenttimesheet.Finish = TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1)); new Client().Save(_currenttimesheet, "Closing Timesheet at Midnight on Mobile Device"); CreateTimeSheet(_currenttimesheet.JobLink.ID, _currenttimesheet.JobLink.JobNumber, _currenttimesheet.JobLink.Name, here, here.Address, "Creating Timeheet at Midnight on Mobile Device"); } } Task.Delay(TimeSpan.FromMinutes(1), token).Wait(token); } }, token ); } } private DateTime _debounce = DateTime.MinValue; async void ClockOnOff_Clicked(object sender, EventArgs e) { if (_debounce > DateTime.Now.Subtract(TimeSpan.FromMilliseconds(500))) return; _debounce = DateTime.MaxValue; string chosenOption = _currenttimesheet != null ? await DisplayActionSheet("Clock off?", "Cancel", null, "Continue", "Cancel") : "Continue"; if (!String.Equals(chosenOption, "Continue")) { _debounce = DateTime.Now; return; } try { using (await MaterialDialog.Instance.LoadingDialogAsync(message: $"Clocking {(_currenttimesheet == null ? "On" : "Off")}")) { var here = GPSLocation(); if (_currenttimesheet != null) { FinishTimeSheet(here); } else { if (!Security.IsAllowed()) { var tracker = App.Data.BluetoothGates.FirstOrDefault(x => App.Bluetooth.Devices.Contains(x.DeviceID)); if (tracker != null) CreateTimeSheet(tracker.JobID, tracker.JobNumber, tracker.JobName, here, here.Address, "Clocking On"); } else { if ((!here.Latitude.Equals(0.0F) && !here.Longitude.Equals(0.0F)) || Security.IsAllowed()) { var job = await ChooseNearbyJob(here); CreateTimeSheet(job?.ID ?? Guid.Empty, job?.JobNumber ?? string.Empty, job?.Name ?? string.Empty , here, here.Address, "Clocking On"); } } } Dispatcher.BeginInvokeOnMainThread(RefreshScreen); } } catch (Exception ex) { InABox.Mobile.MobileLogging.Log(ex); } _debounce = DateTime.Now; } private void FinishTimeSheet(Location here) { if ((_currenttimesheet == null) || (_currenttimesheet.ID == Guid.Empty)) { _currenttimesheet = null; return; } try { if (ZeroLengthTimesheet()) new Client().Delete(_currenttimesheet, "Deleted due to zero duration timesheet"); else { _currenttimesheet.Finish = new TimeSpan(DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes, 0); _currenttimesheet.FinishLocation .CopyFrom(here); //bUpdatingTimesheet = true; new Client().Save(_currenttimesheet, "Clocking Off"); } _currenttimesheet = null; } catch (Exception ex) { InABox.Mobile.MobileLogging.Log(ex); } } #endregion #region Utilities //private void CheckNotificationsPushed(CoreTable table) //{ // try // { // if (!Application.Current.Properties.ContainsKey("LastPushedNotifications")) // { // Application.Current.Properties.Add("LastPushedNotifications", DateTime.Now); // } // DateTime lastPushed = DateTime.Parse(Application.Current.Properties["LastPushedNotifications"].ToString()); // List toNotify = new List(); // foreach (CoreRow row in table.Rows) // { // List list = row.Values; // DateTime created = DateTime.Parse(list[3].ToString()); // if (created > new DateTime(2022, 8, 22)) // prevent spam from buildup of old notifications before this is released // { // if (created > lastPushed) // { // if (list[1] == null) list[1] = ""; // if (list[2] == null) list[2] = ""; // if (list[3] == null) list[3] = DateTime.MinValue; // if (list[4] == null) list[4] = ""; // if (list[5] == null) list[5] = ""; // if (list[6] == null) list[6] = Guid.Empty; // // NotificationShell shell = new NotificationShell // { // ID = Guid.Parse(list[0].ToString()), // Sender = list[1].ToString(), // Title = list[2].ToString(), // Created = DateTime.Parse(list[3].ToString()), // EntityType = list[5].ToString(), // EntityID = Guid.Parse(list[6].ToString()) // }; // toNotify.Add(shell); //add notification to be pushed // } // } // } // if (toNotify.Count > 0) // PushNotificationsAsync(toNotify); // } // catch { } //} //private async Task PushNotificationsAsync(List shells) //{ // try // { // int count = 1; // // foreach (NotificationShell shell in shells) // { // var notification = new NotificationRequest // { // BadgeNumber = 1, // Description = shell.Title, // Title = "New PRS Notification: ", // ReturningData = shell.EntityID.ToString() + "$" + shell.EntityType, // NotificationId = count, // }; // count++; // NotificationImage img = new NotificationImage(); // img.ResourceName = "icon16.png"; // notification.Image = img; // // await LocalNotificationCenter.Current.Show(notification); // } // Application.Current.Properties["LastPushedNotifications"] = DateTime.Now; // } // catch { } //} private async Task ChooseNearbyJob(Location here) { var nearbyjobs = App.Data.Jobs.Where(x => here.DistanceTo(x.Location, UnitOfLength.Kilometers) < 1.0F) .ToArray(); if (nearbyjobs.Length > 1) { Dictionary dict = new Dictionary(); foreach (var job in nearbyjobs) dict[job.DisplayName] = job; string chosenOption = await DisplayActionSheet("Choose job site", "Cancel", null, dict.Keys.ToArray()); if (string.IsNullOrEmpty(chosenOption) || chosenOption.Equals("Cancel")) return null; return dict[chosenOption]; } return nearbyjobs.FirstOrDefault(); } void AddNote_Tapped(object sender, EventArgs e) { if (_currenttimesheet == null) return; var notepage = new TimeSheetNotePage(_currenttimesheet); Navigation.PushAsync(notepage); } private void TaskBtn_Tapped(object sender, EventArgs e) { _selectionpage = new TaskSelectionPage( (task) => { if (_currenttimesheet != null) { // Not sure hwat to do here... } } ); Navigation.PushAsync(_selectionpage); } // private async void RequestUserInput(Guid taskID) // { // const string addtask = "Change current assignment task"; // const string newassignment = "Start a new assignment with this task"; // string chosenOption = await DisplayActionSheet("Choose an option", "Cancel", null, addtask, newassignment); // switch (chosenOption) // { // case addtask: // MainPageUtils.ChangeAssignmentTask(taskID); // break; // case newassignment: // MainPageUtils.SaveCurrentAssignment("PRS Mobile main screen - saving assignment on task change", true); // MainPageUtils.CreateNewAssignment(Guid.Empty, taskID); // break; // default: // break; // } // } private void SaveSiteModuleJobID([CanBeNull] JobShell job) { using (var config = new LocalConfiguration()) { var _sitesettings = config.Load(); _sitesettings.JobID = job?.ID ?? Guid.Empty; config.Save(_sitesettings); } } private void JobBtn_Tapped(object sender, EventArgs e) { _selectionpage = new JobSelectionPage( (job) => { if (_currenttimesheet != null) { if (ZeroLengthTimesheet()) { _currenttimesheet.JobLink.ID = job?.ID ?? Guid.Empty; _currenttimesheet.JobLink.JobNumber = job?.JobNumber ?? string.Empty; _currenttimesheet.JobLink.Name = job?.Name ?? string.Empty; new Client().Save(_currenttimesheet,"Updated Job Number via mobile device"); } else { var here = GPSLocation(); FinishTimeSheet(here); CreateTimeSheet(job?.ID ?? Guid.Empty, job?.JobNumber ?? string.Empty, job?.Name ?? string.Empty, here, here.Address, "Switched Jobs via Mobile Device"); } } SaveSiteModuleJobID(job); } ); Navigation.PushAsync(_selectionpage); } // private bool CheckTimeSheetAgainstGates(TimeSheet timesheet) // { // DateTime now = DateTime.Now; // // //var timesheet = CurrentTimeSheet(); // // //Can't confirm if there is no timesheet // if (timesheet == null) // return false; // // // Can't confirm if there are no devices // if (App.Bluetooth.Devices.Length == 0) // return false; // // if (App.Data.Gates == null) // return false; // // long tsTicks = timesheet.Date.Add(timesheet.Start).Ticks; // long btTicks = App.Bluetooth.TimeStamp.Ticks; // // if (Math.Abs(tsTicks - btTicks) > new TimeSpan(0, 2, 0).Ticks) // return false; // // CoreRow firstgate = null; // List gates = new List(); // // Scan every located d // foreach (var device in App.Bluetooth.Devices) // { // CoreRow gate = App.Data.Gates?.Rows.FirstOrDefault(r => r.Get(c => c.TrackerLink.DeviceID) == device); // if (gate != null) // { // // if ((gate.Get(x => x.IsJobSite) == true) && (firstgate == null)) // firstgate = gate; // // gates.Add(gate.Get(x => x.Gate)); // } // } // if (gates.Any()) // { // timesheet.Gate = String.Join(", ", gates.OrderBy(x => x)); // if (firstgate != null) // { // timesheet.JobLink.ID = firstgate.Get(x => x.JobLink.ID); // timesheet.JobLink.JobNumber = firstgate.Get(x => x.JobLink.JobNumber); // timesheet.JobLink.Name = firstgate.Get(x => x.JobLink.Name); // } // return true; // //new Client().Save(timesheet, "Confirmed Gate Entry by Bluetooth Tracker", (o, e) => { }); // } // // return false; // // } private bool ZeroLengthTimesheet() { if (_currenttimesheet == null) return true; if (!String.IsNullOrWhiteSpace(_currenttimesheet.Notes)) return false; if (_currenttimesheet.Date.Equals(DateTime.Today)) { var diff = (DateTime.Now.TimeOfDay - _currenttimesheet.Start).TotalSeconds; if (Math.Abs(diff) < 120.0F) return true; } return false; } private void CreateTimeSheet(Guid jobid, string jobnumber, String jobname, Location location, String address, String auditmessage) { try { _currenttimesheet = new TimeSheet { Date = DateTime.Today, Address = address, SoftwareVersion = MobileUtils.AppVersion.InstalledVersionNumber + App.DeviceString, Start = (DateTime.Now - DateTime.Today).Floor(TimeSpan.FromMinutes(1)) }; _currenttimesheet.EmployeeLink.ID = App.Data.Me.ID; _currenttimesheet.JobLink.ID = jobid; _currenttimesheet.JobLink.JobNumber = jobnumber; _currenttimesheet.JobLink.Name = jobname; _currenttimesheet.StartLocation.CopyFrom(location); new Client().Save(_currenttimesheet, auditmessage); } catch (Exception ex) { InABox.Mobile.MobileLogging.Log(ex); } } private bool CheckLocation() { // a 15 minute timeout is awfully long for this process. // The App times tick over every 30 seconds, so surely we can // drop this to max 1 or 2 minutes.. // Also, we would expect the GPS / Bluetooth subsystems to take care // of purging stale data if (Security.IsAllowed()) return true; if (Security.IsAllowed()) return GPSTimeStamp() > DateTime.Now.Subtract(new TimeSpan(0, 15, 0)); if (App.Bluetooth.TimeStamp < DateTime.Now.Subtract(new TimeSpan(0, 15, 0))) return false; return App.Data.BluetoothGates.Any(x => App.Bluetooth.DetectedBlueToothMACAddresses.Contains(x.DeviceID)); } private DateTime GPSTimeStamp() { return App.GPS.TimeStamp; } private static Location GPSLocation() { Location here = new InABox.Core.Location() { Latitude = App.GPS.Latitude, Longitude = App.GPS.Longitude, Address = App.GPS.Address, Timestamp = App.GPS.TimeStamp }; return here; } private String GPSAddress() { return App.GPS.Address; } private String GetAddress() { if (Security.IsAllowed()) { if (GPSTimeStamp() < DateTime.Now.Subtract(new TimeSpan(0, 5, 0))) { App.GPS.GetLocation(true); return InABox.Core.Security.IsAllowed()? "" : "Searching for GPS"; } return GPSAddress(); } else { var gate = App.Data.BluetoothGates.FirstOrDefault(x => App.Bluetooth.DetectedBlueToothMACAddresses.Contains(x.DeviceID)); return gate?.Gate ?? "Looking for Gate"; } } #endregion public void SettingsTapped(object sender, EventArgs args) => Navigation.PushAsync(new SettingsPage()); public void AssignmentListTapped(object sender, EventArgs args) => Navigation.PushAsync(new AssignmentList()); public void DeliveryModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new DeliveryModule()); public void EquipmentModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new EquipmentModule()); public void KanbanFormsTapped(object sender, EventArgs args) => Navigation.PushAsync(new KanbanForms()); public void StaffStatusPageTapped(object sender, EventArgs args) => Navigation.PushAsync(new StaffStatusPage()); public void ManufacturingListTapped(object sender, EventArgs args) => Navigation.PushAsync(new ManufacturingList()); private void MeetingsModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new MeetingList()); public void HumanResourcesModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new HumanResourcesModule()); public void NotificationListTapped(object sender, EventArgs args) => Navigation.PushAsync(new NotificationList()); public void ProductListTapped(object sender, EventArgs args) => Navigation.PushAsync(new ProductList()); public void PurchaseOrderListTapped(object sender, EventArgs args) => Navigation.PushAsync(new PurchaseOrderList()); public void SiteModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new SiteModule()); public void KanbanListTapped(object sender, EventArgs args) => Navigation.PushAsync(new KanbanList() { Model = App.Data.Kanbans, Title = "My Tasks" }); public void WarehouseModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new WarehouseModule()); public void UpdatePageTapped(object sender, EventArgs args) => Navigation.PushAsync(new UpdatePage()); private void ScanModuleTapped(object sender, EventArgs e) => Navigation.PushAsync(new DocScannerModule()); } }