MainWindow.xaml.cs 109 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.ComponentModel;
  5. using System.Diagnostics;
  6. using System.Drawing;
  7. using System.Drawing.Drawing2D;
  8. using System.Drawing.Imaging;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Net.Http;
  12. using System.Reflection;
  13. using System.Runtime.InteropServices;
  14. using System.Threading.Tasks;
  15. using System.Windows;
  16. using System.Windows.Controls;
  17. using System.Windows.Forms;
  18. using System.Windows.Input;
  19. using System.Windows.Interop;
  20. using System.Windows.Media;
  21. using System.Windows.Threading;
  22. using AvalonDock.Layout;
  23. using Comal.Classes;
  24. using Comal.Stores;
  25. using Comal.TaskScheduler.Shared;
  26. using H.Pipes;
  27. using InABox.Clients;
  28. using InABox.Configuration;
  29. using InABox.Core;
  30. using InABox.Database;
  31. using InABox.Database.SQLite;
  32. using InABox.DynamicGrid;
  33. using InABox.Mail;
  34. using InABox.Rpc;
  35. using InABox.Scripting;
  36. using InABox.Wpf;
  37. using InABox.WPF;
  38. using NAudio.Wave;
  39. using PRS.Shared;
  40. using InABox.WPF.Themes;
  41. using PRSDesktop.Configuration;
  42. using PRSDesktop.Forms;
  43. using PRSServer;
  44. using SharpAvi.Codecs;
  45. using SharpAvi.Output;
  46. using Syncfusion.Windows.Tools.Controls;
  47. using Application = System.Windows.Application;
  48. using ButtonBase = System.Windows.Controls.Primitives.ButtonBase;
  49. using Color = System.Windows.Media.Color;
  50. using ColorConverter = System.Windows.Media.ColorConverter;
  51. using Control = System.Windows.Controls.Control;
  52. using Image = System.Drawing.Image;
  53. using KeyEventArgs = System.Windows.Input.KeyEventArgs;
  54. using MessageBox = System.Windows.MessageBox;
  55. using Pen = System.Drawing.Pen;
  56. using PixelFormat = System.Drawing.Imaging.PixelFormat;
  57. using SortDirection = InABox.Core.SortDirection;
  58. using InABox.Wpf.Reports;
  59. using Comal.Classes.SecurityDescriptors;
  60. using System.Threading;
  61. using InABox.Formatters;
  62. using PRSDesktop.Forms.Issues;
  63. using Brushes = System.Windows.Media.Brushes;
  64. using System.Windows.Media.Imaging;
  65. using PRSDesktop.Panels.Staging;
  66. using Button = System.Windows.Controls.Button;
  67. using Visibility = System.Windows.Visibility;
  68. using SharpVectors.Converters;
  69. using SVGImage.SVG;
  70. using Cursors = System.Windows.Input.Cursors;
  71. namespace PRSDesktop;
  72. public enum PanelType
  73. {
  74. InPlace,
  75. NewWindow
  76. }
  77. public class SimpleCommand : ICommand
  78. {
  79. public Action OnExecute { get; private set; }
  80. public bool CanExecute(object? parameter) => true;
  81. public event EventHandler? CanExecuteChanged
  82. {
  83. add => CommandManager.RequerySuggested += value;
  84. remove => CommandManager.RequerySuggested -= value;
  85. }
  86. public void Execute(object? parameter)
  87. {
  88. OnExecute?.Invoke();
  89. }
  90. public SimpleCommand(Action onExecute)
  91. {
  92. OnExecute = onExecute;
  93. }
  94. }
  95. public class AppSettings : ILocalConfigurationSettings
  96. {
  97. public AppSettings()
  98. {
  99. Settings = new Dictionary<string, string>();
  100. }
  101. public Dictionary<string, string> Settings { get; set; }
  102. public DynamicGridSelectedFilterSettings Filters { get; set; } = new();
  103. }
  104. /// <summary>
  105. /// Interaction logic for Main.xaml
  106. /// </summary>
  107. public partial class MainWindow : IPanelHostControl
  108. {
  109. //private const int WM_LBUTTONDOWN = 0x201;
  110. private static PipeServer<string>? _client;
  111. private IRpcClientTransport? _transport;
  112. private WaveIn? _audio;
  113. private bool _audioMuted;
  114. private MemoryStream? _audioStream;
  115. private readonly Dictionary<DateTime, Stream> _bitmaps = new();
  116. private DesktopConsole? _console;
  117. private Dictionary<DateTime, Tuple<Rectangle, string>> _notes = new();
  118. private DispatcherTimer? _recorder;
  119. private Process? _recordingnotes;
  120. private int _screenheight = 720;
  121. private int _screenleft;
  122. private int _screentop;
  123. private int _screenwidth = 1280;
  124. private readonly Dictionary<Guid, SecondaryWindow> SecondaryWindows = new();
  125. private CoreTable? _timesheets;
  126. private DailyActivityHistory? ActivityHistory;
  127. private readonly List<Control> CurrentModules = new();
  128. private Fluent.RibbonTabItem? CurrentTab;
  129. private Fluent.Button? CurrentButton;
  130. private readonly int FRAMES_PER_SECOND = 10;
  131. private DatabaseType DatabaseType;
  132. private readonly Dictionary<int, int> messages = new();
  133. private readonly DispatcherTimer NotificationsWatchDog;
  134. private DateTime pausestarted = DateTime.MinValue;
  135. private readonly Scheduler scheduler = new() { Interval = new TimeSpan(0, 5, 0) };
  136. // We use a Guid for the StationID rather than an IP or Mac address
  137. // because we want true single-instance restriction. Using either of
  138. // the above allows for two instances on the once machine, and thus
  139. // double-counting in the Heartbeat() function
  140. private Login station = new() { StationID = Guid.NewGuid().ToString() };
  141. private TimeSpan totalpauses = new(0);
  142. private readonly int VIDEO_HEIGHT = 1080;
  143. private readonly int VIDEO_WIDTH = 1920;
  144. private PanelHost PanelHost;
  145. private AppSettings? _settings;
  146. private AppSettings Settings
  147. {
  148. get
  149. {
  150. _settings ??= LocalConfiguration.Load<AppSettings>();
  151. return _settings;
  152. }
  153. }
  154. public MainWindow()
  155. {
  156. PanelHost = new PanelHost(this);
  157. NotificationsWatchDog = new DispatcherTimer { IsEnabled = false };
  158. NotificationsWatchDog.Tick += Notifications_Tick;
  159. NotificationsWatchDog.Interval = new TimeSpan(0, 2, 0);
  160. ClientFactory.PushHandlers.AddHandler<Notification>(ReceiveNotification);
  161. ClientFactory.RegisterMailer(EmailType.IMAP, typeof(IMAPMailer));
  162. ClientFactory.RegisterMailer(EmailType.Exchange, typeof(ExchangeMailer));
  163. ClientFactory.OnLog += (type, userid, message, parameters) => Logger.Send(LogType.Information, ClientFactory.UserID, message, parameters);
  164. ClientFactory.OnRequestError += ClientFactory_OnRequestError;
  165. HotKeyManager.Initialize();
  166. HotKeyManager.RegisterHotKey(Key.F1, ShowHelp);
  167. //HotKeyManager.RegisterHotKey(Key.F5, ToggleRecording);
  168. //HotKeyManager.RegisterHotKey(Key.F6, ShowRecordingNotes);
  169. //HotKeyManager.RegisterHotKey(Key.F4, ToggleRecordingAudio);
  170. Logger.Send(LogType.Information, "", "Connecting to server");
  171. var settings = App.DatabaseSettings;
  172. bool dbConnected;
  173. DatabaseType = settings.DatabaseType;
  174. switch (DatabaseType)
  175. {
  176. case DatabaseType.Standalone:
  177. ClientFactory.SetClientType(typeof(LocalClient<>), Platform.Wpf, CoreUtils.GetVersion());
  178. DbFactory.ColorScheme = App.DatabaseSettings.ColorScheme;
  179. DbFactory.Logo = App.DatabaseSettings.Logo;
  180. dbConnected = true;
  181. break;
  182. case DatabaseType.Networked:
  183. if (App.DatabaseSettings.Protocol == SerializerProtocol.RPC)
  184. {
  185. _transport = new RpcClientSocketTransport(App.DatabaseSettings.URLs);
  186. _transport.OnClose += TransportConnectionLost;
  187. _transport.OnException += Transport_OnException;
  188. _transport.OnOpen += Transport_OnOpen; ;
  189. dbConnected = _transport.Connect();
  190. ClientFactory.SetClientType(typeof(RpcClient<>), Platform.Wpf, CoreUtils.GetVersion(),
  191. _transport);
  192. }
  193. else
  194. {
  195. var url = RestClient<User>.Ping(App.DatabaseSettings.URLs, out DatabaseInfo info);
  196. ClientFactory.SetClientType(typeof(RestClient<>), Platform.Wpf, CoreUtils.GetVersion(), url, true);
  197. dbConnected = true;
  198. }
  199. break;
  200. case DatabaseType.Local:
  201. //new RPC stuff (temporary disabled - for enabling when RPC is ready)
  202. var pipename = DatabaseServerProperties.GetPipeName(App.DatabaseSettings.LocalServerName, true);
  203. _transport = new RpcClientPipeTransport(pipename);
  204. _transport.OnClose += TransportConnectionLost;
  205. dbConnected = _transport.Connect();
  206. ClientFactory.SetClientType(typeof(RpcClient<>), Platform.Wpf, CoreUtils.GetVersion(), _transport );
  207. //ClientFactory.SetClientType(typeof(IPCClient<>), Platform.Wpf, CoreUtils.GetVersion(),
  208. // DatabaseServerProperties.GetPipeName(App.DatabaseSettings.LocalServerName, false));
  209. //dbConnected = true;
  210. break;
  211. default:
  212. throw new Exception($"Invalid database type {DatabaseType}");
  213. }
  214. InitializeComponent();
  215. if (!dbConnected)
  216. {
  217. switch (DoConnectionFailed())
  218. {
  219. case ConnectionFailedResult.Quit:
  220. Close();
  221. return;
  222. case ConnectionFailedResult.Restart:
  223. App.ShouldRestart = true;
  224. Close();
  225. return;
  226. case ConnectionFailedResult.Ok:
  227. // Do nothing
  228. break;
  229. }
  230. }
  231. ThemeManager.BaseColor = Colors.CornflowerBlue;
  232. Progress.DisplayImage = PRSDesktop.Resources.splash_small.AsBitmapImage();
  233. try
  234. {
  235. var dbInfo = new Client<User>().Info();
  236. ClientFactory.DatabaseID = dbInfo.DatabaseID;
  237. ThemeManager.BaseColor = (Color)ColorConverter.ConvertFromString(dbInfo.ColorScheme);
  238. if (dbInfo.Logo?.Any() == true)
  239. using (var ms = new MemoryStream(dbInfo.Logo))
  240. {
  241. Progress.DisplayImage = new Bitmap(ms).AsBitmapImage();
  242. }
  243. }
  244. catch
  245. {
  246. }
  247. //VideoRecordingStatus.Source = PRSDesktop.Resources.videorecording.AsGrayScale().AsBitmapImage();
  248. //AudioRecordingStatus.Source = PRSDesktop.Resources.audiorecording.AsGrayScale().AsBitmapImage();
  249. //SecondaryWindowStatus.Source = PRSDesktop.Resources.target.AsGrayScale().AsBitmapImage();
  250. ConsoleStatus.Source = PRSDesktop.Resources.view.AsGrayScale().AsBitmapImage();
  251. SelectTask.Source = PRSDesktop.Resources.uparrow.Invert().AsBitmapImage();
  252. Title = $"{(String.Equals(App.Profile?.ToUpper(), "DEFAULT") ? "PRS Desktop" : App.Profile)} (Release {CoreUtils.GetVersion()})";
  253. Logger.Send(LogType.Information, "", "Checking for updates");
  254. if (SupportUtils.CheckForUpdates())
  255. {
  256. Logger.Send(LogType.Information, "", "Update found, closing application.");
  257. Close();
  258. return;
  259. }
  260. Exception? startupException = null;
  261. ValidationStatus? loginStatus = null;
  262. Progress.ShowModal("Loading PRS", progress =>
  263. {
  264. DynamicGridUtils.PreviewReport = (t, m) => { ReportUtils.PreviewReport(t, m, false, Security.IsAllowed<CanDesignReports>()); };
  265. DynamicGridUtils.PrintMenu = (e, s, m, p) => { ReportUtils.PrintMenu(e, s, m, Security.IsAllowed<CanDesignReports>(), p); };
  266. ImportFactory.Register(typeof(ExcelImporter<>), "Excel File", "Excel Files (*.xls;*.xlsx;*.xlsm)|*.xls;*.xlsx;*.xlsm");
  267. ImportFactory.Register(typeof(CustomImporter<>), "Custom", "All Files (*.*)|*.*");
  268. FormUtils.Register();
  269. DigitalFormDocumentFactory.Init(
  270. new WpfDigitalFormDocumentHandler(
  271. b => Dispatcher.BeginInvoke(() =>
  272. {
  273. BackgroundUploadStatus.Visibility = b
  274. ? Visibility.Visible
  275. : Visibility.Hidden;
  276. }
  277. ),
  278. () => _transport?.IsConnected() ?? false
  279. )
  280. );
  281. DigitalFormDocumentFactory.Run();
  282. Logger.Send(LogType.Information, "", "Registering Classes");
  283. progress.Report("Registering Classes");
  284. var tasks = new List<Task>
  285. {
  286. Task.Run(() =>
  287. {
  288. CoreUtils.RegisterClasses(typeof(TaskGrid).Assembly);
  289. CoreUtils.RegisterClasses();
  290. ComalUtils.RegisterClasses();
  291. StoreUtils.RegisterClasses();
  292. PRSSharedUtils.RegisterClasses();
  293. WPFUtils.RegisterClasses();
  294. ReportUtils.RegisterClasses();
  295. ConfigurationUtils.RegisterClasses();
  296. }),
  297. Task.Run(() =>
  298. {
  299. ScriptDocument.DefaultAssemblies.AddRange(
  300. Assembly.Load("RoslynPad.Roslyn.Windows"),
  301. Assembly.Load("RoslynPad.Editor.Windows"),
  302. typeof(Control).Assembly,
  303. typeof(MessageBox).Assembly,
  304. typeof(SolidColorBrush).Assembly
  305. );
  306. ScriptDocument.Initialize();
  307. }),
  308. Task.Run(() => DatabaseUpdateScripts.RegisterScripts())
  309. };
  310. Task.WaitAll(tasks.ToArray());
  311. Logger.Send(LogType.Information, "", "Configuring Application");
  312. progress.Report("Configuring Application");
  313. RegisterModules(progress);
  314. if (DatabaseType == DatabaseType.Standalone)
  315. {
  316. progress.Report("Starting local database...");
  317. try
  318. {
  319. StartLocalDatabase(progress);
  320. }
  321. catch (Exception err)
  322. {
  323. startupException = new Exception(
  324. string.Format(
  325. "Unable to open database ({0})\n\n{1}\n\n{2}",
  326. App.DatabaseSettings.FileName,
  327. err.Message,
  328. err.StackTrace
  329. )
  330. );
  331. }
  332. }
  333. });
  334. if (startupException is null && App.DatabaseSettings.Autologin)
  335. {
  336. try
  337. {
  338. Logger.Send(LogType.Information, "", "Logging in");
  339. Dispatcher.Invoke(() =>
  340. {
  341. loginStatus = TryAutoLogin();
  342. });
  343. if(loginStatus == ValidationStatus.VALID)
  344. {
  345. // Do the AfterLogin() here so that we aren't opening and closing progress windows again and again.
  346. Progress.ShowModal("Loading PRS", progress =>
  347. {
  348. AfterLogin(progress);
  349. });
  350. }
  351. }
  352. catch(Exception e)
  353. {
  354. startupException = e;
  355. }
  356. }
  357. if (startupException != null)
  358. {
  359. MessageWindow.ShowError("Error during startup.", startupException);
  360. }
  361. // If the login status is valid, then we've already loaded everything, so we don't here.
  362. if(loginStatus != ValidationStatus.VALID)
  363. {
  364. Logger.Send(LogType.Information, "", "Logging in");
  365. if (DoLogin() == ValidationStatus.VALID)
  366. {
  367. AfterLogin(null);
  368. }
  369. else
  370. {
  371. ConfigureMainScreen(null);
  372. }
  373. }
  374. ProfileName.Content = App.Profile;
  375. URL.Content = GetDatabaseConnectionDescription();
  376. if (loginStatus == ValidationStatus.VALID && DatabaseType == DatabaseType.Standalone)
  377. {
  378. Progress.ShowModal("Starting Scheduler", progress =>
  379. {
  380. scheduler.Start();
  381. });
  382. }
  383. }
  384. #region Connection Management
  385. private string GetDatabaseConnectionDescription()
  386. {
  387. return DatabaseType switch
  388. {
  389. #if RPC
  390. DatabaseType.Networked => (ClientFactory.Parameters?.FirstOrDefault() as RpcClientSocketTransport)?.Host,
  391. #else
  392. DatabaseType.Networked => ClientFactory.Parameters?.FirstOrDefault() as string,
  393. #endif
  394. DatabaseType.Standalone => App.DatabaseSettings?.FileName,
  395. DatabaseType.Local => App.DatabaseSettings?.LocalServerName,
  396. _ => ""
  397. } ?? "";
  398. }
  399. /// <summary>
  400. /// Reconnect to the server.
  401. /// </summary>
  402. /// <returns><see langword="true"/> if connection was successful.</returns>
  403. private bool Reconnect()
  404. {
  405. if (_transport != null)
  406. {
  407. return _transport.Connect();
  408. }
  409. Logger.Send(LogType.Error, ClientFactory.UserID, "Trying to reconnect without a transport set.");
  410. return true; // Returning true so we don't get stuck in infinite loops in exceptional circumstances.
  411. }
  412. private enum ConnectionFailedResult
  413. {
  414. Quit,
  415. Restart,
  416. Ok
  417. }
  418. /// <summary>
  419. /// To be called when initial connection to the server has failed; asks the user if they want to retry,
  420. /// change the database settings, or simply quit PRS.
  421. /// </summary>
  422. /// <returns>The action to take next.</returns>
  423. /// <exception cref="Exception"></exception>
  424. private ConnectionFailedResult DoConnectionFailed()
  425. {
  426. bool connected = false;
  427. while (!connected)
  428. {
  429. var connectionFailedWindow = new ConnectionFailed();
  430. connectionFailedWindow.ShowDialog();
  431. var reconnect = false;
  432. switch (connectionFailedWindow.Result)
  433. {
  434. case ConnectionFailedWindowResult.OpenDatabaseConfiguration:
  435. var result = ShowDatabaseConfiguration();
  436. switch (result)
  437. {
  438. case DatabaseConfigurationResult.RestartRequired:
  439. var shouldRestart = MessageBox.Show(
  440. "A restart is required to apply these changes. Do you wish to restart now?",
  441. "Restart?",
  442. MessageBoxButton.YesNo);
  443. if (shouldRestart == MessageBoxResult.Yes)
  444. {
  445. return ConnectionFailedResult.Restart;
  446. }
  447. else
  448. {
  449. reconnect = true;
  450. }
  451. break;
  452. case DatabaseConfigurationResult.RestartNotRequired:
  453. reconnect = true;
  454. break;
  455. case DatabaseConfigurationResult.Cancel:
  456. reconnect = true;
  457. break;
  458. default:
  459. throw new Exception($"Invalid enum result {result}");
  460. }
  461. break;
  462. case ConnectionFailedWindowResult.RetryConnection:
  463. reconnect = true;
  464. break;
  465. case ConnectionFailedWindowResult.Quit:
  466. return ConnectionFailedResult.Quit;
  467. default:
  468. throw new Exception($"Invalid enum result {connectionFailedWindow.Result}");
  469. }
  470. if (!reconnect)
  471. {
  472. return ConnectionFailedResult.Quit;
  473. }
  474. connected = Reconnect();
  475. }
  476. return ConnectionFailedResult.Ok;
  477. }
  478. private void Transport_OnOpen(IRpcTransport transport, RpcTransportOpenArgs e)
  479. {
  480. Logger.Send(LogType.Information, ClientFactory.UserID, "Connection opened");
  481. }
  482. private void Transport_OnException(IRpcTransport transport, RpcTransportExceptionArgs e)
  483. {
  484. Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in connection: {CoreUtils.FormatException(e.Exception)}");
  485. }
  486. private void TransportConnectionLost(IRpcTransport transport, RpcTransportCloseArgs e)
  487. {
  488. Logger.Send(LogType.Information, ClientFactory.UserID, "Connection lost");
  489. if (transport is IRpcClientTransport client)
  490. {
  491. Dispatcher.Invoke(() =>
  492. {
  493. var reconnection = new ReconnectionWindow();
  494. var cancellationTokenSource = new CancellationTokenSource();
  495. reconnection.OnCancelled = () => cancellationTokenSource.Cancel();
  496. var ct = cancellationTokenSource.Token;
  497. var work = () =>
  498. {
  499. try
  500. {
  501. DateTime lost = DateTime.Now;
  502. while (!client.IsConnected() && !ct.IsCancellationRequested)
  503. {
  504. try
  505. {
  506. Logger.Send(LogType.Error, ClientFactory.UserID, $"Reconnecting - ({DateTime.Now - lost:hh\\:mm})");
  507. if (client.Connect(ct))
  508. {
  509. break;
  510. }
  511. }
  512. catch (System.Exception e1)
  513. {
  514. Logger.Send(LogType.Error, ClientFactory.UserID, $"Reconnect Failed: {e1.Message}");
  515. // TODO: Remove this suppression
  516. if (e1.Message.StartsWith("The socket is connected, you needn't connect again!"))
  517. {
  518. break;
  519. }
  520. }
  521. }
  522. if (client.IsConnected())
  523. {
  524. Logger.Send(LogType.Information, ClientFactory.UserID, "Reconnected");
  525. ClientFactory.Validate(ClientFactory.SessionID);
  526. Logger.Send(LogType.Information, ClientFactory.UserID, "Validated");
  527. return true;
  528. }
  529. }
  530. catch (Exception e)
  531. {
  532. Logger.Send(LogType.Error, ClientFactory.UserID, $"Reconnect Failed: {e.Message}");
  533. }
  534. return false;
  535. };
  536. var task = Task.Run(() =>
  537. {
  538. var result = work();
  539. Dispatcher.Invoke(() =>
  540. {
  541. reconnection.Close();
  542. });
  543. return result;
  544. }, ct);
  545. reconnection.ShowDialog();
  546. if (!task.Result)
  547. {
  548. Close();
  549. }
  550. });
  551. }
  552. }
  553. #endregion
  554. private bool _loggingOut = false;
  555. private void ClientFactory_OnRequestError(RequestException e)
  556. {
  557. if (e.Status == StatusCode.Unauthenticated)
  558. {
  559. switch (e.Method)
  560. {
  561. case RequestMethod.Query:
  562. case RequestMethod.Save:
  563. case RequestMethod.Delete:
  564. case RequestMethod.MultiQuery:
  565. case RequestMethod.MultiSave:
  566. case RequestMethod.MultiDelete:
  567. if (!_loggingOut)
  568. {
  569. Dispatcher.InvokeAsync(() =>
  570. {
  571. _loggingOut = true;
  572. try
  573. {
  574. Logout(null, true);
  575. }
  576. finally
  577. {
  578. _loggingOut = false;
  579. }
  580. });
  581. }
  582. break;
  583. default:
  584. break;
  585. }
  586. }
  587. }
  588. private void ApplyColorScheme()
  589. {
  590. Color baseColor;
  591. try
  592. {
  593. baseColor = (Color)ColorConverter.ConvertFromString(App.DatabaseSettings.ColorScheme);
  594. }
  595. catch
  596. {
  597. baseColor = Colors.CornflowerBlue;
  598. }
  599. ThemeManager.BaseColor = baseColor;
  600. DynamicGridUtils.SelectionBackground = ThemeManager.SelectionBackgroundBrush;
  601. DynamicGridUtils.SelectionForeground = ThemeManager.SelectionForegroundBrush;
  602. DynamicGridUtils.FilterBackground = ThemeManager.FilterBackgroundBrush;
  603. DynamicGridUtils.FilterForeground = ThemeManager.FilterForegroundBrush;
  604. UpdateRibbonColors();
  605. PanelHost.Refresh();
  606. }
  607. #region Configuration
  608. private bool CanViewMaps => Security.CanView<Equipment>()
  609. || Security.CanView<Job>()
  610. || Security.CanView<TimeSheet>()
  611. || Security.CanView<GPSTracker>();
  612. private void ConfigureMainScreen(IProgress<string>? progress)
  613. {
  614. var bMaps = CanViewMaps;
  615. ProgressSection ConfigureTab(string progress, Func<Fluent.RibbonTabItem?> createTab)
  616. {
  617. return new ProgressSection(progress, () =>
  618. {
  619. var tab = createTab();
  620. if (tab is not null)
  621. {
  622. _ribbon.Tabs.Add(tab);
  623. }
  624. });
  625. }
  626. var sections = new[]
  627. {
  628. new ProgressSection("Initial Setup", () =>
  629. {
  630. _ribbon.Tabs.Clear();
  631. }),
  632. new ProgressSection("Configuring Main Screen", SetupMainScreen),
  633. ConfigureTab("Configuring Projects", SetupProjectsTab),
  634. ConfigureTab("Configuring Manufacturing", SetupManufacturingTab),
  635. ConfigureTab("Configuring Logistics", SetupLogisticsTab),
  636. ConfigureTab("Configuring Products", SetupProductsTab),
  637. ConfigureTab("Configuring Human Resources", SetupHumanResourcesTab),
  638. ConfigureTab("Configuring Accounts Receivable", SetupAccountsReceivableTab),
  639. ConfigureTab("Configuring Accounts Payable", SetupAccountsPayableTab),
  640. ConfigureTab("Configuring Equipment", SetupEquipmentTab),
  641. ConfigureTab("Configuring DigitalForms", SetupDigitalFormsTab),
  642. ConfigureTab("Configuring Dashboards", SetupDashboardsTab),
  643. new ProgressSection("Configuring System Modules", SetupSystemModules)
  644. };
  645. if(progress is not null)
  646. {
  647. Dispatcher.Invoke(SetupScreen);
  648. foreach(var section in sections)
  649. {
  650. progress.Report(section.Message);
  651. Dispatcher.Invoke(section.Action);
  652. }
  653. }
  654. else
  655. {
  656. SetupScreen();
  657. Progress.ShowModal(sections);
  658. }
  659. }
  660. private void SetupScreen()
  661. {
  662. var button = _ribbon.FindVisualChildren<Fluent.DropDownButton>().FirstOrDefault();
  663. if (button != null)
  664. button.Visibility = Visibility.Collapsed;
  665. if (ClientFactory.UserGuid == Guid.Empty)
  666. _ribbonRow.Height = new GridLength(30, GridUnitType.Pixel);
  667. else
  668. _ribbonRow.Height = new GridLength(1, GridUnitType.Auto);
  669. }
  670. private void SetupMainScreen()
  671. {
  672. //DockManager.SidePanelSize = OutstandingDailyReports(false) ? 0.00F : 30.00F;
  673. // Notifications Area
  674. SetFrameworkItemVisibility(SendNotification, Security.CanView<Notification>());
  675. SetFrameworkItemVisibility(Notifications, Security.CanView<Notification>());
  676. SetFrameworkItemVisibility(TaskTracking, Security.IsAllowed<CanTrackTasksInDailyReport>());
  677. UserID.Content = ClientFactory.UserID;
  678. if (ClientFactory.PasswordExpiration != DateTime.MinValue)
  679. {
  680. var timeUntilExpiration = ClientFactory.PasswordExpiration - DateTime.Now;
  681. if (timeUntilExpiration.Days < 14)
  682. {
  683. PasswordExpiryNotice.Content = $"Password will expire in {timeUntilExpiration.Days} days!";
  684. PasswordExpiryNotice.Visibility = Visibility.Visible;
  685. }
  686. else
  687. {
  688. PasswordExpiryNotice.Visibility = Visibility.Collapsed;
  689. }
  690. }
  691. }
  692. private void SetupSystemModules()
  693. {
  694. SetFrameworkItemVisibility(CompanyInformation, Security.CanView<CompanyInformation>());
  695. SetVisibleIfAny(BackstageSeparator0, CompanyInformation);
  696. SetFrameworkItemVisibility(SecurityDefaultsButton, Security.IsAllowed<CanCustomiseSecurityDefaults>());
  697. SetVisibleIfAny(BackstageSeparator1, SecurityDefaultsButton);
  698. BackstageSeparator1a.Visibility = Visibility.Visible;
  699. SystemLogsButton.Visibility = Visibility.Visible;
  700. SetFrameworkItemVisibility(DocumentTypeList, Security.IsAllowed<CanViewDocumentTypes>());
  701. SetFrameworkItemVisibility(EventList, Security.IsAllowed<CanManageEvents>());
  702. SetVisibleIfAny(BackstageSeparator2, DocumentTypeList, EventList);
  703. //SetModuleVisibility<>(VideoRecordingButton, Security.IsAllowed<CanRecordScreen>());
  704. LogoutButton.Visibility = ClientFactory.UserGuid == Guid.Empty ? Visibility.Collapsed : Visibility.Visible;
  705. LoginButton.Visibility = ClientFactory.UserGuid != Guid.Empty ? Visibility.Collapsed : Visibility.Visible;
  706. EditDetailsButton.Visibility = ClientFactory.UserGuid == Guid.Empty ? Visibility.Collapsed : Visibility.Visible;
  707. SetupDock<CanViewContactsDock>(ContactDock, Contacts);
  708. SetupDock<CanViewJobDock>(JobDock, Jobs);
  709. SetupDock<CanViewConsignmentDock>(ConsignmentDock, Consignments);
  710. SetupDock<CanViewDeliveryDock>(DeliveryDock, Deliveries);
  711. SetupDock<CanViewProductDock>(ProductLookupDock, ProductLookup);
  712. SetupDock<CanViewDigitalFormsDock>(DigitalFormsDock, DigitalForms);
  713. SetupDock<CanViewProblemsDock>(ProblemsDock, Problems);
  714. SetupDock<CanViewPickingListDock>(RequisitionsDock, Requisitions);
  715. _ribbon.InvalidateArrange();
  716. }
  717. private Fluent.RibbonTabItem? SetupDashboardsTab() =>
  718. CreateTab<ViewDesktopDashboardsTab>("Dashboards", x => x
  719. .NewGroup(x => x
  720. .Add<DatabaseActivityDashboard>("Database Activity", SvgImages.kpi,
  721. Security.IsAllowed<CanViewDatabaseActivity>()
  722. && Security.IsAllowed<ViewDesktopDatabaseActivityDashboard>())
  723. .Add<UserActivity>("User Activity", SvgImages.kpi,
  724. Security.IsAllowed<CanViewUserActivity>()
  725. && Security.IsAllowed<ViewDesktopUserActivityDashboard>())
  726. .Add<WidgetDashboard>("Quick Status", SvgImages.kpi,
  727. Security.IsAllowed<CanViewQuickStatus>() && Security.IsAllowed<ViewDesktopQuickStatusDashboard>())));
  728. private Fluent.RibbonTabItem? SetupDigitalFormsTab() =>
  729. CreateTab<ViewDesktopDigitalFormsTab>("Digital Forms", x => x
  730. .NewGroup(x => x
  731. .Add<DigitalFormsLibrary>("Forms Library", SvgImages.formslibrary,
  732. Security.IsAllowed<CanAdministerDigitalFormsLibrary>())
  733. .Add<CompletedFormsPanel>("Forms Dashboard", SvgImages.formsinstance,
  734. Security.IsAllowed<CanViewDigitalFormsDashbaord>()
  735. && Security.IsAllowed<ViewDesktopDigitalFormsDashboard>())));
  736. private Fluent.RibbonTabItem? SetupEquipmentTab() =>
  737. CreateTab<ViewDesktopEquipmentTab>("Equipment", x => x
  738. .NewGroup(x => x
  739. .Add<EquipmentPanel>("Equipment List", PRSDesktop.Resources.specifications,
  740. Security.CanView<Equipment>()
  741. && Security.IsAllowed<ViewDesktopEquipmentListScreen>())
  742. .Add<EquipmentMaintenancePanel>("Planned Maintenance", PRSDesktop.Resources.service,
  743. Security.CanView<Equipment>()
  744. && Security.IsAllowed<ViewDesktopEquipmentMaintenanceScreen>())
  745. .Add<EquipmentPlannerPanel>("Equipment Planner", PRSDesktop.Resources.calendar,
  746. Security.CanView<Equipment>()
  747. && Security.CanView<Assignment>()
  748. && Security.IsAllowed<ViewDesktopEquipmentPlannerScreen>())
  749. .Add<GPSTrackers>("GPS Trackers", PRSDesktop.Resources.milestone,
  750. Security.CanView<GPSTracker>() && Security.IsAllowed<ViewDesktopGPSTrackersScreen>())));
  751. private Fluent.RibbonTabItem? SetupAccountsReceivableTab() =>
  752. CreateTab<ViewDesktopAccountsTab>("Accounts Receivable", x => x
  753. .NewGroup(x => x
  754. .Add<CustomerPanel>("Customers", PRSDesktop.Resources.customer,
  755. Security.CanView<Customer>()
  756. && Security.IsAllowed<ViewDesktopCustomersScreen>())
  757. .Add<InvoicePanel>("Invoices", PRSDesktop.Resources.invoice,
  758. Security.CanView<Invoice>()
  759. && Security.IsAllowed<ViewDesktopInvoicesScreen>())
  760. .Add<CustomerReceipts>("Receipts", PRSDesktop.Resources.receipt,
  761. Security.CanView<Receipt>()
  762. && Security.IsAllowed<ViewDesktopReceiptsScreen>())));
  763. private Fluent.RibbonTabItem? SetupAccountsPayableTab() =>
  764. CreateTab<ViewDesktopAccountsTab>("Accounts Payable", x => x
  765. .NewGroup(x => x
  766. .Add<SupplierPanel>("Suppliers", PRSDesktop.Resources.supplier,
  767. Security.CanView<Supplier>()
  768. && Security.IsAllowed<ViewDesktopSuppliersScreen>())
  769. .Add<DataEntryPanel>("Data Entry", PRSDesktop.Resources.pencil,
  770. Security.IsAllowed<CanViewDataEntryPanel>())
  771. .Add<SupplierPurchaseOrderPanel>("Purchase Orders", PRSDesktop.Resources.purchase,
  772. Security.CanView<PurchaseOrder>()
  773. && Security.IsAllowed<ViewDesktopPurchaseOrdersScreen>())
  774. .Add<SupplierBillPanel>("Bills", PRSDesktop.Resources.bill,
  775. Security.CanView<Bill>()
  776. && Security.IsAllowed<ViewDesktopBillsScreen>())
  777. .Add<SupplierPayments>("Payments", PRSDesktop.Resources.payment,
  778. Security.CanView<Payment>()
  779. && Security.IsAllowed<ViewDesktopPaymentsScreen>())));
  780. private Fluent.RibbonTabItem? SetupHumanResourcesTab() =>
  781. CreateTab<ViewDesktopHumanResourcesTab>("Human Resources", x => x
  782. .NewGroup(x => x
  783. .Add<CalendarPanel>("Calendar", PRSDesktop.Resources.assignments,
  784. Security.CanView<Assignment>() && Security.IsAllowed<ViewDesktopCalendarScreen>())
  785. .Add<EmployeeResourcePlannerPanel>("Employee Planner", PRSDesktop.Resources.calendar,
  786. Security.CanView<Assignment>() && Security.IsAllowed<ViewDesktopEmployeePlannerScreen>())
  787. .Add<TimesheetPanel>("Staff Timesheets", PRSDesktop.Resources.clock,
  788. Security.CanView<TimeSheet>() && Security.IsAllowed<ViewDesktopStaffTimeSheetsScreen>())
  789. .Add<LeaveRequestPanel>("Leave Requests", SvgImages.beach,
  790. Security.CanView<LeaveRequest>() && Security.IsAllowed<ViewDesktopLeaveRequestsScreen>())
  791. .Add<MeetingPanel>("Meetings", PRSDesktop.Resources.employees,
  792. Security.IsAllowed<ViewDesktopMeetingsScreen>()))
  793. .NewGroup(x => x
  794. .Add<UserPanel>("User Accounts", PRSDesktop.Resources.user,
  795. Security.CanView<User>() && Security.IsAllowed<ViewDesktopUserAccountsScreen>())
  796. .Add<EmployeePanel>("Employee List", PRSDesktop.Resources.employee,
  797. Security.CanView<Employee>() && Security.IsAllowed<ViewDesktopEmployeeListScreen>())
  798. .Add<OrgChartPanel>("Org Chart", PRSDesktop.Resources.orgchart,
  799. Security.IsAllowed<ViewDesktopOrgChartScreen>())));
  800. private Fluent.RibbonTabItem? SetupProductsTab() =>
  801. CreateTab<ViewDesktopProductManagementTab>("Products", x => x
  802. .NewGroup(x => x
  803. .Add<ProductsPanel>("Product List", PRSDesktop.Resources.product,
  804. Security.CanView<Product>() && Security.IsAllowed<ViewDesktopProductListScreen>())
  805. .Add<StockLocationPanel>("Stock Locations", PRSDesktop.Resources.parcel,
  806. Security.CanView<StockLocation>() && Security.IsAllowed<ViewDesktopStockLocationsScreen>())
  807. .Add<StockMovementPanel>("Stock Movements", PRSDesktop.Resources.forklift,
  808. Security.CanView<StockMovement>() && Security.IsAllowed<ViewDesktopStockMovementsScreen>())
  809. .Add<StockForecastPanel>("Stock Forecast", SvgImages.kpi,
  810. Security.CanView<Product>()
  811. && Security.CanView<JobMaterial>()
  812. && Security.IsAllowed<ViewDesktopStockForecastScreen>())
  813. .Add<ReservationManagementPanel>("Reservation Management", PRSDesktop.Resources.requisition,
  814. Security.IsAllowed<ViewDesktopReservationManagementScreen>())));
  815. private Fluent.RibbonTabItem? SetupLogisticsTab() =>
  816. CreateTab<ViewDesktopLogisticsTab>("Logistics", x => x
  817. .NewGroup(x => x
  818. .Add<ReadyToGoPanel>("Ready To Go", SvgImages.truck,
  819. Security.IsAllowed<CanViewLogisticsReadyToGo>()
  820. && Security.IsAllowed<ViewDesktopReadyToGoScreen>())
  821. .Add<DispatchPanel>("Rack List", PRSDesktop.Resources.barcode,
  822. Security.CanView<Shipment>()
  823. && Security.CanView<DeliveryItem>()
  824. && Security.IsAllowed<ViewDesktopRackListScreen>())
  825. .Add<PickingListPanel>("Picking Lists", SvgImages.box,
  826. Security.CanView<PickingList>() && Security.IsAllowed<ViewDesktopSiteRequisitionsScreen>())
  827. .Add<DeliveryPanel>("Deliveries", SvgImages.truck,
  828. Security.IsAllowed<CanViewDeliveriesModule>() && Security.IsAllowed<ViewDesktopDeliveriesScren>())
  829. .Add<DeliveredOnSitePanel>("Delivered On Site", PRSDesktop.Resources.lifter,
  830. Security.IsAllowed<CanViewDeliveredOnSite>()
  831. && Security.IsAllowed<ViewDesktopDeliveredOnSiteScreen>()))
  832. .NewGroup(x => x
  833. .Add<ConsignmentsPanel>("Incoming Consignments", PRSDesktop.Resources.consignment,
  834. Security.CanView<Consignment>() && Security.IsAllowed<ViewDesktopIncomingConsignmentsScreen>())));
  835. private Fluent.RibbonTabItem? SetupManufacturingTab() =>
  836. CreateTab<ViewDesktopManufacturingTab>("Manufacturing", x => x
  837. .NewGroup(x => x
  838. .Add<DesignManagementPanel>("Design Management", PRSDesktop.Resources.design,
  839. Security.CanView<Job>() && Security.IsAllowed<ViewDesktopDesignManagementScreen>()))
  840. .NewGroup(x => x
  841. .Add<ManufacturingPanel>("Manufacturing Status", PRSDesktop.Resources.factory,
  842. Security.IsAllowed<CanViewFactoryStatus>()
  843. && Security.IsAllowed<ViewDesktopManufacturingStatusScreen>())
  844. .Add<ManufacturingAllocationPanel>("Factory Allocation", PRSDesktop.Resources.assignments,
  845. Security.IsAllowed<CanViewFactoryAllocation>()
  846. && Security.IsAllowed<ViewDesktopFactoryAllocationScreen>())
  847. .Add<FactoryPanel>("Factory Floor", PRSDesktop.Resources.wrench,
  848. Security.IsAllowed<CanViewFactoryFloor>()
  849. && Security.IsAllowed<ViewDesktopFactoryFloorScreen>()))
  850. .NewGroup(x => x
  851. .Add<FactoryProductivityDashboard>("Factory KPIs", SvgImages.kpi,
  852. Security.IsAllowed<CanViewFactoryKPIs>()
  853. && Security.IsAllowed<ViewDesktopFactoryKPIsDashboard>())
  854. .Add<ManufacturingTemplateAnalysis>("Template Analysis", SvgImages.kpi,
  855. Security.IsAllowed<CanViewTemplateAnalysis>()
  856. && Security.IsAllowed<ViewDesktopTemplateAnalysisDashboard>())
  857. .Add<FactoryFloorAnalysis>("Factory Analysis", SvgImages.kpi,
  858. Security.IsAllowed<CanViewFactoryAnalysis>()
  859. && Security.IsAllowed<ViewDesktopFactoryAnalysisDashboard>())));
  860. private Fluent.RibbonTabItem? SetupProjectsTab() =>
  861. CreateTab<ViewDesktopProjectsTab>("Projects", x => x
  862. .NewGroup(x => x
  863. .Add<QuotePanel>("Quotes", PRSDesktop.Resources.quotation,
  864. Security.CanView<Quote>() && Security.IsAllowed<ViewDesktopQuotesScreen>())
  865. .Add<ProjectsPanel>("Projects", PRSDesktop.Resources.project,
  866. Security.CanView<Job>() && Security.IsAllowed<ViewDesktopProjectsScreen>())
  867. .Add<JobResourcePlannerPanel>("Project Planner", PRSDesktop.Resources.calendar,
  868. Security.CanView<Job>() && Security.IsAllowed<ViewDesktopProjectPlannerScreen>()))
  869. .NewGroup(x => x
  870. .Add<KitPanel>("Product Kits", PRSDesktop.Resources.kit,
  871. Security.CanView<Kit>() && Security.IsAllowed<ViewDesktopProductKitsScreen>())
  872. .Add<CostSheetPanel>("Cost Sheets", PRSDesktop.Resources.costsheet,
  873. Security.CanView<CostSheet>() && Security.IsAllowed<ViewDesktopCostSheetsScreen>()),
  874. groupName: "Setup"));
  875. private void SetupDock<TSecurityDescriptor>(LayoutAnchorable layout, IDockPanel dock)
  876. where TSecurityDescriptor : ISecurityDescriptor, new()
  877. {
  878. if (Security.IsAllowed<TSecurityDescriptor>())
  879. {
  880. if (!DockGroup.Children.Any(x => x == layout))
  881. {
  882. DockGroup.Children.Add(layout);
  883. }
  884. if (layout.IsVisible && (ClientFactory.UserGuid != Guid.Empty))
  885. dock.Setup();
  886. }
  887. else
  888. {
  889. DockGroup.RemoveChild(layout);
  890. }
  891. }
  892. private class HeaderTab(string header, MainWindow window)
  893. {
  894. public string Header { get; set; } = header;
  895. public List<Tuple<string, List<HeaderGroup>>> Groups { get; set; } = new();
  896. public HeaderTab NewGroup(Action<HeaderGroup> modify, string? groupName = null)
  897. {
  898. var currentGroup = (groupName is not null ? Groups.FirstOrDefault(x => x.Item1 == groupName) : Groups.LastOrDefault())?.Item2;
  899. if(currentGroup is null)
  900. {
  901. currentGroup = new List<HeaderGroup>();
  902. Groups.Add(new(groupName ?? "", currentGroup));
  903. }
  904. var group = new HeaderGroup(window);
  905. currentGroup.Add(group);
  906. modify(group);
  907. return this;
  908. }
  909. public Fluent.RibbonTabItem? Create()
  910. {
  911. var item = new Fluent.RibbonTabItem
  912. {
  913. Header = Header
  914. };
  915. var hasNonDefault = false;
  916. foreach(var (name, groups) in Groups)
  917. {
  918. var groupBox = new Fluent.RibbonGroupBox
  919. {
  920. Header = name
  921. };
  922. var needsSeparator = false;
  923. foreach(var group in groups)
  924. {
  925. var addedSeparator = false;
  926. foreach(var button in group.Actions)
  927. {
  928. if (button.Visibility != Visibility.Visible) continue;
  929. if (!addedSeparator && needsSeparator)
  930. {
  931. groupBox.Items.Add(new RibbonSeparator());
  932. needsSeparator = false;
  933. addedSeparator = true;
  934. }
  935. if (!group.IsDefaultGroup)
  936. {
  937. hasNonDefault = true;
  938. }
  939. groupBox.Items.Add(button);
  940. needsSeparator = true;
  941. }
  942. }
  943. if (groupBox.Items.Count > 0)
  944. {
  945. item.Groups.Add(groupBox);
  946. }
  947. }
  948. return hasNonDefault ? item : null;
  949. }
  950. }
  951. private class HeaderGroup(MainWindow window)
  952. {
  953. public List<Fluent.Button> Actions { get; set; } = new();
  954. public bool IsDefaultGroup { get; set; } = false;
  955. private bool _locked = false;
  956. private Fluent.Button CreateButton(string name, ImageSource image)
  957. {
  958. if (_locked) throw new Exception("Header Group is locked!");
  959. var button = new Fluent.Button
  960. {
  961. Header = name,
  962. LargeIcon = image,
  963. MinWidth = 60
  964. };
  965. Actions.Add(button);
  966. return button;
  967. }
  968. private Fluent.Button CreateButton(string name, Bitmap image)
  969. {
  970. return CreateButton(name, image.AsBitmapImage());
  971. }
  972. public HeaderGroup Add(string name, ImageSource image, Action onClick)
  973. {
  974. var button = CreateButton(name, image);
  975. button.Command = new SimpleCommand(() => onClick());
  976. return this;
  977. }
  978. public HeaderGroup Add(string name, Bitmap image, Action onClick)
  979. {
  980. var button = CreateButton(name, image);
  981. button.Command = new SimpleCommand(() => onClick());
  982. return this;
  983. }
  984. public HeaderGroup Add<T>(string name, ImageSource image, bool visible = true)
  985. where T : class, IBasePanel, new()
  986. {
  987. var button = CreateButton(name, image);
  988. button.Command = new SimpleCommand(() => window.LoadWindow<T>(button));
  989. var menu = new ContextMenu();
  990. menu.AddItem("Open in New Window", PRSDesktop.Resources.target, () => window.LoadSecondaryWindow<T>(button));
  991. button.ContextMenu = menu;
  992. window.SetFrameworkItemVisibility(button, visible);
  993. return this;
  994. }
  995. public HeaderGroup Add<T>(string name, Bitmap image, bool visible = true)
  996. where T : class, IBasePanel, new()
  997. {
  998. return Add<T>(name, image.AsBitmapImage(), visible: visible);
  999. }
  1000. public HeaderGroup Lock()
  1001. {
  1002. _locked = true;
  1003. return this;
  1004. }
  1005. }
  1006. private Fluent.RibbonTabItem? CreateTab(string header, Action<HeaderTab> modify)
  1007. {
  1008. var tab = new HeaderTab(header, this)
  1009. .NewGroup(x => x
  1010. .Add("Refresh", PRSDesktop.Resources.refresh, PanelHost.Refresh)
  1011. .Lock(),
  1012. groupName: "Actions")
  1013. .NewGroup(x => x
  1014. .Add<UtilityDashboard>("Dashboards", SvgImages.kpi, Security.IsAllowed<CanViewUserDefinedDashboards>())
  1015. .Add<NotificationPanel>("Notification Centre", PRSDesktop.Resources.email, Security.CanView<Notification>())
  1016. .Add<TaskPanel>("Task List", SvgImages.kanban, Security.CanView<Kanban>())
  1017. .Add<AttendancePanel>("In/Out Board", PRSDesktop.Resources.attendance, Security.IsAllowed<CanViewInOutBoard>())
  1018. .Add<LiveMapsPanel>("Live Maps", PRSDesktop.Resources.map, CanViewMaps)
  1019. .Add<DailyReport>("Daily Report", PRSDesktop.Resources.report, Security.IsAllowed<CanViewDailyReports>())
  1020. .Lock());
  1021. foreach(var (_, groupList) in tab.Groups)
  1022. {
  1023. foreach(var group in groupList)
  1024. {
  1025. group.IsDefaultGroup = true;
  1026. }
  1027. }
  1028. modify(tab);
  1029. var result = tab.Create();
  1030. if(result is null)
  1031. {
  1032. // This means no other items were added; in this case don't create the tab.
  1033. return null;
  1034. }
  1035. var box = new Fluent.RibbonGroupBox
  1036. {
  1037. Header = "Print",
  1038. Visibility = Visibility.Collapsed
  1039. };
  1040. result.Groups.Add(box);
  1041. return result;
  1042. }
  1043. private Fluent.RibbonTabItem? CreateTab<TSecurity>(string header, Action<HeaderTab> modify)
  1044. where TSecurity : ISecurityDescriptor, new()
  1045. {
  1046. if (!Security.IsAllowed<TSecurity>()) return null;
  1047. return CreateTab(header, modify);
  1048. }
  1049. #region Visibility
  1050. private void SetFrameworkItemVisibility(FrameworkElement button, bool visible)
  1051. {
  1052. var vResult = true;
  1053. var eResult = ClientFactory.UserGuid != Guid.Empty && visible;
  1054. button.Visibility = vResult && eResult ? Visibility.Visible : Visibility.Collapsed;
  1055. if (button is Fluent.Button rb)
  1056. {
  1057. CustomModules.Register(rb.Header?.ToString() ?? "");
  1058. rb.IsEnabled = !OutstandingDailyReports(false);
  1059. }
  1060. }
  1061. private void SetModuleVisibility<T>(Fluent.Button button, bool visible) where T : class, IBasePanel, new()
  1062. {
  1063. SetFrameworkItemVisibility(button,visible);
  1064. button.MinWidth = 60;
  1065. if (button.Command == null)
  1066. {
  1067. button.Command = new SimpleCommand(() => LoadWindow<T>(button));
  1068. var menu = new ContextMenu();
  1069. menu.Items.Add(new MenuItem()
  1070. {
  1071. Header = "Open in New Window",
  1072. Icon = new System.Windows.Controls.Image() { Source = PRSDesktop.Resources.target.AsBitmapImage() },
  1073. Command = new SimpleCommand(() => LoadSecondaryWindow<T>(button))
  1074. });
  1075. button.ContextMenu = menu;
  1076. }
  1077. }
  1078. private static void SetVisibleIfEither(FrameworkElement separator, FrameworkElement[] left, FrameworkElement[] right)
  1079. {
  1080. var bLeft = false;
  1081. foreach (var button in left)
  1082. bLeft = bLeft || button.Visibility == Visibility.Visible;
  1083. var bRight = false;
  1084. foreach (var button in right)
  1085. bRight = bRight || button.Visibility == Visibility.Visible;
  1086. separator.Visibility = bLeft && bRight ? Visibility.Visible : Visibility.Collapsed;
  1087. }
  1088. private static void SetVisibleIfAny(FrameworkElement separator, params FrameworkElement[] buttons)
  1089. {
  1090. var bVisible = false;
  1091. foreach (var button in buttons)
  1092. bVisible = bVisible || button.Visibility == Visibility.Visible;
  1093. separator.Visibility = bVisible ? Visibility.Visible : Visibility.Collapsed;
  1094. }
  1095. private static void SetTabVisibleIfAny(Fluent.RibbonTabItem tab, params FrameworkElement[] buttons)
  1096. {
  1097. var bVisible = false;
  1098. foreach (var button in buttons)
  1099. bVisible = bVisible || button.Visibility == Visibility.Visible;
  1100. tab.Visibility = bVisible ? Visibility.Visible : Visibility.Collapsed;
  1101. }
  1102. #endregion
  1103. private Fluent.Button? FindModuleButton(string name)
  1104. {
  1105. foreach (var tab in _ribbon.Tabs)
  1106. {
  1107. foreach (var bar in tab.Groups)
  1108. {
  1109. var item = bar.Items.OfType<Fluent.Button>().FirstOrDefault(x => Equals(x.Header, name));
  1110. if (item is not null) return item;
  1111. }
  1112. }
  1113. return null;
  1114. }
  1115. private void LoadApplicationState()
  1116. {
  1117. if (ClientFactory.UserGuid != Guid.Empty)
  1118. {
  1119. _ribbon.IsCollapsed = false;
  1120. if (OutstandingDailyReports(false))
  1121. {
  1122. MessageWindow.ShowMessage("There are outstanding Daily Reports that must be filled out before continuing!"
  1123. + "\n\nAccess to PRS is restricted until this is corrected.",
  1124. "Outstanding Reports");
  1125. if(FindModuleButton("Daily Report") is Fluent.Button button)
  1126. {
  1127. var dailyReportPanel = LoadWindow<DailyReport>(button);
  1128. if(dailyReportPanel is not null)
  1129. {
  1130. dailyReportPanel.OnTimeSheetConfirmed += e =>
  1131. {
  1132. if (!OutstandingDailyReports(true))
  1133. {
  1134. ConfigureMainScreen(null);
  1135. LoadApplicationState();
  1136. }
  1137. };
  1138. }
  1139. }
  1140. return;
  1141. }
  1142. using(new WaitCursor(this))
  1143. {
  1144. _ribbon.IsCollapsed = false;
  1145. LoadInitialWindow();
  1146. }
  1147. }
  1148. }
  1149. private void LoadInitialWindow()
  1150. {
  1151. var module = Settings.Settings.TryGetValue("CurrentPanel", out string? currentPanel)
  1152. ? !currentPanel.IsNullOrWhiteSpace()
  1153. ? currentPanel.Split([" / "], StringSplitOptions.None)
  1154. : [ "Human Resources", "Task List"]
  1155. : [ "Human Resources", "Task List"];
  1156. try
  1157. {
  1158. var bFound = false;
  1159. if (module.Length == 2)
  1160. foreach (Fluent.RibbonTabItem tab in _ribbon.Tabs)
  1161. {
  1162. if (String.Equals(tab.Header, module.First()))
  1163. {
  1164. _ribbon.SelectedTabItem = tab;
  1165. foreach (Fluent.RibbonGroupBox bar in tab.Groups)
  1166. {
  1167. foreach (var item in bar.Items)
  1168. {
  1169. var button = item as Fluent.Button;
  1170. if (button != null && String.Equals(button.Header, module.Last()))
  1171. {
  1172. bFound = true;
  1173. if (button.Command is SimpleCommand command)
  1174. command.Execute(null);
  1175. //button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
  1176. break;
  1177. }
  1178. }
  1179. if (bFound)
  1180. break;
  1181. }
  1182. }
  1183. if (bFound)
  1184. break;
  1185. }
  1186. }
  1187. catch (Exception e)
  1188. {
  1189. MessageWindow.ShowError($"Unable to load {currentPanel}", e);
  1190. }
  1191. }
  1192. private void _ribbon_OnLoaded(object sender, RoutedEventArgs e)
  1193. {
  1194. _ribbon.SelectedTabItem = CurrentTab;
  1195. }
  1196. private void LoadSecondaryWindows()
  1197. {
  1198. if (ClientFactory.UserGuid != Guid.Empty)
  1199. {
  1200. var windows = App.DatabaseSettings.SecondaryWindows;
  1201. foreach (var key in windows.Keys.ToArray())
  1202. {
  1203. SecondaryWindows[key] = new SecondaryWindow(
  1204. key,
  1205. windows[key].Item1,
  1206. windows[key].Item2,
  1207. windows[key].Item3,
  1208. windows[key].Item4,
  1209. windows[key].Item5,
  1210. windows[key].Item6
  1211. );
  1212. SecondaryWindows[key].Closed += (o, e) => { SecondaryWindows.Remove(key); };
  1213. SecondaryWindows[key].Show();
  1214. }
  1215. }
  1216. else
  1217. {
  1218. foreach (var key in SecondaryWindows.Keys.ToArray())
  1219. {
  1220. App.IsClosing = true;
  1221. SecondaryWindows[key].Close();
  1222. App.IsClosing = false;
  1223. }
  1224. }
  1225. }
  1226. private Fluent.RibbonTabItem GetTabItem(FrameworkElement? sender)
  1227. {
  1228. if (sender == null)
  1229. throw new Exception("No Tab Found!");
  1230. if (sender is Fluent.RibbonTabItem)
  1231. return (Fluent.RibbonTabItem)sender;
  1232. return GetTabItem(sender.Parent as FrameworkElement);
  1233. }
  1234. private IEnumerable<IBasePanel> Panels
  1235. {
  1236. get
  1237. {
  1238. if(PanelHost.CurrentPanel is not null)
  1239. {
  1240. yield return PanelHost.CurrentPanel;
  1241. }
  1242. foreach(var window in SecondaryWindows.Values)
  1243. {
  1244. yield return window.Panel;
  1245. }
  1246. }
  1247. }
  1248. #region LoadWindow / LoadSecondaryWindow
  1249. private T? LoadWindow<T>(Fluent.Button sender) where T : class, IBasePanel, new()
  1250. {
  1251. return LoadWindow<T>(sender, new CancelEventArgs());
  1252. }
  1253. public IBasePanel? LoadWindow(Type t, Fluent.Button sender, CancelEventArgs cancel)
  1254. {
  1255. using (new WaitCursor())
  1256. {
  1257. UnloadWindow(cancel);
  1258. if (cancel.Cancel)
  1259. {
  1260. return null;
  1261. }
  1262. CurrentTab = GetTabItem(sender);
  1263. CurrentButton = sender;
  1264. //CurrentButton.IsSelected = true;
  1265. UpdateRibbonColors();
  1266. var moduleName = sender.Header?.ToString() ?? "";
  1267. var panel = PanelHost.LoadPanel(t, moduleName);
  1268. ContentControl.Content = panel;
  1269. Title =
  1270. $"{moduleName} - {(String.Equals(App.Profile?.ToUpper(), "DEFAULT") ? "PRS Desktop" : App.Profile)} (Release {CoreUtils.GetVersion()})";
  1271. PanelHost.Refresh();
  1272. if (panel is NotificationPanel)
  1273. {
  1274. Logger.Send(LogType.Information, ClientFactory.UserID, "Disabling Heartbeat");
  1275. NotificationsWatchDog.IsEnabled = false;
  1276. Notifications.Visibility = Visibility.Collapsed;
  1277. DockingGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Pixel);
  1278. DockingGrid.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Pixel);
  1279. }
  1280. else
  1281. {
  1282. ReloadNotifications();
  1283. }
  1284. if (sender != null)
  1285. {
  1286. var module = string.Format("{0} / {1}", CurrentTab?.Header, sender.Header);
  1287. if (!Settings.Settings.TryGetValue("CurrentPanel", out string? value) || module != value)
  1288. {
  1289. value = module;
  1290. Settings.Settings["CurrentPanel"] = value;
  1291. try
  1292. {
  1293. LocalConfiguration.Save(Settings);
  1294. }
  1295. catch (Exception e)
  1296. {
  1297. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  1298. }
  1299. }
  1300. }
  1301. return panel;
  1302. }
  1303. }
  1304. private T? LoadWindow<T>(Fluent.Button sender, CancelEventArgs cancel) where T : class, IBasePanel, new()
  1305. {
  1306. return LoadWindow(typeof(T), sender, cancel) as T;
  1307. }
  1308. private void LoadSecondaryWindow(Type t, Fluent.Button sender)
  1309. {
  1310. var id = Guid.NewGuid();
  1311. var window = new Tuple<string, string, double, double, double, double>(
  1312. t.EntityName(),
  1313. sender.Header?.ToString() ?? "",
  1314. Left + 100,
  1315. Top + 100,
  1316. Width - 200,
  1317. Height - 200
  1318. );
  1319. App.DatabaseSettings.SecondaryWindows[id] = window;
  1320. new LocalConfiguration<DatabaseSettings>(App.Profile).Save(App.DatabaseSettings);
  1321. SecondaryWindows[id] = new SecondaryWindow(
  1322. id,
  1323. window.Item1,
  1324. window.Item2,
  1325. window.Item3,
  1326. window.Item4,
  1327. window.Item5,
  1328. window.Item6
  1329. );
  1330. SecondaryWindows[id].Show();
  1331. }
  1332. private void LoadSecondaryWindow<T>(Fluent.Button sender) where T : class, IBasePanel, new()
  1333. => LoadSecondaryWindow(typeof(T), sender);
  1334. // private void SecondaryWindow_Click(object sender, RoutedEventArgs e)
  1335. // {
  1336. // var panel = PanelHost.CurrentPanel;
  1337. // if (panel is null) return;
  1338. //
  1339. // var id = Guid.NewGuid();
  1340. // var window = new Tuple<string, string, double, double, double, double>(
  1341. // panel.GetType().EntityName(),
  1342. // PanelHost.CurrentModuleName,
  1343. // Left + 100,
  1344. // Top + 100,
  1345. // Width - 200,
  1346. // Height - 200
  1347. // );
  1348. // App.DatabaseSettings.SecondaryWindows[id] = window;
  1349. // new LocalConfiguration<DatabaseSettings>(App.Profile).Save(App.DatabaseSettings);
  1350. // SecondaryWindows[id] = new SecondaryWindow(
  1351. // id,
  1352. // window.Item1,
  1353. // window.Item2,
  1354. // window.Item3,
  1355. // window.Item4,
  1356. // window.Item5,
  1357. // window.Item6
  1358. // );
  1359. // SecondaryWindows[id].Show();
  1360. // }
  1361. #endregion
  1362. private void UpdateRibbonColors()
  1363. {
  1364. foreach (var tab in _ribbon.Tabs)
  1365. {
  1366. bool bFound = false;
  1367. foreach (var grp in tab.Groups)
  1368. {
  1369. foreach (var btn in grp.Items)
  1370. {
  1371. if (btn is Fluent.Button fluentbutton)
  1372. {
  1373. bFound = bFound || (btn == CurrentButton);
  1374. fluentbutton.Background = (btn == CurrentButton) ? ThemeManager.SelectedTabItemBackgroundBrush : new SolidColorBrush(Colors.White);
  1375. fluentbutton.Foreground = (btn == CurrentButton) ? ThemeManager.SelectedTabItemForegroundBrush : new SolidColorBrush(Colors.Black);
  1376. }
  1377. }
  1378. tab.Background = bFound ? ThemeManager.SelectedTabItemBackgroundBrush : new SolidColorBrush(Colors.White);
  1379. tab.Foreground = bFound ? ThemeManager.SelectedTabItemForegroundBrush : new SolidColorBrush(Colors.Black);
  1380. }
  1381. }
  1382. }
  1383. private static void StartLocalDatabase(IProgress<string> progress)
  1384. {
  1385. var dirName = Path.GetDirectoryName(App.DatabaseSettings.FileName);
  1386. if (!Directory.Exists(dirName) && dirName != null)
  1387. Directory.CreateDirectory(dirName);
  1388. var FileName = App.DatabaseSettings.FileName;
  1389. var Exists = File.Exists(FileName);
  1390. progress.Report("Configuring Stores");
  1391. DbFactory.Stores = CoreUtils.TypeList(
  1392. new[]
  1393. {
  1394. typeof(Store<>).Assembly,
  1395. typeof(EquipmentStore).Assembly
  1396. },
  1397. myType =>
  1398. myType.IsClass
  1399. && !myType.IsAbstract
  1400. && !myType.IsGenericType
  1401. && myType.GetInterfaces().Contains(typeof(IStore))
  1402. ).ToArray();
  1403. DbFactory.DefaultStore = typeof(BaseStore<>);
  1404. DbFactory.ProviderFactory = new SQLiteProviderFactory(App.DatabaseSettings.FileName);
  1405. DbFactory.ColorScheme = App.DatabaseSettings.ColorScheme;
  1406. DbFactory.Logo = App.DatabaseSettings.Logo;
  1407. progress.Report("Starting Local Database");
  1408. DbFactory.Start();
  1409. ClientFactory.DatabaseID = DbFactory.ID;
  1410. progress.Report("Checking Database");
  1411. var users = DbFactory.NewProvider(Logger.Main).Load<User>();
  1412. if (!users.Any())
  1413. {
  1414. var user = new User { UserID = "ADMIN", Password = "admin" };
  1415. DbFactory.NewProvider(Logger.Main).Save(user);
  1416. var employee = DbFactory.NewProvider(Logger.Main).Load(Filter<Employee>.Where(x => x.Code).IsEqualTo("ADMIN")).FirstOrDefault();
  1417. employee ??= new Employee { Code = "ADMIN", Name = "Administrator Account" };
  1418. employee.User.ID = user.ID;
  1419. DbFactory.NewProvider(Logger.Main).Save(employee);
  1420. }
  1421. CoreUtils.GoogleAPIKey = App.DatabaseSettings.GoogleAPIKey;
  1422. Job.JobNumberPrefix = App.DatabaseSettings.JobPrefix;
  1423. PurchaseOrder.PONumberPrefix = App.DatabaseSettings.PurchaseOrderPrefix;
  1424. }
  1425. #endregion
  1426. #region Login Management
  1427. private ValidationStatus? TryAutoLogin()
  1428. {
  1429. ValidationStatus? result = null;
  1430. if (App.DatabaseSettings.LoginType == LoginType.UserID)
  1431. {
  1432. try
  1433. {
  1434. result = ClientFactory.Validate(App.DatabaseSettings.UserID, App.DatabaseSettings.Password);
  1435. }
  1436. catch (Exception e)
  1437. {
  1438. MessageWindow.ShowError("Error connecting to server.\nPlease check the server URL and port number.", e);
  1439. result = null;
  1440. }
  1441. if (result == ValidationStatus.INVALID)
  1442. {
  1443. MessageWindow.ShowMessage("Unable to Login with User ID: " + App.DatabaseSettings.UserID, "Login failed");
  1444. }
  1445. }
  1446. return result;
  1447. }
  1448. private ValidationStatus? DoLogin()
  1449. {
  1450. ValidationStatus? result = null;
  1451. if (result != ValidationStatus.VALID)
  1452. {
  1453. var login = new PinLogin(CoreUtils.GetVersion(), result ?? ValidationStatus.INVALID);
  1454. if (login.ShowDialog() == true)
  1455. {
  1456. result = ValidationStatus.VALID;
  1457. }
  1458. }
  1459. return result ?? ValidationStatus.INVALID;
  1460. }
  1461. /// <summary>
  1462. /// To be called after <see cref="DoLogin(bool)"/> and if a valid login session exists. Configures the main screen and loads the windows.
  1463. /// </summary>
  1464. /// <param name="progress">If not <see langword="null"/>, then rather than opening a new progress window, just uses that.</param>
  1465. private void AfterLogin(IProgress<string>? progress)
  1466. {
  1467. try
  1468. {
  1469. Logger.Send(LogType.Information, "", "Checking Support Ticket Status");
  1470. CheckSupportTicketStatus();
  1471. Logger.Send(LogType.Information, "", "Loading employee");
  1472. LoadCurrentEmployee();
  1473. if (CheckTimesheetBypass(true))
  1474. {
  1475. UpdateCurrentLogin();
  1476. }
  1477. else
  1478. {
  1479. Dispatcher.Invoke(() =>
  1480. {
  1481. MessageWindow.ShowMessage("You must clock on before logging in to PRS!", "Not clocked in.");
  1482. });
  1483. ClientFactory.InvalidateUser();
  1484. App.EmployeeID = Guid.Empty;
  1485. App.EmployeeName = "";
  1486. App.EmployeeCode = "";
  1487. App.EmployeeEmail = "";
  1488. }
  1489. Logger.Send(LogType.Information, "", "Setting colours");
  1490. if (progress is null)
  1491. {
  1492. ApplyColorScheme();
  1493. }
  1494. else
  1495. {
  1496. Dispatcher.Invoke(ApplyColorScheme);
  1497. }
  1498. Logger.Send(LogType.Information, "", "Configuring main window");
  1499. ConfigureMainScreen(progress);
  1500. Logger.Send(LogType.Information, "", "Loading current window");
  1501. if (progress is null)
  1502. {
  1503. LoadApplicationState();
  1504. }
  1505. else
  1506. {
  1507. Dispatcher.Invoke(LoadApplicationState);
  1508. }
  1509. Logger.Send(LogType.Information, "", "Loading secondary window");
  1510. if (progress is null)
  1511. {
  1512. LoadSecondaryWindows();
  1513. }
  1514. else
  1515. {
  1516. Dispatcher.Invoke(LoadSecondaryWindows);
  1517. }
  1518. }
  1519. catch (Exception e)
  1520. {
  1521. }
  1522. }
  1523. /// <summary>
  1524. /// Creates a new <see cref="Login"/> if one does not already exist. Otherwise, updates the <see cref="Login"/> entry in the database with new Station ID.
  1525. /// </summary>
  1526. private void UpdateCurrentLogin()
  1527. {
  1528. if (CoreUtils.GetVersion().Equals("???"))
  1529. return;
  1530. // Register this station with the Server
  1531. // Later on, the heartbeat will check to make sure
  1532. // that the StationID hasn't changed. If it has,
  1533. // then we've logged in somewhere else and we'll
  1534. // drop out of this station
  1535. var curr = new Client<Login>().Query(
  1536. Filter<Login>.Where(x => x.User.ID).IsEqualTo(ClientFactory.UserGuid),
  1537. null
  1538. ).Rows.FirstOrDefault();
  1539. if (curr != null)
  1540. {
  1541. var c = curr.ToObject<Login>();
  1542. c.StationID = station.StationID;
  1543. station = c;
  1544. }
  1545. else
  1546. {
  1547. station.User.ID = ClientFactory.UserGuid;
  1548. station.TimeStamp = DateTime.Now;
  1549. }
  1550. new Client<Login>().Save(station, "", (o, e) => { });
  1551. }
  1552. private void LoadCurrentEmployee()
  1553. {
  1554. var me = new Client<Employee>().Query(
  1555. Filter<Employee>.Where(x => x.User.ID).IsEqualTo(ClientFactory.UserGuid),
  1556. Columns.None<Employee>().Add(x => x.ID).Add(x=>x.Code).Add(x => x.Email).Add(x => x.Name)
  1557. );
  1558. App.EmployeeID = me.Rows.FirstOrDefault()?.Get<Employee, Guid>(x => x.ID) ?? Guid.Empty;
  1559. App.EmployeeCode = me.Rows.FirstOrDefault()?.Get<Employee, string>(x => x.Code) ?? "";
  1560. App.EmployeeName = me.Rows.FirstOrDefault()?.Get<Employee, String>(x => x.Name) ?? "";
  1561. App.EmployeeCode = me.Rows.FirstOrDefault()?.Get<Employee, String>(x => x.Code) ?? "";
  1562. App.EmployeeEmail = me.Rows.FirstOrDefault()?.Get<Employee, String>(x => x.Email) ?? "";
  1563. }
  1564. private void ExecuteLogout()
  1565. {
  1566. new Client<Login>().Delete(station, "");
  1567. station.ID = Guid.Empty;
  1568. App.EmployeeID = Guid.Empty;
  1569. App.EmployeeName = "";
  1570. App.EmployeeEmail = "";
  1571. }
  1572. /// <summary>
  1573. /// Logs the user out and unloads windows
  1574. /// </summary>
  1575. /// <param name="message">A message to display as the reason for logging out, or <c>null</c> for no message.</param>
  1576. private bool Logout(string? message = null, bool force = false)
  1577. {
  1578. // I really don't like all these try-catch blocks; unfortunately, if we are trying to log out and invalidate due to an unauthenticated user,
  1579. // all the queries that get called here will throw exceptions and thus break our system, failing to log out.
  1580. try
  1581. {
  1582. FinalizeAutoTimesheet();
  1583. }
  1584. catch
  1585. {
  1586. if (!force) throw;
  1587. }
  1588. // Try to unload the window;
  1589. try
  1590. {
  1591. UnloadWindow(null);
  1592. if (DatabaseType == DatabaseType.Standalone && !CoreUtils.GetVersion().Equals("???"))
  1593. scheduler.Stop();
  1594. }
  1595. catch
  1596. {
  1597. if (!force) throw;
  1598. }
  1599. // Next, try to set things to being empty
  1600. try
  1601. {
  1602. if (!CoreUtils.GetVersion().Equals("???"))
  1603. if (station.ID != Guid.Empty)
  1604. {
  1605. ExecuteLogout();
  1606. }
  1607. ClearTrackingKanban();
  1608. }
  1609. catch
  1610. {
  1611. if (!force) throw;
  1612. }
  1613. ClientFactory.InvalidateUser();
  1614. ConfigureMainScreen(null);
  1615. LoadSecondaryWindows();
  1616. if (message != null)
  1617. {
  1618. MessageWindow.ShowMessage(message, "Logged out");
  1619. }
  1620. if (DoLogin() == ValidationStatus.VALID)
  1621. {
  1622. AfterLogin(null);
  1623. }
  1624. return true;
  1625. }
  1626. #endregion
  1627. #region Timesheets
  1628. private void RefreshTimeSheets()
  1629. {
  1630. if (App.EmployeeID == Guid.Empty)
  1631. return;
  1632. var filter = Filter<TimeSheet>.Where(x => x.Employee.ID).IsEqualTo(App.EmployeeID);
  1633. filter = filter.And(Filter<TimeSheet>.Where(x => x.Confirmed).IsEqualTo(DateTime.MinValue).Or(x => x.Date).IsEqualTo(DateTime.Today));
  1634. _timesheets = new Client<TimeSheet>().Query(
  1635. filter,
  1636. Columns.None<TimeSheet>().Add(
  1637. x => x.ID,
  1638. x => x.Date,
  1639. x => x.Finish
  1640. )
  1641. );
  1642. }
  1643. private CoreTable GetTimesheet()
  1644. {
  1645. return new Client<TimeSheet>().Query(
  1646. Filter<TimeSheet>.Where(x => x.Date).IsEqualTo(DateTime.Today)
  1647. .And(x => x.Employee.ID).IsEqualTo(App.EmployeeID)
  1648. .And(x => x.Finish).IsEqualTo(TimeSpan.Zero)
  1649. );
  1650. }
  1651. private bool CheckTimesheetBypass(bool message)
  1652. {
  1653. var isClockedOn = IsClockedOn();
  1654. if (!Security.IsAllowed<CanBypassTimeBench>())
  1655. {
  1656. if (!isClockedOn)
  1657. {
  1658. return false;
  1659. }
  1660. return true;
  1661. }
  1662. if (Security.IsAllowed<AutoGenerateTimesheet>())
  1663. if (!isClockedOn)
  1664. {
  1665. var ts = new TimeSheet();
  1666. ts.Date = DateTime.Today;
  1667. ts.Start = DateTime.Now.TimeOfDay;
  1668. ts.Employee.ID = App.EmployeeID;
  1669. ts.Notes = "Automatic Login from PRS Desktop";
  1670. new Client<TimeSheet>().Save(ts, "AutoLogon because Timebench Bypass is enabled", (o, e) => { });
  1671. }
  1672. return true;
  1673. }
  1674. private bool IsClockedOn()
  1675. {
  1676. RefreshTimeSheets();
  1677. if (_timesheets == null)
  1678. return false;
  1679. return _timesheets.Rows.Any(r =>
  1680. r.Get<TimeSheet, DateTime>(c => c.Date).Date == DateTime.Today && r.Get<TimeSheet, TimeSpan>(c => c.Finish) == new TimeSpan());
  1681. }
  1682. private void FinalizeAutoTimesheet()
  1683. {
  1684. if (Security.IsAllowed<AutoGenerateTimesheet>())
  1685. {
  1686. var check = new Client<TimeSheet>().Query(
  1687. Filter<TimeSheet>.Where(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.Employee.ID).IsEqualTo(App.EmployeeID).And(x => x.Finish)
  1688. .IsEqualTo(TimeSpan.Zero)
  1689. );
  1690. if (check.Rows.Any())
  1691. {
  1692. var ts = check.Rows.First().ToObject<TimeSheet>();
  1693. if (DateTime.Now.TimeOfDay < ts.Start.Add(new TimeSpan(0, 2, 0)))
  1694. {
  1695. new Client<TimeSheet>().Delete(ts, "Deleting Auto TimeSheet because TimeBench Bypass is enabled", (o, ex) => { });
  1696. }
  1697. else
  1698. {
  1699. ts.Finish = DateTime.Now.TimeOfDay;
  1700. new Client<TimeSheet>().Save(ts, "Clocking off Auto Timesheet because Timesheet Bypass is enabled", (o, ex) => { });
  1701. }
  1702. }
  1703. }
  1704. }
  1705. #endregion
  1706. private string CurrentPanelSlug()
  1707. {
  1708. var module = Settings.Settings.GetValueOrDefault("CurrentPanel")?.Split(new[] { " / " }, StringSplitOptions.None);
  1709. return module?.LastOrDefault()?.Replace(" ", "_").Replace("/", "") ?? "";
  1710. }
  1711. private bool ShowHelp()
  1712. {
  1713. Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + CurrentPanelSlug()) { UseShellExecute = true });
  1714. return true;
  1715. }
  1716. private void Wiki_Click(object sender, RoutedEventArgs e)
  1717. {
  1718. ShowHelp();
  1719. }
  1720. private void Window_Loaded(object sender, RoutedEventArgs e)
  1721. {
  1722. }
  1723. private void UnloadWindow(CancelEventArgs? cancel)
  1724. {
  1725. PanelHost.UnloadPanel(cancel);
  1726. if (cancel?.Cancel == true)
  1727. {
  1728. return;
  1729. }
  1730. Title =
  1731. $" - {(String.Equals(App.Profile?.ToUpper(), "DEFAULT") ? "PRS Desktop" : App.Profile)} (Release {CoreUtils.GetVersion()})";
  1732. if (CurrentTab is not null)
  1733. {
  1734. var border = Syncfusion.Windows.Shared.VisualUtils.EnumChildrenOfType(CurrentTab, typeof(Border)).LastOrDefault();
  1735. if (border != null)
  1736. {
  1737. ((Border)border).Background = new SolidColorBrush(Colors.Transparent);
  1738. ((Border)border).BorderBrush = new SolidColorBrush(Colors.Transparent);
  1739. }
  1740. var ReportsBar = FindRibbonBar(CurrentTab, x => x.Header.Equals("Print"));
  1741. if (ReportsBar is not null)
  1742. {
  1743. ReportsBar.Items.Clear();
  1744. ReportsBar.Visibility = Visibility.Collapsed;
  1745. ReportsBar.IsLauncherVisible = false;
  1746. }
  1747. var ActionBar = FindRibbonBar(CurrentTab, x => x.Header.Equals("Actions"));
  1748. if (ActionBar is not null)
  1749. {
  1750. ActionBar.IsLauncherVisible = false;
  1751. foreach (var module in CurrentModules)
  1752. ActionBar.Items.Remove(module);
  1753. }
  1754. }
  1755. CurrentTab = null;
  1756. CurrentButton = null;
  1757. ContentControl.Content = null;
  1758. }
  1759. private void RibbonWindow_Activated(object sender, EventArgs e)
  1760. {
  1761. }
  1762. private void RegisterModules(IProgress<string> progress)
  1763. {
  1764. foreach (Fluent.RibbonTabItem tab in _ribbon.Tabs)
  1765. foreach (Fluent.RibbonGroupBox bar in tab.Groups)
  1766. foreach (var item in bar.Items.OfType<RibbonButton>())
  1767. Dispatcher.Invoke(() =>
  1768. {
  1769. if (item.Label != "Refresh")
  1770. if (bar.Header.Equals("Actions"))
  1771. Modules.Register(item.Label);
  1772. });
  1773. }
  1774. private static Fluent.RibbonGroupBox? FindRibbonBar(Fluent.RibbonTabItem tab, Func<Fluent.RibbonGroupBox, bool> predicate)
  1775. {
  1776. foreach (var group in tab.Groups)
  1777. {
  1778. if (group != null)
  1779. if (predicate.Invoke(group))
  1780. return group;
  1781. }
  1782. return null;
  1783. }
  1784. #region Button Event Handlers
  1785. #region Accounts
  1786. #endregion
  1787. #region Dashboards
  1788. #endregion
  1789. private void Console_Click(object sender, RoutedEventArgs a)
  1790. {
  1791. if (_console is null)
  1792. {
  1793. _console = new DesktopConsole("Console");
  1794. _console.Closing += (o, args) => _console = null;
  1795. }
  1796. _console.Show();
  1797. }
  1798. private void RefreshMenu_Click(object sender, RoutedEventArgs e)
  1799. {
  1800. PanelHost.Refresh();
  1801. }
  1802. //private void NotificationsList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
  1803. //{
  1804. // if (NotificationsList.SelectedIndex < 0)
  1805. // return;
  1806. // var editors = NotificationsList.FindVisualChildren<InABox.DynamicGrid.ExtendedRichTextEditor>().ToArray();
  1807. // var selected = editors[NotificationsList.SelectedIndex];
  1808. // selected.Text = (String)selected.Tag; //NotificationsList.SelectedIndex.ToString();
  1809. //}
  1810. //private void ViewNotification_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
  1811. //{
  1812. // Notification notification = (sender as Label).Tag as Notification;
  1813. // NotificationDetails details = new NotificationDetails(notification);
  1814. // details.ShowDialog();
  1815. // ReloadNotifications();
  1816. //}
  1817. private void Library_Click(object sender, RoutedEventArgs e)
  1818. {
  1819. Process.Start(
  1820. new ProcessStartInfo
  1821. {
  1822. FileName = App.DatabaseSettings.LibraryLocation,
  1823. UseShellExecute = true,
  1824. Verb = "open"
  1825. }
  1826. );
  1827. }
  1828. private void SendNotificationClick(object sender, RoutedEventArgs e)
  1829. {
  1830. var form = new NotificationForm { Description = "" };
  1831. if (form.ShowDialog() == true)
  1832. ReloadNotifications();
  1833. }
  1834. private void CompanyInformation_Click(object sender, RoutedEventArgs e)
  1835. {
  1836. var info = new Client<CompanyInformation>().Load().FirstOrDefault();
  1837. if (info == null)
  1838. info = new CompanyInformation();
  1839. new DynamicDataGrid<CompanyInformation>().EditItems(new[] { info });
  1840. }
  1841. private void Setup_Click(object sender, RoutedEventArgs e)
  1842. {
  1843. var tab = _ribbon.SelectedTabItem;
  1844. if (tab is null)
  1845. return;
  1846. var menu = new ContextMenu();
  1847. PanelHost.InitialiseSetupMenu(menu);
  1848. menu.IsOpen = true;
  1849. }
  1850. private void Issues_Click(object sender, RoutedEventArgs e)
  1851. {
  1852. try
  1853. {
  1854. IssuesWindow.Execute();
  1855. CheckSupportTicketStatus();
  1856. }
  1857. catch(Exception err)
  1858. {
  1859. MessageWindow.ShowError("Could not load issues.", err);
  1860. }
  1861. }
  1862. private void Mobile_Click(object sender, RoutedEventArgs args)
  1863. {
  1864. var _baseDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? "";
  1865. var _mobileApp = System.IO.Path.Combine(_baseDirectory, "PRSAvalonia", "PRS.Avalonia.Desktop.exe");
  1866. if (File.Exists(_mobileApp))
  1867. {
  1868. var _info = new ProcessStartInfo(_mobileApp);
  1869. Process.Start(_info);
  1870. }
  1871. }
  1872. private void StartForm<TEntityForm, TEntity, TEntityLink>(DigitalForm form)
  1873. where TEntityForm : BaseEntityForm<TEntity, TEntityLink, TEntityForm>, new()
  1874. where TEntity : Entity, new()
  1875. where TEntityLink : EntityLink<TEntity>, new()
  1876. {
  1877. var entityForm = new TEntityForm();
  1878. entityForm.Form.CopyFrom(form);
  1879. entityForm.Description = form.Description;
  1880. entityForm.FormStarted = DateTime.Now;
  1881. var entity = DFUtils.NewEntity<TEntityForm, TEntity, TEntityLink>(form);
  1882. if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel, entity))
  1883. {
  1884. dataModel.Update(null);
  1885. }
  1886. }
  1887. private void Forms_Click(object sender, RoutedEventArgs e)
  1888. {
  1889. var select = new MultiSelectDialog<DigitalForm>(
  1890. Filter<DigitalForm>.And(
  1891. LookupFactory.DefineChildFilter<KanbanForm, DigitalForm>(Array.Empty<KanbanForm>()),
  1892. Filter<DigitalForm>.Where(x => x.ID).InQuery(
  1893. Filter<EmployeeDigitalForm>.Where(x => x.Employee.ID).IsEqualTo(App.EmployeeID),
  1894. x => x.Form.ID)),
  1895. LookupFactory.DefineChildColumns<KanbanForm, DigitalForm>()
  1896. .Add(x => x.Description),
  1897. false);
  1898. if (select.ShowDialog() == true)
  1899. {
  1900. var digitalForm = select.Data().Rows.FirstOrDefault()?.ToObject<DigitalForm>();
  1901. if (digitalForm is not null)
  1902. {
  1903. StartForm<KanbanForm, Kanban, KanbanLink>(digitalForm);
  1904. }
  1905. };
  1906. }
  1907. #endregion
  1908. private bool OutstandingDailyReports(bool refresh)
  1909. {
  1910. if (!Security.IsAllowed<CanViewDailyReports>() || Security.IsAllowed<BypassOutstandingDailyReports>())
  1911. return false;
  1912. if (refresh)
  1913. RefreshTimeSheets();
  1914. if (_timesheets == null)
  1915. return false;
  1916. return _timesheets.Rows.Any(r => r.Get<TimeSheet, DateTime>(c => c.Date).Date < DateTime.Today);
  1917. }
  1918. private void ShutDownTransport()
  1919. {
  1920. if (_transport != null)
  1921. {
  1922. _transport.OnClose -= TransportConnectionLost;
  1923. if (_transport.IsConnected())
  1924. _transport.Disconnect();
  1925. _transport = null;
  1926. }
  1927. }
  1928. private void Window_Unloaded(object sender, RoutedEventArgs e)
  1929. {
  1930. ShutDownTransport();
  1931. }
  1932. private void RibbonWindow_Closed(object sender, EventArgs e)
  1933. {
  1934. ShutDownTransport();
  1935. //DisconnectRecorderNotes();
  1936. Application.Current.Shutdown();
  1937. }
  1938. //private bool _closingFromSystemMenu = false;
  1939. private void Window_Closing(object sender, CancelEventArgs e)
  1940. {
  1941. /*if (!_closingFromSystemMenu && !CoreUtils.GetVersion().Equals("???"))
  1942. {
  1943. WindowState = WindowState.Minimized;
  1944. e.Cancel = true;
  1945. return;
  1946. }*/
  1947. PanelHost.UnloadPanel(e);
  1948. if (!e.Cancel)
  1949. {
  1950. ISubPanelHost.Global.ShutdownSubPanels(e);
  1951. }
  1952. if (e.Cancel)
  1953. {
  1954. return;
  1955. }
  1956. App.IsClosing = true;
  1957. FinalizeAutoTimesheet();
  1958. if (!CoreUtils.GetVersion().Equals("???"))
  1959. scheduler.Stop();
  1960. CurrentTab = null;
  1961. UpdateRibbonColors();
  1962. if (!CoreUtils.GetVersion().Equals("???"))
  1963. if (station.ID != Guid.Empty)
  1964. ExecuteLogout();
  1965. ShutDownTransport();
  1966. }
  1967. #region Notifications + Heartbeat
  1968. private void ReceiveNotification(Notification notification)
  1969. {
  1970. if (Security.CanView<Notification>())
  1971. {
  1972. Notifications.AddNotification(notification);
  1973. }
  1974. foreach(var panel in Panels.OfType<NotificationPanel>())
  1975. {
  1976. panel.AddNotification(notification);
  1977. }
  1978. }
  1979. private void Notifications_Tick(object? sender, EventArgs e)
  1980. {
  1981. if (ClientFactory.UserGuid != Guid.Empty)
  1982. {
  1983. try
  1984. {
  1985. ReloadNotifications();
  1986. }
  1987. catch (Exception err)
  1988. {
  1989. Logger.Send(LogType.Error, ClientFactory.UserID,
  1990. string.Format("Exception in Notifications_Tick:ReloadNotifications() {0}\n{1}", err.Message, err.StackTrace));
  1991. }
  1992. Heartbeat();
  1993. try
  1994. {
  1995. CheckIsLoggedOn();
  1996. }
  1997. catch (Exception err2)
  1998. {
  1999. Logger.Send(LogType.Error, ClientFactory.UserID,
  2000. string.Format("Exception in Notifications_Tick:CheckIsLoggedOn() {0}\n{1}", err2.Message, err2.StackTrace));
  2001. }
  2002. }
  2003. //else
  2004. //{
  2005. // Logger.Send(LogType.Information, ClientFactory.UserID, "Notifications_Tick: ClientFactory.UserGuid is empty");
  2006. //}
  2007. }
  2008. private void CheckIsLoggedOn()
  2009. {
  2010. if (CoreUtils.GetVersion().Equals("???"))
  2011. return;
  2012. var bLogout = false;
  2013. if (!Security.IsAllowed<CanBypassTimeBench>())
  2014. if (!IsClockedOn())
  2015. {
  2016. Logger.Send(LogType.Information, ClientFactory.UserID, "User is no longer clocked in!");
  2017. bLogout = true;
  2018. Dispatcher.Invoke(() => { Logout(); });
  2019. }
  2020. if (!bLogout)
  2021. new Client<Login>().Query(
  2022. Filter<Login>.Where(x => x.User.ID).IsEqualTo(ClientFactory.UserGuid),
  2023. Columns.None<Login>().Add(x => x.StationID),
  2024. null,
  2025. CoreRange.All,
  2026. (o, e) =>
  2027. {
  2028. if (e is RemoteException remote)
  2029. {
  2030. if (remote.Status == StatusCode.Unauthenticated)
  2031. {
  2032. Logger.Send(LogType.Information, ClientFactory.UserID, "User has been logged out");
  2033. Dispatcher.Invoke(() =>
  2034. {
  2035. Logout("You have been logged out due to inactivity");
  2036. });
  2037. }
  2038. else
  2039. {
  2040. Logger.Send(LogType.Information, ClientFactory.UserID, $"Error in CheckIsLoggedOn(): {CoreUtils.FormatException(remote)}");
  2041. }
  2042. }
  2043. else if (e is not null)
  2044. {
  2045. Logger.Send(LogType.Information, ClientFactory.UserID, $"Error in CheckIsLoggedOn(): {CoreUtils.FormatException(e)}");
  2046. }
  2047. else if (o is not null)
  2048. {
  2049. var row = o.Rows.FirstOrDefault();
  2050. if (row == null)
  2051. {
  2052. station.ID = Guid.Empty;
  2053. new Client<Login>().Save(station, "", (o1, e1) => { });
  2054. }
  2055. else if (!row.Get<Login, string>(c => c.StationID).Equals(station.StationID))
  2056. {
  2057. Logger.Send(LogType.Information, ClientFactory.UserID, "User logged in somewhere else!");
  2058. bLogout = true;
  2059. Dispatcher.Invoke(() => { Logout(); });
  2060. }
  2061. }
  2062. }
  2063. );
  2064. }
  2065. private void Heartbeat()
  2066. {
  2067. //Task.Run(() =>
  2068. //{
  2069. try
  2070. {
  2071. CheckSupportTicketStatus();
  2072. bool IsClockedOn = this.IsClockedOn();
  2073. if (IsClockedOn)
  2074. {
  2075. if ((_kanbantrackingassignment != null) && (_kanbantrackingassignment.Actual.Finish < DateTime.Now.TimeOfDay))
  2076. {
  2077. _kanbantrackingassignment.Actual.Finish = DateTime.Now.TimeOfDay;
  2078. new Client<Assignment>().Save(_kanbantrackingassignment, "");
  2079. }
  2080. if (Security.IsAllowed<MonitorApplicationWindows>())
  2081. {
  2082. if (ActivityHistory == null)
  2083. ActivityHistory = new LocalConfiguration<DailyActivityHistory>().Load();
  2084. var appname = OpenWindowGetter.GetActiveWindowProcess();
  2085. var title = OpenWindowGetter.GetActiveWindowTitle();
  2086. ActivityHistory.Activities[DateTime.Now] = (!string.IsNullOrWhiteSpace(appname) ? appname.Trim() + " - " : "") + title;
  2087. new LocalConfiguration<DailyActivityHistory>().Save(ActivityHistory);
  2088. }
  2089. }
  2090. PanelHost.Heartbeat();
  2091. foreach(var window in SecondaryWindows.Values)
  2092. {
  2093. window.Heartbeat();
  2094. }
  2095. }
  2096. catch (Exception err)
  2097. {
  2098. Logger.Send(LogType.Error, ClientFactory.UserID, string.Format("Exception in Heartbeat: {0}\n{1}", err.Message, err.StackTrace));
  2099. }
  2100. //});
  2101. }
  2102. private void CheckSupportTicketStatus()
  2103. {
  2104. Dispatcher.BeginInvoke(() =>
  2105. {
  2106. try
  2107. {
  2108. IssuesButton.Background = IssuesWindow.Check()
  2109. ? new SolidColorBrush(Colors.Red) { Opacity = 0.5 }
  2110. : Brushes.Transparent;
  2111. }
  2112. catch (Exception e)
  2113. {
  2114. }
  2115. });
  2116. }
  2117. private void Notifications_Changed(object sender)
  2118. {
  2119. if (Notifications.IsActive)
  2120. {
  2121. Notifications.Visibility = Visibility.Visible;
  2122. DockingGrid.ColumnDefinitions[1].Width = new GridLength(4, GridUnitType.Pixel);
  2123. DockingGrid.ColumnDefinitions[2].Width = Equals(0.0, DockingGrid.ColumnDefinitions[2].Width.Value)
  2124. ? new GridLength(300, GridUnitType.Pixel)
  2125. : DockingGrid.ColumnDefinitions[2].Width;
  2126. }
  2127. else
  2128. {
  2129. Notifications.Visibility = Visibility.Collapsed;
  2130. DockingGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Pixel);
  2131. DockingGrid.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Pixel);
  2132. }
  2133. }
  2134. private void ReloadNotifications()
  2135. {
  2136. if (Security.CanView<Notification>())
  2137. Notifications.Refresh();
  2138. if (!NotificationsWatchDog.IsEnabled)
  2139. {
  2140. //Logger.Send(LogType.Information, ClientFactory.UserID, "Enabling Heartbeat");
  2141. NotificationsWatchDog.IsEnabled = true;
  2142. }
  2143. }
  2144. #endregion
  2145. private void RibbonWindow_PreviewMouseUp(object sender, MouseButtonEventArgs e)
  2146. {
  2147. PanelHost.IncrementTrackingModuleClick();
  2148. }
  2149. private void RibbonWindow_PreviewKeyUp(object sender, KeyEventArgs e)
  2150. {
  2151. PanelHost.IncrementTrackingModuleKey();
  2152. }
  2153. public static void ActivateWindow(Window window)
  2154. {
  2155. var hwnd = new WindowInteropHelper(window).EnsureHandle();
  2156. var threadId1 = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
  2157. var threadId2 = GetWindowThreadProcessId(hwnd, IntPtr.Zero);
  2158. if (threadId1 != threadId2)
  2159. {
  2160. AttachThreadInput(threadId1, threadId2, true);
  2161. SetForegroundWindow(hwnd);
  2162. AttachThreadInput(threadId1, threadId2, false);
  2163. }
  2164. else
  2165. {
  2166. SetForegroundWindow(hwnd);
  2167. }
  2168. }
  2169. private static IntPtr GetForegroundWindow()
  2170. {
  2171. var active = GetActiveWindow();
  2172. var window = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => new WindowInteropHelper(x).Handle == active);
  2173. var hwnd = new WindowInteropHelper(window).EnsureHandle();
  2174. return hwnd;
  2175. }
  2176. [DllImport("user32.dll", SetLastError = true)]
  2177. private static extern IntPtr SetForegroundWindow(IntPtr hWnd);
  2178. [DllImport("user32.dll")]
  2179. private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
  2180. [DllImport("user32.dll")]
  2181. private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
  2182. [DllImport("user32.dll")]
  2183. private static extern IntPtr GetActiveWindow();
  2184. #region Modules + Reports
  2185. public void ClearActions()
  2186. {
  2187. foreach (var module in CurrentModules)
  2188. {
  2189. if (module.Parent is Fluent.RibbonGroupBox bar)
  2190. bar.Items.Remove(module);
  2191. }
  2192. CurrentModules.Clear();
  2193. }
  2194. public void ClearReports()
  2195. {
  2196. if (CurrentTab is not null)
  2197. {
  2198. var ReportsBar = FindRibbonBar(CurrentTab, x => x.Header.Equals("Print"));
  2199. if (ReportsBar is not null)
  2200. {
  2201. ReportsBar.Visibility = Visibility.Collapsed;
  2202. ReportsBar.Items.Clear();
  2203. }
  2204. }
  2205. }
  2206. public void CreateReport(PanelAction action)
  2207. {
  2208. if (CurrentTab is null)
  2209. return;
  2210. var ReportsBar = FindRibbonBar(CurrentTab, x => x.Header.Equals("Print"));
  2211. if (ReportsBar is not null)
  2212. {
  2213. var button = new Fluent.Button
  2214. {
  2215. Header = action.Caption,
  2216. LargeIcon = action.Image?.AsBitmapImage(),
  2217. MinWidth = 60
  2218. };
  2219. if (action.Menu is not null)
  2220. {
  2221. button.ContextMenu = action.Menu;
  2222. }
  2223. button.Click += (o, e) =>
  2224. {
  2225. action.Execute();
  2226. };
  2227. ReportsBar.Visibility = Security.IsAllowed<CanPrintReports>() ? Visibility.Visible : Visibility.Collapsed;
  2228. ReportsBar.Items.Add(button);
  2229. }
  2230. }
  2231. private NullConverter<Visibility>? _vis = null;
  2232. public void CreatePanelAction(PanelAction action)
  2233. {
  2234. if (CurrentTab is null)
  2235. return;
  2236. if (_vis == null)
  2237. {
  2238. _vis = new();
  2239. _vis.Converting += (o, e) =>
  2240. {
  2241. };
  2242. }
  2243. var Actions = FindRibbonBar(CurrentTab, x => x.Header.Equals("Actions"));
  2244. if (Actions is not null)
  2245. {
  2246. if (!CurrentModules.Any(x => x.GetType().Equals(typeof(RibbonSeparator))))
  2247. {
  2248. var sep = new RibbonSeparator();
  2249. CurrentModules.Add(sep);
  2250. Actions.Items.Add(sep);
  2251. }
  2252. if (action.OnPopulate != null)
  2253. {
  2254. Fluent.DropDownButton button;
  2255. if (action.OnExecute != null)
  2256. {
  2257. button = new Fluent.SplitButton();
  2258. ((Fluent.SplitButton)button).Click += (o, e) => action.Execute();
  2259. }
  2260. else
  2261. button = new Fluent.DropDownButton();
  2262. button.MinWidth = 60;
  2263. button.DataContext = action;
  2264. button.DropDownOpened += (sender, args) => { button.ItemsSource = CreateMenuItems(action); };
  2265. button.Bind<PanelAction, String>(Fluent.DropDownButton.HeaderProperty, x => x.Caption, null);
  2266. button.Bind<PanelAction, Bitmap?>(Fluent.DropDownButton.LargeIconProperty, x => x.Image,
  2267. new BitmapToBitmapImageConverter());
  2268. button.Bind<PanelAction, ContextMenu?>(ContextMenuProperty, x => x.Menu);
  2269. button.Bind<PanelAction, bool>(IsEnabledProperty, x => x.IsEnabled);
  2270. button.Bind<PanelAction, Visibility>(Fluent.DropDownButton.VisibilityProperty, x => x.Visibility, _vis);
  2271. Actions.Items.Add(button);
  2272. CurrentModules.Add(button);
  2273. }
  2274. else
  2275. {
  2276. var button = new Fluent.Button()
  2277. {
  2278. MinWidth = 60,
  2279. DataContext = action
  2280. };
  2281. button.Bind<PanelAction, String>(Fluent.Button.HeaderProperty, x => x.Caption, null);
  2282. button.Bind<PanelAction, Bitmap?>(Fluent.Button.LargeIconProperty, x => x.Image,
  2283. new BitmapToBitmapImageConverter());
  2284. button.Bind<PanelAction, ContextMenu?>(Fluent.Button.ContextMenuProperty, x => x.Menu);
  2285. button.Bind<PanelAction, bool>(Fluent.Button.IsEnabledProperty, x => x.IsEnabled);
  2286. button.Bind<PanelAction, Visibility>(Fluent.Button.VisibilityProperty, x => x.Visibility, _vis);
  2287. button.Click += (o, e) => action.Execute();
  2288. Actions.Items.Add(button);
  2289. CurrentModules.Add(button);
  2290. }
  2291. }
  2292. }
  2293. private static ObservableCollection<MenuItem> CreateMenuItems(PanelAction action)
  2294. {
  2295. var items = new ObservableCollection<MenuItem>();
  2296. var entries = action.Populate();
  2297. if (entries != null)
  2298. {
  2299. foreach (var entry in entries)
  2300. {
  2301. var item = new Fluent.MenuItem()
  2302. {
  2303. Header = entry.Caption,
  2304. DataContext = entry.Data,
  2305. Icon = entry.Image?.AsBitmapImage()
  2306. };
  2307. item.Click += (o, eventArgs) => entry.Execute();
  2308. items.Add(item);
  2309. }
  2310. }
  2311. return items;
  2312. }
  2313. #endregion
  2314. #region Tracking Kanban
  2315. private class TrackingKanbanFilterItemComponent : DynamicGridFilterComponent<Kanban>
  2316. {
  2317. public TrackingKanbanFilterItemComponent() : base(
  2318. new GlobalConfiguration<CoreFilterDefinitions>(nameof(Kanban)),
  2319. new UserConfiguration<CoreFilterDefinitions>(nameof(Kanban)))
  2320. {
  2321. ButtonText = "Filter";
  2322. }
  2323. public string Text { get; private set; } = "Filter";
  2324. public Bitmap? Image { get; private set; }
  2325. protected override void UpdateButtonText(Bitmap image, string text)
  2326. {
  2327. Text = text;
  2328. Image = image;
  2329. }
  2330. }
  2331. private TrackingKanbanFilterItemComponent? _trackingKanbanFilterComponent;
  2332. private TrackingKanbanFilterItemComponent TrackingKanbanFilterComponent
  2333. {
  2334. get
  2335. {
  2336. if(_trackingKanbanFilterComponent is null)
  2337. {
  2338. _trackingKanbanFilterComponent = new();
  2339. _trackingKanbanFilterComponent.SetSettings(Settings.Filters, false);
  2340. _trackingKanbanFilterComponent.OnFiltersSelected += _trackingKanbanFilterComponent_OnFiltersSelected;
  2341. }
  2342. return _trackingKanbanFilterComponent;
  2343. }
  2344. }
  2345. private ContextMenu? _trackingKanbanMenu;
  2346. private MenuItem? _trackingKanbanFilterMenu;
  2347. private void _trackingKanbanFilterComponent_OnFiltersSelected(DynamicGridSelectedFilterSettings filters)
  2348. {
  2349. if (_trackingKanbanMenu is null) return;
  2350. if (!filters.MultipleFilters)
  2351. {
  2352. _trackingKanbanMenu.IsOpen = false;
  2353. }
  2354. if(_trackingKanbanFilterMenu is not null)
  2355. {
  2356. _trackingKanbanFilterMenu.Header = TrackingKanbanFilterComponent.Text;
  2357. _trackingKanbanFilterMenu.Icon = new System.Windows.Controls.Image() { Source = TrackingKanbanFilterComponent.Image?.AsBitmapImage(24, 24) };
  2358. }
  2359. Settings.Filters = filters;
  2360. LocalConfiguration.Save(Settings);
  2361. }
  2362. private void SelectTask_Click(object sender, RoutedEventArgs e)
  2363. {
  2364. var menu = new ContextMenu();
  2365. var others = new MenuItem() { Header = "Other Tasks" };
  2366. var waiting = new MenuItem() { Header = "Waiting Tasks" };
  2367. using (new WaitCursor())
  2368. {
  2369. var filter =
  2370. Filter<KanbanSubscriber>.Where(x => x.Employee.User.ID).IsEqualTo(ClientFactory.UserGuid)
  2371. .And(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue)
  2372. .And(x => x.Kanban.Closed).IsEqualTo(DateTime.MinValue);
  2373. var kanbanFilter = TrackingKanbanFilterComponent.GetFilter();
  2374. if(kanbanFilter is not null)
  2375. {
  2376. filter = filter.And(x => x.Kanban.ID).InQuery(kanbanFilter, x => x.ID);
  2377. }
  2378. var kanbans = Client.Query(
  2379. filter,
  2380. Columns.None<KanbanSubscriber>()
  2381. .Add(x => x.Kanban.ID)
  2382. .Add(x => x.Kanban.Number)
  2383. .Add(x => x.Kanban.Title)
  2384. .Add(x => x.Kanban.Status)
  2385. .Add(x => x.Assignee),
  2386. new SortOrder<KanbanSubscriber>(x => x.Kanban.Number, SortDirection.Ascending));
  2387. foreach (var subscriber in kanbans.ToObjects<KanbanSubscriber>())
  2388. {
  2389. CreateTaskMenu(subscriber.Assignee ? (subscriber.Kanban.Status != KanbanStatus.Waiting ? menu : waiting) : others,
  2390. $"{subscriber.Kanban.Number} {subscriber.Kanban.Title}",
  2391. subscriber.Kanban.ID);
  2392. }
  2393. menu.AddSeparatorIfNeeded();
  2394. if(others.Items.Count > 0)
  2395. {
  2396. menu.Items.Add(others);
  2397. }
  2398. if(waiting.Items.Count > 0)
  2399. {
  2400. menu.Items.Add(waiting);
  2401. }
  2402. menu.AddSeparatorIfNeeded();
  2403. CreateTaskMenu(menu, "(No Task Selected)", Guid.Empty);
  2404. if(_kanbantrackingassignment is not null && _kanbantrackingassignment.Task.ID != Guid.Empty)
  2405. {
  2406. menu.AddItem("View Task", null, _kanbantrackingassignment.Task.ID, ViewTrackingKanban_Click);
  2407. }
  2408. menu.AddSeparatorIfNeeded();
  2409. var filterItem = menu.AddItem(TrackingKanbanFilterComponent.Text, TrackingKanbanFilterComponent.Image, null);
  2410. TrackingKanbanFilterComponent.PopulateMenu(filterItem);
  2411. _trackingKanbanFilterMenu = filterItem;
  2412. }
  2413. menu.IsOpen = true;
  2414. _trackingKanbanMenu = menu;
  2415. }
  2416. private void ViewTrackingKanban_Click(Guid guid)
  2417. {
  2418. var item = Client.Query(
  2419. Filter<Kanban>.Where(x => x.ID).IsEqualTo(guid),
  2420. DynamicGridUtils.LoadEditorColumns<Kanban>())
  2421. .ToObjects<Kanban>().FirstOrDefault();
  2422. if (item is null) return;
  2423. var grid = DynamicGridUtils.CreateDynamicGrid<Kanban>(typeof(DynamicGrid<>));
  2424. grid.EditItemsNonModal(ISubPanelHost.Global, [item], (items, saved) => { });
  2425. }
  2426. private Assignment? _kanbantrackingassignment = null;
  2427. private CoreFilterDefinitions? _kanbanTrackingFilter = null;
  2428. private void SetTrackingKanban(Guid kanbanID, string header)
  2429. {
  2430. SelectedTaskName.Content = header;
  2431. var createNewAssignment = false;
  2432. if (_kanbantrackingassignment is not null)
  2433. {
  2434. if (_kanbantrackingassignment.Actual.Finish < DateTime.Now.TimeOfDay)
  2435. {
  2436. _kanbantrackingassignment.Actual.Finish = DateTime.Now.TimeOfDay;
  2437. Client.Save(_kanbantrackingassignment, "");
  2438. }
  2439. // Update Existing Kanban
  2440. if (kanbanID == Guid.Empty)
  2441. _kanbantrackingassignment = null;
  2442. else if (_kanbantrackingassignment.Task.ID != kanbanID)
  2443. {
  2444. createNewAssignment = true;
  2445. }
  2446. }
  2447. else if (kanbanID != Guid.Empty)
  2448. {
  2449. createNewAssignment = true;
  2450. }
  2451. if (createNewAssignment)
  2452. {
  2453. _kanbantrackingassignment = new Assignment();
  2454. _kanbantrackingassignment.Task.ID = kanbanID;
  2455. _kanbantrackingassignment.Employee.ID = App.EmployeeID;
  2456. _kanbantrackingassignment.Title = header;
  2457. _kanbantrackingassignment.Date = DateTime.Today;
  2458. _kanbantrackingassignment.Actual.Start = DateTime.Now.TimeOfDay;
  2459. _kanbantrackingassignment.Actual.Finish = DateTime.Now.TimeOfDay.Add(new TimeSpan(0, 2, 0));
  2460. Client.Save(_kanbantrackingassignment, "");
  2461. }
  2462. }
  2463. private void ClearTrackingKanban()
  2464. => SetTrackingKanban(Guid.Empty, "(No Task Selected)");
  2465. private void CreateTaskMenu(ItemsControl menu, string title, Guid id)
  2466. {
  2467. menu.AddCheckMenuItem(title, (title, id), TrackingKanbanMenuItem_Click, isChecked: _kanbantrackingassignment?.Task.ID == id);
  2468. }
  2469. private void TrackingKanbanMenuItem_Click((string title, Guid id) tuple, bool isChecked)
  2470. {
  2471. if (isChecked)
  2472. {
  2473. SetTrackingKanban(tuple.id, tuple.title);
  2474. }
  2475. }
  2476. #endregion
  2477. private void DockPanelBorder_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
  2478. {
  2479. if (sender is not Border border || !border.IsVisible)
  2480. return;
  2481. var dock = border.Child is IDockPanel panel ? panel : border.Child?.FindVisualChildren<IDockPanel>().FirstOrDefault();
  2482. if (dock is null)
  2483. return;
  2484. dock.Refresh();
  2485. }
  2486. private void DockPanel_OnIsActiveChanged(object? sender, EventArgs e)
  2487. {
  2488. if (sender is not LayoutAnchorable layout)
  2489. return;
  2490. var content = layout.Content as DependencyObject;
  2491. var dock = content is IDockPanel panel ? panel : content?.FindVisualChildren<IDockPanel>().FirstOrDefault();
  2492. if (dock is null)
  2493. return;
  2494. if (layout.IsActive && layout.IsVisible)
  2495. dock.Refresh();
  2496. }
  2497. private void _ribbon_OnPreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
  2498. {
  2499. e.Handled = true;
  2500. }
  2501. #region Backstage Functions
  2502. private enum DatabaseConfigurationResult
  2503. {
  2504. RestartRequired,
  2505. RestartNotRequired,
  2506. Cancel
  2507. }
  2508. private DatabaseConfigurationResult ShowDatabaseConfiguration()
  2509. {
  2510. DatabaseConfigurationResult result;
  2511. var config = new DataBaseConfiguration();
  2512. if (config.ShowDialog() == true)
  2513. {
  2514. var newsettings = new LocalConfiguration<DatabaseSettings>(App.Profile).Load();
  2515. if (newsettings.RestartRequired(App.DatabaseSettings))
  2516. {
  2517. result = DatabaseConfigurationResult.RestartRequired;
  2518. }
  2519. else
  2520. {
  2521. result = DatabaseConfigurationResult.RestartNotRequired;
  2522. }
  2523. if ((newsettings.DatabaseType == DatabaseType.Standalone) && (newsettings.ColorScheme != App.DatabaseSettings.ColorScheme))
  2524. {
  2525. App.DatabaseSettings.ColorScheme = newsettings.ColorScheme;
  2526. ApplyColorScheme();
  2527. }
  2528. }
  2529. else
  2530. {
  2531. result = DatabaseConfigurationResult.Cancel;
  2532. }
  2533. return result;
  2534. }
  2535. private void DatabaseSettings_OnClick(object sender, RoutedEventArgs e)
  2536. {
  2537. switch (ShowDatabaseConfiguration())
  2538. {
  2539. case DatabaseConfigurationResult.RestartRequired:
  2540. MessageBox.Show("Please restart the application to apply these changes!");
  2541. break;
  2542. }
  2543. }
  2544. private void CompanyInformation_OnClick(object sender, RoutedEventArgs e)
  2545. {
  2546. var info = new Client<CompanyInformation>().Load().FirstOrDefault();
  2547. if (info == null)
  2548. info = new CompanyInformation();
  2549. new DynamicDataGrid<CompanyInformation>().EditItems(new[] { info });
  2550. }
  2551. private void SecurityDefaultsButton_OnClick(object sender, RoutedEventArgs e)
  2552. {
  2553. var window = new GlobalTokenWindow();
  2554. window.ShowDialog();
  2555. Security.Reset();
  2556. }
  2557. private void SystemLogsButton_OnClick(object sender, RoutedEventArgs e)
  2558. {
  2559. var logfile = CoreUtils.GetLogFile();
  2560. if (File.Exists(logfile))
  2561. {
  2562. var startInfo = new ProcessStartInfo("notepad.exe", logfile);
  2563. startInfo.Verb = "open";
  2564. startInfo.UseShellExecute = true;
  2565. Process.Start(startInfo);
  2566. }
  2567. else
  2568. {
  2569. MessageBox.Show(logfile + " does not exist!");
  2570. }
  2571. }
  2572. private void CheckForUpdates_OnClick(object sender, RoutedEventArgs e)
  2573. {
  2574. if (SupportUtils.CheckForUpdates())
  2575. {
  2576. Close();
  2577. }
  2578. else
  2579. {
  2580. if (MessageWindow.ShowYesNo(
  2581. "You appear to be using the latest version already!\n\nRun the installer anyway?", "Update"))
  2582. {
  2583. if (SupportUtils.DownloadAndRunInstaller())
  2584. {
  2585. Close();
  2586. }
  2587. }
  2588. }
  2589. }
  2590. private void OpenSupportSession_OnClick(object sender, RoutedEventArgs e)
  2591. {
  2592. SupportUtils.OpenSupportSession();
  2593. }
  2594. private void DocumentTypeList_OnClick(object sender, RoutedEventArgs e)
  2595. {
  2596. var list = new MasterList(typeof(DocumentType));
  2597. list.ShowDialog();
  2598. }
  2599. private void EventList_Click(object sender, RoutedEventArgs e)
  2600. {
  2601. var list = new MasterList(typeof(Event));
  2602. list.ShowDialog();
  2603. }
  2604. private void EditDetailsButton_OnClick(object sender, RoutedEventArgs e)
  2605. {
  2606. var employee = new Client<Employee>().Query(
  2607. Filter<Employee>.Where(x => x.User.ID).IsEqualTo(ClientFactory.UserGuid))
  2608. .Rows.FirstOrDefault()?.ToObject<Employee>();
  2609. var item = new MyDetailsConfiguration(employee);
  2610. item.User = ClientFactory.UserGuid;
  2611. var buttons = new DynamicEditorButtons();
  2612. buttons.Add("Change Password", null, item, (sender, item) =>
  2613. {
  2614. var details = (item as MyDetailsConfiguration)!;
  2615. var changePassword = new ChangePassword(details.User);
  2616. if (changePassword.ShowDialog() == true && changePassword.Password != null)
  2617. {
  2618. var newUser = new User();
  2619. newUser.SetID(details.User);
  2620. ChangePassword.ChangeUserPassword(newUser, changePassword.Password);
  2621. new Client<User>().Save(newUser, "Changed Password");
  2622. MessageBox.Show("Password changed!");
  2623. }
  2624. });
  2625. var editor = new DynamicEditorForm(typeof(MyDetailsConfiguration), buttons: buttons);
  2626. if (employee == null)
  2627. {
  2628. editor.OnFormCustomiseEditor += (sender, items, column, editor) =>
  2629. {
  2630. editor.Editable = Editable.Disabled;
  2631. };
  2632. }
  2633. editor.Items = new BaseObject[] { item };
  2634. if (editor.ShowDialog() == true)
  2635. {
  2636. if (employee != null)
  2637. {
  2638. item.SaveTo(employee);
  2639. new Client<Employee>().Save(employee, "Edited by user");
  2640. }
  2641. }
  2642. }
  2643. private void LogoutButton_OnClick(object sender, RoutedEventArgs e)
  2644. {
  2645. Logout();
  2646. }
  2647. private void LoginButton_OnClick(object sender, RoutedEventArgs e)
  2648. {
  2649. if (DoLogin() == ValidationStatus.VALID)
  2650. AfterLogin(null);
  2651. }
  2652. private void ExitButton_OnClick(object sender, RoutedEventArgs e)
  2653. {
  2654. //_closingFromSystemMenu = true;
  2655. Close();
  2656. }
  2657. #endregion
  2658. }