EmployeeQualificationDashboard.xaml.cs 21 KB

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