Add PersonState Fluxor state management (Phase D1)
- PersonState.cs: State record with Persons, SelectedPerson, IsLoading, Error - PersonActions.cs: Load/Create/Update/Delete actions with success/failure variants - PersonReducers.cs: Pure reducers for all person actions - PersonEffects.cs: Async effects calling IPersonService 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
660d143333
commit
d8ca8ed0a9
|
|
@ -0,0 +1,73 @@
|
|||
using Koogle.Application.DTOs;
|
||||
|
||||
namespace Koogle.Web.Store.PersonState;
|
||||
|
||||
/// <summary>
|
||||
/// Action to load all persons for the current club.
|
||||
/// </summary>
|
||||
public record LoadPersonsAction;
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when persons are loaded successfully.
|
||||
/// </summary>
|
||||
public record LoadPersonsSuccessAction(IReadOnlyList<PersonDto> Persons);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when loading persons fails.
|
||||
/// </summary>
|
||||
public record LoadPersonsFailureAction(string Error);
|
||||
|
||||
/// <summary>
|
||||
/// Action to create a new person.
|
||||
/// </summary>
|
||||
public record CreatePersonAction(CreatePersonDto Dto);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when person is created successfully.
|
||||
/// </summary>
|
||||
public record CreatePersonSuccessAction(PersonDto Person);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when creating person fails.
|
||||
/// </summary>
|
||||
public record CreatePersonFailureAction(string Error);
|
||||
|
||||
/// <summary>
|
||||
/// Action to update an existing person.
|
||||
/// </summary>
|
||||
public record UpdatePersonAction(UpdatePersonDto Dto);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when person is updated successfully.
|
||||
/// </summary>
|
||||
public record UpdatePersonSuccessAction(PersonDto Person);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when updating person fails.
|
||||
/// </summary>
|
||||
public record UpdatePersonFailureAction(string Error);
|
||||
|
||||
/// <summary>
|
||||
/// Action to delete a person.
|
||||
/// </summary>
|
||||
public record DeletePersonAction(Guid Id);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when person is deleted successfully.
|
||||
/// </summary>
|
||||
public record DeletePersonSuccessAction(Guid Id);
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when deleting person fails.
|
||||
/// </summary>
|
||||
public record DeletePersonFailureAction(string Error);
|
||||
|
||||
/// <summary>
|
||||
/// Action to select a person for editing.
|
||||
/// </summary>
|
||||
public record SelectPersonAction(PersonDto? Person);
|
||||
|
||||
/// <summary>
|
||||
/// Action to clear person error state.
|
||||
/// </summary>
|
||||
public record ClearPersonErrorAction;
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
using Fluxor;
|
||||
using Koogle.Application.Interfaces;
|
||||
|
||||
namespace Koogle.Web.Store.PersonState;
|
||||
|
||||
/// <summary>
|
||||
/// Side effects for person state management.
|
||||
/// Handles async operations like API calls.
|
||||
/// </summary>
|
||||
public class PersonEffects
|
||||
{
|
||||
private readonly IPersonService _personService;
|
||||
private readonly ILogger<PersonEffects> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the PersonEffects class.
|
||||
/// </summary>
|
||||
public PersonEffects(IPersonService personService, ILogger<PersonEffects> logger)
|
||||
{
|
||||
_personService = personService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles LoadPersonsAction - loads all persons from service.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public async Task HandleLoadPersons(LoadPersonsAction action, IDispatcher dispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Loading persons");
|
||||
var persons = await _personService.GetAllAsync();
|
||||
dispatcher.Dispatch(new LoadPersonsSuccessAction(persons));
|
||||
_logger.LogInformation("Loaded {Count} persons", persons.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load persons");
|
||||
dispatcher.Dispatch(new LoadPersonsFailureAction(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles CreatePersonAction - creates a new person.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public async Task HandleCreatePerson(CreatePersonAction action, IDispatcher dispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Creating person {Name}", action.Dto.Name);
|
||||
var person = await _personService.CreateAsync(action.Dto);
|
||||
dispatcher.Dispatch(new CreatePersonSuccessAction(person));
|
||||
_logger.LogInformation("Created person {Name} with ID {Id}", person.Name, person.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create person {Name}", action.Dto.Name);
|
||||
dispatcher.Dispatch(new CreatePersonFailureAction(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles UpdatePersonAction - updates an existing person.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public async Task HandleUpdatePerson(UpdatePersonAction action, IDispatcher dispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Updating person {Id}", action.Dto.Id);
|
||||
var person = await _personService.UpdateAsync(action.Dto);
|
||||
dispatcher.Dispatch(new UpdatePersonSuccessAction(person));
|
||||
_logger.LogInformation("Updated person {Name}", person.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to update person {Id}", action.Dto.Id);
|
||||
dispatcher.Dispatch(new UpdatePersonFailureAction(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles DeletePersonAction - deletes a person.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public async Task HandleDeletePerson(DeletePersonAction action, IDispatcher dispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Deleting person {Id}", action.Id);
|
||||
var success = await _personService.DeleteAsync(action.Id);
|
||||
|
||||
if (success)
|
||||
{
|
||||
dispatcher.Dispatch(new DeletePersonSuccessAction(action.Id));
|
||||
_logger.LogInformation("Deleted person {Id}", action.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcher.Dispatch(new DeletePersonFailureAction("Person konnte nicht gelöscht werden."));
|
||||
_logger.LogWarning("Failed to delete person {Id} - not found or already deleted", action.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to delete person {Id}", action.Id);
|
||||
dispatcher.Dispatch(new DeletePersonFailureAction(ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
using Fluxor;
|
||||
|
||||
namespace Koogle.Web.Store.PersonState;
|
||||
|
||||
/// <summary>
|
||||
/// Reducers for person state management.
|
||||
/// </summary>
|
||||
public static class PersonReducers
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles LoadPersonsAction - sets loading state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(LoadPersonsAction))]
|
||||
public static PersonState OnLoadPersons(PersonState state)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = true,
|
||||
Error = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles LoadPersonsSuccessAction - updates persons list.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnLoadPersonsSuccess(PersonState state, LoadPersonsSuccessAction action)
|
||||
=> state with
|
||||
{
|
||||
Persons = action.Persons,
|
||||
IsLoading = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles LoadPersonsFailureAction - sets error state.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnLoadPersonsFailure(PersonState state, LoadPersonsFailureAction action)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = false,
|
||||
Error = action.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles CreatePersonAction - sets loading state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(CreatePersonAction))]
|
||||
public static PersonState OnCreatePerson(PersonState state)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = true,
|
||||
Error = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles CreatePersonSuccessAction - adds person to list.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnCreatePersonSuccess(PersonState state, CreatePersonSuccessAction action)
|
||||
=> state with
|
||||
{
|
||||
Persons = [.. state.Persons, action.Person],
|
||||
IsLoading = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles CreatePersonFailureAction - sets error state.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnCreatePersonFailure(PersonState state, CreatePersonFailureAction action)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = false,
|
||||
Error = action.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles UpdatePersonAction - sets loading state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(UpdatePersonAction))]
|
||||
public static PersonState OnUpdatePerson(PersonState state)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = true,
|
||||
Error = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles UpdatePersonSuccessAction - replaces person in list.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnUpdatePersonSuccess(PersonState state, UpdatePersonSuccessAction action)
|
||||
=> state with
|
||||
{
|
||||
Persons = state.Persons.Select(p => p.Id == action.Person.Id ? action.Person : p).ToList(),
|
||||
SelectedPerson = state.SelectedPerson?.Id == action.Person.Id ? action.Person : state.SelectedPerson,
|
||||
IsLoading = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles UpdatePersonFailureAction - sets error state.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnUpdatePersonFailure(PersonState state, UpdatePersonFailureAction action)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = false,
|
||||
Error = action.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles DeletePersonAction - sets loading state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(DeletePersonAction))]
|
||||
public static PersonState OnDeletePerson(PersonState state)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = true,
|
||||
Error = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles DeletePersonSuccessAction - removes person from list.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnDeletePersonSuccess(PersonState state, DeletePersonSuccessAction action)
|
||||
=> state with
|
||||
{
|
||||
Persons = state.Persons.Where(p => p.Id != action.Id).ToList(),
|
||||
SelectedPerson = state.SelectedPerson?.Id == action.Id ? null : state.SelectedPerson,
|
||||
IsLoading = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles DeletePersonFailureAction - sets error state.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnDeletePersonFailure(PersonState state, DeletePersonFailureAction action)
|
||||
=> state with
|
||||
{
|
||||
IsLoading = false,
|
||||
Error = action.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles SelectPersonAction - sets selected person.
|
||||
/// </summary>
|
||||
[ReducerMethod]
|
||||
public static PersonState OnSelectPerson(PersonState state, SelectPersonAction action)
|
||||
=> state with
|
||||
{
|
||||
SelectedPerson = action.Person
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handles ClearPersonErrorAction - clears error state.
|
||||
/// </summary>
|
||||
[ReducerMethod(typeof(ClearPersonErrorAction))]
|
||||
public static PersonState OnClearError(PersonState state)
|
||||
=> state with
|
||||
{
|
||||
Error = null
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
using Fluxor;
|
||||
using Koogle.Application.DTOs;
|
||||
|
||||
namespace Koogle.Web.Store.PersonState;
|
||||
|
||||
/// <summary>
|
||||
/// Fluxor state for person management.
|
||||
/// </summary>
|
||||
[FeatureState]
|
||||
public record PersonState
|
||||
{
|
||||
/// <summary>
|
||||
/// List of all persons for the current club.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PersonDto> Persons { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected person for editing.
|
||||
/// </summary>
|
||||
public PersonDto? SelectedPerson { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a person 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 PersonState() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates the initial state.
|
||||
/// </summary>
|
||||
public static PersonState Initial => new()
|
||||
{
|
||||
Persons = [],
|
||||
SelectedPerson = null,
|
||||
IsLoading = false,
|
||||
Error = null
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue