using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.Linq; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using Comal.Classes; using InABox.Clients; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; using Syncfusion.UI.Xaml.Kanban; using Syncfusion.Windows.Tools.Controls; namespace PRSDesktop { public class UserTasksHeaderImageConverter : IValueConverter { public static Dictionary Images { get; set; } public static Dictionary Employees { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var anonymous = Resources.anonymous.AsBitmapImage(); if (Images == null) return anonymous; if (Employees == null) return anonymous; var dataContext = value as ColumnTag; if (dataContext == null) return anonymous; var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance); if (getter == null) return anonymous; var column = (KanbanColumn)getter.GetValue(dataContext); if (column == null) return anonymous; if (!Guid.TryParse(column.Categories, out var empid)) return anonymous; if (!Employees.TryGetValue(empid, out var imageid)) return anonymous; if (!Images.TryGetValue(imageid, out var data)) return anonymous; return ImageUtils.LoadImage(data); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } public class UserTasksHeaderTimeConverter : IValueConverter { public static IEnumerable Kanbans { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (Kanbans == null) return "0:00"; var dataContext = value as ColumnTag; if (dataContext == null) return "0:00"; var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance); if (getter == null) return "0:00"; var column = (KanbanColumn)getter.GetValue(dataContext); if (column == null) return "0:00"; double result = 0.0F; foreach (var kanban in Kanbans.Where(x => Equals(x.Category, column.Categories))) result += kanban.EstimatedTime.TotalHours; return string.Format("{0:F2}", result); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } //public class TaskHeaderWidthConverter : IValueConverter //{ // public object Convert(object value, Type targetType, object parameter, CultureInfo culture) // { // var dataContext = (value as ColumnTag); // if (dataContext == null) // return 150; // PropertyInfo getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance); // if (getter == null) // return 150; // KanbanColumn column = (KanbanColumn)getter.GetValue(dataContext); // if (column == null) // return 150; // return Math.Max(150, column.Width) - 20 ; // } // public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) // { // throw new NotImplementedException(); // } //} /// /// Interaction logic for TaskListPanel.xaml /// public partial class TasksByUserControl : UserControl, ITaskControl { private Dictionary _employees; private List _models = new(); private ILookup _teamemployees; private Dictionary _teams; private bool bPopulating; public TasksByUserControl() { InitializeComponent(); } public bool IsReady { get; set; } #region Setup public void Setup() { SetupToolbar(); SplitPanel.AnchorWidth = Host.KanbanSettings.UserSettings.AnchorWidth; TeamsRow.Height = new GridLength(Host.KanbanSettings.UserSettings.TeamsHeight); LoadEmployees(); foreach (var team in Host.KanbanSettings.UserSettings.SelectedTeams) SelectedTeams.SelectedItems.Add(_teams.FirstOrDefault(x => Equals(x.Key, team))); PopulateEmployees(); PopulateKanbanTypes(); } private void PopulateKanbanTypes() { TaskType.Items.Add(""); foreach(var kanbanType in Host.KanbanTypes) { TaskType.Items.Add(kanbanType); } } #endregion private void SetupToolbar() { IncludeCompleted.Visibility = Security.IsAllowed() ? Visibility.Visible : Visibility.Collapsed; IncludeCompleted.IsChecked = IncludeCompleted.Visibility == Visibility.Visible ? Host.KanbanSettings.UserSettings.IncludeCompleted : true; IncludeObserved.IsChecked = Host.KanbanSettings.UserSettings.IncludeObserved; IncludeManaged.IsChecked = Host.KanbanSettings.UserSettings.IncludeManaged; ViewType.SelectedIndex = Host.KanbanSettings.UserSettings.CompactView ? 1 : 0; } public void Refresh(bool resetselection) { var _swimlanes = new Dictionary { { "Open", 0 }, { "In Progress", 1 }, { "Waiting", 2 }, { "Complete", 3 } }; var filter = new Filter(c => c.Kanban.Closed).IsEqualTo(DateTime.MinValue) .And(x => x.Kanban.Locked).IsEqualTo(false); var privateFilter = new Filter(x => x.Kanban.Private).IsEqualTo(false); if (App.EmployeeID != Guid.Empty) { privateFilter = privateFilter.Or(x => x.Employee.ID).IsEqualTo(App.EmployeeID); } filter.And(privateFilter); if (Host.Job != null) { if (Host.Job.ID != Guid.Empty) filter = filter.And(c => c.Kanban.JobLink.ID).IsEqualTo(Host.Job.ID); else filter = filter.And(c => c.Kanban.JobLink.ID).None(); } if (!Host.KanbanSettings.UserSettings.IncludeCompleted) filter = filter.And(new Filter(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue)); var emps = _employees.Where(x => Host.KanbanSettings.UserSettings.SelectedEmployees.Contains(x.Key)); //if (Host.Settings.UserSettings.IncludeObserved) // filter = filter.And(x => x.Manager).IsEqualTo(false); //else // filter = filter.And(x => x.Assignee).IsEqualTo(true); filter = filter.And(c => c.Employee.ID).InList(emps.Select(x => x.Key).ToArray()); if (!Host.KanbanSettings.UserSettings.IncludeObserved) { if (Host.KanbanSettings.UserSettings.IncludeManaged) filter = filter.And(new Filter(x => x.Manager).IsEqualTo(true).Or(x => x.Assignee).IsEqualTo(true)); else filter = filter.And(x => x.Assignee).IsEqualTo(true); } using (new WaitCursor()) { var kanbans = new Client().Query( filter, new Columns ( 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(x => x.Kanban.DueDate) { Direction = SortDirection.Ascending } ); var models = new List(); foreach (var row in kanbans.Rows) { var empid = row.Get(e => e.Kanban.EmployeeLink.ID); var mgrid = row.Get(e => e.Kanban.ManagerLink.ID); var subid = row.Get(e => e.Employee.ID); var empValid = Entity.IsEntityLinkValid(x => x.Kanban.EmployeeLink, row); var mgrValid = Entity.IsEntityLinkValid(x => x.Kanban.ManagerLink, row); var completed = row.Get(e => e.Kanban.Completed); var locked = row.Get(e => e.Kanban.Locked); var typeID = row.Get(e => e.Kanban.Type.ID); var typeCode = row.Get(e => e.Kanban.Type.Code); var job = row.Get(x => x.Kanban.JobLink.JobNumber); var model = new TaskModel(); model.Title = row.Get(x => x.Kanban.Title); model.ID = row.Get(x => x.Kanban.ID).ToString(); model.Description = row.Get(x => x.Kanban.Summary) ?? ""; model.Category = row.Get(c => c.Employee.ID).ToString(); model.Assignee = row.Get(c => c.Kanban.Category); if (string.IsNullOrWhiteSpace(model.Assignee) || !_swimlanes.ContainsKey(model.Assignee)) model.Assignee = "Open"; var kanbancolor = subid == empid ? TaskModel.KanbanColor( row.Get(x => x.Kanban.DueDate), row.Get(x => x.Kanban.Completed)) : subid == mgrid ? Color.Silver : Color.Plum; if (row.Get(x => x.Kanban.Locked)) kanbancolor = kanbancolor.MixColors(0.5F, Color.White); model.ColorKey = ImageUtils.ColorToString(kanbancolor); model.Image = null; model.ImageURL = null; model.Attachments = row.Get(x => x.Kanban.Attachments) > 0; // ? PRSDesktop.Resources.attachment.AsBitmapImage() : null; model.DueDate = row.Get(x => x.Kanban.DueDate); model.CompletedDate = row.Get(x => x.Kanban.Completed); model.Locked = row.Get(x => x.Kanban.Locked); // ? PRSDesktop.Resources.locked.AsBitmapImage() : null; model.EstimatedTime = row.Get(x => x.Kanban.EstimatedTime); var notes = new List> { new() }; var kanbanNotes = row.Get(x => x.Kanban.Notes); if(kanbanNotes != null) { foreach (var line in kanbanNotes) { if (line == "===================================") { notes.Add(new()); } else { notes.Last().Add(line); } } } model.Notes = string.Join("\n===================================\n", notes.Reverse>().Select(x => string.Join('\n', x))); model.EmployeeID = empid; model.ManagerID = mgrid; var sEmp = empid == row.Get(c => c.Employee.ID) ? "" : !empValid ? " to (Unallocated)" : " to " + row.Get(x => x.Kanban.EmployeeLink.Name); var sMgr = !mgrValid || mgrid == empid ? "" : " by " + row.Get(x => x.Kanban.ManagerLink.Name); model.AssignedTo = !string.IsNullOrEmpty(sEmp) || !string.IsNullOrWhiteSpace(sMgr) ? string.Format("Assigned{0}{1}", sEmp, sMgr) : ""; model.JobID = row.Get(x => x.Kanban.JobLink.ID); model.JobNumber = row.Get(x => x.Kanban.JobLink.JobNumber)?.Trim() ?? ""; model.JobName = row.Get(x => x.Kanban.JobLink.Name); model.Checked = false; //ischecked?.Invoke() == true; model.Type = new KanbanType { ID = typeID, Code = typeCode }; model.Number = row.Get(x => x.Kanban.Number); model.PropertyChanged += Model_PropertyChanged; models.Add(model); } UserTasksHeaderTimeConverter.Kanbans = models; Kanban.Columns.Clear(); foreach (var employee in emps) Kanban.Columns.Add(new KanbanColumn { Categories = employee.Key.ToString(), Title = employee.Value, Width = Math.Max(150, Kanban.ActualWidth / emps.ToArray().Length - 1.0F) }); //var template = Resources["SimpleHeader"] as DataTemplate; //var boundary = template.FindName("Boundary", null); if (Kanban.Columns.Count > 0) Kanban.ColumnWidth = Math.Max(150, (Kanban.ActualWidth - 20F) / Kanban.Columns.Count - 1.0F); _models = models.OrderBy(x => _swimlanes[x.Assignee]).ThenBy(x => x.DueDate).ToList(); FilterKanbans(); } } private void Model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { if(e.PropertyName == nameof(TaskModel.Assignee)) { using (new WaitCursor()) { var model = sender as TaskModel; var models = new TaskModel[] { model }; var kanban = Host.LoadKanbans(models, new Columns(x => x.ID, x => x.Category)).First(); kanban.Category = model.Assignee; new Client().Save(kanban, string.Format("Task Category Updated to {0}", model.Assignee), (o, err) => { }); model.Checked = false; FilterKanbans(); } } } private void SaveSettings() { Host.KanbanSettings.UserSettings.AnchorWidth = SplitPanel.AnchorWidth; Host.KanbanSettings.UserSettings.TeamsHeight = SelectedTeams.ActualHeight; var teams = SelectedTeams.SelectedItems.Select(x => ((KeyValuePair)x).Key); Host.KanbanSettings.UserSettings.SelectedTeams = teams.ToArray(); var emps = SelectedEmployees.SelectedItems.Select(x => ((KeyValuePair)x).Key); emps = emps.Where(e => _teamemployees.Any(t => t.Contains(e))); Host.KanbanSettings.UserSettings.SelectedEmployees = emps.ToArray(); Host.KanbanSettings.UserSettings.IncludeCompleted = IncludeCompleted.IsChecked == true; Host.KanbanSettings.UserSettings.IncludeObserved = IncludeObserved.IsChecked == true; Host.KanbanSettings.UserSettings.IncludeManaged = IncludeManaged.IsChecked == true; Host.SaveSettings(); } private void Kanban_SizeChanged(object sender, SizeChangedEventArgs e) { Kanban.ColumnWidth = Kanban.ActualWidth / Kanban.Columns.Count - 1.0F; } private void SplitPanel_OnChanged(object sender, DynamicSplitPanelSettings e) { if (!IsReady || Equals(Host.KanbanSettings.UserSettings.AnchorWidth, e.AnchorWidth)) return; SaveSettings(); } private void LoadEmployees() { var empfilter = LookupFactory.DefineFilter(); UserTasksHeaderImageConverter.Images = new Client().Query( new Filter(x => x.ID).InQuery(empfilter, x => x.Thumbnail.ID), new Columns(x => x.ID).Add(x => x.Data)).ToDictionary(x => x.ID, x => x.Data); var query = new MultiQuery(); query.Add( LookupFactory.DefineFilter(), new Columns(x => x.ID) .Add(x => x.Name) .Add(x => x.Thumbnail.ID), new SortOrder(x => x.Name) ); query.Add( LookupFactory.DefineFilter(), new Columns(x => x.ID) .Add(x => x.Name), new SortOrder(x => x.Name) ); query.Add( LookupFactory.DefineFilter(), new Columns(x => x.EmployeeLink.ID) .Add(x => x.TeamLink.ID) ); query.Query(); _teams = query.Get().ToDictionary(x => x.ID, x => x.Name); _employees = query.Get().ToDictionary(x => x.ID, x => x.Name, x => x.Name); UserTasksHeaderImageConverter.Employees = query.Get().ToDictionary(x => x.ID, x => x.Thumbnail.ID); _teamemployees = query.Get().ToLookup(x => x.TeamLink.ID, x => x.EmployeeLink.ID); SelectedTeams.ItemsSource = _teams; } private void PopulateEmployees() { bPopulating = true; try { var availableemployees = new List(); foreach (KeyValuePair team in SelectedTeams.SelectedItems) availableemployees.AddRange(_teamemployees[team.Key].Where(x => !availableemployees.Contains(x))); 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))) SelectedEmployees.SelectedItems.Add(_employees.FirstOrDefault(x => Equals(x.Key, employee))); } catch (Exception e) { } bPopulating = false; } private bool FilterKanban(TaskModel model, string searches, params Func[] properties) { foreach (var search in searches.Split(' ')) foreach (var property in properties) if (!property(model).Contains(search)) return false; return true; } private void FilterKanbans() { IEnumerable Items = _models; if (TaskType.SelectedItem is KanbanType kanbanType) { Items = Items.Where(x => x.Type.ID == kanbanType.ID); } if (!string.IsNullOrWhiteSpace(Search.Text)) { var searches = Search.Text.Split(); Items = Items.Where(x => x.Search(searches)); } if(object.Equals(Kanban.ItemsSource, Items)) { // Triggers a refresh. Kanban.ItemsSource = null; } Kanban.ItemsSource = Items; } private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (Kanban != null) Kanban.CardTemplate = ViewType.SelectedIndex > 0 ? Resources["CompactKanban"] as DataTemplate : Resources["FullKanban"] as DataTemplate; if (IsReady) { Host.KanbanSettings.StatusSettings.CompactView = ViewType.SelectedIndex > 0; Host.SaveSettings(); } } private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e) { FilterKanbans(); } private void IncludeLocked_Checked(object sender, RoutedEventArgs e) { if (!IsReady) return; SaveSettings(); Refresh(true); } private void IncludeManaged_Checked(object sender, RoutedEventArgs e) { if (!IsReady) return; SaveSettings(); Refresh(true); } private void IncludeObserved_Checked(object sender, RoutedEventArgs e) { if (!IsReady) return; SaveSettings(); Refresh(true); } private void IncludeCompleted_Checked(object sender, RoutedEventArgs e) { if (!IsReady) return; SaveSettings(); Refresh(true); } private void Search_KeyUp(object sender, KeyEventArgs e) { FilterKanbans(); } private void Export_Click(object sender, RoutedEventArgs e) { } private void Kanban_CardDragStart(object sender, KanbanDragStartEventArgs e) { var models = SelectedModels(e.SelectedCard.Content as TaskModel).ToList(); if (models.Any(x => x.Locked || x.EmployeeID != Guid.Parse(x.Category.ToString())) || !Host.CanChangeTasks(models)) e.IsCancel = true; } private void Kanban_CardDragEnd(object sender, KanbanDragEndEventArgs e) { using (new WaitCursor()) { var target = e.TargetColumn.Categories; var targetCategory = e.TargetKey; var models = SelectedModels(e.SelectedCard.Content as TaskModel).Where(x => !Equals(x.Category, target)).ToList(); if (!models.Any()) return; var kanbans = Host.LoadKanbans(models, new Columns(x => x.ID, x => x.EmployeeLink.ID, x => x.Private, x => x.Number)); var subscribers = new ClientKanbanSubscriberSet(kanbans.Select(x => x.ID)); var targetID = Guid.Parse(target); var updated = new List(); foreach (var kanban in kanbans) { if (!kanban.Private) { kanban.EmployeeLink.ID = targetID; subscribers.EnsureAssignee(kanban.ID, kanban.EmployeeLink.ID); updated.Add(kanban); } else { MessageBox.Show($"Cannot change assignee for task {kanban.Number} because it is private."); models.RemoveAll(x => x.ID == kanban.ID.ToString()); } } new Client().Save(updated, string.Format("Task Employee Updated to {0}", target), (o, err) => { }); subscribers.Save(false); foreach (var model in models) { model.Checked = false; model.Category = target; model.EmployeeID = targetID; } FilterKanbans(); } } #region ITaskControl Support public ITaskHost Host { get; set; } public KanbanViewType KanbanViewType => KanbanViewType.User; public IEnumerable SelectedModels(TaskModel? sender = null) { var result = _models.Where(x => x.Checked).ToList(); if (sender != null && !result.Contains(sender)) result.Add(sender); return result; } #endregion #region Kanban Actions private void DoEdit(TaskModel task) { if (task == null) return; var result = Host.EditReferences(new[] { task }); if (result) Refresh(true); } private void TaskMenu_Opened(object sender, RoutedEventArgs e) { Host.PopulateMenu(this, (sender as ContextMenu)!); } private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount > 1) { var task = ((Border)sender).Tag as TaskModel; DoEdit(task); e.Handled = true; } } private void CheckBox_Checked(object sender, RoutedEventArgs e) { } #endregion #region Employee List Actions private void SelectedTeams_ItemChecked(object sender, ItemCheckedEventArgs e) { if (!IsReady) return; PopulateEmployees(); SaveSettings(); Refresh(true); } private void SelectedTeams_SizeChanged(object sender, SizeChangedEventArgs e) { if (!IsReady || Equals(Host.KanbanSettings.UserSettings.TeamsHeight, SelectedTeams.ActualHeight)) return; SaveSettings(); } private void EmployeesSelectionChanged(object sender, SelectionChangedEventArgs e) { if (!IsReady || bPopulating) return; SaveSettings(); Refresh(true); } public string SectionName => "Tasks By User"; public DataModel DataModel(Selection selection) { return new AutoDataModel(new Filter(x => x.ID).IsEqualTo(Guid.Empty)); } #endregion private void Kanban_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { e.Handled = true; } } }