feat(uns): equipment is a tree leaf linking to the detail page; drop EquipmentModal
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns
|
||||
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Uns
|
||||
@inject IUnsTreeService Svc
|
||||
@inject NavigationManager Nav
|
||||
|
||||
<PageTitle>UNS</PageTitle>
|
||||
|
||||
@@ -41,7 +42,6 @@
|
||||
<UnsTree Roots="_roots" Filter="@_filter"
|
||||
OnToggleExpand="ToggleAsync"
|
||||
OnAddChild="HandleAddChild"
|
||||
OnAddVirtualTag="HandleAddVirtualTag"
|
||||
OnEdit="HandleEdit"
|
||||
OnDelete="HandleDelete" />
|
||||
</div>
|
||||
@@ -64,31 +64,6 @@
|
||||
OnSaved="OnModalSavedAsync"
|
||||
OnCancel="CloseModals" />
|
||||
|
||||
<EquipmentModal Visible="_equipmentModalVisible"
|
||||
IsNew="_equipmentModalIsNew"
|
||||
UnsLineId="@_equipmentModalLineId"
|
||||
Existing="_equipmentModalExisting"
|
||||
Lines="_equipmentModalLineOptions"
|
||||
Drivers="_equipmentModalDriverOptions"
|
||||
OnSaved="OnModalSavedAsync"
|
||||
OnCancel="CloseModals" />
|
||||
|
||||
<TagModal Visible="_tagModalVisible"
|
||||
IsNew="_tagModalIsNew"
|
||||
EquipmentId="@_tagModalEquipmentId"
|
||||
Existing="_tagModalExisting"
|
||||
Drivers="_tagModalDriverOptions"
|
||||
OnSaved="OnEquipmentChildModalSavedAsync"
|
||||
OnCancel="CloseModals" />
|
||||
|
||||
<VirtualTagModal Visible="_vtagModalVisible"
|
||||
IsNew="_vtagModalIsNew"
|
||||
EquipmentId="@_vtagModalEquipmentId"
|
||||
Existing="_vtagModalExisting"
|
||||
Scripts="_vtagModalScriptOptions"
|
||||
OnSaved="OnEquipmentChildModalSavedAsync"
|
||||
OnCancel="CloseModals" />
|
||||
|
||||
<ImportEquipmentModal Visible="_importModalVisible"
|
||||
OnImported="OnImportedAsync"
|
||||
OnCancel="CloseModals" />
|
||||
@@ -127,7 +102,7 @@
|
||||
private string? _filter;
|
||||
private bool _loading = true;
|
||||
|
||||
// Guards the async modal openers (HandleAddChild/HandleAddVirtualTag/HandleEdit) so a rapid
|
||||
// Guards the async modal openers (HandleAddChild/HandleEdit) so a rapid
|
||||
// double-action can't race two service loads into the same modal state.
|
||||
private bool _modalBusy;
|
||||
|
||||
@@ -144,34 +119,9 @@
|
||||
private LineEditDto? _lineModalExisting;
|
||||
private IReadOnlyList<(string Id, string Display)> _lineModalAreaOptions = Array.Empty<(string, string)>();
|
||||
|
||||
// --- Equipment modal state ---
|
||||
private bool _equipmentModalVisible;
|
||||
private bool _equipmentModalIsNew;
|
||||
private string? _equipmentModalLineId;
|
||||
private EquipmentEditDto? _equipmentModalExisting;
|
||||
private IReadOnlyList<(string Id, string Display)> _equipmentModalLineOptions = Array.Empty<(string, string)>();
|
||||
private IReadOnlyList<(string Id, string Display)> _equipmentModalDriverOptions = Array.Empty<(string, string)>();
|
||||
|
||||
// --- Tag modal state ---
|
||||
private bool _tagModalVisible;
|
||||
private bool _tagModalIsNew;
|
||||
private string? _tagModalEquipmentId;
|
||||
private TagEditDto? _tagModalExisting;
|
||||
private IReadOnlyList<(string Id, string Display, string DriverType)> _tagModalDriverOptions = Array.Empty<(string, string, string)>();
|
||||
|
||||
// --- Virtual-tag modal state ---
|
||||
private bool _vtagModalVisible;
|
||||
private bool _vtagModalIsNew;
|
||||
private string? _vtagModalEquipmentId;
|
||||
private VirtualTagEditDto? _vtagModalExisting;
|
||||
private IReadOnlyList<(string Id, string Display)> _vtagModalScriptOptions = Array.Empty<(string, string)>();
|
||||
|
||||
// --- Import-equipment-CSV modal state ---
|
||||
private bool _importModalVisible;
|
||||
|
||||
// --- Owning equipment to refresh in place after a tag/virtual-tag mutation ---
|
||||
private string? _childRefreshEquipmentId;
|
||||
|
||||
// --- Delete-confirm state ---
|
||||
private UnsNode? _confirmNode;
|
||||
private bool _confirmBusy;
|
||||
@@ -214,43 +164,21 @@
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Toggles a node's expansion. For equipment nodes whose children have not yet
|
||||
/// been loaded, lazily fetches the tag/virtual-tag leaves on first expand.
|
||||
/// Toggles a structural node's expansion. Equipment nodes are leaves (no expander),
|
||||
/// so this path only ever fires for Enterprise/Cluster/Area/Line.
|
||||
/// </summary>
|
||||
private async Task ToggleAsync(UnsNode node)
|
||||
private Task ToggleAsync(UnsNode node)
|
||||
{
|
||||
if (node.Loading) return;
|
||||
if (node.Loading) { return Task.CompletedTask; }
|
||||
|
||||
node.Expanded = !node.Expanded;
|
||||
|
||||
if (node.Kind == UnsNodeKind.Equipment && node.Expanded && !node.Loaded)
|
||||
{
|
||||
node.Error = null;
|
||||
node.Loading = true;
|
||||
StateHasChanged();
|
||||
try
|
||||
{
|
||||
var kids = await Svc.LoadEquipmentChildrenAsync(node.EntityId!);
|
||||
node.Children.Clear();
|
||||
node.Children.AddRange(kids);
|
||||
node.Loaded = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
node.Error = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
node.Loading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the create modal for a node's primary child: a cluster gets a new area; an area gets a
|
||||
/// new line scoped to its cluster; a line gets a new equipment scoped to its cluster; an equipment
|
||||
/// gets a new tag scoped to its candidate drivers.
|
||||
/// Opens the create modal for a structural node's primary child: a cluster gets a new area; an area
|
||||
/// gets a new line scoped to its cluster. A line's "+ Equipment" navigates to the equipment detail
|
||||
/// page in create mode rather than opening a modal.
|
||||
/// </summary>
|
||||
private async Task HandleAddChild(UnsNode node)
|
||||
{
|
||||
@@ -277,21 +205,7 @@
|
||||
break;
|
||||
|
||||
case UnsNodeKind.Line:
|
||||
_equipmentModalIsNew = true;
|
||||
_equipmentModalExisting = null;
|
||||
_equipmentModalLineId = node.EntityId;
|
||||
_equipmentModalLineOptions = LinesForCluster(node.ClusterId);
|
||||
_equipmentModalDriverOptions = await Svc.LoadDriversForClusterAsync(node.ClusterId!);
|
||||
_equipmentModalVisible = true;
|
||||
break;
|
||||
|
||||
case UnsNodeKind.Equipment:
|
||||
_tagModalIsNew = true;
|
||||
_tagModalExisting = null;
|
||||
_tagModalEquipmentId = node.EntityId;
|
||||
_childRefreshEquipmentId = node.EntityId;
|
||||
_tagModalDriverOptions = await Svc.LoadTagDriversForEquipmentAsync(node.EntityId!);
|
||||
_tagModalVisible = true;
|
||||
Nav.NavigateTo($"/uns/equipment/new?lineId={node.EntityId}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -301,31 +215,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Opens the create modal for a new virtual tag scoped to the clicked equipment.</summary>
|
||||
private async Task HandleAddVirtualTag(UnsNode node)
|
||||
{
|
||||
if (_modalBusy) { return; }
|
||||
_modalBusy = true;
|
||||
try
|
||||
{
|
||||
CloseModals();
|
||||
_vtagModalIsNew = true;
|
||||
_vtagModalExisting = null;
|
||||
_vtagModalEquipmentId = node.EntityId;
|
||||
_childRefreshEquipmentId = node.EntityId;
|
||||
_vtagModalScriptOptions = await Svc.LoadScriptsAsync();
|
||||
_vtagModalVisible = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_modalBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the edit modal for the clicked node, loading the entity first to prefill the form and
|
||||
/// capture its RowVersion. Tag/VirtualTag edits also stash the owning equipment id so a successful
|
||||
/// save can refresh just that equipment's children in place.
|
||||
/// capture its RowVersion. Only Area and Line are edited via a modal here; equipment is edited on
|
||||
/// its own detail page (opened via the tree's "Open" link).
|
||||
/// </summary>
|
||||
private async Task HandleEdit(UnsNode node)
|
||||
{
|
||||
@@ -354,39 +247,6 @@
|
||||
_lineModalAreaOptions = AreaOptionsForCluster(node.ClusterId);
|
||||
_lineModalVisible = true;
|
||||
break;
|
||||
|
||||
case UnsNodeKind.Equipment:
|
||||
var equipment = await Svc.LoadEquipmentAsync(node.EntityId!);
|
||||
if (equipment is null) { return; }
|
||||
_equipmentModalIsNew = false;
|
||||
_equipmentModalExisting = equipment;
|
||||
_equipmentModalLineId = equipment.UnsLineId;
|
||||
_equipmentModalLineOptions = LinesForCluster(node.ClusterId);
|
||||
_equipmentModalDriverOptions = await Svc.LoadDriversForClusterAsync(node.ClusterId!);
|
||||
_equipmentModalVisible = true;
|
||||
break;
|
||||
|
||||
case UnsNodeKind.Tag:
|
||||
var tag = await Svc.LoadTagAsync(node.EntityId!);
|
||||
if (tag is null) { return; }
|
||||
_tagModalIsNew = false;
|
||||
_tagModalExisting = tag;
|
||||
_tagModalEquipmentId = tag.EquipmentId;
|
||||
_childRefreshEquipmentId = tag.EquipmentId;
|
||||
_tagModalDriverOptions = await Svc.LoadTagDriversForEquipmentAsync(tag.EquipmentId);
|
||||
_tagModalVisible = true;
|
||||
break;
|
||||
|
||||
case UnsNodeKind.VirtualTag:
|
||||
var vtag = await Svc.LoadVirtualTagAsync(node.EntityId!);
|
||||
if (vtag is null) { return; }
|
||||
_vtagModalIsNew = false;
|
||||
_vtagModalExisting = vtag;
|
||||
_vtagModalEquipmentId = vtag.EquipmentId;
|
||||
_childRefreshEquipmentId = vtag.EquipmentId;
|
||||
_vtagModalScriptOptions = await Svc.LoadScriptsAsync();
|
||||
_vtagModalVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -422,8 +282,7 @@
|
||||
|
||||
/// <summary>
|
||||
/// Performs the pending delete. Loads the entity's RowVersion first, then dispatches on Kind.
|
||||
/// Area/Line/Equipment trigger a full structural reload on success; Tag/VirtualTag refresh only the
|
||||
/// owning equipment's children in place so the rest of the user's expansion is preserved.
|
||||
/// Area/Line/Equipment trigger a full structural reload on success.
|
||||
/// </summary>
|
||||
private async Task ConfirmDeleteAsync()
|
||||
{
|
||||
@@ -436,42 +295,8 @@
|
||||
var node = _confirmNode;
|
||||
UnsMutationResult result;
|
||||
|
||||
// Tag/VirtualTag deletes refresh just the owning equipment's children rather than the
|
||||
// whole tree, so they're handled separately from the structural Area/Line/Equipment path.
|
||||
switch (node.Kind)
|
||||
{
|
||||
case UnsNodeKind.Tag:
|
||||
var tag = await Svc.LoadTagAsync(node.EntityId!);
|
||||
if (tag is null) { await ReloadAndCloseAsync(); return; }
|
||||
result = await Svc.DeleteTagAsync(node.EntityId!, tag.RowVersion);
|
||||
if (result.Ok)
|
||||
{
|
||||
await RefreshEquipmentChildrenAsync(tag.EquipmentId);
|
||||
CloseModals();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
_confirmError = result.Error;
|
||||
}
|
||||
return;
|
||||
|
||||
case UnsNodeKind.VirtualTag:
|
||||
var vtag = await Svc.LoadVirtualTagAsync(node.EntityId!);
|
||||
if (vtag is null) { await ReloadAndCloseAsync(); return; }
|
||||
result = await Svc.DeleteVirtualTagAsync(node.EntityId!, vtag.RowVersion);
|
||||
if (result.Ok)
|
||||
{
|
||||
await RefreshEquipmentChildrenAsync(vtag.EquipmentId);
|
||||
CloseModals();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
_confirmError = result.Error;
|
||||
}
|
||||
return;
|
||||
|
||||
case UnsNodeKind.Area:
|
||||
var area = await Svc.LoadAreaAsync(node.EntityId!);
|
||||
if (area is null) { await ReloadAndCloseAsync(); return; }
|
||||
@@ -519,70 +344,6 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a successful Tag/VirtualTag modal save by refreshing only the owning equipment's children
|
||||
/// in place — never a full structural reload, which would collapse the user's expansion.
|
||||
/// </summary>
|
||||
private async Task OnEquipmentChildModalSavedAsync()
|
||||
{
|
||||
if (_childRefreshEquipmentId is not null)
|
||||
{
|
||||
await RefreshEquipmentChildrenAsync(_childRefreshEquipmentId);
|
||||
}
|
||||
CloseModals();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads a single equipment node's tag/virtual-tag children in place, leaving the rest of the tree
|
||||
/// (and the user's expansion) untouched. Falls back to a full structural reload only if the node
|
||||
/// can no longer be found in the current tree. Either branch only mutates state — the caller is
|
||||
/// responsible for calling StateHasChanged() afterwards (every current caller does).
|
||||
/// </summary>
|
||||
private async Task RefreshEquipmentChildrenAsync(string equipmentId)
|
||||
{
|
||||
var node = FindEquipmentNode(equipmentId);
|
||||
if (node is null)
|
||||
{
|
||||
// Fallback: the equipment node is no longer in the current tree — reload the whole structure.
|
||||
_roots = await Svc.LoadStructureAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var kids = await Svc.LoadEquipmentChildrenAsync(equipmentId);
|
||||
node.Children.Clear();
|
||||
node.Children.AddRange(kids);
|
||||
node.ChildCount = node.Children.Count;
|
||||
node.Loaded = true;
|
||||
node.Expanded = true;
|
||||
}
|
||||
|
||||
/// <summary>Recursively walks the current tree for the Equipment node with the given id, or null.</summary>
|
||||
private UnsNode? FindEquipmentNode(string equipmentId)
|
||||
{
|
||||
foreach (var root in _roots)
|
||||
{
|
||||
var found = FindEquipmentNode(root, equipmentId);
|
||||
if (found is not null) { return found; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static UnsNode? FindEquipmentNode(UnsNode node, string equipmentId)
|
||||
{
|
||||
if (node.Kind == UnsNodeKind.Equipment && node.EntityId == equipmentId)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
var found = FindEquipmentNode(child, equipmentId);
|
||||
if (found is not null) { return found; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Reloads the tree after a successful delete and closes the confirm modal.</summary>
|
||||
private async Task ReloadAndCloseAsync()
|
||||
{
|
||||
@@ -603,24 +364,7 @@
|
||||
_lineModalAreaId = null;
|
||||
_lineModalExisting = null;
|
||||
_lineModalAreaOptions = Array.Empty<(string, string)>();
|
||||
_equipmentModalVisible = false;
|
||||
_equipmentModalIsNew = false;
|
||||
_equipmentModalLineId = null;
|
||||
_equipmentModalExisting = null;
|
||||
_equipmentModalLineOptions = Array.Empty<(string, string)>();
|
||||
_equipmentModalDriverOptions = Array.Empty<(string, string)>();
|
||||
_tagModalVisible = false;
|
||||
_tagModalIsNew = false;
|
||||
_tagModalEquipmentId = null;
|
||||
_tagModalExisting = null;
|
||||
_tagModalDriverOptions = Array.Empty<(string, string, string)>();
|
||||
_vtagModalVisible = false;
|
||||
_vtagModalIsNew = false;
|
||||
_vtagModalEquipmentId = null;
|
||||
_vtagModalExisting = null;
|
||||
_vtagModalScriptOptions = Array.Empty<(string, string)>();
|
||||
_importModalVisible = false;
|
||||
_childRefreshEquipmentId = null;
|
||||
_confirmNode = null;
|
||||
_confirmError = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user