From cc485a83e40b525b8004eaf16f13c4e0fe42c87f Mon Sep 17 00:00:00 2001 From: PhilBradbury Date: Wed, 10 Jun 2026 11:42:03 +0100 Subject: [PATCH 01/16] Added new property to hold the current calculated grde so we can filter on it as a simple integer where required. --- PPMTool.Data/Entities/Person.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/PPMTool.Data/Entities/Person.cs b/PPMTool.Data/Entities/Person.cs index 7823693e..418af7a7 100644 --- a/PPMTool.Data/Entities/Person.cs +++ b/PPMTool.Data/Entities/Person.cs @@ -308,5 +308,13 @@ public double GetWorkloadModelTotalOnDate(DateTime date) .OrderBy(x => x.ChangeDate) .LastOrDefault(); } + + /// + /// Stores the calculated grade based on the person's current WLM and the date. + /// Used for filtering by Grade on the Manage People page currently. + /// + /// + [NotMapped] + public int CurrentGrade { get; set; } } } From d92f4ee14abc59409b00cb5f2288c191f970d1f5 Mon Sep 17 00:00:00 2001 From: PhilBradbury Date: Wed, 10 Jun 2026 11:43:02 +0100 Subject: [PATCH 02/16] Altered the filtering for Line Manager to use the Person.LineManager.Name property rather than custom filtering based on pattern matching and Linq. --- PPMTool/Pages/People.razor | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/PPMTool/Pages/People.razor b/PPMTool/Pages/People.razor index 52e01d8e..a048dbf0 100644 --- a/PPMTool/Pages/People.razor +++ b/PPMTool/Pages/People.razor @@ -64,7 +64,7 @@ SPDX-License-Identifier: apache-2.0 - + - - - + + + - + @if (skillsEnabled) @@ -95,7 +95,7 @@ SPDX-License-Identifier: apache-2.0 - + From bc0f797d94a0e47db725ec0c462bf79329c6ffb2 Mon Sep 17 00:00:00 2001 From: PhilBradbury Date: Wed, 10 Jun 2026 11:44:27 +0100 Subject: [PATCH 03/16] Kept the Skilltag filtering as custom but removed similar for Line Manager filtering. Also adjusted filtering by CurrentGrade to use a calculated integer value. --- PPMTool/Pages/People.razor.cs | 113 +++++++++++++++------------------- 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/PPMTool/Pages/People.razor.cs b/PPMTool/Pages/People.razor.cs index bd54972e..f5659f6b 100644 --- a/PPMTool/Pages/People.razor.cs +++ b/PPMTool/Pages/People.razor.cs @@ -112,87 +112,74 @@ private void LoadData(LoadDataArgs args) query = query.Where(x => ActiveUser.Person != null && x.PersonId == ActiveUser.Person!.PersonId); } - // Now apply the custom filters (pattern matching syntax for non-null object) + + // ---- CUSTOM FILTERS ONLY ---- if (args.Filters is { } filters && filters.Any()) { - // Filter on Skill Tags - var filter = args.Filters.FirstOrDefault(x => x.Property == "SkillTags"); - var filterValue = filter?.FilterValue as string; - if (filter != null && filterValue != null) - { - query = query.Where(x => x.OwnedSkills.Any(x => x.SkillTag.Name.Trim().ToLower().Contains(filterValue.Trim().ToLower()))); - } + // SkillTags custom filter + var skillFilter = filters.FirstOrDefault(x => x.Property == "SkillTags"); + var skillFilterValue = (skillFilter?.FilterValue as string)?.Trim(); - // Add filter on Line Manager - filter = filters.FirstOrDefault(f => f.Property == "LineManager"); - filterValue = (filter?.FilterValue as string)?.Trim(); - if (!string.IsNullOrEmpty(filterValue)) + if (!string.IsNullOrWhiteSpace(skillFilterValue)) { - var filterValueLower = filterValue.ToLower(); + var filterValueLower = skillFilterValue.ToLower(); + query = query.Where(x => - x.LineManager != null && - (x.LineManager.Name ?? "").Trim().ToLower().Contains(filterValueLower)); + x.OwnedSkills.Any(s => + (s.SkillTag.Name ?? "").Trim().ToLower().Contains(filterValueLower))); } + } - // Add any filters than are none of the special ones - var stdFilters = args.Filters.Where(x => x.Property != "SkillTags" && x.Property != "LineManager"); - if (stdFilters.Any()) - { - // Filter via the Where method - query = query.Where(args.Filter); - } + // ---- BUILT-IN RADZEN FILTERING ---- + if (!string.IsNullOrWhiteSpace(args.Filter)) + { + query = query.Where(args.Filter); } - // Sorting needed - if (!string.IsNullOrEmpty(args.OrderBy)) + // ---- SORTING ---- + if (args.Sorts != null && args.Sorts.Count() > 0) { - // Check details of the sort - if (args.Sorts is { } sorts && sorts.Any()) + var sort = args.Sorts.First(); + + // Special-case sort + if (sort.Property == "SkillsCount") + { + query = sort.SortOrder == SortOrder.Ascending + ? query.OrderBy(x => x.OwnedSkills.Count()) + : query.OrderByDescending(x => x.OwnedSkills.Count()); + } + else { - var sort = args.Sorts?.First(); - if (sort.Property == "SkillsCount") - { - // Apply the ordering process on skills count manually - if (sort.SortOrder == SortOrder.Ascending) - { - query = query.OrderBy(x => TagService.GetCountForPerson(Context, x.PersonId)); - } - else - { - query = query.OrderByDescending(x => TagService.GetCountForPerson(Context, x.PersonId)); - } - } - else if (sort.Property == "LineManager") - { - query = sort.SortOrder == SortOrder.Ascending ? - query.OrderBy(x => x.LineManager != null ? x.LineManager.Name : "") : - query.OrderByDescending(x => x.LineManager != null ? x.LineManager.Name : ""); - } - - // No special handling required - else - { - // Sort via the OrderBy method - query = query.OrderBy(args.OrderBy); - } + query = query.OrderBy(args.OrderBy); } } - - // Important!!! Make sure the Count property of RadzenDataGrid is set. - var data = query.ToList(); - count = data.Count(); - - // Perform paging via Skip and Take. - if (args.Skip == null) + else if (!string.IsNullOrWhiteSpace(args.OrderBy)) { - people = data.Take(pageCount).ToList(); + query = query.OrderBy(args.OrderBy); } - else + + // ---- COUNT BEFORE PAGING ---- + count = query.Count(); + + + foreach (Person p in query) { - people = data.Skip(args.Skip.Value).Take(args.Top.Value).ToList(); + p.CurrentGrade = p.WorkloadModelChanges + .Where(x => x.ChangeDate <= DateTime.Today) + .OrderBy(x => x.ChangeDate) + .Select(x => x.Grade) + .LastOrDefault(); } - Debug.WriteLine($"** {data.Count()} people loaded. {people.Count()} displayed."); + + // ---- PAGING ---- + var skip = args.Skip ?? 0; + var take = args.Top ?? pageCount; + + people = query.Skip(skip).Take(take).ToList(); + + Debug.WriteLine($"** {count} people loaded. {people.Count()} displayed."); + } } } \ No newline at end of file From a097e1e9bb8d73b2b58e063356302b8dc8490842 Mon Sep 17 00:00:00 2001 From: PhilBradbury Date: Wed, 10 Jun 2026 13:14:01 +0100 Subject: [PATCH 04/16] Updated the CurrenGrade property to get the grade based on today's date rather than being set in the calling code. --- PPMTool.Data/Entities/Person.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PPMTool.Data/Entities/Person.cs b/PPMTool.Data/Entities/Person.cs index 418af7a7..f30e427b 100644 --- a/PPMTool.Data/Entities/Person.cs +++ b/PPMTool.Data/Entities/Person.cs @@ -315,6 +315,6 @@ public double GetWorkloadModelTotalOnDate(DateTime date) /// /// [NotMapped] - public int CurrentGrade { get; set; } + public int? CurrentGrade { get => GetGradeOnDate(DateTime.Today); } } } From 622b517080ce704dac22a49d173cd5ec0208acd1 Mon Sep 17 00:00:00 2001 From: PhilBradbury Date: Wed, 10 Jun 2026 13:14:45 +0100 Subject: [PATCH 05/16] Removed population of the CurrentGrade property from the code as it's been moved into the entity. --- PPMTool/Pages/People.razor.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/PPMTool/Pages/People.razor.cs b/PPMTool/Pages/People.razor.cs index f5659f6b..744a4326 100644 --- a/PPMTool/Pages/People.razor.cs +++ b/PPMTool/Pages/People.razor.cs @@ -161,17 +161,6 @@ private void LoadData(LoadDataArgs args) // ---- COUNT BEFORE PAGING ---- count = query.Count(); - - foreach (Person p in query) - { - p.CurrentGrade = p.WorkloadModelChanges - .Where(x => x.ChangeDate <= DateTime.Today) - .OrderBy(x => x.ChangeDate) - .Select(x => x.Grade) - .LastOrDefault(); - } - - // ---- PAGING ---- var skip = args.Skip ?? 0; var take = args.Top ?? pageCount; From 15ff715200ffaf56469b3fd48f51ba9fc7b3b49f Mon Sep 17 00:00:00 2001 From: PhilBradbury Date: Wed, 10 Jun 2026 13:47:57 +0100 Subject: [PATCH 06/16] Adjusted default FilterMode to be simple. Adjusted Faculty and School filtering to use the Code for each as the filterable property. Removed filter from some columns where it didn't make much sense (costs, day rate, dates etc.) --- PPMTool/Pages/Projects.razor | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/PPMTool/Pages/Projects.razor b/PPMTool/Pages/Projects.razor index 1ccabce3..03d33715 100644 --- a/PPMTool/Pages/Projects.razor +++ b/PPMTool/Pages/Projects.razor @@ -44,7 +44,7 @@ SPDX-License-Identifier: apache-2.0 - + @@ -71,18 +71,18 @@ SPDX-License-Identifier: apache-2.0 @p.ProjectStatus.ToNiceString() - + - + - + - + - + - - + + } - + - + - + @@ -113,20 +127,48 @@ SPDX-License-Identifier: apache-2.0 @(p.CostModel.GetAttribute().Name) - + - - - + + + } - +