EquipmentSchedulesDashboard.xaml.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.DynamicGrid;
  5. using InABox.WPF;
  6. using Microsoft.CodeAnalysis.VisualBasic.Syntax;
  7. using org.omg.PortableInterceptor;
  8. using PRSDesktop.Forms;
  9. using PRSDesktop.WidgetGroups;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.ComponentModel;
  13. using System.Globalization;
  14. using System.Linq;
  15. using System.Runtime.CompilerServices;
  16. using System.Threading.Tasks;
  17. using System.Windows.Controls;
  18. using System.Windows.Data;
  19. using System.Windows.Media;
  20. using System.Windows.Media.Imaging;
  21. using InABox.Configuration;
  22. using Client = InABox.Clients.Client;
  23. using System.Windows;
  24. using InABox.Wpf;
  25. namespace PRSDesktop.Dashboards;
  26. public class EquipmentSchedulesDashboardProperties : IUserConfigurationSettings, IDashboardProperties
  27. {
  28. public bool OnlyShowSchedules { get; set; } = true;
  29. public DynamicGridSelectedFilterSettings Filters { get; set; } = new();
  30. }
  31. public class ScheduleViewModel : INotifyPropertyChanged
  32. {
  33. private Guid employeeID;
  34. private string? employeeName;
  35. private string? employeeMobile;
  36. private string? employeeEmail;
  37. private BitmapImage? employeeImage;
  38. public bool HasEmployee => EmployeeID != Guid.Empty;
  39. public Guid ID { get; set; }
  40. public string Title { get; set; }
  41. public DateTime DueDate { get; set; }
  42. public BitmapImage? EmployeeImage
  43. {
  44. get => employeeImage;
  45. set
  46. {
  47. employeeImage = value;
  48. NotifyPropertyChanged();
  49. }
  50. }
  51. public Guid EmployeeID
  52. {
  53. get => employeeID;
  54. set
  55. {
  56. employeeID = value;
  57. NotifyPropertyChanged(nameof(HasEmployee));
  58. }
  59. }
  60. public string? EmployeeName
  61. {
  62. get => employeeName;
  63. set
  64. {
  65. employeeName = value;
  66. NotifyPropertyChanged();
  67. }
  68. }
  69. public string? EmployeeMobile
  70. {
  71. get => employeeMobile;
  72. set
  73. {
  74. employeeMobile = value;
  75. NotifyPropertyChanged();
  76. NotifyPropertyChanged(nameof(EmployeeContact));
  77. }
  78. }
  79. public string? EmployeeEmail
  80. {
  81. get => employeeEmail;
  82. set
  83. {
  84. employeeEmail = value;
  85. NotifyPropertyChanged();
  86. NotifyPropertyChanged(nameof(EmployeeContact));
  87. }
  88. }
  89. public string? EmployeeContact
  90. {
  91. get
  92. {
  93. var str = "";
  94. if (EmployeeEmail is not null) str = EmployeeEmail;
  95. if(EmployeeMobile is not null)
  96. {
  97. if (string.IsNullOrWhiteSpace(str))
  98. {
  99. str = EmployeeMobile;
  100. }
  101. else
  102. {
  103. str += $", {EmployeeMobile}";
  104. }
  105. }
  106. return string.IsNullOrWhiteSpace(str) ? null : str;
  107. }
  108. }
  109. public event PropertyChangedEventHandler? PropertyChanged;
  110. private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
  111. {
  112. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  113. }
  114. }
  115. public class EquipmentScheduleViewModel
  116. {
  117. public bool HasLocation { get; set; }
  118. public Guid ID { get; set; }
  119. public string Code { get; set; }
  120. public string Description { get; set; }
  121. public List<ScheduleViewModel> Schedules { get; set; }
  122. }
  123. [ValueConversion(typeof(DateTime), typeof(SolidColorBrush))]
  124. class ScheduleBackgroundConverter : IValueConverter
  125. {
  126. public Color GetColor(DateTime date)
  127. {
  128. var diff = date - DateTime.Today;
  129. if (diff < TimeSpan.Zero)
  130. return Colors.Salmon;
  131. else if (diff.TotalDays <= 7)
  132. return Colors.Orange;
  133. else if (diff.Days <= 30)
  134. return Colors.LightYellow;
  135. else
  136. return Colors.LightGreen;
  137. }
  138. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  139. {
  140. var date = (DateTime)value;
  141. var color = GetColor(date);
  142. return new SolidColorBrush(color);
  143. }
  144. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  145. {
  146. throw new NotImplementedException();
  147. }
  148. }
  149. /// <summary>
  150. /// Interaction logic for EquipmentSchedulesDashboard.xaml
  151. /// </summary>
  152. public partial class EquipmentSchedulesDashboard : UserControl, IDashboardWidget<EquipmentDashboardGroup, EquipmentSchedulesDashboardProperties>, IHeaderDashboard, IBasePanel
  153. {
  154. public EquipmentSchedulesDashboardProperties Properties { get; set; } = null!;
  155. public DashboardHeader Header { get; set; } = new();
  156. public event LoadSettings<EquipmentSchedulesDashboardProperties>? LoadSettings;
  157. public event SaveSettings<EquipmentSchedulesDashboardProperties>? SaveSettings;
  158. public event DataModelUpdateEvent? OnUpdateDataModel;
  159. private Dictionary<Guid, Equipment> Equipment = new();
  160. private Dictionary<Guid, BitmapImage?> EmployeeImages = new();
  161. private Dictionary<Guid, Employee> Employees = new();
  162. private List<EquipmentScheduleViewModel> Models = new();
  163. public EquipmentSchedulesDashboard()
  164. {
  165. InitializeComponent();
  166. }
  167. public void Setup()
  168. {
  169. LoadEmployeeImages();
  170. SetupHeader();
  171. }
  172. #region IBasePanel
  173. public string SectionName => "EquipmentSchedules";
  174. public DataModel DataModel(Selection selection)
  175. {
  176. return new AutoDataModel<Equipment>(new Filter<Equipment>().All());
  177. }
  178. public bool IsReady { get; set; }
  179. public void CreateToolbarButtons(IPanelHost host)
  180. {
  181. }
  182. public Dictionary<string, object[]> Selected()
  183. {
  184. return new();
  185. }
  186. public void Heartbeat(TimeSpan time)
  187. {
  188. }
  189. #endregion
  190. #region Header
  191. private CheckBox ShowSchedulesBox = null!;
  192. private FilterButton<Equipment> FilterBtn = null!;
  193. public void SetupHeader()
  194. {
  195. ShowSchedulesBox = new CheckBox
  196. {
  197. Content = "Only show schedules?",
  198. IsChecked = Properties.OnlyShowSchedules,
  199. VerticalAlignment = VerticalAlignment.Center
  200. };
  201. ShowSchedulesBox.Checked += ShowSchedulesBox_Checked;
  202. ShowSchedulesBox.Unchecked += ShowSchedulesBox_Checked;
  203. FilterBtn = new FilterButton<Equipment>(
  204. new GlobalConfiguration<CoreFilterDefinitions>(nameof(EquipmentSchedulesDashboard)),
  205. new UserConfiguration<CoreFilterDefinitions>(nameof(EquipmentSchedulesDashboard)))
  206. {
  207. Width = 25,
  208. Height = 25,
  209. Padding = new()
  210. };
  211. FilterBtn.BuiltInFilters.Add(new("Exclude Disposed", () => new Filter<Equipment>(x => x.Disposed).IsEqualTo(null)));
  212. FilterBtn.SetSettings(Properties.Filters, false);
  213. FilterBtn.OnFiltersSelected += FilterBtn_OnFiltersSelected;
  214. FilterBtn.OnFilterRefresh += FilterBtn_OnFilterRefresh;
  215. Header.BeginUpdate()
  216. .Clear()
  217. .Add(ShowSchedulesBox)
  218. .AddRight(FilterBtn)
  219. .EndUpdate();
  220. }
  221. private void ShowSchedulesBox_Checked(object sender, RoutedEventArgs e)
  222. {
  223. Properties.OnlyShowSchedules = ShowSchedulesBox.IsChecked == true;
  224. Refresh();
  225. }
  226. private void FilterBtn_OnFilterRefresh()
  227. {
  228. Refresh();
  229. }
  230. private void FilterBtn_OnFiltersSelected(DynamicGridSelectedFilterSettings filters)
  231. {
  232. Properties.Filters = filters;
  233. }
  234. #endregion
  235. private void LoadEmployeeImages()
  236. {
  237. Task.Run(() =>
  238. {
  239. var employees = new Client<Employee>()
  240. .Query(
  241. LookupFactory.DefineFilter<Employee>(),
  242. Columns.None<Employee>().Add(
  243. x => x.ID,
  244. x => x.Thumbnail.ID,
  245. x => x.Name,
  246. x => x.Mobile,
  247. x => x.Email))
  248. .ToObjects<Employee>().ToList();
  249. var documents = new Client<Document>()
  250. .Query(
  251. new Filter<Document>(x => x.ID).InList(employees.Select(x => x.Thumbnail.ID).ToArray()),
  252. Columns.None<Document>().Add(x => x.ID).Add(x => x.Data))
  253. .ToDictionary<Document, Guid, byte[]>(x => x.ID, x => x.Data);
  254. return new { Employees = employees, Documents = documents };
  255. }).ContinueWith((task) =>
  256. {
  257. EmployeeImages = task.Result.Employees.ToDictionary(
  258. x => x.ID,
  259. x =>
  260. {
  261. var document = task.Result.Documents.GetValueOrDefault(x.Thumbnail.ID);
  262. if (document is null)
  263. return null;
  264. return ImageUtils.BitmapImageFromBytes(document);
  265. });
  266. Employees = task.Result.Employees.ToDictionary(x => x.ID, x => x);
  267. lock (Models)
  268. {
  269. var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
  270. foreach (var model in Models)
  271. {
  272. foreach (var schedule in model.Schedules)
  273. {
  274. var employee = Employees?.GetValueOrDefault(schedule.EmployeeID);
  275. schedule.EmployeeImage = EmployeeImages?.GetValueOrDefault(schedule.EmployeeID) ?? anonymous;
  276. schedule.EmployeeMobile = employee?.Mobile;
  277. schedule.EmployeeName = employee?.Name;
  278. schedule.EmployeeEmail = employee?.Email;
  279. }
  280. }
  281. }
  282. }, TaskScheduler.FromCurrentSynchronizationContext());
  283. }
  284. public void Refresh()
  285. {
  286. var eqFilter = FilterBtn.GetFilter();
  287. var results = Client.QueryMultiple(
  288. new KeyedQueryDef<Equipment>(
  289. eqFilter,
  290. Columns.None<Equipment>().Add(x => x.ID)
  291. .Add(x => x.Code)
  292. .Add(x => x.Description)
  293. .Add(x => x.TrackerLink.ID)
  294. .Add(x => x.TrackerLink.Location.Latitude)
  295. .Add(x => x.TrackerLink.Location.Longitude)
  296. .Add(x => x.TrackerLink.Location.Timestamp)),
  297. new KeyedQueryDef<Schedule>(
  298. new Filter<Schedule>(x => x.DocumentClass).IsEqualTo(typeof(Equipment).EntityName())
  299. .And(x => x.IncludeInAggregate).IsEqualTo(true),
  300. Columns.None<Schedule>().Add(x => x.ID)
  301. .Add(x => x.Title)
  302. .Add(x => x.DueDate)
  303. .Add(x => x.DocumentID)
  304. .Add(x => x.EmployeeLink.ID)));
  305. var equipmentItems = results.Get<Equipment>().Rows.Select(x => x.ToObject<Equipment>());
  306. Equipment = equipmentItems.ToDictionary(x => x.ID, x => x);
  307. var schedules = results.Get<Schedule>().Rows
  308. .Select(x => x.ToObject<Schedule>())
  309. .GroupBy(x => x.DocumentID)
  310. .ToDictionary(x => x.Key, x => x.OrderBy(x => x.DueDate).ToList());
  311. var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
  312. lock (Models)
  313. {
  314. Models = new List<EquipmentScheduleViewModel>();
  315. foreach (var equipmentItem in equipmentItems)
  316. {
  317. var equipmentSchedules = schedules.GetValueOrDefault(equipmentItem.ID);
  318. if(equipmentSchedules is null && Properties.OnlyShowSchedules)
  319. {
  320. continue;
  321. }
  322. var model = new EquipmentScheduleViewModel
  323. {
  324. Code = equipmentItem.Code,
  325. Description = equipmentItem.Description,
  326. ID = equipmentItem.ID,
  327. HasLocation = equipmentItem.TrackerLink.ID != Guid.Empty
  328. };
  329. model.Schedules = (equipmentSchedules ?? Enumerable.Empty<Schedule>())
  330. .Select(x =>
  331. {
  332. var employee = Employees.GetValueOrDefault(x.EmployeeLink.ID);
  333. return new ScheduleViewModel
  334. {
  335. Title = x.Title,
  336. DueDate = x.DueDate,
  337. ID = x.ID,
  338. EmployeeImage = EmployeeImages.GetValueOrDefault(x.EmployeeLink.ID) ?? anonymous,
  339. EmployeeID = x.EmployeeLink.ID,
  340. EmployeeEmail = employee?.Email,
  341. EmployeeMobile = employee?.Mobile,
  342. EmployeeName = employee?.Name
  343. };
  344. })
  345. .ToList() ?? [];
  346. Models.Add(model);
  347. }
  348. }
  349. EquipmentList.ItemsSource = Models;
  350. }
  351. public void Shutdown(CancelEventArgs? cancel)
  352. {
  353. }
  354. private void Border_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  355. {
  356. if (sender is not FrameworkElement element || element.Tag is not Guid equipmentID) return;
  357. var menu = new ContextMenu();
  358. menu.AddItem("View Equipment", null, equipmentID, ViewEquipment_Click);
  359. menu.AddItem("Add Schedule", null, equipmentID, CreateSchedule_Click);
  360. menu.IsOpen = true;
  361. e.Handled = true;
  362. }
  363. private void CreateSchedule_Click(Guid equipmentID)
  364. {
  365. var schedule = new Schedule();
  366. schedule.DocumentClass = typeof(Equipment).EntityName();
  367. schedule.DocumentID = equipmentID;
  368. if (DynamicGridUtils.EditEntity(schedule))
  369. {
  370. Refresh();
  371. }
  372. }
  373. private void Grid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  374. {
  375. if (sender is not FrameworkElement element || element.Tag is not Guid equipmentID) return;
  376. var menu = new ContextMenu();
  377. menu.AddItem("Add Schedule", null, equipmentID, CreateSchedule_Click);
  378. menu.IsOpen = true;
  379. e.Handled = true;
  380. }
  381. private void Schedule_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  382. {
  383. if (sender is not FrameworkElement element || element.Tag is not Guid scheduleID) return;
  384. var menu = new ContextMenu();
  385. menu.AddItem("View Schedule", null, scheduleID, ViewSchedule_Click);
  386. menu.IsOpen = true;
  387. e.Handled = true;
  388. }
  389. private void Employee_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  390. {
  391. if (sender is not FrameworkElement element || element.Tag is not Guid employeeID) return;
  392. if (employeeID == Guid.Empty) return;
  393. var menu = new ContextMenu();
  394. menu.AddItem("View Employee", null, employeeID, ViewEmployee_Click);
  395. menu.IsOpen = true;
  396. e.Handled = true;
  397. }
  398. private void ViewEquipment_Click(Guid equipmentID)
  399. {
  400. if (DynamicGridUtils.EditEntity<Equipment>(equipmentID))
  401. {
  402. Refresh();
  403. }
  404. }
  405. private void ViewSchedule_Click(Guid scheduleID)
  406. {
  407. if (DynamicGridUtils.EditEntity<Schedule>(scheduleID))
  408. {
  409. Refresh();
  410. }
  411. }
  412. private void ViewEmployee_Click(Guid employeeID)
  413. {
  414. if (DynamicGridUtils.EditEntity<Employee>(employeeID))
  415. {
  416. Refresh();
  417. }
  418. }
  419. private void EquipmentLocation_Click(object sender, System.Windows.RoutedEventArgs e)
  420. {
  421. var equipmentID = (Guid)(sender as Button)!.Tag;
  422. if (!Equipment.TryGetValue(equipmentID, out var equipment))
  423. return;
  424. var form = new MapForm(
  425. equipment.TrackerLink.Location.Latitude,
  426. equipment.TrackerLink.Location.Longitude,
  427. equipment.TrackerLink.Location.Timestamp);
  428. form.ShowDialog();
  429. }
  430. }
  431. public class EquipmentSchedulesDashboardElement :
  432. DashboardElement<EquipmentSchedulesDashboard, EquipmentDashboardGroup, EquipmentSchedulesDashboardProperties> { }