|
@@ -18,10 +18,11 @@ using InABox.DynamicGrid;
|
|
|
using InABox.WPF;
|
|
|
using Syncfusion.UI.Xaml.Kanban;
|
|
|
using Syncfusion.Windows.Tools.Controls;
|
|
|
+using static com.sun.tools.javac.code.Symbol;
|
|
|
|
|
|
namespace PRSDesktop;
|
|
|
|
|
|
-public class TasksByUserEmployeeHeader
|
|
|
+public class TasksByUserEmployeeHeader : INotifyPropertyChanged
|
|
|
{
|
|
|
public Guid EmployeeID { get; set; }
|
|
|
|
|
@@ -38,6 +39,21 @@ public class TasksByUserEmployeeHeader
|
|
|
|
|
|
private TasksByUserModel Model;
|
|
|
|
|
|
+ public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
+
|
|
|
+ // Create the OnPropertyChanged method to raise the event
|
|
|
+ // The calling member's name will be used as the parameter.
|
|
|
+ protected void OnPropertyChanged([CallerMemberName] string? name = null)
|
|
|
+ {
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
|
+
|
|
|
+ if (name.Equals(nameof(Tasks)))
|
|
|
+ {
|
|
|
+ OnPropertyChanged(nameof(NumTasks));
|
|
|
+ OnPropertyChanged(nameof(NumHours));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public TasksByUserEmployeeHeader(Guid employeeID, string name, BitmapImage image, TasksByUserModel model)
|
|
|
{
|
|
|
EmployeeID = employeeID;
|
|
@@ -45,55 +61,84 @@ public class TasksByUserEmployeeHeader
|
|
|
Image = image;
|
|
|
Model = model;
|
|
|
}
|
|
|
+
|
|
|
+ public void UpdateTasks()
|
|
|
+ {
|
|
|
+ OnPropertyChanged(nameof(Tasks));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public class TasksByUserEmployeeCategory
|
|
|
{
|
|
|
public Guid EmployeeID { get; set; }
|
|
|
|
|
|
- public List<TaskModel> Tasks { get; set; } = new();
|
|
|
+ public string Category { get; set; }
|
|
|
+
|
|
|
+ public SuspendableObservableCollection<TaskModel> Tasks { get; set; } = new();
|
|
|
|
|
|
- public TasksByUserEmployeeCategory(Guid employeeID)
|
|
|
+ public TasksByUserEmployeeCategory(Guid employeeID, string category)
|
|
|
{
|
|
|
EmployeeID = employeeID;
|
|
|
+ Category = category;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-public class TasksByUserCategory
|
|
|
+public class TasksByUserCategory : INotifyPropertyChanged
|
|
|
{
|
|
|
+ private bool _collapsed;
|
|
|
+ public bool Collapsed
|
|
|
+ {
|
|
|
+ get => _collapsed;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _collapsed = value;
|
|
|
+ OnPropertyChanged();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public string Category { get; set; }
|
|
|
|
|
|
public IEnumerable<TasksByUserEmployeeCategory> EmployeeCategories => EmployeeCategoryDictionary.Values;
|
|
|
|
|
|
public Dictionary<Guid, TasksByUserEmployeeCategory> EmployeeCategoryDictionary { get; set; } = new();
|
|
|
|
|
|
- public TasksByUserCategory(string category)
|
|
|
+ public TasksByUserCategory(string category, bool collapsed)
|
|
|
{
|
|
|
Category = category;
|
|
|
+ Collapsed = collapsed;
|
|
|
+ }
|
|
|
+
|
|
|
+ public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
+
|
|
|
+ // Create the OnPropertyChanged method to raise the event
|
|
|
+ // The calling member's name will be used as the parameter.
|
|
|
+ protected void OnPropertyChanged([CallerMemberName] string? name = null)
|
|
|
+ {
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class TasksByUserModel
|
|
|
{
|
|
|
- public List<TasksByUserEmployeeHeader> SectionHeaders { get; set; } = new();
|
|
|
+ public SuspendableObservableCollection<TasksByUserEmployeeHeader> SectionHeaders { get; set; } = new();
|
|
|
|
|
|
- public List<TasksByUserCategory> Categories { get; set; } = new();
|
|
|
+ public SuspendableObservableCollection<TasksByUserCategory> Categories { get; set; } = new();
|
|
|
}
|
|
|
|
|
|
public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, ITaskControl
|
|
|
{
|
|
|
private static readonly BitmapImage anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
|
|
|
|
|
|
- public TasksByUserModel Model { get; set; }
|
|
|
+ public TasksByUserModel Model { get; set; } = new();
|
|
|
|
|
|
private ILookup<Guid, Guid> TeamEmployees;
|
|
|
|
|
|
private Dictionary<Guid, EmployeeModel> Employees;
|
|
|
|
|
|
- private KanbanViewMode _mode;
|
|
|
-
|
|
|
private bool bPopulating;
|
|
|
|
|
|
+ private KanbanViewMode _mode;
|
|
|
+
|
|
|
public KanbanViewMode Mode
|
|
|
{
|
|
|
get => _mode;
|
|
@@ -154,7 +199,7 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
SelectedTeams.ItemsSource = teams;
|
|
|
foreach (var team in Host.KanbanSettings.UserSettings.SelectedTeams)
|
|
|
{
|
|
|
- SelectedTeams.SelectedItems.Add(teams.Where(x => x.Key == team));
|
|
|
+ SelectedTeams.SelectedItems.Add(teams.FirstOrDefault(x => x.Key == team));
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -169,7 +214,7 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
|
|
|
SelectedEmployees.ItemsSource = Employees.Where(x => availableemployees.Contains(x.Key));
|
|
|
SelectedEmployees.SelectedItems.Clear();
|
|
|
- foreach (var employee in Host.KanbanSettings.UserSettings.SelectedEmployees.Where(x => availableemployees.Contains(x)))
|
|
|
+ foreach (var employee in Host.KanbanSettings.UserSettings.SelectedEmployees.Where(availableemployees.Contains))
|
|
|
SelectedEmployees.SelectedItems.Add(Employees.FirstOrDefault(x => Equals(x.Key, employee)));
|
|
|
}
|
|
|
catch (Exception e)
|
|
@@ -207,16 +252,54 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
LoadEmployees();
|
|
|
PopulateEmployees();
|
|
|
|
|
|
- Mode = Host.KanbanSettings.StatusSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
|
|
|
+ Mode = Host.KanbanSettings.UserSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
|
|
|
|
|
|
PopulateKanbanTypes();
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
+ #region Filters
|
|
|
+
|
|
|
+ private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ if (!IsReady)
|
|
|
+ return;
|
|
|
+ SaveSettings();
|
|
|
+ Refresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ if (!IsReady)
|
|
|
+ return;
|
|
|
+ SaveSettings();
|
|
|
+ Refresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void IncludeManaged_Checked(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ if (!IsReady)
|
|
|
+ return;
|
|
|
+ SaveSettings();
|
|
|
+ Refresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ FilterKanbans();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Search_KeyUp(object sender, KeyEventArgs e)
|
|
|
+ {
|
|
|
+ FilterKanbans();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
#region Refresh
|
|
|
|
|
|
- private Filter<KanbanSubscriber> GetKanbanSubscriberFilter()
|
|
|
+ private Filter<KanbanSubscriber> GetKanbanSubscriberFilter(Filter<KanbanSubscriber>? additional = null)
|
|
|
{
|
|
|
var filter = new Filter<KanbanSubscriber>(c => c.Kanban.Closed).IsEqualTo(DateTime.MinValue)
|
|
|
.And(x => x.Kanban.Locked).IsEqualTo(false);
|
|
@@ -249,82 +332,138 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
else
|
|
|
filter = filter.And(x => x.Assignee).IsEqualTo(true);
|
|
|
}
|
|
|
- return filter;
|
|
|
+ if (additional is not null)
|
|
|
+ {
|
|
|
+ return additional.And(filter);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return filter;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private void ReloadColumns()
|
|
|
{
|
|
|
+ Model.SectionHeaders.SupressNotification = true;
|
|
|
Model.SectionHeaders.Clear();
|
|
|
- foreach (var employeeID in Host.KanbanSettings.UserSettings.SelectedEmployees)
|
|
|
+
|
|
|
+ var emps = Host.KanbanSettings.UserSettings.SelectedEmployees.OrderBy(x => Employees[x].Name).ToArray();
|
|
|
+
|
|
|
+ foreach (var employeeID in emps)
|
|
|
{
|
|
|
if (Employees.TryGetValue(employeeID, out var employee))
|
|
|
{
|
|
|
Model.SectionHeaders.Add(new TasksByUserEmployeeHeader(employeeID, employee.Name, employee.Image ?? anonymous, Model));
|
|
|
}
|
|
|
}
|
|
|
+ Model.SectionHeaders.SupressNotification = false;
|
|
|
+ Model.Categories.SupressNotification = true;
|
|
|
+
|
|
|
+ var oldCategories = Model.Categories.ToDictionary(x => x.Category);
|
|
|
+
|
|
|
+ Model.Categories.Clear();
|
|
|
+
|
|
|
+ foreach (var category in _categoryOrder)
|
|
|
+ {
|
|
|
+ if (category.Equals(Kanban.COMPLETE) && !Host.KanbanSettings.UserSettings.IncludeCompleted)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ var newCategory = new TasksByUserCategory(category, oldCategories.GetValueOrDefault(category)?.Collapsed ?? false);
|
|
|
+ foreach (var employeeID in emps)
|
|
|
+ {
|
|
|
+ if (Employees.TryGetValue(employeeID, out var employee))
|
|
|
+ {
|
|
|
+ var cat = new TasksByUserEmployeeCategory(employeeID, category);
|
|
|
+ newCategory.EmployeeCategoryDictionary[employeeID] = cat;
|
|
|
+ var header = Model.SectionHeaders.First(x => x.EmployeeID == employeeID);
|
|
|
+ cat.Tasks.CollectionChanged += (s, e) =>
|
|
|
+ {
|
|
|
+ header.UpdateTasks();
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Model.Categories.Add(newCategory);
|
|
|
+ }
|
|
|
+ Model.Categories.SupressNotification = false;
|
|
|
}
|
|
|
|
|
|
- public void Refresh()
|
|
|
- {
|
|
|
- var categoryOrder = new Dictionary<string, int>
|
|
|
+ private static readonly string[] _categoryOrder = new[]
|
|
|
{
|
|
|
- { Kanban.OPEN, 0 },
|
|
|
- { Kanban.INPROGRESS, 1 },
|
|
|
- { Kanban.WAITING, 2 },
|
|
|
- { Kanban.COMPLETE, 3 }
|
|
|
+ Kanban.OPEN,
|
|
|
+ Kanban.INPROGRESS,
|
|
|
+ Kanban.WAITING,
|
|
|
+ Kanban.COMPLETE
|
|
|
};
|
|
|
- var filter = GetKanbanSubscriberFilter();
|
|
|
|
|
|
+ private Columns<Kanban> GetKanbanColumns()
|
|
|
+ {
|
|
|
+ return new Columns<Kanban>(
|
|
|
+ x => x.ID,
|
|
|
+ x => x.DueDate,
|
|
|
+ x => x.Completed,
|
|
|
+ x => x.Description,
|
|
|
+ x => x.Summary,
|
|
|
+ x => x.Category,
|
|
|
+ x => x.EmployeeLink.ID,
|
|
|
+ x => x.EmployeeLink.Name,
|
|
|
+ x => x.ManagerLink.ID,
|
|
|
+ x => x.ManagerLink.Name,
|
|
|
+ x => x.Notes,
|
|
|
+ x => x.Title,
|
|
|
+ x => x.JobLink.ID,
|
|
|
+ x => x.JobLink.JobNumber,
|
|
|
+ x => x.JobLink.Name,
|
|
|
+ x => x.Type.ID,
|
|
|
+ x => x.Type.Code,
|
|
|
+ x => x.Number,
|
|
|
+ x => x.Attachments,
|
|
|
+ x => x.Locked,
|
|
|
+ x => x.EstimatedTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ private IEnumerable<KanbanSubscriber> LoadSubscribers(Filter<KanbanSubscriber>? filter = null)
|
|
|
+ {
|
|
|
+ filter = GetKanbanSubscriberFilter(filter);
|
|
|
+
|
|
|
+ var results = Client.QueryMultiple(
|
|
|
+ new KeyedQueryDef<KanbanSubscriber>(
|
|
|
+ filter,
|
|
|
+ new Columns<KanbanSubscriber>(x => x.ID, x => x.Employee.ID, x => x.Kanban.ID),
|
|
|
+ new SortOrder<KanbanSubscriber>(x => x.Kanban.DueDate) { Direction = SortDirection.Ascending }),
|
|
|
+ new KeyedQueryDef<Kanban>(
|
|
|
+ new Filter<Kanban>(x => x.ID).InQuery(filter, x => x.Kanban.ID),
|
|
|
+ GetKanbanColumns()));
|
|
|
+
|
|
|
+ var kanbans = results.GetObjects<Kanban>().ToDictionary(x => x.ID);
|
|
|
+ return results.GetObjects<KanbanSubscriber>().Select(x =>
|
|
|
+ {
|
|
|
+ if (kanbans.TryGetValue(x.Kanban.ID, out var kanban))
|
|
|
+ {
|
|
|
+ x.Kanban.Synchronise(kanban);
|
|
|
+ }
|
|
|
+ return x;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Refresh()
|
|
|
+ {
|
|
|
using (new WaitCursor())
|
|
|
{
|
|
|
- var kanbans = new Client<KanbanSubscriber>().Query(
|
|
|
- filter,
|
|
|
- new Columns<KanbanSubscriber>
|
|
|
- (
|
|
|
- x => x.Kanban.ID,
|
|
|
- x => x.Kanban.DueDate,
|
|
|
- x => x.Kanban.Completed,
|
|
|
- //x => x.Kanban.Description,
|
|
|
- x => x.Kanban.Summary,
|
|
|
- x => x.Kanban.Category,
|
|
|
- x => x.Kanban.EmployeeLink.ID,
|
|
|
- x => x.Kanban.EmployeeLink.Name,
|
|
|
- x => x.Kanban.ManagerLink.ID,
|
|
|
- x => x.Kanban.ManagerLink.Name,
|
|
|
- x => x.Kanban.Notes,
|
|
|
- x => x.Kanban.Title,
|
|
|
- x => x.Kanban.JobLink.ID,
|
|
|
- x => x.Kanban.JobLink.JobNumber,
|
|
|
- x => x.Kanban.JobLink.Name,
|
|
|
- x => x.Kanban.Type.ID,
|
|
|
- x => x.Kanban.Type.Code,
|
|
|
- x => x.Kanban.Number,
|
|
|
- x => x.Kanban.Attachments,
|
|
|
- x => x.Kanban.Locked,
|
|
|
- x => x.Employee.ID,
|
|
|
- x => x.Kanban.EstimatedTime
|
|
|
- ),
|
|
|
- new SortOrder<KanbanSubscriber>(x => x.Kanban.DueDate) { Direction = SortDirection.Ascending }
|
|
|
- );
|
|
|
- var models = CreateModels(kanbans.ToObjects<KanbanSubscriber>()).ToList();
|
|
|
+ var models = CreateModels(LoadSubscribers()).ToList();
|
|
|
|
|
|
ReloadColumns();
|
|
|
|
|
|
- AllTasks = models.OrderBy(x => categoryOrder[x.Category]).ThenBy(x => x.DueDate).ToList();
|
|
|
+ AllTasks = models.OrderBy(x => x.DueDate).ToList();
|
|
|
FilterKanbans();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Take the full list of kanbans loaded from the database, and filter based on the search UI elements, filtering into the columns.
|
|
|
+ /// </summary>
|
|
|
private void FilterKanbans()
|
|
|
{
|
|
|
- var categoryOrder = new Dictionary<string, int>
|
|
|
- {
|
|
|
- { Kanban.OPEN, 0 },
|
|
|
- { Kanban.INPROGRESS, 1 },
|
|
|
- { Kanban.WAITING, 2 },
|
|
|
- { Kanban.COMPLETE, 3 }
|
|
|
- };
|
|
|
-
|
|
|
IEnumerable<TaskModel> filtered = AllTasks;
|
|
|
if (TaskType.SelectedItem is KanbanType kanbanType)
|
|
|
{
|
|
@@ -336,30 +475,31 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
filtered = filtered.Where(x => x.Search(searches));
|
|
|
}
|
|
|
|
|
|
- var categories = new Dictionary<string, TasksByUserCategory>();
|
|
|
-
|
|
|
- foreach (var task in filtered)
|
|
|
+ var categoryMap = Model.Categories.ToDictionary(x => x.Category, x => x.EmployeeCategoryDictionary);
|
|
|
+ foreach(var category in Model.Categories)
|
|
|
{
|
|
|
- if(!categories.TryGetValue(task.Category, out var category))
|
|
|
+ foreach(var empCat in category.EmployeeCategories)
|
|
|
{
|
|
|
- category = new TasksByUserCategory(task.Category);
|
|
|
- categories.Add(task.Category, category);
|
|
|
+ empCat.Tasks.Clear();
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if(!category.EmployeeCategoryDictionary.TryGetValue(task.EmployeeCategory, out var employeeCategory))
|
|
|
- {
|
|
|
- employeeCategory = new TasksByUserEmployeeCategory(task.EmployeeCategory);
|
|
|
- category.EmployeeCategoryDictionary.Add(task.EmployeeCategory, employeeCategory);
|
|
|
- }
|
|
|
+ SelectedTasks.Clear();
|
|
|
|
|
|
- employeeCategory.Tasks.Add(task);
|
|
|
- if (task.Checked)
|
|
|
+ foreach (var task in filtered)
|
|
|
+ {
|
|
|
+ if(categoryMap.TryGetValue(task.Category, out var categoryDict))
|
|
|
{
|
|
|
- SelectedTasks.Add(task);
|
|
|
+ if (categoryDict.TryGetValue(task.EmployeeCategory, out var employeeCategory))
|
|
|
+ {
|
|
|
+ employeeCategory.Tasks.Add(task);
|
|
|
+ if (task.Checked)
|
|
|
+ {
|
|
|
+ SelectedTasks.Add(task);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- Model.Categories.AddRange(categories.Values.OrderBy(x => categoryOrder[x.Category]));
|
|
|
}
|
|
|
|
|
|
private IEnumerable<TaskModel> CreateModels(IEnumerable<KanbanSubscriber> subscribers)
|
|
@@ -367,26 +507,31 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
foreach(var subscriber in subscribers)
|
|
|
{
|
|
|
var kanban = subscriber.Kanban;
|
|
|
-
|
|
|
- var empValid = Entity.IsEntityLinkValid<KanbanSubscriber, EmployeeLink>(x => x.Kanban.EmployeeLink, row);
|
|
|
- var mgrValid = Entity.IsEntityLinkValid<KanbanSubscriber, EmployeeLink>(x => x.Kanban.ManagerLink, row);
|
|
|
-
|
|
|
- var completed = row.Get<KanbanSubscriber, DateTime>(e => e.Kanban.Completed);
|
|
|
- var locked = row.Get<KanbanSubscriber, bool>(e => e.Kanban.Locked);
|
|
|
- var typeID = row.Get<KanbanSubscriber, Guid>(e => e.Kanban.Type.ID);
|
|
|
- var typeCode = row.Get<KanbanSubscriber, string>(e => e.Kanban.Type.Code);
|
|
|
- var job = row.Get<KanbanSubscriber, string>(x => x.Kanban.JobLink.JobNumber);
|
|
|
-
|
|
|
- var model = new TaskModel();
|
|
|
-
|
|
|
- model.Title = kanban.Title;
|
|
|
- model.ID = kanban.ID;
|
|
|
- model.Description = kanban.Summary ?? "";
|
|
|
- model.EmployeeCategory = subscriber.Employee.ID;
|
|
|
- model.Category = kanban.Category;
|
|
|
-
|
|
|
- if (model.Category.IsNullOrWhiteSpace())
|
|
|
- model.Category = "Open";
|
|
|
+ var model = new TaskModel
|
|
|
+ {
|
|
|
+ Title = kanban.Title,
|
|
|
+ ID = kanban.ID,
|
|
|
+ Description = kanban.Summary ?? "",
|
|
|
+ EmployeeCategory = subscriber.Employee.ID,
|
|
|
+ Category = kanban.Category.NotWhiteSpaceOr("Open"),
|
|
|
+ Attachments = kanban.Attachments > 0,
|
|
|
+ DueDate = kanban.DueDate,
|
|
|
+ CompletedDate = kanban.Completed,
|
|
|
+ Locked = kanban.Locked,
|
|
|
+ EstimatedTime = kanban.EstimatedTime,
|
|
|
+ EmployeeID = kanban.EmployeeLink.ID,
|
|
|
+ ManagerID = kanban.ManagerLink.ID,
|
|
|
+ JobID = kanban.JobLink.ID,
|
|
|
+ JobNumber = kanban.JobLink.JobNumber?.Trim() ?? "",
|
|
|
+ JobName = kanban.JobLink.Name,
|
|
|
+ Type = new KanbanType
|
|
|
+ {
|
|
|
+ ID = kanban.Type.ID,
|
|
|
+ Code = kanban.Type.Code
|
|
|
+ },
|
|
|
+ Number = kanban.Number,
|
|
|
+ Checked = SelectedTasks.Any(x => x.ID == kanban.ID)
|
|
|
+ };
|
|
|
|
|
|
var colour = subscriber.Employee.ID == kanban.EmployeeLink.ID
|
|
|
? TaskModel.KanbanColor(
|
|
@@ -401,12 +546,6 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
}
|
|
|
model.Color = System.Windows.Media.Color.FromArgb(colour.A, colour.R, colour.G, colour.B);
|
|
|
|
|
|
- model.Attachments = kanban.Attachments > 0;
|
|
|
- model.DueDate = kanban.DueDate;
|
|
|
- model.CompletedDate = kanban.Completed;
|
|
|
- model.Locked = kanban.Locked;
|
|
|
- model.EstimatedTime = kanban.EstimatedTime;
|
|
|
-
|
|
|
var notes = new List<List<string>> { new() };
|
|
|
var kanbanNotes = kanban.Notes;
|
|
|
if (kanbanNotes != null)
|
|
@@ -425,36 +564,27 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
}
|
|
|
model.Notes = string.Join("\n===================================\n", notes.Reverse<List<string>>().Select(x => string.Join('\n', x)));
|
|
|
|
|
|
- model.EmployeeID = kanban.EmployeeLink.ID;
|
|
|
- model.ManagerID = kanban.ManagerLink.ID;
|
|
|
-
|
|
|
- var employeeString = kanban.EmployeeLink.ID == subscriber.Employee.ID
|
|
|
- ? ""
|
|
|
- : kanban.EmployeeLink.ID == Guid.Empty
|
|
|
- ? " to (Unallocated)"
|
|
|
- : " to " + kanban.EmployeeLink.Name;
|
|
|
+ SetTaskModelAssignedTo(model, kanban, subscriber.Employee.ID);
|
|
|
|
|
|
- var managerString = kanban.ManagerLink.ID == Guid.Empty || kanban.ManagerLink.ID == subscriber.Employee.ID
|
|
|
- ? ""
|
|
|
- : " by " + kanban.ManagerLink.Name;
|
|
|
+ yield return model;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- model.AssignedTo = !string.IsNullOrEmpty(employeeString) || !managerString.IsNullOrWhiteSpace()
|
|
|
- ? $"Assigned{employeeString}{managerString}"
|
|
|
- : "";
|
|
|
+ private static void SetTaskModelAssignedTo(TaskModel model, IKanban kanban, Guid subscriberID)
|
|
|
+ {
|
|
|
+ var employeeString = kanban.EmployeeLink.ID == subscriberID
|
|
|
+ ? ""
|
|
|
+ : kanban.EmployeeLink.ID == Guid.Empty
|
|
|
+ ? " to (Unallocated)"
|
|
|
+ : " to " + kanban.EmployeeLink.Name;
|
|
|
|
|
|
- model.JobID = kanban.JobLink.ID;
|
|
|
- model.JobNumber = kanban.JobLink.JobNumber?.Trim() ?? "";
|
|
|
- model.JobName = kanban.JobLink.Name;
|
|
|
- model.Checked = SelectedTasks.Any(x => x.ID == model.ID);
|
|
|
- model.Type = new KanbanType
|
|
|
- {
|
|
|
- ID = typeID,
|
|
|
- Code = typeCode
|
|
|
- };
|
|
|
- model.Number = kanban.Number;
|
|
|
+ var managerString = kanban.ManagerLink.ID == Guid.Empty || kanban.ManagerLink.ID == subscriberID
|
|
|
+ ? ""
|
|
|
+ : " by " + kanban.ManagerLink.Name;
|
|
|
|
|
|
- yield return model;
|
|
|
- }
|
|
|
+ model.AssignedTo = !string.IsNullOrEmpty(employeeString) || !managerString.IsNullOrWhiteSpace()
|
|
|
+ ? $"Assigned{employeeString}{managerString}"
|
|
|
+ : "";
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
@@ -502,6 +632,85 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
e.CanExecute = true;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ private void ItemsControl_DragOver(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ if (sender is not FrameworkElement element || element.Tag is not TasksByUserEmployeeCategory category) return;
|
|
|
+
|
|
|
+ e.Effects = DragDropEffects.None;
|
|
|
+ if (e.Data.GetDataPresent(typeof(TaskModel)))
|
|
|
+ {
|
|
|
+ var model = e.Data.GetData(typeof(TaskModel)) as TaskModel;
|
|
|
+ if (model is not null
|
|
|
+ && (model.Category != category.Category || model.EmployeeCategory != category.EmployeeID)
|
|
|
+ && !SelectedTasks.Any(x => x.Locked)
|
|
|
+ && SelectedTasks.Concat(CoreUtils.One(model)).Any(x => x.IsAssignee))
|
|
|
+ {
|
|
|
+ e.Effects = DragDropEffects.Move;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ItemsControl_Drop(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ if (sender is not FrameworkElement element || element.Tag is not TasksByUserEmployeeCategory category) return;
|
|
|
+
|
|
|
+ if (e.Data.GetDataPresent(typeof(TaskModel)))
|
|
|
+ {
|
|
|
+ var models = SelectedModels(e.Data.GetData(typeof(TaskModel)) as TaskModel)
|
|
|
+ .Where(x => (!x.Category.Equals(category.Category) || x.EmployeeID != category.EmployeeID) && x.IsAssignee)
|
|
|
+ .ToList();
|
|
|
+ if (!models.Any())
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var changingCategory = models.Any(x => !x.Category.Equals(category.Category));
|
|
|
+ var completing = changingCategory && category.Category.Equals(Kanban.COMPLETE);
|
|
|
+ var completed = DateTime.Now;
|
|
|
+
|
|
|
+ if (completing)
|
|
|
+ {
|
|
|
+ if (MessageBox.Show($"Are you sure you want to complete the selected tasks?", "Confirm Completion",
|
|
|
+ MessageBoxButton.YesNo) != MessageBoxResult.Yes)
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var kanbans = Host.LoadKanbans(models, new Columns<Kanban>(x => x.ID, x => x.EmployeeLink.ID, x => x.Private, x => x.Number));
|
|
|
+ var subscribers = new ClientKanbanSubscriberSet(kanbans.Select(x => x.ID));
|
|
|
+
|
|
|
+ foreach(var kanban in kanbans)
|
|
|
+ {
|
|
|
+ if (!kanban.Private)
|
|
|
+ {
|
|
|
+ kanban.EmployeeLink.ID = category.EmployeeID;
|
|
|
+ subscribers.EnsureAssignee(kanban.ID, kanban.EmployeeLink.ID);
|
|
|
+
|
|
|
+ kanban.Category = category.Category;
|
|
|
+ if (completing)
|
|
|
+ {
|
|
|
+ kanban.Completed = completed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ MessageBox.Show($"Cannot change assignee for task {kanban.Number} because it is private.");
|
|
|
+ models.RemoveAll(x => x.ID == kanban.ID);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Client.Save(kanbans.Where(x => x.IsChanged()), $"Task Employee Updated");
|
|
|
+ subscribers.Save(true);
|
|
|
+
|
|
|
+ var kanbanIDs = models.Select(x => x.ID).ToArray();
|
|
|
+ AllTasks.RemoveAll(x => kanbanIDs.Contains(x.ID));
|
|
|
+
|
|
|
+ AllTasks.AddRange(CreateModels(LoadSubscribers(new Filter<KanbanSubscriber>(x => x.Kanban.ID).InList(kanbanIDs))));
|
|
|
+ AllTasks.Sort((x, y) => x.DueDate.CompareTo(y.DueDate));
|
|
|
+
|
|
|
+ FilterKanbans();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
#endregion
|
|
|
|
|
|
#region ITaskControl
|
|
@@ -550,7 +759,7 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
var teams = SelectedTeams.SelectedItems.Select(x => ((KeyValuePair<Guid, string>)x).Key);
|
|
|
Host.KanbanSettings.UserSettings.SelectedTeams = teams.ToArray();
|
|
|
|
|
|
- var emps = SelectedEmployees.SelectedItems.Select(x => ((KeyValuePair<Guid, string>)x).Key);
|
|
|
+ var emps = SelectedEmployees.SelectedItems.Select(x => ((KeyValuePair<Guid, EmployeeModel>)x).Key);
|
|
|
emps = emps.Where(e => TeamEmployees.Any(t => t.Contains(e)));
|
|
|
Host.KanbanSettings.UserSettings.SelectedEmployees = emps.ToArray();
|
|
|
|
|
@@ -584,58 +793,59 @@ public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, I
|
|
|
SaveSettings();
|
|
|
}
|
|
|
|
|
|
- private void EmployeesSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
+ private void SelectedEmployees_ItemChecked(object sender, ItemCheckedEventArgs e)
|
|
|
{
|
|
|
- if (!IsReady || bPopulating)
|
|
|
+ if (!IsReady || bPopulating || sender != SelectedEmployees)
|
|
|
return;
|
|
|
SaveSettings();
|
|
|
Refresh();
|
|
|
}
|
|
|
|
|
|
- #endregion
|
|
|
-
|
|
|
- private void Export_Click(object sender, RoutedEventArgs e)
|
|
|
- {
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
|
|
|
- {
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
|
|
|
- {
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private void IncludeManaged_Checked(object sender, RoutedEventArgs e)
|
|
|
- {
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
{
|
|
|
+ Mode = ViewType.SelectedIndex switch
|
|
|
+ {
|
|
|
+ 0 => KanbanViewMode.Full,
|
|
|
+ 1 => KanbanViewMode.Compact,
|
|
|
+ _ => KanbanViewMode.Full
|
|
|
+ };
|
|
|
|
|
|
+ if (IsReady)
|
|
|
+ {
|
|
|
+ Host.KanbanSettings.UserSettings.CompactView = Mode == KanbanViewMode.Compact;
|
|
|
+ Host.SaveSettings();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
- {
|
|
|
-
|
|
|
- }
|
|
|
+ #endregion
|
|
|
|
|
|
- private void Search_KeyUp(object sender, KeyEventArgs e)
|
|
|
+ private void Export_Click(object sender, RoutedEventArgs e)
|
|
|
{
|
|
|
+ var form = new DynamicExportForm(typeof(Kanban), GetKanbanColumns().ColumnNames());
|
|
|
+ if (form.ShowDialog() != true)
|
|
|
+ return;
|
|
|
+ var export = Client.Query(
|
|
|
+ new Filter<Kanban>(x => x.ID).InQuery(GetKanbanSubscriberFilter(), x => x.Kanban.ID),
|
|
|
+ new Columns<Kanban>(form.Fields),
|
|
|
+ LookupFactory.DefineSort<Kanban>()
|
|
|
+ );
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- private void ItemsControl_DragOver(object sender, DragEventArgs e)
|
|
|
- {
|
|
|
+ var employee = "Tasks for " + string.Join(',', Employees.Where(x => Host.KanbanSettings.UserSettings.SelectedEmployees.Contains(x.Key)).Select(x => x.Value.Name));
|
|
|
|
|
|
+ ExcelExporter.DoExport<Kanban>(
|
|
|
+ export,
|
|
|
+ string.Format(
|
|
|
+ "{0} ({1:dd-MMM-yy})",
|
|
|
+ employee,
|
|
|
+ DateTime.Today
|
|
|
+ )
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- private void ItemsControl_Drop(object sender, DragEventArgs e)
|
|
|
+ private void FoldButton_Click(object sender, RoutedEventArgs e)
|
|
|
{
|
|
|
+ if (sender is not FrameworkElement element || element.Tag is not TasksByUserCategory category) return;
|
|
|
|
|
|
+ category.Collapsed = !category.Collapsed;
|
|
|
}
|
|
|
}
|