From fc105acd7c3ca996f5fd649f12424cc1e7b2ee78 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 11 May 2026 11:18:36 -0400 Subject: [PATCH] feat(ui/templates): new-folder, new-template, move-template dialogs --- .../Components/Pages/Design/Templates.razor | 197 +++++++++++++++++- .../TemplateService.cs | 4 +- .../TemplateServiceTests.cs | 12 ++ 3 files changed, 209 insertions(+), 4 deletions(-) diff --git a/src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor b/src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor index 222b5e6..17deddd 100644 --- a/src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor +++ b/src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor @@ -41,6 +41,84 @@ } + @if (_showNewFolderDialog) + { + + } + + @if (_showNewTemplateDialog) + { + + } + + @if (_showMoveTemplateDialog) + { + + } + @if (_loading) { @@ -518,9 +596,122 @@ } }; - private void OpenNewFolderDialog(int? parentFolderId) { /* Task 17 */ } - private void OpenNewTemplateDialog(int? parentFolderId) { /* Task 17 */ } - private void OpenMoveTemplateDialog(int templateId, string label) { /* Task 17 */ } + // New-folder dialog state + private bool _showNewFolderDialog; + private int? _newFolderParentId; + private string _newFolderName = string.Empty; + private string? _newFolderError; + + private void OpenNewFolderDialog(int? parentFolderId) + { + _newFolderParentId = parentFolderId; + _newFolderName = string.Empty; + _newFolderError = null; + _showNewFolderDialog = true; + } + + private async Task SubmitNewFolder() + { + _newFolderError = null; + var user = await GetCurrentUserAsync(); + var result = await TemplateFolderService.CreateFolderAsync(_newFolderName.Trim(), _newFolderParentId, user); + if (result.IsSuccess) + { + _showNewFolderDialog = false; + _toast.ShowSuccess($"Folder '{result.Value.Name}' created."); + await LoadTemplatesAsync(); + } + else + { + _newFolderError = result.Error; + } + } + + // New-template dialog state + private bool _showNewTemplateDialog; + private int? _newTemplateFolderId; + private string _newTemplateName = string.Empty; + private string? _newTemplateDescription; + private string? _newTemplateError; + + private void OpenNewTemplateDialog(int? folderId) + { + _newTemplateFolderId = folderId; + _newTemplateName = string.Empty; + _newTemplateDescription = null; + _newTemplateError = null; + _showNewTemplateDialog = true; + } + + private async Task SubmitNewTemplate() + { + _newTemplateError = null; + var user = await GetCurrentUserAsync(); + var result = await TemplateService.CreateTemplateAsync( + _newTemplateName.Trim(), _newTemplateDescription?.Trim(), null, user, folderId: _newTemplateFolderId); + if (result.IsSuccess) + { + _showNewTemplateDialog = false; + _toast.ShowSuccess($"Template '{result.Value.Name}' created."); + await LoadTemplatesAsync(); + await SelectTemplate(result.Value.Id); + } + else + { + _newTemplateError = result.Error; + } + } + + // Move-template dialog state + private bool _showMoveTemplateDialog; + private int _moveTemplateId; + private string _moveTemplateName = string.Empty; + private int? _moveTemplateTargetFolderId; + private string? _moveTemplateError; + + private void OpenMoveTemplateDialog(int templateId, string label) + { + _moveTemplateId = templateId; + _moveTemplateName = label; + _moveTemplateTargetFolderId = null; + _moveTemplateError = null; + _showMoveTemplateDialog = true; + } + + private async Task SubmitMoveTemplate() + { + _moveTemplateError = null; + var user = await GetCurrentUserAsync(); + var result = await TemplateService.MoveTemplateAsync(_moveTemplateId, _moveTemplateTargetFolderId, user); + if (result.IsSuccess) + { + _showMoveTemplateDialog = false; + _toast.ShowSuccess($"Template '{_moveTemplateName}' moved."); + await LoadTemplatesAsync(); + } + else + { + _moveTemplateError = result.Error; + } + } + + // Flat list of folders with indentation labels, for the picker. + private IEnumerable<(int? Id, string Label)> EnumerateFolderOptions() + { + yield return (null, "(Root)"); + foreach (var f in WalkFolderHierarchy(_folders.Where(f => f.ParentFolderId == null), 0)) + yield return f; + } + + private IEnumerable<(int? Id, string Label)> WalkFolderHierarchy(IEnumerable level, int depth) + { + foreach (var f in level.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase)) + { + yield return ((int?)f.Id, new string(' ', depth * 2) + f.Name); + foreach (var sub in WalkFolderHierarchy(_folders.Where(c => c.ParentFolderId == f.Id), depth + 1)) + yield return sub; + } + } // Rename folder dialog state private bool _showRenameFolderDialog; diff --git a/src/ScadaLink.TemplateEngine/TemplateService.cs b/src/ScadaLink.TemplateEngine/TemplateService.cs index 4112f88..48ced9d 100644 --- a/src/ScadaLink.TemplateEngine/TemplateService.cs +++ b/src/ScadaLink.TemplateEngine/TemplateService.cs @@ -30,6 +30,7 @@ public class TemplateService string? description, int? parentTemplateId, string user, + int? folderId = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(name)) @@ -46,7 +47,8 @@ public class TemplateService var template = new Template(name) { Description = description, - ParentTemplateId = parentTemplateId + ParentTemplateId = parentTemplateId, + FolderId = folderId }; // Check acyclicity (inheritance) — for new templates this is mostly a parent-exists check, diff --git a/tests/ScadaLink.TemplateEngine.Tests/TemplateServiceTests.cs b/tests/ScadaLink.TemplateEngine.Tests/TemplateServiceTests.cs index 72c37e4..8dd3533 100644 --- a/tests/ScadaLink.TemplateEngine.Tests/TemplateServiceTests.cs +++ b/tests/ScadaLink.TemplateEngine.Tests/TemplateServiceTests.cs @@ -70,6 +70,18 @@ public class TemplateServiceTests Assert.Contains("not found", result.Error); } + [Fact] + public async Task CreateTemplate_WithFolderId_SetsFolderId() + { + var folder = new TemplateFolder("Dev") { Id = 7 }; + _repoMock.Setup(r => r.GetFolderByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync(folder); + + var result = await _service.CreateTemplateAsync("X", null, null, "admin", folderId: 7); + + Assert.True(result.IsSuccess); + Assert.Equal(7, result.Value.FolderId); + } + [Fact] public async Task DeleteTemplate_Success() {