EmployeeQualificationDashboard.xaml.cs 21 KB

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