K11: add Fluxor CategoryState
- CategoryState: categories list, loading, error - CategoryActions: CRUD + select/clear - CategoryReducers: state transitions - CategoryEffects: async service calls
This commit is contained in:
parent
595c92df76
commit
6917fa6bed
|
|
@ -1709,7 +1709,7 @@ public enum CashBookEntryType { Income = 0, Expense = 1 }
|
|||
| ✓ | K8 | Application | Service Implementations | 4 |
|
||||
| ✓ | K9 | Application | Category Seeder | 1 |
|
||||
| ✓ | K10 | Application | Day Close Integration | 1 |
|
||||
| ☐ | K11 | Web | Fluxor CategoryState | 4 |
|
||||
| ✓ | K11 | Web | Fluxor CategoryState | 4 |
|
||||
| ☐ | K12 | Web | Fluxor CashBookState | 4 |
|
||||
| ☐ | K13 | Web | CashBook UI | 3 |
|
||||
| ☐ | K14 | Web | Categories UI | 2 |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
using Koogle.Application.DTOs;
|
||||
|
||||
namespace Koogle.Web.Store.CategoryState;
|
||||
|
||||
/// <summary>
|
||||
/// Action to load all booking categories for the current club.
|
||||
/// </summary>
|
||||
public record LoadCategoriesAction(bool IncludeInactive = false);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when categories are loaded successfully.
|
||||
/// </summary>
|
||||
public record LoadCategoriesSuccessAction(IReadOnlyList<BookingCategoryDto> Categories);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when loading categories fails.
|
||||
/// </summary>
|
||||
public record LoadCategoriesFailureAction(string Error);
|
||||
|
||||
/// <summary>
|
||||
/// Action to create a new booking category.
|
||||
/// </summary>
|
||||
public record CreateCategoryAction(CreateBookingCategoryDto Dto);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when category is created successfully.
|
||||
/// </summary>
|
||||
public record CreateCategorySuccessAction(BookingCategoryDto Category);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when creating category fails.
|
||||
/// </summary>
|
||||
public record CreateCategoryFailureAction(string Error);
|
||||
|
||||
/// <summary>
|
||||
/// Action to update an existing booking category.
|
||||
/// </summary>
|
||||
public record UpdateCategoryAction(UpdateBookingCategoryDto Dto);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when category is updated successfully.
|
||||
/// </summary>
|
||||
public record UpdateCategorySuccessAction(BookingCategoryDto Category);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when updating category fails.
|
||||
/// </summary>
|
||||
public record UpdateCategoryFailureAction(string Error);
|
||||
|
||||
/// <summary>
|
||||
/// Action to delete a booking category.
|
||||
/// </summary>
|
||||
public record DeleteCategoryAction(Guid Id);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when category is deleted successfully.
|
||||
/// </summary>
|
||||
public record DeleteCategorySuccessAction(Guid Id);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when deleting category fails.
|
||||
/// </summary>
|
||||
public record DeleteCategoryFailureAction(string Error);
|
||||
|
||||
/// <summary>
|
||||
/// Action to select a category for editing.
|
||||
/// </summary>
|
||||
public record SelectCategoryAction(BookingCategoryDto? Category);
|
||||
|
||||
/// <summary>
|
||||
/// Action to clear category error state.
|
||||
/// </summary>
|
||||
public record ClearCategoryErrorAction;
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
using Fluxor;
|
||||
using Koogle.Application.Interfaces;
|
||||
|
||||
namespace Koogle.Web.Store.CategoryState;
|
||||
|
||||
/// <summary>
|
||||
/// Side effects for booking category state management.
|
||||
/// Handles async operations like API calls.
|
||||
/// </summary>
|
||||
public class CategoryEffects
|
||||
{
|
||||
private readonly IBookingCategoryService _categoryService;
|
||||
private readonly ILogger<CategoryEffects> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the CategoryEffects class.
|
||||
/// </summary>
|
||||
public CategoryEffects(IBookingCategoryService categoryService, ILogger<CategoryEffects> logger)
|
||||
{
|
||||
_categoryService = categoryService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles LoadCategoriesAction - loads all categories from service.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public async Task HandleLoadCategories(LoadCategoriesAction action, IDispatcher dispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Loading booking categories");
|
||||
var categories = await _categoryService.GetAllAsync(action.IncludeInactive);
|
||||
dispatcher.Dispatch(new LoadCategoriesSuccessAction(categories));
|
||||
_logger.LogInformation("Loaded {Count} booking categories", categories.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load booking categories");
|
||||
dispatcher.Dispatch(new LoadCategoriesFailureAction(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles CreateCategoryAction - creates a new category.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public async Task HandleCreateCategory(CreateCategoryAction action, IDispatcher dispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Creating booking category {Name}", action.Dto.Name);
|
||||
var category = await _categoryService.CreateAsync(action.Dto);
|
||||
dispatcher.Dispatch(new CreateCategorySuccessAction(category));
|
||||
_logger.LogInformation("Created booking category {Name} with ID {Id}", category.Name, category.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create booking category {Name}", action.Dto.Name);
|
||||
dispatcher.Dispatch(new CreateCategoryFailureAction(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles UpdateCategoryAction - updates an existing category.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public async Task HandleUpdateCategory(UpdateCategoryAction action, IDispatcher dispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Updating booking category {Id}", action.Dto.Id);
|
||||
var category = await _categoryService.UpdateAsync(action.Dto);
|
||||
dispatcher.Dispatch(new UpdateCategorySuccessAction(category));
|
||||
_logger.LogInformation("Updated booking category {Name}", category.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to update booking category {Id}", action.Dto.Id);
|
||||
dispatcher.Dispatch(new UpdateCategoryFailureAction(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles DeleteCategoryAction - deletes a category.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public async Task HandleDeleteCategory(DeleteCategoryAction action, IDispatcher dispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Deleting booking category {Id}", action.Id);
|
||||
var success = await _categoryService.DeleteAsync(action.Id);
|
||||
|
||||
if (success)
|
||||
{
|
||||
dispatcher.Dispatch(new DeleteCategorySuccessAction(action.Id));
|
||||
_logger.LogInformation("Deleted booking category {Id}", action.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcher.Dispatch(new DeleteCategoryFailureAction("Kategorie konnte nicht gelöscht werden."));
|
||||
_logger.LogWarning("Failed to delete booking category {Id} - not found or system category", action.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to delete booking category {Id}", action.Id);
|
||||
dispatcher.Dispatch(new DeleteCategoryFailureAction(ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
using Fluxor;
|
||||
|
||||
namespace Koogle.Web.Store.CategoryState;
|
||||
|
||||
/// <summary>
|
||||
/// Reducers for booking category state management.
|
||||
/// </summary>
|
||||
public static class CategoryReducers
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles LoadCategoriesAction - sets loading state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(LoadCategoriesAction))]
|
||||
public static CategoryState OnLoadCategories(CategoryState state)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = true,
|
||||
Error = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles LoadCategoriesSuccessAction - updates categories list.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnLoadCategoriesSuccess(CategoryState state, LoadCategoriesSuccessAction action)
|
||||
=> state with
|
||||
{
|
||||
Categories = action.Categories,
|
||||
IsLoading = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles LoadCategoriesFailureAction - sets error state.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnLoadCategoriesFailure(CategoryState state, LoadCategoriesFailureAction action)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = false,
|
||||
Error = action.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles CreateCategoryAction - sets loading state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(CreateCategoryAction))]
|
||||
public static CategoryState OnCreateCategory(CategoryState state)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = true,
|
||||
Error = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles CreateCategorySuccessAction - adds category to list.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnCreateCategorySuccess(CategoryState state, CreateCategorySuccessAction action)
|
||||
=> state with
|
||||
{
|
||||
Categories = [.. state.Categories, action.Category],
|
||||
IsLoading = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles CreateCategoryFailureAction - sets error state.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnCreateCategoryFailure(CategoryState state, CreateCategoryFailureAction action)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = false,
|
||||
Error = action.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles UpdateCategoryAction - sets loading state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(UpdateCategoryAction))]
|
||||
public static CategoryState OnUpdateCategory(CategoryState state)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = true,
|
||||
Error = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles UpdateCategorySuccessAction - replaces category in list.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnUpdateCategorySuccess(CategoryState state, UpdateCategorySuccessAction action)
|
||||
=> state with
|
||||
{
|
||||
Categories = state.Categories.Select(c => c.Id == action.Category.Id ? action.Category : c).ToList(),
|
||||
SelectedCategory = state.SelectedCategory?.Id == action.Category.Id ? action.Category : state.SelectedCategory,
|
||||
IsLoading = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles UpdateCategoryFailureAction - sets error state.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnUpdateCategoryFailure(CategoryState state, UpdateCategoryFailureAction action)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = false,
|
||||
Error = action.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles DeleteCategoryAction - sets loading state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(DeleteCategoryAction))]
|
||||
public static CategoryState OnDeleteCategory(CategoryState state)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = true,
|
||||
Error = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles DeleteCategorySuccessAction - removes category from list.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnDeleteCategorySuccess(CategoryState state, DeleteCategorySuccessAction action)
|
||||
=> state with
|
||||
{
|
||||
Categories = state.Categories.Where(c => c.Id != action.Id).ToList(),
|
||||
SelectedCategory = state.SelectedCategory?.Id == action.Id ? null : state.SelectedCategory,
|
||||
IsLoading = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles DeleteCategoryFailureAction - sets error state.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnDeleteCategoryFailure(CategoryState state, DeleteCategoryFailureAction action)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = false,
|
||||
Error = action.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles SelectCategoryAction - sets selected category.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static CategoryState OnSelectCategory(CategoryState state, SelectCategoryAction action)
|
||||
=> state with
|
||||
{
|
||||
SelectedCategory = action.Category
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles ClearCategoryErrorAction - clears error state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(ClearCategoryErrorAction))]
|
||||
public static CategoryState OnClearError(CategoryState state)
|
||||
=> state with
|
||||
{
|
||||
Error = null
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
using Fluxor;
|
||||
using Koogle.Application.DTOs;
|
||||
|
||||
namespace Koogle.Web.Store.CategoryState;
|
||||
|
||||
/// <summary>
|
||||
/// Fluxor state for booking category management.
|
||||
/// </summary>
|
||||
[FeatureState]
|
||||
public record CategoryState
|
||||
{
|
||||
/// <summary>
|
||||
/// List of all booking categories for the current club.
|
||||
/// </summary>
|
||||
public IReadOnlyList<BookingCategoryDto> Categories { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected category for editing.
|
||||
/// </summary>
|
||||
public BookingCategoryDto? SelectedCategory { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a category operation is in progress.
|
||||
/// </summary>
|
||||
public bool IsLoading { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if operation failed.
|
||||
/// </summary>
|
||||
public string? Error { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor for Fluxor initialization.
|
||||
/// </summary>
|
||||
private CategoryState() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates the initial state.
|
||||
/// </summary>
|
||||
public static CategoryState Initial => new()
|
||||
{
|
||||
Categories = [],
|
||||
SelectedCategory = null,
|
||||
IsLoading = false,
|
||||
Error = null
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue