TasksByUserControl.xaml.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Runtime.CompilerServices;
  9. using System.Windows;
  10. using System.Windows.Controls;
  11. using System.Windows.Data;
  12. using System.Windows.Input;
  13. using System.Windows.Media.Imaging;
  14. using AvalonDock.Layout;
  15. using com.sun.org.apache.bcel.@internal.generic;
  16. using Comal.Classes;
  17. using InABox.Clients;
  18. using InABox.Core;
  19. using InABox.DynamicGrid;
  20. using InABox.Wpf;
  21. using InABox.WPF;
  22. using Syncfusion.UI.Xaml.Kanban;
  23. using Syncfusion.Windows.Tools.Controls;
  24. using static com.sun.tools.javac.code.Symbol;
  25. namespace PRSDesktop;
  26. public class TasksByUserEmployeeHeader : INotifyPropertyChanged
  27. {
  28. public Guid EmployeeID { get; set; }
  29. public string Name { get; set; }
  30. public BitmapImage Image { get; set; }
  31. public int NumTasks { get => Tasks.Count(); }
  32. public double NumHours { get => Tasks.Sum(x => x.EstimatedTime.TotalHours); }
  33. public IEnumerable<TaskModel> Tasks => Model.Categories
  34. .SelectMany(x => x.EmployeeCategoryDictionary.GetValueOrDefault(EmployeeID)?.Tasks ?? Enumerable.Empty<TaskModel>());
  35. private TasksByUserModel Model;
  36. public event PropertyChangedEventHandler? PropertyChanged;
  37. // Create the OnPropertyChanged method to raise the event
  38. // The calling member's name will be used as the parameter.
  39. protected void OnPropertyChanged([CallerMemberName] string? name = null)
  40. {
  41. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  42. if (name.Equals(nameof(Tasks)))
  43. {
  44. OnPropertyChanged(nameof(NumTasks));
  45. OnPropertyChanged(nameof(NumHours));
  46. }
  47. }
  48. public TasksByUserEmployeeHeader(Guid employeeID, string name, BitmapImage image, TasksByUserModel model)
  49. {
  50. EmployeeID = employeeID;
  51. Name = name;
  52. Image = image;
  53. Model = model;
  54. }
  55. public void UpdateTasks()
  56. {
  57. OnPropertyChanged(nameof(Tasks));
  58. }
  59. }
  60. public class TasksByUserEmployeeCategory
  61. {
  62. public Guid EmployeeID { get; set; }
  63. public KanbanStatus Status { get; set; }
  64. public SuspendableObservableCollection<TaskModel> Tasks { get; set; } = new();
  65. public TasksByUserEmployeeCategory(Guid employeeID, KanbanStatus status)
  66. {
  67. EmployeeID = employeeID;
  68. Status = status;
  69. }
  70. }
  71. public class TasksByUserCategory : INotifyPropertyChanged
  72. {
  73. private bool _collapsed;
  74. public bool Collapsed
  75. {
  76. get => _collapsed;
  77. set
  78. {
  79. _collapsed = value;
  80. OnPropertyChanged();
  81. }
  82. }
  83. public KanbanStatus Status { get; set; }
  84. public IEnumerable<TasksByUserEmployeeCategory> EmployeeCategories => EmployeeCategoryDictionary.Values;
  85. public Dictionary<Guid, TasksByUserEmployeeCategory> EmployeeCategoryDictionary { get; set; } = new();
  86. public TasksByUserCategory(KanbanStatus status, bool collapsed)
  87. {
  88. Status = status;
  89. Collapsed = collapsed;
  90. }
  91. public event PropertyChangedEventHandler? PropertyChanged;
  92. // Create the OnPropertyChanged method to raise the event
  93. // The calling member's name will be used as the parameter.
  94. protected void OnPropertyChanged([CallerMemberName] string? name = null)
  95. {
  96. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  97. }
  98. }
  99. public class TasksByUserModel
  100. {
  101. public SuspendableObservableCollection<TasksByUserEmployeeHeader> SectionHeaders { get; set; } = new();
  102. public SuspendableObservableCollection<TasksByUserCategory> Categories { get; set; } = new();
  103. }
  104. public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, ITaskControl
  105. {
  106. private static readonly BitmapImage anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
  107. public TasksByUserModel Model { get; set; } = new();
  108. private ILookup<Guid, Guid> TeamEmployees;
  109. private Dictionary<Guid, EmployeeModel> Employees;
  110. private bool bPopulating;
  111. private KanbanViewMode _mode;
  112. public KanbanViewMode Mode
  113. {
  114. get => _mode;
  115. set
  116. {
  117. _mode = value;
  118. OnPropertyChanged();
  119. }
  120. }
  121. public TasksByUserControl()
  122. {
  123. InitializeComponent();
  124. }
  125. #region INotifyPropertyChanged
  126. public event PropertyChangedEventHandler? PropertyChanged;
  127. // Create the OnPropertyChanged method to raise the event
  128. // The calling member's name will be used as the parameter.
  129. protected void OnPropertyChanged([CallerMemberName] string? name = null)
  130. {
  131. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  132. }
  133. #endregion
  134. #region Setup
  135. private void LoadEmployees()
  136. {
  137. var empfilter = LookupFactory.DefineFilter<Employee>();
  138. var results = Client.QueryMultiple(
  139. new KeyedQueryDef<Employee>(
  140. LookupFactory.DefineFilter<Employee>(),
  141. Columns.None<Employee>().Add(x => x.ID)
  142. .Add(x => x.Name)
  143. .Add(x => x.Thumbnail.ID),
  144. new SortOrder<Employee>(x => x.Name)),
  145. new KeyedQueryDef<Team>(
  146. LookupFactory.DefineFilter<Team>(),
  147. Columns.None<Team>().Add(x => x.ID)
  148. .Add(x => x.Name),
  149. new SortOrder<Team>(x => x.Name)),
  150. new KeyedQueryDef<EmployeeTeam>(
  151. LookupFactory.DefineFilter<EmployeeTeam>(),
  152. Columns.None<EmployeeTeam>().Add(x => x.EmployeeLink.ID)
  153. .Add(x => x.TeamLink.ID)));
  154. TeamEmployees = results.Get<EmployeeTeam>().ToLookup<EmployeeTeam, Guid, Guid>(x => x.TeamLink.ID, x => x.EmployeeLink.ID);
  155. Employees = results.GetObjects<Employee>().ToDictionary(
  156. x => x.ID,
  157. x => new EmployeeModel(x.ID, x.Name, x.Thumbnail.ID, null));
  158. var teams = results.GetObjects<Team>().ToDictionary(x => x.ID, x => x.Name);
  159. SelectedTeams.ItemsSource = teams;
  160. foreach (var team in Host.KanbanSettings.UserSettings.SelectedTeams)
  161. {
  162. SelectedTeams.SelectedItems.Add(teams.FirstOrDefault(x => x.Key == team));
  163. }
  164. }
  165. private void PopulateEmployees()
  166. {
  167. bPopulating = true;
  168. try
  169. {
  170. var availableemployees = new List<Guid>();
  171. foreach (var team in SelectedTeams.SelectedItems.Select(v => (KeyValuePair<Guid, string>)v))
  172. availableemployees.AddRange(TeamEmployees[team.Key].Where(x => !availableemployees.Contains(x)));
  173. SelectedEmployees.ItemsSource = Employees.Where(x => availableemployees.Contains(x.Key));
  174. SelectedEmployees.SelectedItems.Clear();
  175. foreach (var employee in Host.KanbanSettings.UserSettings.SelectedEmployees.Where(availableemployees.Contains))
  176. SelectedEmployees.SelectedItems.Add(Employees.FirstOrDefault(x => Equals(x.Key, employee)));
  177. }
  178. catch (Exception e)
  179. {
  180. }
  181. bPopulating = false;
  182. }
  183. private void SetupToolbar()
  184. {
  185. IncludeCompleted.Visibility = Security.IsAllowed<CanHideTaskCompletedColumn>() ? Visibility.Visible : Visibility.Collapsed;
  186. IncludeCompleted.IsChecked = IncludeCompleted.Visibility == Visibility.Visible ? Host.KanbanSettings.UserSettings.IncludeCompleted : true;
  187. IncludeObserved.IsChecked = Host.KanbanSettings.UserSettings.IncludeObserved;
  188. IncludeManaged.IsChecked = Host.KanbanSettings.UserSettings.IncludeManaged;
  189. ViewType.SelectedIndex = Host.KanbanSettings.UserSettings.CompactView ? 1 : 0;
  190. }
  191. private void PopulateKanbanTypes()
  192. {
  193. TaskType.Items.Add("");
  194. foreach (var kanbanType in Host.KanbanTypes)
  195. {
  196. TaskType.Items.Add(kanbanType);
  197. }
  198. }
  199. public void Setup()
  200. {
  201. SetupToolbar();
  202. SplitPanel.AnchorWidth = Host.KanbanSettings.UserSettings.AnchorWidth;
  203. TeamsRow.Height = new GridLength(Host.KanbanSettings.UserSettings.TeamsHeight);
  204. LoadEmployees();
  205. PopulateEmployees();
  206. Mode = Host.KanbanSettings.UserSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
  207. PopulateKanbanTypes();
  208. FilterButton.SetSettings(Host.KanbanSettings.Filters, false);
  209. }
  210. #endregion
  211. #region Filters
  212. private void TaskPanelFilterButton_OnFilterRefresh()
  213. {
  214. Refresh();
  215. }
  216. private void FilterButton_OnFiltersSelected(DynamicGridSelectedFilterSettings filters)
  217. {
  218. Host.KanbanSettings.Filters = filters;
  219. Host.SaveSettings();
  220. }
  221. private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
  222. {
  223. if (!IsReady)
  224. return;
  225. SaveSettings();
  226. Refresh();
  227. }
  228. private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
  229. {
  230. if (!IsReady)
  231. return;
  232. SaveSettings();
  233. Refresh();
  234. }
  235. private void IncludeManaged_Checked(object sender, RoutedEventArgs e)
  236. {
  237. if (!IsReady)
  238. return;
  239. SaveSettings();
  240. Refresh();
  241. }
  242. private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
  243. {
  244. if (!IsReady)
  245. return;
  246. FilterKanbans();
  247. }
  248. private void Search_KeyUp(object sender, KeyEventArgs e)
  249. {
  250. FilterKanbans();
  251. }
  252. #endregion
  253. #region Refresh
  254. private Filter<KanbanSubscriber> GetKanbanSubscriberFilter(Filter<KanbanSubscriber>? additional = null)
  255. {
  256. var filter = new Filter<KanbanSubscriber>(c => c.Kanban.Closed).IsEqualTo(DateTime.MinValue)
  257. .And(x => x.Kanban.Locked).IsEqualTo(false);
  258. var privateFilter = new Filter<KanbanSubscriber>(x => x.Kanban.Private).IsEqualTo(false);
  259. if (App.EmployeeID != Guid.Empty)
  260. {
  261. privateFilter = privateFilter.Or(x => x.Employee.ID).IsEqualTo(App.EmployeeID);
  262. }
  263. filter.And(privateFilter);
  264. if (Host.Master != null)
  265. filter = filter.And(c => c.Kanban.JobLink.ID).IsEqualTo(Host.Master.ID);
  266. if (!Host.KanbanSettings.UserSettings.IncludeCompleted)
  267. filter = filter.And(new Filter<KanbanSubscriber>(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue));
  268. var emps = Employees.Where(x => Host.KanbanSettings.UserSettings.SelectedEmployees.Contains(x.Key));
  269. filter = filter.And(c => c.Employee.ID).InList(emps.Select(x => x.Key).ToArray());
  270. if (!Host.KanbanSettings.UserSettings.IncludeObserved)
  271. {
  272. if (Host.KanbanSettings.UserSettings.IncludeManaged)
  273. filter = filter.And(new Filter<KanbanSubscriber>(x => x.Manager).IsEqualTo(true).Or(x => x.Assignee).IsEqualTo(true));
  274. else
  275. filter = filter.And(x => x.Assignee).IsEqualTo(true);
  276. }
  277. if (additional is not null)
  278. {
  279. return additional.And(filter);
  280. }
  281. else
  282. {
  283. return filter;
  284. }
  285. }
  286. private void ReloadColumns()
  287. {
  288. Model.SectionHeaders.SupressNotification = true;
  289. Model.SectionHeaders.Clear();
  290. var emps = Host.KanbanSettings.UserSettings.SelectedEmployees.Where(x => Employees.ContainsKey(x)).OrderBy(x => Employees[x].Name).ToArray();
  291. foreach (var employeeID in emps)
  292. {
  293. if (Employees.TryGetValue(employeeID, out var employee))
  294. {
  295. Model.SectionHeaders.Add(new TasksByUserEmployeeHeader(employeeID, employee.Name, employee.Image ?? anonymous, Model));
  296. }
  297. }
  298. Model.SectionHeaders.SupressNotification = false;
  299. Model.Categories.SupressNotification = true;
  300. var oldCategories = Model.Categories.ToDictionary(x => x.Status);
  301. Model.Categories.Clear();
  302. foreach (var status in _statusOrder)
  303. {
  304. if (status == KanbanStatus.Complete && !Host.KanbanSettings.UserSettings.IncludeCompleted)
  305. {
  306. continue;
  307. }
  308. var newCategory = new TasksByUserCategory(status, oldCategories.GetValueOrDefault(status)?.Collapsed ?? false);
  309. foreach (var employeeID in emps)
  310. {
  311. if (Employees.TryGetValue(employeeID, out var employee))
  312. {
  313. var cat = new TasksByUserEmployeeCategory(employeeID, status);
  314. newCategory.EmployeeCategoryDictionary[employeeID] = cat;
  315. var header = Model.SectionHeaders.First(x => x.EmployeeID == employeeID);
  316. cat.Tasks.CollectionChanged += (s, e) =>
  317. {
  318. header.UpdateTasks();
  319. };
  320. }
  321. }
  322. Model.Categories.Add(newCategory);
  323. }
  324. Model.Categories.SupressNotification = false;
  325. }
  326. private static readonly KanbanStatus[] _statusOrder = new[]
  327. {
  328. KanbanStatus.Open,
  329. KanbanStatus.InProgress,
  330. KanbanStatus.Waiting,
  331. KanbanStatus.Complete
  332. };
  333. private Columns<Kanban> GetKanbanColumns()
  334. {
  335. return Columns.None<Kanban>().Add(
  336. x => x.ID,
  337. x => x.DueDate,
  338. x => x.Completed,
  339. x => x.Description,
  340. x => x.Summary,
  341. x => x.Status,
  342. x => x.EmployeeLink.ID,
  343. x => x.EmployeeLink.Name,
  344. x => x.ManagerLink.ID,
  345. x => x.ManagerLink.Name,
  346. x => x.Notes,
  347. x => x.Title,
  348. x => x.JobLink.ID,
  349. x => x.JobLink.JobNumber,
  350. x => x.JobLink.Name,
  351. x => x.Type.ID,
  352. x => x.Type.Code,
  353. x => x.Number,
  354. x => x.Attachments,
  355. x => x.Locked,
  356. x => x.EstimatedTime);
  357. }
  358. private IEnumerable<KanbanSubscriber> LoadSubscribers(Filter<KanbanSubscriber>? filter = null)
  359. {
  360. filter = GetKanbanSubscriberFilter(filter);
  361. var kanbanFilter = new Filter<Kanban>(x => x.ID).InQuery(filter, x => x.Kanban.ID);
  362. var buttonFilter = FilterButton.GetFilter();
  363. if(buttonFilter is not null)
  364. {
  365. kanbanFilter.And(buttonFilter);
  366. }
  367. var results = Client.QueryMultiple(
  368. new KeyedQueryDef<KanbanSubscriber>(
  369. filter,
  370. Columns.None<KanbanSubscriber>().Add(x => x.ID, x => x.Employee.ID, x => x.Kanban.ID),
  371. new SortOrder<KanbanSubscriber>(x => x.Kanban.DueDate) { Direction = SortDirection.Ascending }),
  372. new KeyedQueryDef<Kanban>(
  373. kanbanFilter,
  374. GetKanbanColumns()));
  375. var kanbans = results.GetObjects<Kanban>().ToDictionary(x => x.ID);
  376. return results.GetObjects<KanbanSubscriber>().Select(x =>
  377. {
  378. if (kanbans.TryGetValue(x.Kanban.ID, out var kanban))
  379. {
  380. x.Kanban.Synchronise(kanban);
  381. return x;
  382. }
  383. else
  384. {
  385. return null;
  386. }
  387. }).NotNull();
  388. }
  389. public void Refresh()
  390. {
  391. using (new WaitCursor())
  392. {
  393. var models = CreateModels(LoadSubscribers()).ToList();
  394. ReloadColumns();
  395. AllTasks = models.OrderBy(x => x.DueDate).ToList();
  396. FilterKanbans();
  397. }
  398. }
  399. /// <summary>
  400. /// Take the full list of kanbans loaded from the database, and filter based on the search UI elements, filtering into the columns.
  401. /// </summary>
  402. private void FilterKanbans()
  403. {
  404. IEnumerable<TaskModel> filtered = AllTasks;
  405. if (TaskType.SelectedItem is KanbanType kanbanType)
  406. {
  407. filtered = filtered.Where(x => x.Type.ID == kanbanType.ID);
  408. }
  409. if (!string.IsNullOrWhiteSpace(Search.Text))
  410. {
  411. var searches = Search.Text.Split();
  412. filtered = filtered.Where(x => x.Search(searches));
  413. }
  414. var categoryMap = Model.Categories.ToDictionary(x => x.Status, x => x.EmployeeCategoryDictionary);
  415. foreach(var category in Model.Categories)
  416. {
  417. foreach(var empCat in category.EmployeeCategories)
  418. {
  419. empCat.Tasks.Clear();
  420. }
  421. }
  422. SelectedTasks.Clear();
  423. foreach (var task in filtered)
  424. {
  425. if(categoryMap.TryGetValue(task.Status, out var categoryDict))
  426. {
  427. if (categoryDict.TryGetValue(task.EmployeeCategory, out var employeeCategory))
  428. {
  429. employeeCategory.Tasks.Add(task);
  430. if (task.Checked)
  431. {
  432. SelectedTasks.Add(task);
  433. }
  434. }
  435. }
  436. }
  437. }
  438. private IEnumerable<TaskModel> CreateModels(IEnumerable<KanbanSubscriber> subscribers)
  439. {
  440. foreach(var subscriber in subscribers)
  441. {
  442. var kanban = subscriber.Kanban;
  443. var model = new TaskModel
  444. {
  445. Title = kanban.Title,
  446. ID = kanban.ID,
  447. Description = kanban.Summary ?? "",
  448. EmployeeCategory = subscriber.Employee.ID,
  449. Status = kanban.Status,
  450. Attachments = kanban.Attachments > 0,
  451. DueDate = kanban.DueDate,
  452. CompletedDate = kanban.Completed,
  453. Locked = kanban.Locked,
  454. EstimatedTime = kanban.EstimatedTime,
  455. EmployeeID = kanban.EmployeeLink.ID,
  456. ManagerID = kanban.ManagerLink.ID,
  457. JobID = kanban.JobLink.ID,
  458. JobNumber = kanban.JobLink.JobNumber?.Trim() ?? "",
  459. JobName = kanban.JobLink.Name,
  460. Type = new KanbanType
  461. {
  462. ID = kanban.Type.ID,
  463. Code = kanban.Type.Code
  464. },
  465. Number = kanban.Number,
  466. Checked = SelectedTasks.Any(x => x.ID == kanban.ID)
  467. };
  468. var colour = subscriber.Employee.ID == kanban.EmployeeLink.ID
  469. ? TaskModel.KanbanColor(
  470. kanban.DueDate,
  471. kanban.Completed)
  472. : subscriber.Employee.ID == kanban.ManagerLink.ID
  473. ? Color.Silver
  474. : Color.Plum;
  475. if (kanban.Locked)
  476. {
  477. colour = colour.MixColors(0.5F, Color.White);
  478. }
  479. model.Color = System.Windows.Media.Color.FromArgb(colour.A, colour.R, colour.G, colour.B);
  480. var notes = new List<List<string>> { new() };
  481. var kanbanNotes = kanban.Notes;
  482. if (kanbanNotes != null)
  483. {
  484. foreach (var line in kanbanNotes)
  485. {
  486. if (line == "===================================")
  487. {
  488. notes.Add(new());
  489. }
  490. else
  491. {
  492. notes.Last().Add(line);
  493. }
  494. }
  495. }
  496. model.Notes = string.Join("\n===================================\n", notes.Reverse<List<string>>().Select(x => string.Join('\n', x)));
  497. SetTaskModelAssignedTo(model, kanban, subscriber.Employee.ID);
  498. yield return model;
  499. }
  500. }
  501. private static void SetTaskModelAssignedTo(TaskModel model, IKanban kanban, Guid subscriberID)
  502. {
  503. var employeeString = kanban.EmployeeLink.ID == subscriberID
  504. ? ""
  505. : kanban.EmployeeLink.ID == Guid.Empty
  506. ? " to (Unallocated)"
  507. : " to " + kanban.EmployeeLink.Name;
  508. var managerString = kanban.ManagerLink.ID == Guid.Empty || kanban.ManagerLink.ID == subscriberID
  509. ? ""
  510. : " by " + kanban.ManagerLink.Name;
  511. model.AssignedTo = !string.IsNullOrEmpty(employeeString) || !managerString.IsNullOrWhiteSpace()
  512. ? $"Assigned{employeeString}{managerString}"
  513. : "";
  514. }
  515. #endregion
  516. #region Kanban
  517. private readonly List<TaskModel> SelectedTasks = new();
  518. private List<TaskModel> AllTasks { get; set; } = new();
  519. private void DoEdit(TaskModel task)
  520. {
  521. var result = Host.EditReferences(new[] { task });
  522. if (result)
  523. {
  524. Refresh();
  525. }
  526. }
  527. private void EditTask_Executed(object sender, ExecutedRoutedEventArgs e)
  528. {
  529. if (e.Parameter is not TaskModel model) return;
  530. DoEdit(model);
  531. }
  532. private void OpenTaskMenu_Executed(object sender, ExecutedRoutedEventArgs e)
  533. {
  534. if (e.Parameter is not KanbanResources.OpenTaskMenuCommandArgs args) return;
  535. Host.PopulateMenu(this, args.Model, args.Menu);
  536. }
  537. private void SelectTask_Executed(object sender, ExecutedRoutedEventArgs e)
  538. {
  539. if (e.Parameter is not TaskModel model) return;
  540. if (!SelectedTasks.Remove(model))
  541. {
  542. SelectedTasks.Add(model);
  543. }
  544. }
  545. private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
  546. {
  547. e.CanExecute = true;
  548. }
  549. private void ItemsControl_DragOver(object sender, DragEventArgs e)
  550. {
  551. if (sender is not FrameworkElement element || element.Tag is not TasksByUserEmployeeCategory category) return;
  552. e.Effects = DragDropEffects.None;
  553. if (e.Data.GetDataPresent(typeof(TaskModel)))
  554. {
  555. var model = e.Data.GetData(typeof(TaskModel)) as TaskModel;
  556. if (model is not null
  557. && (model.Status != category.Status || model.EmployeeCategory != category.EmployeeID)
  558. && !SelectedTasks.Any(x => x.Locked)
  559. && SelectedTasks.Concat(CoreUtils.One(model)).Any(x => x.IsAssignee))
  560. {
  561. e.Effects = DragDropEffects.Move;
  562. }
  563. }
  564. }
  565. private void ItemsControl_Drop(object sender, DragEventArgs e)
  566. {
  567. if (sender is not FrameworkElement element || element.Tag is not TasksByUserEmployeeCategory category) return;
  568. if (e.Data.GetDataPresent(typeof(TaskModel)))
  569. {
  570. var models = SelectedModels(e.Data.GetData(typeof(TaskModel)) as TaskModel)
  571. .Where(x => (!x.Status.Equals(category.Status) || x.EmployeeID != category.EmployeeID) && x.IsAssignee)
  572. .ToList();
  573. if (!models.Any())
  574. {
  575. return;
  576. }
  577. var changingCategory = models.Any(x => !x.Status.Equals(category.Status));
  578. var completing = changingCategory && category.Status == KanbanStatus.Complete;
  579. var completed = DateTime.Now;
  580. if (completing)
  581. {
  582. if (!MessageWindow.ShowYesNo($"Are you sure you want to complete the selected tasks?", "Confirm Completion"))
  583. return;
  584. }
  585. var kanbans = Host.LoadKanbans(models, Columns.Required<Kanban>().Add(x => x.ID, x => x.EmployeeLink.ID, x => x.Private, x => x.Number, x => x.Status));
  586. var subscribers = new ClientKanbanSubscriberSet(kanbans.Select(x => x.ID));
  587. foreach(var kanban in kanbans)
  588. {
  589. if (!kanban.Private)
  590. {
  591. kanban.EmployeeLink.ID = category.EmployeeID;
  592. subscribers.EnsureAssignee(kanban.ID, kanban.EmployeeLink.ID);
  593. kanban.Status = category.Status;
  594. if (completing)
  595. {
  596. kanban.Completed = completed;
  597. }
  598. }
  599. else
  600. {
  601. MessageBox.Show($"Cannot change assignee for task {kanban.Number} because it is private.");
  602. models.RemoveAll(x => x.ID == kanban.ID);
  603. }
  604. }
  605. Client.Save(kanbans.Where(x => x.IsChanged()), $"Task Employee Updated");
  606. subscribers.Save(true);
  607. var kanbanIDs = models.Select(x => x.ID).ToArray();
  608. AllTasks.RemoveAll(x => kanbanIDs.Contains(x.ID));
  609. AllTasks.AddRange(CreateModels(LoadSubscribers(new Filter<KanbanSubscriber>(x => x.Kanban.ID).InList(kanbanIDs))));
  610. AllTasks.Sort((x, y) => x.DueDate.CompareTo(y.DueDate));
  611. FilterKanbans();
  612. }
  613. }
  614. #endregion
  615. #region ITaskControl
  616. public ITaskHost Host { get; set; }
  617. public KanbanViewType KanbanViewType => KanbanViewType.User;
  618. public bool IsReady { get; set; }
  619. public string SectionName => "Task List";
  620. public DataModel DataModel(Selection selection)
  621. {
  622. var ids = SelectedModels().Select(x => x.ID).ToArray();
  623. return new AutoDataModel<Kanban>(new Filter<Kanban>(x => x.ID).InList(ids));
  624. }
  625. public IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null)
  626. {
  627. if (sender is null)
  628. {
  629. return SelectedTasks;
  630. }
  631. else
  632. {
  633. var result = SelectedTasks.ToList();
  634. if (!result.Contains(sender))
  635. {
  636. result.Add(sender);
  637. }
  638. return result;
  639. }
  640. }
  641. #endregion
  642. #region Settings
  643. private void SaveSettings()
  644. {
  645. Host.KanbanSettings.UserSettings.AnchorWidth = SplitPanel.AnchorWidth;
  646. Host.KanbanSettings.UserSettings.TeamsHeight = SelectedTeams.ActualHeight;
  647. var teams = SelectedTeams.SelectedItems.Select(x => ((KeyValuePair<Guid, string>)x).Key);
  648. Host.KanbanSettings.UserSettings.SelectedTeams = teams.ToArray();
  649. var emps = SelectedEmployees.SelectedItems.Select(x => ((KeyValuePair<Guid, EmployeeModel>)x).Key);
  650. emps = emps.Where(e => TeamEmployees.Any(t => t.Contains(e)));
  651. Host.KanbanSettings.UserSettings.SelectedEmployees = emps.ToArray();
  652. Host.KanbanSettings.UserSettings.IncludeCompleted = IncludeCompleted.IsChecked == true;
  653. Host.KanbanSettings.UserSettings.IncludeObserved = IncludeObserved.IsChecked == true;
  654. Host.KanbanSettings.UserSettings.IncludeManaged = IncludeManaged.IsChecked == true;
  655. Host.SaveSettings();
  656. }
  657. private void SplitPanel_OnChanged(object sender, DynamicSplitPanelSettings e)
  658. {
  659. if (!IsReady || Equals(Host.KanbanSettings.UserSettings.AnchorWidth, e.AnchorWidth))
  660. return;
  661. SaveSettings();
  662. }
  663. private void SelectedTeams_ItemChecked(object sender, ItemCheckedEventArgs e)
  664. {
  665. if (!IsReady)
  666. return;
  667. PopulateEmployees();
  668. SaveSettings();
  669. Refresh();
  670. }
  671. private void SelectedTeams_SizeChanged(object sender, SizeChangedEventArgs e)
  672. {
  673. if (!IsReady || Equals(Host.KanbanSettings.UserSettings.TeamsHeight, SelectedTeams.ActualHeight))
  674. return;
  675. SaveSettings();
  676. }
  677. private void SelectedEmployees_ItemChecked(object sender, ItemCheckedEventArgs e)
  678. {
  679. if (!IsReady || bPopulating || sender != SelectedEmployees)
  680. return;
  681. SaveSettings();
  682. Refresh();
  683. }
  684. private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
  685. {
  686. if (!IsReady)
  687. return;
  688. Mode = ViewType.SelectedIndex switch
  689. {
  690. 0 => KanbanViewMode.Full,
  691. 1 => KanbanViewMode.Compact,
  692. _ => KanbanViewMode.Full
  693. };
  694. if (IsReady)
  695. {
  696. Host.KanbanSettings.UserSettings.CompactView = Mode == KanbanViewMode.Compact;
  697. Host.SaveSettings();
  698. }
  699. }
  700. #endregion
  701. private void Export_Click(object sender, RoutedEventArgs e)
  702. {
  703. var form = new DynamicExportForm(typeof(Kanban), GetKanbanColumns().ColumnNames());
  704. if (form.ShowDialog() != true)
  705. return;
  706. var export = Client.Query(
  707. new Filter<Kanban>(x => x.ID).InQuery(GetKanbanSubscriberFilter(), x => x.Kanban.ID),
  708. Columns.None<Kanban>().Add(form.Fields),
  709. LookupFactory.DefineSort<Kanban>()
  710. );
  711. var employee = "Tasks for " + string.Join(',', Employees.Where(x => Host.KanbanSettings.UserSettings.SelectedEmployees.Contains(x.Key)).Select(x => x.Value.Name));
  712. ExcelExporter.DoExport<Kanban>(
  713. export,
  714. string.Format(
  715. "{0} ({1:dd-MMM-yy})",
  716. employee,
  717. DateTime.Today
  718. )
  719. );
  720. }
  721. private void FoldButton_Click(object sender, RoutedEventArgs e)
  722. {
  723. if (sender is not FrameworkElement element || element.Tag is not TasksByUserCategory category) return;
  724. category.Collapsed = !category.Collapsed;
  725. }
  726. }