fix(centralui): apply status/stuck query-string filters on the Site Calls page

This commit is contained in:
Joseph Doherty
2026-05-21 05:08:50 -04:00
parent 44f1ee372a
commit b3b02a8cb6
2 changed files with 138 additions and 0 deletions

View File

@@ -1,3 +1,5 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using ScadaLink.CentralUI.Components.Shared;
using ScadaLink.Commons.Entities.Sites;
@@ -26,11 +28,32 @@ namespace ScadaLink.CentralUI.Components.Pages.SiteCalls;
/// a relay that never reaches the site is a transient transport condition, surfaced
/// to the operator differently from a generic failure.
/// </para>
///
/// <para>
/// Query-string drill-in: the Health-dashboard Site Call KPI tiles deep-link here
/// with <c>?status=Parked</c> (Parked tile) or <c>?stuck=true</c> (Stuck tile). On
/// initialization those params seed <see cref="_statusFilter"/> / <see cref="_stuckOnly"/>
/// BEFORE the first <see cref="RefreshAll"/>, so the first grid load is already
/// filtered and the filter card controls reflect the seeded values. Parsing is lax
/// — an absent, blank, or unrecognised value is silently dropped and the page loads
/// unfiltered, mirroring <c>AuditLogPage</c>'s drill-in convention.
/// </para>
/// </summary>
public partial class SiteCallsReport
{
private const int PageSize = 50;
[Inject] private NavigationManager Navigation { get; set; } = null!;
// The Status filter <select> options — the exact strings the dropdown binds and
// the KPI tiles emit (e.g. ?status=Parked). A query-string status only seeds the
// filter when it matches one of these (case-insensitively); anything else is
// dropped so a hand-crafted bad URL still renders the page unfiltered.
private static readonly string[] ValidStatuses =
{
"Submitted", "Forwarded", "Attempted", "Delivered", "Parked", "Failed", "Discarded",
};
private ToastNotification _toast = default!;
private List<Site> _sites = new();
@@ -77,9 +100,51 @@ public partial class SiteCallsReport
Logger.LogWarning(ex, "Failed to load sites for the Site Calls source-site filter.");
}
// Seed filters from ?status= / ?stuck= BEFORE the first fetch so the initial
// grid load is already filtered (and the filter card controls reflect it).
ApplyQueryStringFilters();
await RefreshAll();
}
/// <summary>
/// Pre-apply the Health-dashboard KPI-tile drill-in filters from the URL query
/// string. <c>?status=&lt;status&gt;</c> seeds <see cref="_statusFilter"/> when it
/// matches a known status (case-insensitive); <c>?stuck=true</c> seeds
/// <see cref="_stuckOnly"/>. Lax parsing — an absent, blank, or unrecognised value
/// is silently dropped, leaving the filter empty (the no-param behaviour).
/// </summary>
private void ApplyQueryStringFilters()
{
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
var query = QueryHelpers.ParseQuery(uri.Query);
if (query.Count == 0)
{
return;
}
if (query.TryGetValue("status", out var statusValues))
{
var v = statusValues.ToString();
// Round-trip the dropdown's own option strings (the KPI tile emits the
// canonical casing, e.g. ?status=Parked); normalise to that casing so the
// <select> binds. An unrecognised value leaves the filter unset.
var match = ValidStatuses.FirstOrDefault(
s => string.Equals(s, v?.Trim(), StringComparison.OrdinalIgnoreCase));
if (match is not null)
{
_statusFilter = match;
}
}
if (query.TryGetValue("stuck", out var stuckValues)
&& bool.TryParse(stuckValues.ToString(), out var stuck))
{
_stuckOnly = stuck;
}
}
/// <summary>Re-fetch the current page (Refresh button, and after a relay action).</summary>
private async Task RefreshAll()
{