Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cc485a8
Added new property to hold the current calculated grde so we can filt…
PhilBradbury Jun 10, 2026
d92f4ee
Altered the filtering for Line Manager to use the Person.LineManager.…
PhilBradbury Jun 10, 2026
bc0f797
Kept the Skilltag filtering as custom but removed similar for Line Ma…
PhilBradbury Jun 10, 2026
a097e1e
Updated the CurrenGrade property to get the grade based on today's da…
PhilBradbury Jun 10, 2026
622b517
Removed population of the CurrentGrade property from the code as it's…
PhilBradbury Jun 10, 2026
15ff715
Adjusted default FilterMode to be simple.
PhilBradbury Jun 10, 2026
7a40ee6
Removed Custom filtering for Faculty/School.
PhilBradbury Jun 10, 2026
0751533
Merge branch 'dev' into 1366-filtering-on-some-pages-broken
PhilBradbury Jun 11, 2026
805d3b0
Merge branch 'dev' into 1366-filtering-on-some-pages-broken
PhilBradbury Jun 11, 2026
d626202
Added filtering back to most columns. Had to adjust the Project class…
PhilBradbury Jun 11, 2026
7aed35d
Merge branch '1366-filtering-on-some-pages-broken' of https://github.…
PhilBradbury Jun 11, 2026
b041340
Formatting fix
PhilBradbury Jun 11, 2026
d891473
Potential fix for pull request finding
aharwood2 Jun 17, 2026
bf5799d
Updated EndDate column to be checking against correct property.
PhilBradbury Jun 17, 2026
0600fcd
Merge branch '1366-filtering-on-some-pages-broken' of https://github.…
PhilBradbury Jun 17, 2026
6363606
Merge branch 'dev' into 1366-filtering-on-some-pages-broken
PhilBradbury Jun 17, 2026
f40ca3b
Merge remote-tracking branch 'origin/dev' into 1366-filtering-on-some…
aharwood2 Jun 30, 2026
7339951
Improved documentation of existing shadow properties
aharwood2 Jul 1, 2026
0cd497d
Refactor of timesheet template shadow property so that it is privatel…
aharwood2 Jul 1, 2026
91d5409
Addressed review comment from co-pilot regarding the DB query complex…
aharwood2 Jul 1, 2026
1f03b90
Addition of manual filtering helpers for Radzen data grids to allow o…
aharwood2 Jul 1, 2026
483e56c
Switched to use a shadow property instead so at least we are consistent
aharwood2 Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions PPMTool.Data/Entities/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,5 +308,23 @@ public double GetWorkloadModelTotalOnDate(DateTime date)
.OrderBy(x => x.ChangeDate)
.LastOrDefault();
}

/// <summary>
/// Shadow property to store a comma-separated list of the person's skill tag names.
/// Used for filtering on the Manage People page.
/// </summary>
[NotMapped]
public string SkillTags
{
get => string.Join(", ", OwnedSkills.Select(skill => skill.SkillTag.Name).OrderBy(name => name));
}

/// <summary>
/// Shadow property to store the current grade of the person based on the person's current WLM and the date.
/// Used for filtering by Grade on the Manage People page currently.
/// </summary>
/// <returns></returns>
[NotMapped]
public int? CurrentGrade { get => GetGradeOnDate(DateTime.Today); }
}
}
8 changes: 8 additions & 0 deletions PPMTool.Data/Entities/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ public class Project : BaseTask, ILoggableObject
/// </summary>
public virtual ICollection<FundingSource> FundingSources { get; set; } = new List<FundingSource>();

/// <summary>
/// Shadow property to allow easy filtering on FundsReceived in datagrids.
/// Requires additional logic in the service layer to populate this property when retrieving projects from the database.
/// </summary>
/// <returns></returns>
[NotMapped]
public double FundsReceived { get; set; }

/// <summary>
/// Constructor also adds default status messages
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion PPMTool.Data/Entities/Setting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public class Setting : ILoggableObject
public SettingType SettingType { get; set; }

/// <summary>
/// Shadow property to get the name of the setting type as a string for use in the UI. This is not mapped to the database as it is derived from the SettingType enum.
/// Shadow property to get the name of the setting type as a string for use in the UI.
/// This is not mapped to the database as it is derived from the SettingType enum.
/// </summary>
[NotMapped]
public string SettingTypeAsText => SettingType.ToString();
Expand Down
18 changes: 14 additions & 4 deletions PPMTool.Data/Entities/TimesheetEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,26 +73,36 @@ public class TimesheetEntry : ILoggableObject
public double SundayHours { get; set; }

/// <summary>
/// Total hours for the week for this entry
/// Total hours for the week for this entry. This must be explicitly updated with the <see cref="UpdateTotalHours"/> method.
/// </summary>
[NotMapped]
public double TotalHours { get; private set; }

/// <summary>
/// Represents whether this task is part of the user's template
/// Represents whether this task is part of the user's template. This must be explicitly set with the <see cref="SetIsInTemplate(bool)"/> method.
/// </summary>
[NotMapped]
public bool IsInTemplate { get; set; }
public bool IsInTemplate { get; private set; }

/// <summary>
/// Method to sum up the hours in the entry
/// Method to sum up the hours in the entry and update the TotalHours property.
/// This should be called whenever the hours for any day are modified.
/// </summary>
/// <returns></returns>
public void UpdateTotalHours()
{
TotalHours = MondayHours + TuesdayHours + WednesdayHours + ThursdayHours + FridayHours + SaturdayHours + SundayHours;
}

/// <summary>
/// Sets the IsInTemplate property to indicate whether this task is part of the user's template.
/// </summary>
/// <param name="isInTemplate"></param>
public void SetIsInTemplate(bool isInTemplate)
{
IsInTemplate = isInTemplate;
}

/// <summary>
/// Returns a useful string to identify the entity
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion PPMTool/Pages/AddTimesheet.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ private List<TimesheetEntry> OrderByTemplate(Timesheet timesheet, string templat
// Sets a boolean for use in the datagrid to show which items are part of the template
foreach (TimesheetEntry e in orderedResults)
{
e.IsInTemplate = order.Contains(e.InnateCodeTask.InnateCodeTaskId);
e.SetIsInTemplate(order.Contains(e.InnateCodeTask.InnateCodeTaskId));
}

return orderedResults;
Expand Down
14 changes: 7 additions & 7 deletions PPMTool/Pages/People.razor
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ SPDX-License-Identifier: apache-2.0
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Person" Property="ShortName" Title="Initials" Width="80px" FilterOperator="FilterOperator.Contains" MinWidth="10px" />
<RadzenDataGridColumn TItem="Person" Property="LineManager" Title="Line Manager" MinWidth="10px">
<RadzenDataGridColumn TItem="Person" Property="LineManager.Name" Title="Line Manager" FilterProperty="LineManager.Name" SortProperty="LineManager.Name" MinWidth="10px">
<Template Context="p">
<span style="display: flex; align-items: center">
@if (p.LineManager?.IsCurrentlyAbsent() ?? false)
Expand All @@ -75,16 +75,16 @@ SPDX-License-Identifier: apache-2.0
</span>
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Person" Property="StartDate" Title="Started" Width="80px" FormatString="{0:d}" MinWidth="10px" />
<RadzenDataGridColumn TItem="Person" Property="EndDate" Title="Left" Width="80px" FormatString="{0:d}" MinWidth="10px" />
<RadzenDataGridColumn TItem="Person" Property="WorkloadModelChanges" Title="Current Project Work FTE" Width="50px" MinWidth="10px" Sortable="false">
<RadzenDataGridColumn TItem="Person" Property="StartDate" Title="Started" Width="80px" FormatString="{0:d}" MinWidth="10px" Filterable="false" />
<RadzenDataGridColumn TItem="Person" Property="EndDate" Title="Left" Width="80px" FormatString="{0:d}" MinWidth="10px" Filterable="false" />
<RadzenDataGridColumn TItem="Person" Property="WorkloadModelChanges" Title="Current Project Work FTE" Width="50px" MinWidth="10px" Sortable="false" Filterable="false">
<Template Context="p">
@p.WorkloadModelChanges.Where(x => x.ChangeDate <= DateTime.Today).OrderBy(x => x.ChangeDate).LastOrDefault()?.ProjectWorkFTE
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Person" Property="WorkloadModelChanges" Title="Current Grade" Width="50px" MinWidth="10px" Sortable="false">
<RadzenDataGridColumn TItem="Person" Property="CurrentGrade" Title="Current Grade" Width="50px" MinWidth="10px" Sortable="false" TextAlign="TextAlign.Center">
<Template Context="p">
@p.WorkloadModelChanges.Where(x => x.ChangeDate <= DateTime.Today).OrderBy(x => x.ChangeDate).LastOrDefault()?.Grade
@p.CurrentGrade
</Template>
</RadzenDataGridColumn>
@if (skillsEnabled)
Expand All @@ -95,7 +95,7 @@ SPDX-License-Identifier: apache-2.0
</Template>
</RadzenDataGridColumn>

<RadzenDataGridColumn TItem="Person" Property="SkillsCount" Title="Skill Count" Width="3rem" MinWidth="10px">
<RadzenDataGridColumn TItem="Person" Property="SkillsCount" Title="Skill Count" Width="3rem" MinWidth="10px" Filterable="false">
<Template Context="p">
@TagService.GetCountForPerson(Context, p.PersonId).ToString()
</Template>
Expand Down
96 changes: 27 additions & 69 deletions PPMTool/Pages/People.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,87 +112,45 @@ 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)
if (args.Filters is { } filters && filters.Any())
// ---- GRID FILTERING ----
if (!string.IsNullOrWhiteSpace(args.Filter))
{
// 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())));
}
query = query.Where(args.Filter);
}

// ---- SORTING ----
if (args.Sorts != null && args.Sorts.Count() > 0)
{
var sort = args.Sorts.First();

// Add filter on Line Manager
filter = filters.FirstOrDefault(f => f.Property == "LineManager");
filterValue = (filter?.FilterValue as string)?.Trim();
if (!string.IsNullOrEmpty(filterValue))
// Special-case sort
if (sort.Property == "SkillsCount")
{
var filterValueLower = filterValue.ToLower();
query = query.Where(x =>
x.LineManager != null &&
(x.LineManager.Name ?? "").Trim().ToLower().Contains(filterValueLower));
query = sort.SortOrder == SortOrder.Ascending
? query.OrderBy(x => x.OwnedSkills.Count())
: query.OrderByDescending(x => x.OwnedSkills.Count());
}

// 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())
else
{
// Filter via the Where method
query = query.Where(args.Filter);
query = query.OrderBy(args.OrderBy);
}
}

// Sorting needed
if (!string.IsNullOrEmpty(args.OrderBy))
else if (!string.IsNullOrWhiteSpace(args.OrderBy))
{
// Check details of the sort
if (args.Sorts is { } sorts && sorts.Any())
{
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();
// ---- COUNT BEFORE PAGING ----
count = query.Count();

// Perform paging via Skip and Take.
if (args.Skip == null)
{
people = data.Take(pageCount).ToList();
}
else
{
people = data.Skip(args.Skip.Value).Take(args.Top.Value).ToList();
}
// ---- PAGING ----
var skip = args.Skip ?? 0;
var take = args.Top ?? pageCount;

people = query.Skip(skip).Take(take).ToList();

Debug.WriteLine($"** {data.Count()} people loaded. {people.Count()} displayed.");
Debug.WriteLine($"** {count} people loaded. {people.Count()} displayed.");
}

}
}
Loading
Loading