feat(uns): equipment detail page shell + Details tab + create-redirect

This commit is contained in:
Joseph Doherty
2026-06-11 14:36:48 -04:00
parent 5cae3c5b96
commit 7fbfeca451
5 changed files with 363 additions and 0 deletions
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
/// <summary>The UNS-line and driver options the equipment page's Details tab offers, scoped to the
/// cluster that owns a given line. Empty lists when the line can't be resolved to a cluster.</summary>
public sealed record EquipmentPickContext(
IReadOnlyList<(string Id, string Display)> Lines,
IReadOnlyList<(string Id, string Display)> Drivers);
@@ -205,6 +205,18 @@ public interface IUnsTreeService
/// <returns>The cluster's drivers projected to <c>(DriverInstanceId, Display)</c> pairs.</returns>
Task<IReadOnlyList<(string DriverInstanceId, string Display)>> LoadDriversForClusterAsync(string clusterId, CancellationToken ct = default);
/// <summary>
/// Loads the UNS-line and driver <c>(Id, Display)</c> option lists the equipment page's Details tab
/// offers, scoped to the cluster that owns the supplied line: every line in that cluster (for the
/// line picker) and that cluster's drivers (reusing <see cref="LoadDriversForClusterAsync"/>).
/// Centralizes the resolution the page would otherwise need the loaded tree to perform. Returns
/// empty lists when <paramref name="lineId"/> is null/empty or cannot be resolved to a cluster.
/// </summary>
/// <param name="lineId">The line whose owning cluster scopes the option lists; null/empty yields empties.</param>
/// <param name="ct">A token to cancel the load.</param>
/// <returns>The cluster-scoped line and driver options, or empty lists when the line can't be resolved.</returns>
Task<EquipmentPickContext> LoadEquipmentPickContextAsync(string? lineId, CancellationToken ct = default);
/// <summary>
/// Creates a new UNS area under a cluster. Fails if an area with the same id already exists.
/// Whitespace-only notes are stored as <c>null</c>.
@@ -266,6 +266,33 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
.ToList();
}
/// <inheritdoc />
public async Task<EquipmentPickContext> LoadEquipmentPickContextAsync(string? lineId, CancellationToken ct = default)
{
var empty = new EquipmentPickContext(Array.Empty<(string, string)>(), Array.Empty<(string, string)>());
if (string.IsNullOrEmpty(lineId)) return empty;
await using var db = await dbFactory.CreateDbContextAsync(ct);
// line -> area -> clusterId
var clusterId = await (from l in db.UnsLines.AsNoTracking()
join a in db.UnsAreas.AsNoTracking() on l.UnsAreaId equals a.UnsAreaId
where l.UnsLineId == lineId
select a.ClusterId).FirstOrDefaultAsync(ct);
if (string.IsNullOrEmpty(clusterId)) return empty;
// all lines in that cluster (for the line <select>)
var lines = await (from l in db.UnsLines.AsNoTracking()
join a in db.UnsAreas.AsNoTracking() on l.UnsAreaId equals a.UnsAreaId
where a.ClusterId == clusterId
orderby l.Name
select new { l.UnsLineId, l.Name }).ToListAsync(ct);
var lineOptions = lines.Select(x => (x.UnsLineId, x.Name)).ToList();
var drivers = await LoadDriversForClusterAsync(clusterId, ct);
return new EquipmentPickContext(lineOptions, drivers);
}
/// <inheritdoc />
public async Task<UnsMutationResult> CreateAreaAsync(
string clusterId,