diff --git a/src/Web/Components/Features/Categories/CategoriesPage.razor b/src/Web/Components/Features/Categories/CategoriesPage.razor index 9f87d6a..1e4aabe 100644 --- a/src/Web/Components/Features/Categories/CategoriesPage.razor +++ b/src/Web/Components/Features/Categories/CategoriesPage.razor @@ -33,10 +33,17 @@ + TextAlign="TextAlign.Right" Width="180px" Title="Actions"> } + + + diff --git a/src/Web/Components/Features/Categories/CategoriesPage.razor.cs b/src/Web/Components/Features/Categories/CategoriesPage.razor.cs index f7383a1..9b4cb23 100644 --- a/src/Web/Components/Features/Categories/CategoriesPage.razor.cs +++ b/src/Web/Components/Features/Categories/CategoriesPage.razor.cs @@ -36,6 +36,11 @@ public partial class CategoriesPage : ComponentBase private bool _isLoading = true; + // Archive state + private bool _showArchiveDialog = false; + private string? _categoryToArchiveId = null; + private string _archiveConfirmMessage = ""; + protected override async Task OnInitializedAsync() { await LoadCategories(); @@ -121,4 +126,40 @@ private async Task OnUpdateRow(CategoryEditModel cat) await CategoryClient.UpdateAsync(cat.Id, command); } + /// + /// Shows the archive confirmation dialog. + /// + private void HandleArchive(string categoryId, string categoryName) + { + _categoryToArchiveId = categoryId; + _archiveConfirmMessage = $"Archive '{categoryName}'? It will no longer appear in issue forms."; + _showArchiveDialog = true; + } + + /// + /// Handles the archive confirmation. + /// + private async Task HandleArchiveConfirm() + { + _showArchiveDialog = false; + if (string.IsNullOrEmpty(_categoryToArchiveId)) return; + + var success = await CategoryClient.ArchiveAsync(_categoryToArchiveId); + if (success) + { + _categories = _categories.Where(c => c.Id != _categoryToArchiveId).ToList(); + await InvokeAsync(StateHasChanged); + } + _categoryToArchiveId = null; + } + + /// + /// Handles the archive cancellation. + /// + private void HandleArchiveCancel() + { + _showArchiveDialog = false; + _categoryToArchiveId = null; + } + } diff --git a/src/Web/Components/Features/Categories/CategoryApiClient.cs b/src/Web/Components/Features/Categories/CategoryApiClient.cs index b0f6d87..de688da 100644 --- a/src/Web/Components/Features/Categories/CategoryApiClient.cs +++ b/src/Web/Components/Features/Categories/CategoryApiClient.cs @@ -23,6 +23,9 @@ public interface ICategoryApiClient /// Updates an existing category. Task UpdateAsync(string id, UpdateCategoryCommand command, CancellationToken cancellationToken = default); + + /// Archives a category. + Task ArchiveAsync(string id, CancellationToken cancellationToken = default); } /// Typed HTTP client for the Categories API. @@ -70,4 +73,18 @@ public async Task> GetAllAsync(CancellationToken cancel : null; } + /// + public async Task ArchiveAsync(string id, CancellationToken cancellationToken = default) + { + try + { + var response = await _httpClient.DeleteAsync($"/api/v1/categories/{id}", cancellationToken).ConfigureAwait(false); + return response.IsSuccessStatusCode; + } + catch (HttpRequestException) + { + return false; + } + } + } diff --git a/src/Web/Components/Features/Statuses/StatusApiClient.cs b/src/Web/Components/Features/Statuses/StatusApiClient.cs index 03409a6..dd5cc41 100644 --- a/src/Web/Components/Features/Statuses/StatusApiClient.cs +++ b/src/Web/Components/Features/Statuses/StatusApiClient.cs @@ -23,6 +23,9 @@ public interface IStatusApiClient /// Updates an existing status. Task UpdateAsync(string id, UpdateStatusCommand command, CancellationToken cancellationToken = default); + + /// Archives a status by its identifier. + Task ArchiveAsync(string id, CancellationToken cancellationToken = default); } /// Typed HTTP client for the Statuses API. @@ -70,4 +73,18 @@ public async Task> GetAllAsync(CancellationToken cancella : null; } + /// + public async Task ArchiveAsync(string id, CancellationToken cancellationToken = default) + { + try + { + var response = await _httpClient.DeleteAsync($"/api/v1/statuses/{id}", cancellationToken).ConfigureAwait(false); + return response.IsSuccessStatusCode; + } + catch (HttpRequestException) + { + return false; + } + } + } diff --git a/src/Web/Components/Features/Statuses/StatusesPage.razor b/src/Web/Components/Features/Statuses/StatusesPage.razor index 881aeea..0fb5683 100644 --- a/src/Web/Components/Features/Statuses/StatusesPage.razor +++ b/src/Web/Components/Features/Statuses/StatusesPage.razor @@ -5,48 +5,60 @@ Statuses — IssueManager
-
-

Statuses

- -
+
+

Statuses

+ +
- @if (_isLoading) - { -
Loading...
- } - else - { -
- - - - - - - - - - - - - - - - - - - - - - -
- } +@if (_isLoading) +{ +
Loading...
+} +else +{ +
+ + + + + + + + + + + + + + + + + + + + + + +
+}
+ + + diff --git a/src/Web/Components/Features/Statuses/StatusesPage.razor.cs b/src/Web/Components/Features/Statuses/StatusesPage.razor.cs index 50806ce..363d0e3 100644 --- a/src/Web/Components/Features/Statuses/StatusesPage.razor.cs +++ b/src/Web/Components/Features/Statuses/StatusesPage.razor.cs @@ -27,6 +27,11 @@ public partial class StatusesPage : ComponentBase private StatusEditModel? _editingStatus; private bool _isLoading = true; + // Archive dialog state + private bool _showArchiveDialog = false; + private string? _statusToArchiveId = null; + private string? _statusToArchiveName = null; + protected override async Task OnInitializedAsync() { await LoadStatuses(); @@ -102,4 +107,33 @@ private async Task OnUpdateRow(StatusEditModel status) }; await StatusClient.UpdateAsync(status.Id, command); } + + private void ShowArchiveDialog(string id, string name) + { + _statusToArchiveId = id; + _statusToArchiveName = name; + _showArchiveDialog = true; + } + + private async Task HandleArchiveConfirm() + { + _showArchiveDialog = false; + if (string.IsNullOrEmpty(_statusToArchiveId)) return; + + var success = await StatusClient.ArchiveAsync(_statusToArchiveId); + if (success) + { + _statuses = _statuses.Where(s => s.Id != _statusToArchiveId).ToList(); + await InvokeAsync(StateHasChanged); + } + _statusToArchiveId = null; + _statusToArchiveName = null; + } + + private void HandleArchiveCancel() + { + _showArchiveDialog = false; + _statusToArchiveId = null; + _statusToArchiveName = null; + } } diff --git a/tests/Web.Tests.Bunit/Components/Features/Categories/CategoriesPageTests.cs b/tests/Web.Tests.Bunit/Components/Features/Categories/CategoriesPageTests.cs index e156cfb..4d723f9 100644 --- a/tests/Web.Tests.Bunit/Components/Features/Categories/CategoriesPageTests.cs +++ b/tests/Web.Tests.Bunit/Components/Features/Categories/CategoriesPageTests.cs @@ -27,6 +27,7 @@ public CategoriesPageTests() { _ctx = new BunitContext(); _ctx.JSInterop.Mode = JSRuntimeMode.Loose; + _ctx.AddAuthorization(); _mockCategoryClient = Substitute.For(); _mockCategoryClient.GetAllAsync(Arg.Any()) .Returns(Task.FromResult>([])); diff --git a/tests/Web.Tests.Bunit/Components/Features/Statuses/StatusesPageTests.cs b/tests/Web.Tests.Bunit/Components/Features/Statuses/StatusesPageTests.cs index 17f6a21..8841e9d 100644 --- a/tests/Web.Tests.Bunit/Components/Features/Statuses/StatusesPageTests.cs +++ b/tests/Web.Tests.Bunit/Components/Features/Statuses/StatusesPageTests.cs @@ -27,6 +27,7 @@ public StatusesPageTests() { _ctx = new BunitContext(); _ctx.JSInterop.Mode = JSRuntimeMode.Loose; + _ctx.AddAuthorization(); _mockStatusClient = Substitute.For(); _mockStatusClient.GetAllAsync(Arg.Any()) .Returns(Task.FromResult>([]));