Areas page now shows a single TreeView with sites as roots and areas as children. Context menus: sites get "Add Area", areas get "Add Child Area", "Edit Area", "Delete Area" — each navigating to a dedicated page. The Delete Area page shows a TreeView of the area and all recursive children with assigned instances. Deletion is blocked if any instances are assigned to the area or its descendants.
134 lines
4.6 KiB
Plaintext
134 lines
4.6 KiB
Plaintext
@page "/admin/areas"
|
|
@using ScadaLink.Security
|
|
@using ScadaLink.Commons.Entities.Instances
|
|
@using ScadaLink.Commons.Entities.Sites
|
|
@using ScadaLink.Commons.Interfaces.Repositories
|
|
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDesign)]
|
|
@inject ISiteRepository SiteRepository
|
|
@inject ITemplateEngineRepository TemplateEngineRepository
|
|
@inject NavigationManager NavigationManager
|
|
|
|
<div class="container-fluid mt-3">
|
|
<h4 class="mb-3">Area Management</h4>
|
|
|
|
<ToastNotification @ref="_toast" />
|
|
|
|
@if (_loading)
|
|
{
|
|
<LoadingSpinner IsLoading="true" />
|
|
}
|
|
else if (_errorMessage != null)
|
|
{
|
|
<div class="alert alert-danger">@_errorMessage</div>
|
|
}
|
|
else
|
|
{
|
|
<TreeView TItem="AreaTreeNode" Items="_treeRoots"
|
|
ChildrenSelector="n => n.Children"
|
|
HasChildrenSelector="n => n.Children.Count > 0"
|
|
KeySelector="n => n.Key"
|
|
StorageKey="areas-tree">
|
|
<NodeContent Context="node">
|
|
@if (node.Kind == AreaNodeKind.Site)
|
|
{
|
|
<span class="fw-semibold">@node.Label</span>
|
|
<span class="badge bg-secondary ms-1">@node.AreaCount</span>
|
|
}
|
|
else
|
|
{
|
|
<span>@node.Label</span>
|
|
}
|
|
</NodeContent>
|
|
<ContextMenu Context="node">
|
|
@if (node.Kind == AreaNodeKind.Site)
|
|
{
|
|
<button class="dropdown-item"
|
|
@onclick='() => NavigationManager.NavigateTo($"/admin/areas/add?siteId={node.SiteId}")'>
|
|
Add Area
|
|
</button>
|
|
}
|
|
else
|
|
{
|
|
<button class="dropdown-item"
|
|
@onclick='() => NavigationManager.NavigateTo($"/admin/areas/add?siteId={node.SiteId}&parentAreaId={node.Area!.Id}")'>
|
|
Add Child Area
|
|
</button>
|
|
<button class="dropdown-item"
|
|
@onclick='() => NavigationManager.NavigateTo($"/admin/areas/{node.Area!.Id}/edit")'>
|
|
Edit Area
|
|
</button>
|
|
<div class="dropdown-divider"></div>
|
|
<button class="dropdown-item text-danger"
|
|
@onclick='() => NavigationManager.NavigateTo($"/admin/areas/{node.Area!.Id}/delete")'>
|
|
Delete Area
|
|
</button>
|
|
}
|
|
</ContextMenu>
|
|
<EmptyContent>
|
|
<span class="text-muted fst-italic">No sites configured.</span>
|
|
</EmptyContent>
|
|
</TreeView>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
record AreaTreeNode(string Key, string Label, AreaNodeKind Kind, List<AreaTreeNode> Children,
|
|
int SiteId, Area? Area = null, int AreaCount = 0);
|
|
enum AreaNodeKind { Site, Area }
|
|
|
|
private List<Site> _sites = new();
|
|
private List<AreaTreeNode> _treeRoots = new();
|
|
private bool _loading = true;
|
|
private string? _errorMessage;
|
|
|
|
private ToastNotification _toast = default!;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadDataAsync();
|
|
}
|
|
|
|
private async Task LoadDataAsync()
|
|
{
|
|
_loading = true;
|
|
try
|
|
{
|
|
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
|
|
_treeRoots = new();
|
|
foreach (var site in _sites)
|
|
{
|
|
var areas = (await TemplateEngineRepository.GetAreasBySiteIdAsync(site.Id)).ToList();
|
|
var rootAreas = areas.Where(a => a.ParentAreaId == null).OrderBy(a => a.Name);
|
|
var children = rootAreas.Select(a => BuildAreaNode(a, site.Id)).ToList();
|
|
_treeRoots.Add(new AreaTreeNode(
|
|
Key: $"site-{site.Id}",
|
|
Label: site.Name,
|
|
Kind: AreaNodeKind.Site,
|
|
Children: children,
|
|
SiteId: site.Id,
|
|
AreaCount: areas.Count));
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_errorMessage = $"Failed to load data: {ex.Message}";
|
|
}
|
|
_loading = false;
|
|
}
|
|
|
|
private AreaTreeNode BuildAreaNode(Area area, int siteId)
|
|
{
|
|
var children = area.Children
|
|
.OrderBy(c => c.Name)
|
|
.Select(c => BuildAreaNode(c, siteId))
|
|
.ToList();
|
|
return new AreaTreeNode(
|
|
Key: $"area-{area.Id}",
|
|
Label: area.Name,
|
|
Kind: AreaNodeKind.Area,
|
|
Children: children,
|
|
SiteId: siteId,
|
|
Area: area);
|
|
}
|
|
}
|