Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateAlarmDialog.razor
T
Joseph Doherty 9e243493fb ui: Central UI density/consistency sweep + Theme 0.4.1
Applies the family-wide admin-UI cleanup playbook to the Central UI so the
Blazor surfaces stop diverging from the shared kit: buttons are grouped rather
than individually sized, long cell values are contained instead of widening
tables, and hard-coded colours give way to theme tokens.

The headline fix is that MainLayout passed Accent="#2f5fd0" to ThemeShell,
which the kit emits as an inline style on the shell root. Being a descendant of
<html>, it beat the [data-bs-theme="dark"] override for the entire app, so the
dark accent had never rendered. Declaring --accent in site.css :root instead
lets both schemes resolve; light is unchanged because the value already matched
the kit's light default.

Theme pins to 0.4.1, which upstreams the local .btn sizing block verbatim, so
that block is deleted here rather than duplicated. Verified byte-identical
before removal; the repo now declares no --bs-btn-* anywhere.

NOT purely cosmetic, contrary to the sweep's stated scope: four detail-modal
surfaces (NotificationReport, ConfigurationAuditLog, ParkedMessages,
SiteCallsReport) were additionally refactored from holding the selected record
to holding its id and re-resolving from the current page each render, with the
resolve doubling as the visibility gate. A background refresh that drops the
row now closes the modal instead of showing a stale snapshot. This is a
behaviour change and is called out rather than buried: a full-suite run turned
up one intermittent CentralUI failure, CloseButton_DismissesModal, whose stack
(GetRequiredEventBindingEntry during DispatchEventAsync) indicates the handler
was disposed between render and click — a window the previous field-held record
made structurally impossible. Treat the modal lifecycle here as unreviewed.

Build 0/0; suite green apart from that one intermittent failure.
2026-08-11 05:50:12 -04:00

135 lines
5.3 KiB
Plaintext

@using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums
@*
M10 residual: body component for the template "Add / Edit Alarm" dialog hosted
by IDialogService.ShowAsync. Renders ONLY the form fields + action buttons
inside the host's .modal-body; the host owns the backdrop, header, focus trap,
Escape, and focus restoration.
Same contract as TemplateAttributeDialog: OnSaveAsync returns null on success
(dialog closes with true) or the error to render inline (dialog stays open).
*@
<div class="row g-3">
<div class="col-12">
<label class="form-label" for="alarm-name">Name</label>
<input id="alarm-name" type="text" class="form-control" @bind="_name" readonly="@Editing" />
</div>
<div class="col-12">
<label class="form-label" for="alarm-trigger-type">Trigger Type</label>
<select id="alarm-trigger-type" class="form-select" @bind="_triggerType" disabled="@Editing">
@foreach (var tt in Enum.GetValues<AlarmTriggerType>())
{
<option value="@tt">@tt</option>
}
</select>
</div>
<div class="col-12">
<label class="form-label" for="alarm-priority">Priority</label>
<input id="alarm-priority" type="number" class="form-control" @bind="_priority" min="0" max="1000" />
</div>
<div class="col-12">
<label class="form-label">Trigger Configuration</label>
<AlarmTriggerEditor TriggerType="@_triggerType"
Value="@_triggerConfig"
ValueChanged="@(v => _triggerConfig = v)"
AvailableAttributes="@AvailableAttributes"
FallbackPriority="@_priority" />
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="_isLocked" id="alarmLocked" />
<label class="form-check-label" for="alarmLocked">Locked</label>
</div>
</div>
@if (_error != null)
{
<div class="col-12"><div class="text-danger small" data-test="alarm-form-error">@_error</div></div>
}
</div>
<div class="modal-footer px-0 pb-0 mt-3">
<div class="btn-group btn-group-sm" role="group">
<button class="btn btn-outline-secondary" @onclick="() => Context.Cancel()" disabled="@_busy">Cancel</button>
<button class="btn btn-success" @onclick="Submit" disabled="@_busy">@(Editing ? "Save" : "Add")</button>
</div>
</div>
@code {
/// <summary>Host-supplied context: Close(true) on a successful save, Cancel on dismiss.</summary>
[Parameter, EditorRequired] public DialogContext<bool> Context { get; set; } = default!;
/// <summary>True when editing an existing alarm (name + trigger type become read-only).</summary>
[Parameter] public bool Editing { get; set; }
[Parameter] public string InitialName { get; set; } = string.Empty;
[Parameter] public int InitialPriority { get; set; }
[Parameter] public AlarmTriggerType InitialTriggerType { get; set; }
[Parameter] public string? InitialTriggerConfig { get; set; }
[Parameter] public bool InitialIsLocked { get; set; }
/// <summary>Attribute choices offered by the structured trigger editor's picker.</summary>
[Parameter] public IReadOnlyList<AlarmAttributeChoice> AvailableAttributes { get; set; }
= Array.Empty<AlarmAttributeChoice>();
/// <summary>
/// Persists the draft. Returns <c>null</c> on success (the dialog closes with
/// <c>true</c>) or the message to render inline, leaving the dialog open.
/// </summary>
[Parameter, EditorRequired] public Func<AlarmDraft, Task<string?>> OnSaveAsync { get; set; } = default!;
private string _name = string.Empty;
private int _priority;
private AlarmTriggerType _triggerType;
private string? _triggerConfig;
private bool _isLocked;
private string? _error;
private bool _busy;
protected override void OnInitialized()
{
_name = InitialName;
_priority = InitialPriority;
_triggerType = InitialTriggerType;
_triggerConfig = InitialTriggerConfig;
_isLocked = InitialIsLocked;
}
private async Task Submit()
{
if (_busy) return;
_error = null;
if (string.IsNullOrWhiteSpace(_name)) { _error = "Name is required."; return; }
_busy = true;
try
{
var error = await OnSaveAsync(new AlarmDraft(_name, _triggerType, _priority, _triggerConfig, _isLocked));
if (error == null)
{
Context.Close(true);
}
else
{
_error = error;
}
}
finally
{
_busy = false;
}
}
/// <summary>The authored alarm as submitted; the page performs the add/update.</summary>
/// <param name="Name">Alarm name (ignored on edit — the stored name is fixed).</param>
/// <param name="TriggerType">Selected trigger type (ignored on edit — server-fixed).</param>
/// <param name="Priority">Priority level.</param>
/// <param name="TriggerConfig">Structured trigger configuration payload.</param>
/// <param name="IsLocked">Whether instances may override the alarm.</param>
public sealed record AlarmDraft(
string Name,
AlarmTriggerType TriggerType,
int Priority,
string? TriggerConfig,
bool IsLocked);
}