TasksByStatusControl.xaml.cs 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.WPF;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Globalization;
  8. using System.Linq;
  9. using System.Linq.Expressions;
  10. using System.Reflection;
  11. using System.Text;
  12. using System.Threading.Tasks;
  13. using System.Windows;
  14. using System.Windows.Controls;
  15. using System.Windows.Data;
  16. using System.Windows.Documents;
  17. using System.Windows.Input;
  18. using System.Drawing;
  19. using System.Windows.Media;
  20. using System.Windows.Media.Imaging;
  21. using Color = System.Drawing.Color;
  22. using InABox.DynamicGrid;
  23. using InABox.Wpf;
  24. using MailKit.Search;
  25. using NPOI.SS.Formula.Functions;
  26. using System.ComponentModel;
  27. using System.Runtime.CompilerServices;
  28. using System.Collections.ObjectModel;
  29. using System.Collections.Specialized;
  30. using Syncfusion.UI.Xaml.Kanban;
  31. namespace PRSDesktop;
  32. public class TasksByStatusColumn : INotifyPropertyChanged
  33. {
  34. public KanbanStatus Status { get; }
  35. public string Title { get; }
  36. public int NumTasks { get => Tasks.Count; }
  37. public double NumHours { get => Tasks.Sum(x => x.EstimatedTime.TotalHours); }
  38. public ObservableCollection<TaskModel> Tasks { get; } = new();
  39. public TasksByStatusColumn(KanbanStatus status, string title)
  40. {
  41. Status = status;
  42. Title = title;
  43. Tasks.CollectionChanged += Tasks_CollectionChanged;
  44. }
  45. private void Tasks_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  46. {
  47. OnPropertyChanged(nameof(Tasks));
  48. }
  49. public event PropertyChangedEventHandler? PropertyChanged;
  50. // Create the OnPropertyChanged method to raise the event
  51. // The calling member's name will be used as the parameter.
  52. protected void OnPropertyChanged([CallerMemberName] string? name = null)
  53. {
  54. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  55. if(name == nameof(Tasks))
  56. {
  57. OnPropertyChanged(nameof(NumTasks));
  58. OnPropertyChanged(nameof(NumHours));
  59. }
  60. }
  61. }
  62. public class EmployeeModel
  63. {
  64. public Guid ID { get; set; }
  65. public string Name { get; set; }
  66. public BitmapImage? Image { get; set; }
  67. public Guid ThumbnailID { get; set; }
  68. public EmployeeModel(Guid id, string name, Guid thumbnail, BitmapImage? image)
  69. {
  70. ID = id;
  71. Name = name;
  72. Image = image;
  73. ThumbnailID = thumbnail;
  74. }
  75. }
  76. public class SuspendableObservableCollection<T> : ObservableCollection<T>
  77. {
  78. private bool _notificationSupressed = false;
  79. private bool _supressNotification = false;
  80. public bool SupressNotification
  81. {
  82. get
  83. {
  84. return _supressNotification;
  85. }
  86. set
  87. {
  88. _supressNotification = value;
  89. if (_supressNotification == false && _notificationSupressed)
  90. {
  91. this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  92. _notificationSupressed = false;
  93. }
  94. }
  95. }
  96. protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
  97. {
  98. if (SupressNotification)
  99. {
  100. _notificationSupressed = true;
  101. return;
  102. }
  103. base.OnCollectionChanged(e);
  104. }
  105. }
  106. /// <summary>
  107. /// Interaction logic for TasksByStatusControl.xaml
  108. /// </summary>
  109. public partial class TasksByStatusControl : UserControl, ITaskControl, INotifyPropertyChanged
  110. {
  111. private enum Suppress
  112. {
  113. This
  114. }
  115. public SuspendableObservableCollection<TasksByStatusColumn> Columns { get; private set; } = new();
  116. private List<EmployeeModel> Employees = new();
  117. public TasksByStatusControl()
  118. {
  119. InitializeComponent();
  120. }
  121. #region INotifyPropertyChanged
  122. public event PropertyChangedEventHandler? PropertyChanged;
  123. // Create the OnPropertyChanged method to raise the event
  124. // The calling member's name will be used as the parameter.
  125. protected void OnPropertyChanged([CallerMemberName] string? name = null)
  126. {
  127. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  128. }
  129. #endregion
  130. #region Setup
  131. public void Setup()
  132. {
  133. var employeesTask = Task.Run(() => LoadEmployees());
  134. var kanbanTypesTask = Task.Run(() => LoadKanbanTypes());
  135. SetupToolbar();
  136. SetupColumns();
  137. employeesTask.Wait();
  138. SetupEmployeeList();
  139. kanbanTypesTask.Wait();
  140. SetupKanbanTypesLookup(kanbanTypesTask.Result);
  141. Mode = Host.KanbanSettings.StatusSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
  142. FilterButton.SetSettings(Host.KanbanSettings.Filters, false);
  143. }
  144. private void SetupToolbar()
  145. {
  146. IncludeCompleted.Visibility = Security.IsAllowed<CanHideTaskCompletedColumn>() ? Visibility.Visible : Visibility.Collapsed;
  147. IncludeCompleted.IsChecked = IncludeCompleted.Visibility == Visibility.Visible ? Host.KanbanSettings.StatusSettings.IncludeCompleted : true;
  148. IncludeObserved.IsChecked = Host.KanbanSettings.StatusSettings.IncludeObserved;
  149. ViewType.SelectedIndex = Host.KanbanSettings.StatusSettings.CompactView ? 1 : 0;
  150. }
  151. #endregion
  152. #region Columns
  153. private void SetupColumns()
  154. {
  155. Columns.SupressNotification = true;
  156. Columns.Clear();
  157. Columns.Add(new TasksByStatusColumn(KanbanStatus.Open, "To Do"));
  158. Columns.Add(new TasksByStatusColumn(KanbanStatus.InProgress, "In Progress"));
  159. Columns.Add(new TasksByStatusColumn(KanbanStatus.Waiting, "Waiting for Others"));
  160. if (Host.KanbanSettings.StatusSettings.IncludeCompleted)
  161. Columns.Add(new TasksByStatusColumn(KanbanStatus.Complete, "Completed"));
  162. Columns.SupressNotification = false;
  163. }
  164. private TasksByStatusColumn? GetColumn(KanbanStatus category)
  165. {
  166. return Columns.FirstOrDefault(x => x.Status.Equals(category));
  167. }
  168. private void Column_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  169. {
  170. if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return;
  171. var menu = element.ContextMenu;
  172. menu.Items.Clear();
  173. menu.AddItem("New Task", null, CreateTask);
  174. menu.AddItem($"Select all {column.Title} tasks", null, column, SelectAll);
  175. menu.AddItem($"Deselect all {column.Title} tasks", null, column, DeselectAll);
  176. }
  177. private void SelectColumn(TasksByStatusColumn column, bool selected)
  178. {
  179. SelectedTasks.Clear();
  180. if (selected)
  181. {
  182. SelectedTasks.AddRange(column.Tasks);
  183. foreach (var task in Tasks)
  184. task.Checked = task.Status == column.Status ? selected : !selected;
  185. }
  186. FilterKanbans();
  187. }
  188. private void DeselectAll(TasksByStatusColumn column)
  189. {
  190. SelectColumn(column, false);
  191. }
  192. private void SelectAll(TasksByStatusColumn column)
  193. {
  194. SelectColumn(column, true);
  195. }
  196. #endregion
  197. #region Kanban Types
  198. private IEnumerable<KanbanType> LoadKanbanTypes()
  199. {
  200. return Client.Query(
  201. Filter<KanbanType>.Where(x => x.Hidden).IsEqualTo(false),
  202. InABox.Core.Columns.None<KanbanType>().Add(x => x.ID, x => x.Description),
  203. new SortOrder<KanbanType>(x => x.Description))
  204. .ToObjects<KanbanType>();
  205. }
  206. private void SetupKanbanTypesLookup(IEnumerable<KanbanType> kanbanTypes)
  207. {
  208. var tasktypes = new Dictionary<Guid, string>
  209. {
  210. { CoreUtils.FullGuid, "All Types" },
  211. { Guid.Empty, "Unallocated Types" }
  212. };
  213. foreach(var type in kanbanTypes)
  214. {
  215. tasktypes.Add(type.ID, type.Description);
  216. }
  217. TaskTypes.ItemsSource = tasktypes;
  218. if (tasktypes.ContainsKey(Host.KanbanSettings.StatusSettings.SelectedType))
  219. TaskTypes.SelectedValue = Host.KanbanSettings.StatusSettings.SelectedType;
  220. else
  221. TaskTypes.SelectedValue = CoreUtils.FullGuid;
  222. TaskTypesLabel.Visibility = Visibility.Visible;
  223. TaskTypes.Visibility = Visibility.Visible;
  224. }
  225. #endregion
  226. #region Employees
  227. private EmployeeModel SelectedEmployee;
  228. private void LoadEmployees()
  229. {
  230. Employees.Clear();
  231. var employeeFilter = Security.IsAllowed<CanViewOthersTasks>()
  232. ? Filter<Employee>.Where(x => x.CanAllocateTasks).IsEqualTo(true)
  233. .And(Filter<Employee>.Where(x => x.FinishDate).IsEqualTo(DateTime.MinValue)
  234. .Or(x => x.FinishDate).IsGreaterThan(DateTime.Today))
  235. : Filter<Employee>.Where(x => x.ID).IsEqualTo(App.EmployeeID);
  236. var employees = Client.Query<Employee>(
  237. employeeFilter,
  238. InABox.Core.Columns.None<Employee>().Add(x => x.ID)
  239. .Add(x => x.Thumbnail.ID)
  240. .Add(x => x.Name));
  241. var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
  242. anonymous.Freeze();
  243. if (Security.IsAllowed<CanViewOthersTasks>())
  244. {
  245. var everyone = PRSDesktop.Resources.everyone.AsBitmapImage();
  246. everyone.Freeze();
  247. Employees.Add(new EmployeeModel(CoreUtils.FullGuid, "All Staff", Guid.Empty, everyone));
  248. Employees.Add(new EmployeeModel(Guid.Empty, "Unallocated", Guid.Empty, null));
  249. }
  250. foreach (var employee in employees.ToObjects<Employee>())
  251. {
  252. var name = employee.ID == App.EmployeeID ? "My Tasks" : employee.Name;
  253. var model = new EmployeeModel(employee.ID, name, employee.Thumbnail.ID, anonymous);
  254. if (employee.ID == App.EmployeeID)
  255. {
  256. Employees.Insert(0, model);
  257. }
  258. else
  259. {
  260. Employees.Add(model);
  261. }
  262. }
  263. }
  264. private void SetupEmployeeList()
  265. {
  266. if (Security.IsAllowed<CanViewOthersTasks>())
  267. {
  268. EmployeeListColumn.Width = new GridLength(1.0F, GridUnitType.Auto);
  269. var thumbnails = Employees
  270. .Select(e => e.ThumbnailID)
  271. .Where(x => x != Guid.Empty).ToArray();
  272. EmployeeList.ItemsSource = Employees;
  273. SelectedEmployee = Employees.FirstOrDefault(x => x.ID == Host.KanbanSettings.StatusSettings.SelectedEmployee) ?? Employees.First();
  274. EmployeeList.SelectedItem = SelectedEmployee;
  275. if (thumbnails.Any())
  276. new Client<Document>().Query(
  277. Filter<Document>.Where(x => x.ID).InList(thumbnails),
  278. InABox.Core.Columns.None<Document>().Add(x => x.ID, x => x.Data),
  279. null,
  280. CoreRange.All,
  281. (data, error) =>
  282. {
  283. if (data != null)
  284. ProcessThumbnails(data);
  285. }
  286. );
  287. }
  288. else
  289. {
  290. EmployeeListColumn.Width = new GridLength(0.0F, GridUnitType.Pixel);
  291. EmployeeList.ItemsSource = Employees;
  292. SelectedEmployee = Employees.FirstOrDefault(x => x.ID == Host.KanbanSettings.StatusSettings.SelectedEmployee) ?? Employees.First();
  293. EmployeeList.SelectedItem = SelectedEmployee;
  294. }
  295. }
  296. private bool _updatingEmployees = false;
  297. private void ProcessThumbnails(CoreTable data)
  298. {
  299. Dispatcher.Invoke(() =>
  300. {
  301. foreach (var row in data.Rows)
  302. {
  303. var id = row.Get<Document, Guid>(x => x.ID);
  304. var model = Employees.FirstOrDefault(x => x.ThumbnailID.Equals(id));
  305. if (model != null)
  306. {
  307. model.Image = ImageUtils.LoadImage(row.Get<Document, byte[]>(x => x.Data));
  308. }
  309. }
  310. _updatingEmployees = true;
  311. EmployeeList.ItemsSource = null;
  312. EmployeeList.ItemsSource = Employees;
  313. SelectedEmployee = Employees.FirstOrDefault(x => x.ID == Host.KanbanSettings.StatusSettings.SelectedEmployee) ?? Employees.First();
  314. EmployeeList.SelectedItem = SelectedEmployee;
  315. _updatingEmployees = false;
  316. });
  317. }
  318. private void Employees_SelectionChanged(object sender, SelectionChangedEventArgs e)
  319. {
  320. if (_updatingEmployees || EventSuppressor.IsSet(Suppress.This))
  321. return;
  322. SelectedEmployee = (EmployeeList.SelectedItem as EmployeeModel)!;
  323. Host.KanbanSettings.StatusSettings.SelectedEmployee = SelectedEmployee.ID;
  324. Host.SaveSettings();
  325. SelectedTasks.Clear();
  326. if (IsReady)
  327. Refresh();
  328. }
  329. #endregion
  330. #region Kanbans
  331. private List<TaskModel> AllTasks { get; set; } = new();
  332. private IEnumerable<TaskModel> Tasks => Columns.SelectMany(x => x.Tasks);
  333. private readonly List<TaskModel> SelectedTasks = new();
  334. private void CreateTask()
  335. {
  336. var result = Host.CreateKanban(
  337. kanban =>
  338. {
  339. kanban.EmployeeLink.ID = SelectedEmployee.ID != CoreUtils.FullGuid ? SelectedEmployee.ID : App.EmployeeID;
  340. kanban.ManagerLink.ID = App.EmployeeID;
  341. kanban.ManagerLink.UserLink.ID = ClientFactory.UserGuid;
  342. });
  343. if (result != null)
  344. Refresh();
  345. }
  346. private void DoEdit(TaskModel task)
  347. {
  348. var result = Host.EditReferences(new[] { task });
  349. if (result)
  350. {
  351. Refresh();
  352. }
  353. }
  354. private void EditTask_Executed(object sender, ExecutedRoutedEventArgs e)
  355. {
  356. if (e.Parameter is not TaskModel model) return;
  357. DoEdit(model);
  358. }
  359. private void OpenTaskMenu_Executed(object sender, ExecutedRoutedEventArgs e)
  360. {
  361. if (e.Parameter is not KanbanResources.OpenTaskMenuCommandArgs args) return;
  362. Host.PopulateMenu(this, args.Model, args.Menu);
  363. }
  364. private void SelectTask_Executed(object sender, ExecutedRoutedEventArgs e)
  365. {
  366. if (e.Parameter is not TaskModel model) return;
  367. if (!SelectedTasks.Remove(model))
  368. {
  369. SelectedTasks.Add(model);
  370. }
  371. }
  372. private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
  373. {
  374. e.CanExecute = true;
  375. }
  376. private void ItemsControl_DragOver(object sender, DragEventArgs e)
  377. {
  378. if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return;
  379. e.Effects = DragDropEffects.None;
  380. if (e.Data.GetDataPresent(typeof(TaskModel)))
  381. {
  382. var model = e.Data.GetData(typeof(TaskModel)) as TaskModel;
  383. if(model is not null && model.Status != column.Status && !SelectedTasks.Any(x => x.Locked))
  384. {
  385. e.Effects = DragDropEffects.Move;
  386. }
  387. }
  388. }
  389. private void ChangeStatus(IEnumerable<TaskModel> tasks, KanbanStatus status)
  390. {
  391. var models = tasks
  392. .Where(x => !x.Status.Equals(status))
  393. .Where(x => !x.Locked)
  394. .ToList();
  395. if (!models.Any())
  396. {
  397. return;
  398. }
  399. if(status == KanbanStatus.Complete)
  400. {
  401. if (!MessageWindow.ShowYesNo($"Are you sure you want to complete the selected tasks?", "Confirm Completion"))
  402. return;
  403. }
  404. var completed = DateTime.Now;
  405. var kanbans = Host.LoadKanbans(tasks, InABox.Core.Columns.Required<Kanban>().Add(x => x.ID).Add(x => x.Status));
  406. foreach (var kanban in kanbans)
  407. {
  408. kanban.Status = status;
  409. }
  410. if (status == KanbanStatus.Complete)
  411. {
  412. foreach (var kanban in kanbans)
  413. {
  414. kanban.Completed = completed;
  415. }
  416. }
  417. Client.Save(kanbans, $"Task Status Updated to {status}", (o, err) =>
  418. {
  419. if (err is not null)
  420. {
  421. CoreUtils.LogException("", err);
  422. }
  423. });
  424. foreach (var model in models)
  425. {
  426. model.Checked = false;
  427. model.Status = status;
  428. }
  429. if (status == KanbanStatus.Complete)
  430. {
  431. foreach (var model in models)
  432. {
  433. model.CompletedDate = completed;
  434. }
  435. }
  436. FilterKanbans();
  437. }
  438. private void ItemsControl_Drop(object sender, DragEventArgs e)
  439. {
  440. if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return;
  441. if (e.Data.GetDataPresent(typeof(TaskModel)))
  442. {
  443. ChangeStatus(SelectedModels(e.Data.GetData(typeof(TaskModel)) as TaskModel), column.Status);
  444. }
  445. }
  446. #endregion
  447. #region Filters
  448. private Guid JobFilterID = Guid.Empty;
  449. private string SearchText = "";
  450. private Guid SelectedType = CoreUtils.FullGuid;
  451. private void FilterButton_OnFilterRefresh()
  452. {
  453. Refresh();
  454. }
  455. private void FilterButton_OnFiltersSelected(DynamicGridSelectedFilterSettings filters)
  456. {
  457. Host.KanbanSettings.Filters = filters;
  458. Host.SaveSettings();
  459. }
  460. private Filter<KanbanSubscriber> GetKanbanSubscriberFilter()
  461. {
  462. var filter = Filter<KanbanSubscriber>.Where(x => x.Kanban.Closed).IsEqualTo(DateTime.MinValue);
  463. if (Host.Master != null)
  464. filter = filter.And(x => x.Kanban.JobLink.ID).IsEqualTo(Host.Master.ID);
  465. // All Tasks (EmployeeID.HasValue == false) or Unallocated (EmployeeID = Guid.Empty) are retrieved directly from the Kanban Table
  466. // so if we are here, we can assume that we are pulling subscriber data
  467. var empfilter = Filter<KanbanSubscriber>.Where(x => x.Employee.ID).IsEqualTo(SelectedEmployee.ID);
  468. filter.Ands.Add(empfilter);
  469. if (SelectedEmployee.ID != App.EmployeeID)
  470. filter = filter.And(x => x.Kanban.Private).IsEqualTo(false);
  471. return filter;
  472. }
  473. private Filter<Kanban>? GetKanbanFilter()
  474. {
  475. var filters = new Filters<Kanban>();
  476. filters.Add(Filter<Kanban>.Where(x => x.Closed).IsEqualTo(DateTime.MinValue));
  477. filters.Add(FilterButton.GetFilter());
  478. if (Host.Master != null)
  479. filters.Add(Filter<Kanban>.Where(x => x.JobLink.ID).IsEqualTo(Host.Master.ID));
  480. if (SelectedEmployee.ID != CoreUtils.FullGuid)
  481. {
  482. if (SelectedEmployee.ID != Guid.Empty)
  483. {
  484. var empfilter = Filter<Kanban>.Where(x => x.EmployeeLink.ID).IsEqualTo(SelectedEmployee.ID)
  485. .Or(x => x.ManagerLink.ID).IsEqualTo(SelectedEmployee.ID);
  486. filters.Add(empfilter);
  487. }
  488. else
  489. {
  490. filters.Add(Filter<Kanban>.Where(x => x.EmployeeLink.ID).IsEqualTo(SelectedEmployee.ID));
  491. }
  492. }
  493. if (SelectedEmployee.ID != App.EmployeeID)
  494. filters.Add(Filter<Kanban>.Where(x => x.Private).IsEqualTo(false));
  495. return filters.Combine();
  496. }
  497. private void TaskTypesLabel_OnClick(object sender, RoutedEventArgs e)
  498. {
  499. var list = new MasterList(typeof(KanbanType));
  500. list.ShowDialog();
  501. SetupKanbanTypesLookup(LoadKanbanTypes());
  502. }
  503. private void TaskTypes_SelectionChanged(object sender, SelectionChangedEventArgs e)
  504. {
  505. if (!IsReady || EventSuppressor.IsSet(Suppress.This))
  506. return;
  507. if (e.AddedItems.Count > 0)
  508. {
  509. var item = (KeyValuePair<Guid, string>)e.AddedItems[0];
  510. SelectedType = item.Key;
  511. }
  512. else
  513. {
  514. SelectedType = CoreUtils.FullGuid;
  515. }
  516. Host.KanbanSettings.StatusSettings.SelectedType = SelectedType;
  517. Host.SaveSettings();
  518. FilterKanbans();
  519. }
  520. private void JobFilterBtn_OnClick(object sender, RoutedEventArgs e)
  521. {
  522. if (JobFilterID != Guid.Empty)
  523. {
  524. JobFilterBtn.Content = "Filter Job";
  525. JobFilterID = Guid.Empty;
  526. FilterKanbans();
  527. return;
  528. }
  529. var window = new ThemableWindow();
  530. var grid = new ProjectsGrid();
  531. grid.Reconfigure(options =>
  532. {
  533. options.EditRows = false;
  534. options.DeleteRows = false;
  535. options.AddRows = false;
  536. options.MultiSelect = false;
  537. options.ExportData = false;
  538. options.ImportData = false;
  539. });
  540. grid.OnSelectItem += (object sender, DynamicGridSelectionEventArgs e) =>
  541. {
  542. if (grid.SelectedRows.Count() == 0)
  543. return;
  544. else
  545. {
  546. var item = grid.SelectedRows[0];
  547. AddJobFilter(item);
  548. window.Close();
  549. }
  550. };
  551. grid.Refresh(true, true);
  552. window.Content = grid;
  553. window.ShowDialog();
  554. }
  555. private void AddJobFilter(CoreRow item)
  556. {
  557. JobFilterID = item.Get<Job, Guid>(x => x.ID);
  558. JobFilterBtn.Content = item.Get<Job, string>(x => x.JobNumber) + " (click to cancel)";
  559. FilterKanbans();
  560. }
  561. private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
  562. {
  563. if (!IsReady)
  564. return;
  565. Host.KanbanSettings.StatusSettings.IncludeCompleted = IncludeCompleted.IsChecked ?? false;
  566. Host.SaveSettings();
  567. SetupColumns();
  568. FilterKanbans();
  569. }
  570. private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
  571. {
  572. if (!IsReady)
  573. return;
  574. Host.KanbanSettings.StatusSettings.IncludeObserved = IncludeObserved.IsChecked ?? false;
  575. Host.SaveSettings();
  576. FilterKanbans();
  577. }
  578. private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
  579. {
  580. if (!IsReady)
  581. return;
  582. Host.KanbanSettings.StatusSettings.IncludeLocked = IncludeLocked.IsChecked ?? false;
  583. Host.SaveSettings();
  584. FilterKanbans();
  585. }
  586. private void Search_KeyUp(object sender, KeyEventArgs e)
  587. {
  588. if (string.IsNullOrWhiteSpace(Search.Text) || e.Key == Key.Return)
  589. {
  590. SearchText = Search.Text;
  591. FilterKanbans();
  592. }
  593. }
  594. #endregion
  595. #region Refresh
  596. private static Columns<Kanban> GetKanbanColumns()
  597. {
  598. return InABox.Core.Columns.None<Kanban>().Add(
  599. x => x.ID,
  600. x => x.DueDate,
  601. x => x.Completed,
  602. x => x.Summary,
  603. x => x.Status,
  604. x => x.EmployeeLink.ID,
  605. x => x.ManagerLink.ID,
  606. x => x.Notes,
  607. x => x.Title,
  608. x => x.JobLink.ID,
  609. x => x.JobLink.JobNumber,
  610. x => x.JobLink.Name,
  611. x => x.Type.ID,
  612. x => x.Type.Code,
  613. x => x.Number,
  614. x => x.Attachments,
  615. x => x.EstimatedTime,
  616. x => x.Locked);
  617. }
  618. public void Refresh()
  619. {
  620. using var cursor = new WaitCursor();
  621. IEnumerable<Kanban> kanbans;
  622. if (SelectedEmployee.ID != CoreUtils.FullGuid && SelectedEmployee.ID != Guid.Empty)
  623. {
  624. var filter = Filter<Kanban>.Where(x => x.ID).InQuery(GetKanbanSubscriberFilter(), x => x.Kanban.ID);
  625. filter.And(FilterButton.GetFilter() ?? new Filter<Kanban>().All());
  626. kanbans = Client.Query(
  627. filter,
  628. GetKanbanColumns().Cast<Kanban>(),
  629. new SortOrder<Kanban>(x => x.DueDate) { Direction = SortDirection.Ascending }
  630. ).ToObjects<Kanban>();
  631. }
  632. else
  633. {
  634. kanbans = new Client<Kanban>().Query(
  635. GetKanbanFilter(),
  636. GetKanbanColumns().Cast<Kanban>(),
  637. new SortOrder<Kanban>(x => x.DueDate) { Direction = SortDirection.Ascending }
  638. ).ToObjects<Kanban>();
  639. }
  640. AllTasks = CreateModels(kanbans).ToList();
  641. FilterKanbans();
  642. }
  643. private IEnumerable<TaskModel> CreateModels(IEnumerable<Kanban> kanbans)
  644. {
  645. foreach(var kanban in kanbans)
  646. {
  647. TaskModel? model = null;
  648. try
  649. {
  650. model = new TaskModel();
  651. var employee = Employees.FirstOrDefault(x => x.ID == kanban.EmployeeLink.ID);
  652. var manager = Employees.FirstOrDefault(x => x.ID == kanban.ManagerLink.ID);
  653. model.Title = kanban.Title;
  654. model.ID = kanban.ID;
  655. model.Description = kanban.Summary ?? "";
  656. model.Status = kanban.Status;
  657. var colour = SelectedEmployee.ID == Guid.Empty
  658. || SelectedEmployee.ID == CoreUtils.FullGuid
  659. || kanban.EmployeeLink.ID == SelectedEmployee.ID
  660. ? TaskModel.KanbanColor(kanban.DueDate, kanban.Completed)
  661. : (kanban.ManagerLink.ID == SelectedEmployee.ID
  662. ? Color.Silver
  663. : Color.Plum);
  664. if (kanban.Locked)
  665. {
  666. colour = colour.MixColors(0.5F, Color.White);
  667. }
  668. model.Color = System.Windows.Media.Color.FromArgb(colour.A, colour.R, colour.G, colour.B);
  669. model.Image = employee?.Image;
  670. model.Attachments = kanban.Attachments > 0;
  671. model.DueDate = kanban.DueDate;
  672. model.CompletedDate = kanban.Completed;
  673. model.Locked = kanban.Locked;
  674. model.EstimatedTime = kanban.EstimatedTime;
  675. if (kanban.Notes is not null)
  676. {
  677. model.Notes = string.Join(
  678. "\n===================================\n",
  679. kanban.Notes.Reverse());
  680. }
  681. model.EmployeeID = kanban.EmployeeLink.ID;
  682. model.ManagerID = kanban.ManagerLink.ID;
  683. var employeeString = "";
  684. if (kanban.EmployeeLink.ID != SelectedEmployee.ID)
  685. {
  686. if (kanban.EmployeeLink.ID == Guid.Empty)
  687. {
  688. employeeString = "Unallocated";
  689. }
  690. else
  691. {
  692. employeeString = employee is not null
  693. ? (employee.ID == App.EmployeeID
  694. ? App.EmployeeName
  695. : employee.Name)
  696. : "";
  697. }
  698. }
  699. var managerString = "";
  700. if (kanban.ManagerLink.ID != SelectedEmployee.ID)
  701. {
  702. if (kanban.ManagerLink.ID != Guid.Empty)
  703. {
  704. managerString = manager is not null
  705. ? (manager.ID == App.EmployeeID
  706. ? App.EmployeeName
  707. : manager.Name)
  708. : "";
  709. }
  710. }
  711. model.Manager = managerString;
  712. if (!string.IsNullOrEmpty(employeeString))
  713. {
  714. if (!managerString.IsNullOrWhiteSpace() && !managerString.Equals(employeeString))
  715. {
  716. model.AssignedTo = $"Assigned to {employeeString} by {managerString}";
  717. }
  718. else
  719. {
  720. model.AssignedTo = $"Assigned to {employeeString}";
  721. }
  722. }
  723. else
  724. {
  725. if (!managerString.IsNullOrWhiteSpace())
  726. {
  727. model.AssignedTo = $"Allocated by {managerString}";
  728. }
  729. else
  730. {
  731. model.AssignedTo = "";
  732. }
  733. }
  734. model.JobID = kanban.JobLink.ID;
  735. model.JobNumber = kanban.JobLink.JobNumber.NotWhiteSpaceOr("");
  736. model.JobName = kanban.JobLink.Name;
  737. model.Checked = SelectedTasks.Any(x => x.ID == model.ID);
  738. model.Type = new KanbanType
  739. {
  740. ID = kanban.Type.ID,
  741. Code = kanban.Type.Code
  742. };
  743. model.Number = kanban.Number;
  744. }
  745. catch(Exception e)
  746. {
  747. CoreUtils.LogException("", e);
  748. }
  749. if(model is not null)
  750. {
  751. yield return model;
  752. }
  753. }
  754. }
  755. /// <summary>
  756. /// Take the full list of kanbans loaded from the database, and filter based on the search UI elements, filtering into the columns.
  757. /// </summary>
  758. private void FilterKanbans()
  759. {
  760. IEnumerable<TaskModel> filtered;
  761. if (JobFilterID == Guid.Empty)
  762. {
  763. filtered = AllTasks;
  764. }
  765. else
  766. {
  767. filtered = AllTasks.Where(x => x.JobSearch(JobFilterID));
  768. }
  769. if (!Host.KanbanSettings.StatusSettings.IncludeLocked)
  770. {
  771. filtered = filtered.Where(x => !x.Locked);
  772. }
  773. if (!Host.KanbanSettings.StatusSettings.IncludeObserved
  774. && SelectedEmployee.ID != CoreUtils.FullGuid)
  775. {
  776. filtered = filtered.Where(x => x.EmployeeID == SelectedEmployee.ID || x.ManagerID == SelectedEmployee.ID);
  777. }
  778. if (!Host.KanbanSettings.StatusSettings.IncludeCompleted)
  779. {
  780. filtered = filtered.Where(x => x.CompletedDate.IsEmpty());
  781. }
  782. if (SelectedType != CoreUtils.FullGuid)
  783. {
  784. filtered = filtered.Where(x => x.Type.ID == SelectedType);
  785. }
  786. filtered = filtered.Where(x => x.Search(SearchText.Split()))
  787. .OrderBy(x => x.EmployeeID == SelectedEmployee.ID ? 0 : 1)
  788. .ThenBy(x => x.DueDate);
  789. SelectedTasks.Clear();
  790. foreach (var column in Columns)
  791. {
  792. column.Tasks.Clear();
  793. }
  794. foreach (var task in filtered)
  795. {
  796. var column = GetColumn(task.Status);
  797. column?.Tasks.Add(task);
  798. if (task.Checked)
  799. {
  800. SelectedTasks.Add(task);
  801. }
  802. }
  803. }
  804. #endregion
  805. #region ITaskControl
  806. public ITaskHost Host { get; set; }
  807. public KanbanViewType KanbanViewType => KanbanViewType.Status;
  808. public bool IsReady { get; set; }
  809. public string SectionName => "Task List";
  810. public DataModel DataModel(Selection selection)
  811. {
  812. var ids = SelectedModels().Select(x => x.ID).ToArray();
  813. return new AutoDataModel<Kanban>(Filter<Kanban>.Where(x => x.ID).InList(ids));
  814. }
  815. public IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null)
  816. {
  817. if(sender is null)
  818. {
  819. return SelectedTasks;
  820. }
  821. else
  822. {
  823. var result = SelectedTasks.ToList();
  824. if (!result.Contains(sender))
  825. {
  826. result.Add(sender);
  827. }
  828. return result;
  829. }
  830. }
  831. #endregion
  832. private void Export_Click(object sender, RoutedEventArgs e)
  833. {
  834. var form = new DynamicExportForm(typeof(Kanban), GetKanbanColumns().ColumnNames());
  835. if (form.ShowDialog() != true)
  836. return;
  837. var export = new Client<Kanban>().Query(
  838. GetKanbanFilter(),
  839. InABox.Core.Columns.None<Kanban>().Add(form.Fields),
  840. LookupFactory.DefineSort<Kanban>()
  841. );
  842. var employee = "Tasks for All Staff";
  843. if (SelectedEmployee.ID != CoreUtils.FullGuid)
  844. {
  845. if (SelectedEmployee.ID == Guid.Empty)
  846. {
  847. employee = "Unallocated Tasks";
  848. }
  849. else
  850. {
  851. var model = Employees.FirstOrDefault(x => x.ID == SelectedEmployee.ID);
  852. employee = model == null ? "Tasks for (Unknown)" : "Tasks for " + (model.ID == App.EmployeeID ? App.EmployeeName : model.Name);
  853. }
  854. }
  855. ExcelExporter.DoExport<Kanban>(
  856. export,
  857. string.Format(
  858. "{0} ({1:dd-MMM-yy})",
  859. employee,
  860. DateTime.Today
  861. )
  862. );
  863. }
  864. private KanbanViewMode _mode;
  865. public KanbanViewMode Mode
  866. {
  867. get => _mode;
  868. set
  869. {
  870. _mode = value;
  871. OnPropertyChanged();
  872. }
  873. }
  874. private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
  875. {
  876. if (!IsReady || EventSuppressor.IsSet(Suppress.This))
  877. return;
  878. Mode = ViewType.SelectedIndex switch
  879. {
  880. 0 => KanbanViewMode.Full,
  881. 1 => KanbanViewMode.Compact,
  882. _ => KanbanViewMode.Full
  883. };
  884. Host.KanbanSettings.StatusSettings.CompactView = Mode == KanbanViewMode.Compact;
  885. Host.SaveSettings();
  886. }
  887. }