Finished adding basic category adding functionality
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Elevation="1">
|
||||
<MudStaticNavDrawerToggle DrawerId="nav-drawer" Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" />
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
@inject ICategoryProvider CategoryProvider;
|
||||
|
||||
<MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
|
||||
<MudText Typo="Typo.h6">Categories</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudList T="string">
|
||||
@foreach (Category category in _categories)
|
||||
{
|
||||
<MudListItem Text=@category.CategoryName OnClick="@(() => OnCategoryItemClicked(category.CategoryName))"></MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
@ChildContent
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
public List<Category> _categories = new();
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> ListItemClickedCallback { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var categories = await CategoryProvider.AllCategories();
|
||||
_categories.AddRange(categories);
|
||||
}
|
||||
|
||||
public async Task RefreshData()
|
||||
{
|
||||
_categories.Clear();
|
||||
var categories = await CategoryProvider.AllCategories();
|
||||
_categories.AddRange(categories);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task OnCategoryItemClicked(string categoryName)
|
||||
{
|
||||
await ListItemClickedCallback.InvokeAsync(categoryName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
@using System.ComponentModel.DataAnnotations;
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">Create a Category</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form">
|
||||
<MudTextField @bind-Value="Model.Name"
|
||||
For="@(() => Model.Name)"
|
||||
Label="Category Name"
|
||||
Variant="Variant.Filled" />
|
||||
|
||||
<MudDivider Class="pt-4" DividerType="DividerType.Middle"/>
|
||||
|
||||
<MudText Typo="Typo.h6">Item Tag Identifier</MudText>
|
||||
<MudText Typo="Typo.subtitle2">This will be the format of the identifier used for each archive entry.</MudText>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudText Typo="Typo.body2">Format Preview: </MudText>
|
||||
<MudText Type="Typo.body2" Color="Color.Primary">@FormatPreview</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudTextField @bind-Value="Model.FieldSeparator"
|
||||
@bind-Value:after="UpdateFormatPreview"
|
||||
For="@(() => Model.FieldSeparator)"
|
||||
Label="Field Separator"
|
||||
Variant="Variant.Filled"
|
||||
MaxLength="1" />
|
||||
|
||||
<MudDivider Class="pt-4" />
|
||||
|
||||
<MudNumericField
|
||||
Value="Model.NumFields"
|
||||
ValueChanged="@((int newCount) => OnNumFieldsChanged(newCount))"
|
||||
Label="Number of fields in the item identifiers"
|
||||
Variant="Variant.Filled"
|
||||
Min="1"></MudNumericField>
|
||||
|
||||
<MudDivider Class="pt-4" />
|
||||
|
||||
<MudGrid Class="pr-2 pt-2 pb-2 pl-8" Justify="Justify.FlexStart" Spacing="3">
|
||||
@for (int index = 0; index < Model.FieldNames.Count; ++index)
|
||||
{
|
||||
var localIndex = index;
|
||||
|
||||
<MudItem xs="12" sm="6" md="6">
|
||||
<CategoryFieldCardComponent Index="localIndex"
|
||||
FieldName="@Model.FieldNames[localIndex]"
|
||||
FieldDescription="@Model.FieldDescriptions[localIndex]"
|
||||
OnNameUpdate="HandleNameUpdate"
|
||||
OnDescriptionUpdate="HandleDescriptionUpdate"/>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">OK</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@inject ICategoryProvider CategoryProvider;
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public CategoryModel Model { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public bool IsUpdate { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string OriginalName { get; set; } = string.Empty;
|
||||
|
||||
private MudForm _form = default!;
|
||||
private string FormatPreview { get; set; } = string.Empty;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
Model ??= new CategoryModel { NumFields = 1 };
|
||||
UpdateStateFromModel();
|
||||
}
|
||||
|
||||
private void OnNumFieldsChanged(int newCount)
|
||||
{
|
||||
if (newCount < 1) return; // Prevent invalid counts
|
||||
Model.NumFields = newCount;
|
||||
UpdateStateFromModel();
|
||||
}
|
||||
|
||||
private void UpdateStateFromModel()
|
||||
{
|
||||
Model.FieldNames ??= new List<string>();
|
||||
Model.FieldDescriptions ??= new List<string>();
|
||||
|
||||
while (Model.FieldNames.Count < Model.NumFields)
|
||||
{
|
||||
Model.FieldNames.Add($"Field {Model.FieldNames.Count + 1}");
|
||||
}
|
||||
while (Model.FieldNames.Count > Model.NumFields)
|
||||
{
|
||||
Model.FieldNames.RemoveAt(Model.FieldNames.Count - 1);
|
||||
}
|
||||
|
||||
while (Model.FieldDescriptions.Count < Model.NumFields)
|
||||
{
|
||||
Model.FieldDescriptions.Add("");
|
||||
}
|
||||
while (Model.FieldDescriptions.Count > Model.NumFields)
|
||||
{
|
||||
Model.FieldDescriptions.RemoveAt(Model.FieldDescriptions.Count - 1);
|
||||
}
|
||||
|
||||
UpdateFormatPreview();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void UpdateFormatPreview()
|
||||
{
|
||||
var fieldNames = Model.FieldNames.Select(name => string.IsNullOrEmpty(name) ? "<...>" : $"<{name}>");
|
||||
FormatPreview = string.Join(Model.FieldSeparator, fieldNames);
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_form.IsValid) return;
|
||||
|
||||
var categoryToSave = Model.ToCategory();
|
||||
if (IsUpdate)
|
||||
{
|
||||
int lines = await CategoryProvider.UpdateCategoryAsync(OriginalName, categoryToSave);
|
||||
Console.WriteLine($"{lines} effected");
|
||||
}
|
||||
else
|
||||
{
|
||||
await CategoryProvider.InsertCategoryAsync(categoryToSave);
|
||||
}
|
||||
MudDialog.Close(DialogResult.Ok(Model.ToCategory()));
|
||||
}
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
// In your MudDialog component's @code block
|
||||
|
||||
private void HandleNameUpdate((int Index, string NewValue) data)
|
||||
{
|
||||
if (data.Index < Model.FieldNames.Count)
|
||||
{
|
||||
Model.FieldNames[data.Index] = data.NewValue;
|
||||
UpdateFormatPreview(); // Update the preview in real-time
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDescriptionUpdate((int Index, string NewValue) data)
|
||||
{
|
||||
if (data.Index < Model.FieldDescriptions.Count)
|
||||
{
|
||||
Model.FieldDescriptions[data.Index] = data.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@* CategoryFieldCardComponent.razor *@
|
||||
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>
|
||||
<MudTextField @bind-Value="FieldName"
|
||||
@bind-Value:after="OnNameChanged"
|
||||
Label="Field Name"
|
||||
Variant="Variant.Filled"
|
||||
Immediate="true"
|
||||
/>
|
||||
|
||||
<MudTextField @bind-Value="FieldDescription"
|
||||
@bind-Value:after="OnDescriptionChanged"
|
||||
Label="Field Description"
|
||||
Variant="Variant.Filled"
|
||||
Lines="2"
|
||||
Class="mt-3"
|
||||
Immediate="true"
|
||||
/>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
@code {
|
||||
// IN: The index of this field in the parent's list
|
||||
[Parameter] public int Index { get; set; }
|
||||
|
||||
// IN: The initial values from the parent
|
||||
[Parameter] public string FieldName { get; set; } = "";
|
||||
[Parameter] public string FieldDescription { get; set; } = "";
|
||||
|
||||
// OUT: Callbacks to notify the parent of changes
|
||||
[Parameter] public EventCallback<(int Index, string NewValue)> OnNameUpdate { get; set; }
|
||||
[Parameter] public EventCallback<(int Index, string NewValue)> OnDescriptionUpdate { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
// Sync internal state when parent's data changes
|
||||
}
|
||||
|
||||
private async Task OnNameChanged()
|
||||
{
|
||||
await OnNameUpdate.InvokeAsync((Index, FieldName));
|
||||
}
|
||||
|
||||
private async Task OnDescriptionChanged()
|
||||
{
|
||||
await OnDescriptionUpdate.InvokeAsync((Index, FieldDescription));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using OpenArchival.Database;
|
||||
using OpenArchival.Database.Category;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class CategoryFieldValidationModel
|
||||
{
|
||||
[Required(ErrorMessage = "A field name must be provided.")]
|
||||
public string FieldName { get; set; } = "";
|
||||
|
||||
public string Description { get; set; } = "";
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using OpenArchival.Database.Category;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class CategoryModel
|
||||
{
|
||||
[Required(ErrorMessage = "Category name is required.")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Field separator is required.")]
|
||||
[StringLength(1, ErrorMessage = "Separator must be a single character.")]
|
||||
public string FieldSeparator { get; set; } = "-";
|
||||
|
||||
[Required(ErrorMessage = "At least one field is needed")]
|
||||
[Range(1, int.MaxValue, ErrorMessage = "At least one field must be created.")]
|
||||
public int NumFields { get; set; } = 1;
|
||||
|
||||
public List<string> FieldNames { get; set; } = [""];
|
||||
|
||||
public List<string> FieldDescriptions { get; set; } = [""];
|
||||
|
||||
public Category ToCategory()
|
||||
{
|
||||
return new Category() { CategoryName = Name, FieldSeparator = FieldSeparator, FieldNames = FieldNames.ToArray(), FieldDescriptions = FieldDescriptions.ToArray() };
|
||||
}
|
||||
public static CategoryModel FromCategory(Category category)
|
||||
{
|
||||
return new CategoryModel() { Name = category.CategoryName, FieldSeparator=category.FieldSeparator, NumFields=category.FieldNames.Length, FieldNames = new(category.FieldNames), FieldDescriptions = new(category.FieldDescriptions)};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
@page "/categories"
|
||||
@inject IDialogService DialogService
|
||||
@inject ICategoryProvider CategoryProvider;
|
||||
|
||||
<CategoriesListComponent @ref=_categoriesListComponent ListItemClickedCallback="ShowFilledDialog">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OnAddClick">Add Category</MudButton>
|
||||
</CategoriesListComponent>
|
||||
|
||||
|
||||
@code {
|
||||
CategoriesListComponent _categoriesListComponent = default!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private async Task ShowFilledDialog(string categoryName)
|
||||
{
|
||||
Category? category = await CategoryProvider.GetCategoryAsync(categoryName);
|
||||
|
||||
if (category is null)
|
||||
{
|
||||
throw new ArgumentNullException($"The passed in categoryName={categoryName} resulted in no category in the database");
|
||||
}
|
||||
|
||||
CategoryModel validationModel = CategoryModel.FromCategory(category);
|
||||
|
||||
var parameters = new DialogParameters { ["Model"] = validationModel, ["IsUpdate"] = true, ["OriginalName"] = category.CategoryName};
|
||||
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, BackdropClick=false};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<CategoryCreatorDialog>("Create a Category", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result is not null && !result.Canceled && _categoriesListComponent is not null)
|
||||
{
|
||||
StateHasChanged();
|
||||
await _categoriesListComponent.RefreshData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnAddClick()
|
||||
{
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, BackdropClick=false };
|
||||
var dialog = await DialogService.ShowAsync<CategoryCreatorDialog>("Create a Category", options);
|
||||
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result is not null && !result.Canceled && _categoriesListComponent is not null)
|
||||
{
|
||||
StateHasChanged();
|
||||
await _categoriesListComponent.RefreshData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@using System.Net.Http
|
||||
@using Microsoft.AspNetCore.Components;
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using MudBlazor.StaticInput
|
||||
@@ -12,3 +13,4 @@
|
||||
@using MudBlazor.Services
|
||||
@using OpenArchival.Blazor
|
||||
@using OpenArchival.Blazor.Components
|
||||
@using OpenArchival.Database.Category;
|
||||
|
||||
Reference in New Issue
Block a user