@page "/folder-structure" @attribute [Authorize] @inject IUserSessionService Session @inject IUserContextAccessor UserContext @inject ISessionManager SessionMgr @inject IElevationCoordinator Elevation @inject IFolderStructureService FolderSvc @inject ICsvValidationService CsvValidation @inject TranslationSource T @rendermode InteractiveServer

@T["tab.folderStructure"]

@T["folderstruct.subtitle"]

@if (!Session.HasProfile) { return; } @if (UserContext.Role < UserRole.TechN1) { return; }
@if (_mode == InputMode.Csv) {
} else {
@foreach (var root in _tree) { } @if (_tree.Count == 0) {

@T["folderstruct.builder.empty"]

}
} @if (_rows.Count > 0) {
@string.Format(T["folderstruct.rowsSummary"], _rows.Count(r => r.IsValid), _rows.Count(r => !r.IsValid))
}
@if (_running) { }
@if (!string.IsNullOrEmpty(_error)) {
@_error
} @if (_summary != null) {
@string.Format(T["folderstruct.result"], _summary.SuccessCount, _summary.FailedCount)
} @code { private enum InputMode { Csv, Builder } private InputMode _mode = InputMode.Csv; private List _sites = new(); private string _libraryTitle = string.Empty; private List> _rows = new(); private readonly List _tree = new(); private bool _running; private string _status = string.Empty, _error = string.Empty; private int _current, _total; private BulkOperationSummary? _summary; private CancellationTokenSource? _cts; private void SetMode(InputMode mode) { _mode = mode; _rows.Clear(); _summary = null; _error = string.Empty; if (mode == InputMode.Builder) RebuildFromTree(); } private async Task LoadFile(InputFileChangeEventArgs e) { _rows.Clear(); using var stream = e.File.OpenReadStream(maxAllowedSize: 5 * 1024 * 1024); _rows = CsvValidation.ParseAndValidateFolders(stream); } private void AddRoot() { _tree.Add(new FolderNode()); RebuildFromTree(); } private void RemoveRoot(FolderNode node) { _tree.Remove(node); RebuildFromTree(); } // Convert the visual tree into validation rows so the count + Create button share the CSV path. private void RebuildFromTree() { _rows = FolderNode.Flatten(_tree) .Select(r => new CsvValidationRow(r, new List())) .ToList(); } private async Task RunCreate() { _error = string.Empty; _summary = null; _running = true; _cts = new CancellationTokenSource(); var validRows = _rows.Where(r => r.IsValid && r.Record != null).Select(r => r.Record!).ToList(); var siteUrl = _sites.FirstOrDefault()?.Url; if (string.IsNullOrWhiteSpace(siteUrl)) { _error = T["folderstruct.err.selectSite"]; _running = false; return; } var progress = new Progress(p => { _status = p.Message; _current = p.Current; _total = p.Total; InvokeAsync(StateHasChanged); }); try { _summary = await Elevation.RunAsync(async c => { var ctx = await SessionMgr.GetOrCreateContextAsync(siteUrl, Session.CurrentProfile!, c); return await FolderSvc.CreateFoldersAsync(ctx, _libraryTitle, validRows, progress, c); }, _cts.Token); _status = string.Format(T["folderstruct.status.complete"], _summary.SuccessCount); } catch (OperationCanceledException) { _status = T["status.cancelled"]; } catch (Exception ex) { _error = ex.Message; } finally { _running = false; await InvokeAsync(StateHasChanged); } } private void Cancel() => _cts?.Cancel(); }