TasksByUserControl.xaml.cs 28 KB

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