EmployeeQualificationDashboard.xaml.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.DynamicGrid;
  5. using InABox.WPF;
  6. using PRSDesktop.WidgetGroups;
  7. using Syncfusion.UI.Xaml.Grid.Helpers;
  8. using Syncfusion.UI.Xaml.ScrollAxis;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Data;
  12. using System.Globalization;
  13. using System.Linq;
  14. using System.Windows;
  15. using System.Windows.Controls;
  16. using System.Windows.Data;
  17. using System.Windows.Input;
  18. using System.Windows.Media;
  19. using InABox.Configuration;
  20. using System.ComponentModel;
  21. namespace PRSDesktop
  22. {
  23. class CellBackgroundConverter : IValueConverter
  24. {
  25. private EmployeeQualificationDashboard Dashboard;
  26. public CellBackgroundConverter(EmployeeQualificationDashboard dashboard)
  27. {
  28. Dashboard = dashboard;
  29. }
  30. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  31. {
  32. if(value is DateTime date)
  33. {
  34. if (date == DateTime.MinValue || date == DateTime.MaxValue)
  35. return DependencyProperty.UnsetValue;
  36. var diff = date - DateTime.Now;
  37. if (diff <= TimeSpan.Zero)
  38. return new SolidColorBrush(Colors.Salmon);
  39. else if (diff.TotalDays < 7)
  40. return new SolidColorBrush(Colors.Orange);
  41. else if (diff.TotalDays < 30)
  42. return new SolidColorBrush(Colors.Yellow);
  43. return DependencyProperty.UnsetValue;
  44. }
  45. if(value is EmployeeQualificationDashboard.CellValue cellValue)
  46. {
  47. switch (cellValue)
  48. {
  49. case EmployeeQualificationDashboard.CellValue.Required:
  50. return Dashboard.Resources["shadedBackground"];
  51. case EmployeeQualificationDashboard.CellValue.None:
  52. return DependencyProperty.UnsetValue;
  53. }
  54. }
  55. return DependencyProperty.UnsetValue;
  56. }
  57. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  58. {
  59. throw new NotImplementedException();
  60. }
  61. }
  62. public class CellContentConverter : IValueConverter
  63. {
  64. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  65. {
  66. if (value is null || value is not DateTime date || date == DateTime.MinValue)
  67. return DependencyProperty.UnsetValue;
  68. if(date == DateTime.MaxValue)
  69. return "Perm.";
  70. return date.ToString("dd/MM/yy");
  71. }
  72. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  73. {
  74. throw new NotImplementedException();
  75. }
  76. }
  77. public class EmployeeQualificationDashboardProperties : IUserConfigurationSettings, IDashboardProperties
  78. {
  79. public Dictionary<Guid, bool>? Employees { get; set; } = null;
  80. public Dictionary<Guid, bool>? Qualifications { get; set; } = null;
  81. }
  82. public class EmployeeQualificationDashboardElement : DashboardElement<EmployeeQualificationDashboard, HumanResources, EmployeeQualificationDashboardProperties> { }
  83. /// <summary>
  84. /// Interaction logic for EmployeeQualificationDashboard.xaml
  85. /// </summary>
  86. public partial class EmployeeQualificationDashboard : UserControl, IDashboardWidget<HumanResources, EmployeeQualificationDashboardProperties>, IRequiresCanView<EmployeeQualification>, IHeaderDashboard
  87. {
  88. private DataTable DataTable;
  89. private CoreTable CoreTable;
  90. private List<Guid> AllEmployees;
  91. private Dictionary<Guid, bool> Employees;
  92. private Dictionary<Guid, bool> Qualifications;
  93. private Dictionary<Guid, string> ColumnHeaders;
  94. private List<Guid> QualificationIDs;
  95. private List<Guid> EmployeeIDs;
  96. public enum CellValue
  97. {
  98. None,
  99. Required
  100. }
  101. private Filter<Employee> EmployeeFilter { get; set; } = new Filter<Employee>().All()
  102. .And(new Filter<Employee>(x => x.StartDate).IsEqualTo(DateTime.MinValue)
  103. .Or(x => x.StartDate).IsLessThan(DateTime.Now))
  104. .And(new Filter<Employee>(x => x.FinishDate).IsEqualTo(DateTime.MinValue)
  105. .Or(x => x.FinishDate).IsGreaterThan(DateTime.Now));
  106. public EmployeeQualificationDashboardProperties Properties { get; set; }
  107. public DashboardHeader Header { get; set; } = new();
  108. public event LoadSettings<EmployeeQualificationDashboardProperties>? LoadSettings;
  109. public event SaveSettings<EmployeeQualificationDashboardProperties>? SaveSettings;
  110. public EmployeeQualificationDashboard()
  111. {
  112. InitializeComponent();
  113. }
  114. #region CorePanel Stuff
  115. public void Refresh()
  116. {
  117. //Properties.Qualifications = Qualifications;
  118. //Properties.Employees = Employees;
  119. CoreTable = new("Qualifications");
  120. ColumnHeaders = new();
  121. var employeeFilter = EmployeeFilter;
  122. var employeeIDs = Employees.Where(x => x.Value).Select(x => x.Key).ToArray();
  123. var results = Client.QueryMultiple(
  124. new KeyedQueryDef<Employee>(nameof(Employee),
  125. new Filter<Employee>(x => x.ID).InList(employeeIDs),
  126. Columns.None<Employee>().Add(x => x.ID, x => x.Name)),
  127. new KeyedQueryDef<EmployeeQualification>(nameof(EmployeeQualification),
  128. new Filter<EmployeeQualification>(x => x.Employee.ID).InList(employeeIDs),
  129. Columns.None<EmployeeQualification>().Add(
  130. x => x.Employee.ID,
  131. x => x.Qualification.ID,
  132. x => x.Expiry)),
  133. new KeyedQueryDef<Qualification>(nameof(Qualification),
  134. new Filter<Qualification>(x => x.ID).InList(Qualifications.Where(x => x.Value).Select(x => x.Key).ToArray()),
  135. Columns.None<Qualification>().Add(x => x.ID, x => x.Description)),
  136. new KeyedQueryDef<EmployeeRequiredQualification>(
  137. new Filter<EmployeeRequiredQualification>(x => x.Employee.ID).InList(employeeIDs),
  138. Columns.None<EmployeeRequiredQualification>().Add(x => x.Employee.ID, x => x.Qualification.ID)));
  139. CoreTable.Columns.Add(new CoreColumn() { ColumnName = "Employee", DataType = typeof(string) });
  140. var columns = new List<Guid>();
  141. var qualifications = results[nameof(Qualification)];
  142. foreach (var qualification in qualifications.Rows)
  143. {
  144. var qID = qualification.Get<Qualification, Guid>(x => x.ID);
  145. if (!columns.Contains(qID))
  146. {
  147. CoreTable.Columns.Add(new CoreColumn() { ColumnName = qID.ToString(), DataType = typeof(object) });
  148. columns.Add(qID);
  149. ColumnHeaders.Add(qID, qualification.Get<Qualification, string>(x => x.Description));
  150. }
  151. }
  152. QualificationIDs = columns;
  153. EmployeeIDs = new();
  154. var requiredQualifications = results.Get<EmployeeRequiredQualification>()
  155. .ToObjects<EmployeeRequiredQualification>()
  156. .GroupBy(x => x.Employee.ID, x => x.Qualification.ID)
  157. .ToDictionary(x => x.Key, x => x.ToHashSet());
  158. var employeeQualifications = results[nameof(EmployeeQualification)];
  159. foreach (var employee in results[nameof(Employee)].Rows)
  160. {
  161. var employeeID = employee.Get<Employee, Guid>(x => x.ID);
  162. EmployeeIDs.Add(employeeID);
  163. var required = requiredQualifications.GetValueOrDefault(employeeID) ?? new HashSet<Guid>();
  164. List<object?> values = new()
  165. {
  166. employee["Name"]
  167. };
  168. var thisQualifications = employeeQualifications.Rows
  169. .Where(x => x.Get<EmployeeQualification, Guid>(x => x.Employee.ID) == employeeID)
  170. .ToList();
  171. foreach (var qID in columns)
  172. {
  173. var employeeQualification = thisQualifications
  174. .Where(x => x.Get<EmployeeQualification, Guid>(x => x.Qualification.ID) == qID)
  175. .FirstOrDefault();
  176. if(employeeQualification != null)
  177. {
  178. var expiry = employeeQualification.Get<EmployeeQualification, DateTime>(x => x.Expiry);
  179. if(expiry == DateTime.MinValue || expiry == DateTime.MaxValue)
  180. {
  181. values.Add(DateTime.MaxValue);
  182. }
  183. else
  184. {
  185. values.Add(expiry);
  186. }
  187. }
  188. else if (required.Contains(qID))
  189. {
  190. values.Add(CellValue.Required);
  191. }
  192. else
  193. {
  194. values.Add(CellValue.None);
  195. }
  196. }
  197. var row = CoreTable.NewRow();
  198. row.LoadValues(values);
  199. CoreTable.Rows.Add(row);
  200. }
  201. DataTable = CoreTable.ToDataTable();
  202. DataGrid.ItemsSource = DataTable;
  203. foreach(var column in CoreTable.Columns)
  204. {
  205. if(Guid.TryParse(column.ColumnName, out var qID))
  206. {
  207. column.ColumnName = ColumnHeaders[qID];
  208. }
  209. }
  210. }
  211. public static bool IsEmployeeActive(Employee employee)
  212. => (employee.StartDate == DateTime.MinValue || employee.StartDate < DateTime.Now)
  213. && (employee.FinishDate == DateTime.MinValue || employee.FinishDate > DateTime.Now);
  214. private void LoadEmployees()
  215. {
  216. var employees = Client.Query(null, Columns.None<Employee>().Add(x => x.ID, x => x.StartDate, x => x.FinishDate))
  217. .ToObjects<Employee>()
  218. .ToDictionary(
  219. x => x.ID,
  220. IsEmployeeActive);
  221. if (Properties.Employees is not null)
  222. {
  223. employees = employees
  224. .ToDictionary(
  225. x => x.Key,
  226. x => !Properties.Employees.ContainsKey(x.Key) || Properties.Employees[x.Key]);
  227. }
  228. Employees = employees;
  229. }
  230. private void LoadQualifications()
  231. {
  232. var qualifications = Client.Query(null, Columns.None<Qualification>().Add(x => x.ID))
  233. .Rows.Select(x => x.Get<Qualification, Guid>(x => x.ID)).ToDictionary(x => x, x => true);
  234. if (Properties.Qualifications is not null)
  235. {
  236. qualifications = qualifications
  237. .ToDictionary(
  238. x => x.Key,
  239. x => !Properties.Qualifications.ContainsKey(x.Key) || Properties.Qualifications[x.Key]);
  240. }
  241. Qualifications = qualifications;
  242. }
  243. public void Setup()
  244. {
  245. var employeeFilter = EmployeeFilter;
  246. var results = Client.QueryMultiple(
  247. new KeyedQueryDef<Employee>(nameof(Employee),
  248. new Filter<Employee>().All(),
  249. Columns.None<Employee>().Add(x => x.ID, x => x.StartDate, x => x.FinishDate)),
  250. new KeyedQueryDef<Qualification>(nameof(Qualification),
  251. new Filter<Qualification>().All(),
  252. Columns.None<Qualification>().Add(x => x.ID)));
  253. var employees = results[nameof(Employee)].ToObjects<Employee>().ToDictionary(
  254. x => x.ID,
  255. IsEmployeeActive);
  256. if (Properties.Employees is not null)
  257. {
  258. employees = employees
  259. .ToDictionary(
  260. x => x.Key,
  261. x => !Properties.Employees.ContainsKey(x.Key) || Properties.Employees[x.Key]);
  262. }
  263. var qualifications = results[nameof(Qualification)].Rows.Select(x => x.Get<Qualification, Guid>(x => x.ID)).ToDictionary(x => x, x => true);
  264. if(Properties.Qualifications is not null)
  265. {
  266. qualifications = qualifications
  267. .ToDictionary(
  268. x => x.Key,
  269. x => !Properties.Qualifications.ContainsKey(x.Key) || Properties.Qualifications[x.Key]);
  270. }
  271. Employees = employees;
  272. Qualifications = qualifications;
  273. DataGrid.CellDoubleTapped += DataGrid_CellDoubleTapped;
  274. SetupHeader();
  275. }
  276. public void Shutdown(CancelEventArgs? cancel)
  277. {
  278. }
  279. #endregion
  280. #region Header
  281. public void SetupHeader()
  282. {
  283. var exportButton = new Button
  284. {
  285. Content = "Export",
  286. Padding = new Thickness(5)
  287. };
  288. exportButton.Click += (o, e) =>
  289. {
  290. DoExport();
  291. };
  292. Header.BeginUpdate()
  293. .Clear()
  294. .AddRight(exportButton);
  295. Header.EndUpdate();
  296. }
  297. private void DoExport()
  298. {
  299. var newTable = new CoreTable(CoreTable.TableName);
  300. newTable.LoadColumns(CoreTable.Columns);
  301. foreach (var row in CoreTable.Rows)
  302. {
  303. var newRow = newTable.NewRow();
  304. foreach (var value in row.Values)
  305. {
  306. object? newValue;
  307. if (value is DateTime date)
  308. {
  309. if (date == DateTime.MaxValue)
  310. newValue = "Permanent";
  311. else if (date == DateTime.MinValue)
  312. newValue = "";
  313. else
  314. newValue = date;
  315. }
  316. else if(value is CellValue cellValue)
  317. {
  318. newValue = "";
  319. }
  320. else
  321. {
  322. newValue = value;
  323. }
  324. newRow.Values.Add(newValue);
  325. }
  326. newTable.Rows.Add(newRow);
  327. }
  328. ExcelExporter.DoExport(newTable, "EmployeeQualifications");
  329. }
  330. #endregion
  331. #region Grid Events
  332. private EmployeeQualification? GetCellEmployeeQualification(int row, int column)
  333. {
  334. var employeeID = EmployeeIDs[row - 1];
  335. var qualificationID = QualificationIDs[column - 1];
  336. var qualification = new Client<EmployeeQualification>()
  337. .Query(
  338. new Filter<EmployeeQualification>(x => x.Employee.ID).IsEqualTo(employeeID)
  339. .And(x => x.Qualification.ID).IsEqualTo(qualificationID));
  340. return qualification.Rows.FirstOrDefault()?.ToObject<EmployeeQualification>();
  341. }
  342. private object? GetCellData(int row, int column)
  343. {
  344. var cell = DataGrid.GetGridCellInfo(new RowColumnIndex(row, column));
  345. if (!cell.IsDataRowCell)
  346. return null;
  347. var propertyCollection = DataGrid.View.GetPropertyAccessProvider();
  348. return propertyCollection.GetValue(cell.RowData, cell.Column.MappingName);
  349. }
  350. private string GetColumnHeader(int column)
  351. {
  352. var header = DataGrid.GetHeaderCell(DataGrid.Columns[column]);
  353. if (header is null)
  354. return "";
  355. return header.Content?.ToString() ?? "";
  356. }
  357. private string GetRowHeader(int row) => GetCellData(row, 0)?.ToString() ?? "";
  358. private void DoEditQualification(int row, int column)
  359. {
  360. var qualification = GetCellEmployeeQualification(row, column);
  361. if (qualification is null)
  362. return;
  363. var grid = DynamicGridUtils.CreateDynamicGrid(typeof(DynamicDataGrid<>), typeof(EmployeeQualification)) as DynamicDataGrid<EmployeeQualification>;
  364. if (grid!.EditItems(new[] { qualification }))
  365. {
  366. new Client<EmployeeQualification>().Save(qualification, "Edited by user from dashboard");
  367. Refresh();
  368. }
  369. }
  370. private void DataGrid_CellDoubleTapped(object? sender, Syncfusion.UI.Xaml.Grid.GridCellDoubleTappedEventArgs e)
  371. {
  372. var rowIndex = e.RowColumnIndex.RowIndex;
  373. var columnIndex = e.RowColumnIndex.ColumnIndex;
  374. DoEditQualification(rowIndex, columnIndex);
  375. }
  376. private void DataGrid_AutoGeneratingColumn(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingColumnArgs e)
  377. {
  378. if (e.Column.ValueBinding is not Binding value) return;
  379. if (Guid.TryParse(e.Column.HeaderText, out var qID))
  380. {
  381. e.Column.HeaderStyle = Resources["QualificationHeaderStyle"] as Style;
  382. e.Column.HeaderText = ColumnHeaders[qID];
  383. e.Column.Width = 55;
  384. e.Column.TextAlignment = TextAlignment.Center;
  385. e.Column.ShowHeaderToolTip = true;
  386. e.Column.ShowToolTip = true;
  387. var style = new Style();
  388. style.Setters.Add(new Setter(BackgroundProperty,
  389. new Binding(value.Path.Path) { Converter = new CellBackgroundConverter(this) }));
  390. e.Column.CellStyle = style;
  391. e.Column.DisplayBinding = new Binding
  392. { Path = new PropertyPath(e.Column.MappingName), Converter = new CellContentConverter() };
  393. }
  394. else
  395. {
  396. e.Column.HeaderStyle = Resources["EmployeeHeaderStyle"] as Style;
  397. }
  398. }
  399. private bool OpenCellTooltip(ToolTip tooltip, int row, int column)
  400. {
  401. var data = GetCellData(row, column);
  402. if(data is CellValue cellValue)
  403. {
  404. if (cellValue == CellValue.Required)
  405. {
  406. tooltip.Content = new TextBlock { Text = $"{GetRowHeader(row)} requires '{GetColumnHeader(column)}', but they do not have it." };
  407. return true;
  408. }
  409. }
  410. return false;
  411. }
  412. private void DataGrid_CellToolTipOpening(object sender, Syncfusion.UI.Xaml.Grid.GridCellToolTipOpeningEventArgs e)
  413. {
  414. var vc = DataGrid.GetVisualContainer();
  415. var p = Mouse.GetPosition(vc);
  416. var rci = vc.PointToCellRowColumnIndex(p);
  417. if (rci.RowIndex > 0 && rci.ColumnIndex > 0)
  418. {
  419. if(!OpenCellTooltip(e.ToolTip, rci.RowIndex, rci.ColumnIndex))
  420. {
  421. e.ToolTip.IsOpen = false;
  422. e.ToolTip.Visibility = Visibility.Collapsed;
  423. }
  424. else
  425. {
  426. e.ToolTip.Visibility = Visibility.Visible;
  427. }
  428. }
  429. }
  430. private void PopulateCellMenu(ContextMenu menu, int row, int column)
  431. {
  432. var data = GetCellData(row, column);
  433. if (data is null || data is not DateTime)
  434. {
  435. menu.AddItem("No Qualification", null, null, false);
  436. return;
  437. }
  438. menu.AddItem("Edit Qualification", null, new Tuple<int, int>(row, column), EditQualification_Click);
  439. }
  440. private void EditQualification_Click(Tuple<int, int> obj)
  441. {
  442. DoEditQualification(obj.Item1, obj.Item2);
  443. }
  444. private void DataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  445. {
  446. var vc = DataGrid.GetVisualContainer();
  447. var p = Mouse.GetPosition(vc);
  448. var rci = vc.PointToCellRowColumnIndex(p);
  449. var menu = DataGrid.ContextMenu;
  450. menu.Items.Clear();
  451. if(rci.RowIndex == 0)
  452. {
  453. menu.AddItem("Select Qualifications", null, SelectQualification_Click);
  454. if (Properties.Qualifications is not null)
  455. {
  456. menu.AddItem("Reset to Default", null, () =>
  457. {
  458. Properties.Qualifications = null;
  459. LoadQualifications();
  460. Refresh();
  461. });
  462. }
  463. if (rci.ColumnIndex > 0)
  464. {
  465. var column = CoreTable.Columns[rci.ColumnIndex];
  466. var hideQualification = new MenuItem() { Header = $"Hide '{column.ColumnName}'" };
  467. hideQualification.Click += (sender, e) =>
  468. {
  469. Qualifications[QualificationIDs[rci.ColumnIndex - 1]] = false;
  470. Properties.Qualifications = Qualifications;
  471. Refresh();
  472. };
  473. menu.Items.Add(hideQualification);
  474. }
  475. }
  476. if(rci.ColumnIndex == 0)
  477. {
  478. menu.AddItem("Select Employees", null, SelectEmployee_Click);
  479. if(Properties.Employees is not null)
  480. {
  481. menu.AddItem("Reset to Default", null, () =>
  482. {
  483. Properties.Employees = null;
  484. LoadEmployees();
  485. Refresh();
  486. });
  487. }
  488. if (rci.RowIndex > 0)
  489. {
  490. var row = CoreTable.Rows[rci.RowIndex - 1];
  491. var hideEmployee = new MenuItem() { Header = $"Hide '{row["Employee"]}'" };
  492. hideEmployee.Click += (sender, e) =>
  493. {
  494. Employees[EmployeeIDs[rci.RowIndex - 1]] = false;
  495. Properties.Employees = Employees;
  496. Refresh();
  497. };
  498. menu.Items.Add(hideEmployee);
  499. }
  500. }
  501. if(rci.RowIndex > 0 && rci.ColumnIndex > 0)
  502. {
  503. PopulateCellMenu(menu, rci.RowIndex, rci.ColumnIndex);
  504. }
  505. }
  506. private void SelectEmployee_Click()
  507. {
  508. var window = new EntitySelectionWindow(typeof(Employee), Employees.Where(x => x.Value).Select(x => x.Key).ToHashSet(), typeof(EmployeeSelectionGrid));
  509. window.ShowDialog();
  510. Employees = Employees.ToDictionary(
  511. x => x.Key,
  512. x => window.Entities.Contains(x.Key));
  513. Properties.Employees = Employees;
  514. Refresh();
  515. }
  516. private void SelectQualification_Click()
  517. {
  518. var window = new EntitySelectionWindow(typeof(Qualification), Qualifications.Where(x => x.Value).Select(x => x.Key).ToHashSet());
  519. window.ShowDialog();
  520. Qualifications = Qualifications.ToDictionary(
  521. x => x.Key,
  522. x => window.Entities.Contains(x.Key));
  523. Properties.Qualifications = Qualifications;
  524. Refresh();
  525. }
  526. #endregion
  527. }
  528. class EmployeeSelectionGrid : EntitySelectionGrid<Employee>
  529. {
  530. public override DynamicGridColumns GenerateColumns()
  531. {
  532. var columns = new DynamicGridColumns();
  533. columns.Add<Employee>(x => x.Name, 0, "Name", "", Alignment.MiddleLeft);
  534. return columns;
  535. }
  536. }
  537. }