TasksByUserControl.xaml.cs 28 KB

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