select Person and Quick-Assign expenses

This commit is contained in:
beo3000 2025-12-25 21:36:43 +01:00
parent 033a3ebd4e
commit 8d2222de8f
4 changed files with 138 additions and 18 deletions

View File

@ -1,7 +1,7 @@
## Optimierung Spieltag-Details
Die Erfassung von Strafen muss möglichst schnell und komfortabel mögiich sein. Dafür muss die Seite DayDetails opimiert werden.
Es soll möglich sein eine Person in der Teilnehmerliste auszuwählen. Bei der Erfassung einer neuen Strafe kann diese Person direkt vorbelegt werden. Außerdem soll zusätzlich zum Button "Strafe hinzufügen" ein Menü-Button angezeigt werden, der eine Kurzwahl aller Strafen mit der Option "IsOneClick" ermöglicht.
Die Erfassung von Strafen muss möglichst schnell und komfortabel mögiich sein. Dafür muss die Seite DayDetails optimiert werden.
Es soll möglich sein eine Person in der Teilnehmerliste auszuwählen. Bei der Erfassung einer neuen Strafe soll diese Person direkt vorbelegt werden. Außerdem soll zusätzlich zum Button "Strafe hinzufügen" ein Menü-Button angezeigt werden, der eine Kurzwahl aller Strafen mit der Option "IsOneClick" ermöglicht.
Der Benutzer soll visuell leicht erkennen können, ob und welcher Teilnehmer aktuell markiert wurde.

View File

@ -124,17 +124,28 @@
[Parameter]
public Guid DayId { get; set; }
[Parameter]
public Guid? PreselectedPersonId { get; set; }
private MudForm? _form;
private bool _isValid;
private ExpenseDto? _selectedExpense;
private DayParticipantDto? _selectedParticipant;
private decimal _customPrice;
private bool _initialized;
private bool IsInverseMode => _selectedExpense?.IsInverse == true;
protected override void OnParametersSet()
{
base.OnParametersSet();
if (!_initialized && PreselectedPersonId.HasValue)
{
_selectedParticipant = Participants.FirstOrDefault(p => p.PersonId == PreselectedPersonId.Value);
_initialized = true;
}
if (_selectedExpense is not null)
{
_customPrice = _selectedExpense.Price;

View File

@ -99,16 +99,27 @@ else
}
else
{
<MudList T="DayParticipantDto" Dense="true">
<MudList T="DayParticipantDto" Dense="true" Class="participant-list">
@foreach (var participant in Day.Participants.OrderBy(p => p.PersonName))
{
<MudListItem T="DayParticipantDto">
var isSelected = _selectedParticipantId == participant.PersonId;
<MudListItem T="DayParticipantDto"
Class="@(isSelected ? "selected-participant" : "selectable-participant")"
OnClick="@(() => ToggleParticipantSelection(participant.PersonId))">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Style="width: 100%">
<MudStack Row="true" AlignItems="AlignItems.Center">
<MudAvatar Size="Size.Small" Color="@(participant.PersonStatus == PersonStatus.Member ? Color.Primary : Color.Secondary)">
@participant.PersonName[0]
</MudAvatar>
<MudText>@participant.PersonName</MudText>
<MudBadge Visible="@isSelected"
Color="Color.Success"
Icon="@Icons.Material.Filled.Check"
Overlap="true"
Bordered="true">
<MudAvatar Size="Size.Small"
Color="@(isSelected ? Color.Success : (participant.PersonStatus == PersonStatus.Member ? Color.Primary : Color.Secondary))"
Style="@(isSelected ? "border: 2px solid var(--mud-palette-success)" : "")">
@participant.PersonName[0]
</MudAvatar>
</MudBadge>
<MudText Style="@(isSelected ? "font-weight: 600" : "")">@participant.PersonName</MudText>
@if (participant.PersonStatus == PersonStatus.Guest)
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Secondary">Gast</MudChip>
@ -119,12 +130,21 @@ else
<MudIconButton Icon="@Icons.Material.Filled.PersonRemove"
Size="Size.Small"
Color="Color.Error"
OnClick="@(() => RemoveParticipant(participant))" />
OnClick="@(() => RemoveParticipant(participant))"
OnClickStopPropagation="true" />
}
</MudStack>
</MudListItem>
}
</MudList>
@if (_selectedParticipantId.HasValue)
{
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mt-2">
<MudChip T="string" Color="Color.Success" Size="Size.Small" OnClose="ClearParticipantSelection">
@SelectedParticipant?.PersonName ausgewählt
</MudChip>
</MudStack>
}
}
</MudPaper>
</MudItem>
@ -204,14 +224,36 @@ else
</MudText>
@if (Day.Status != DayStatus.Closed)
{
<MudButton Variant="Variant.Text"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddExpenseDialog"
Size="Size.Small"
Disabled="@(Day.Participants.Count == 0)">
Strafe hinzufügen
</MudButton>
<MudStack Row="true" Spacing="1">
@if (OneClickExpenses.Count > 0 && _selectedParticipantId.HasValue)
{
<MudMenu Icon="@Icons.Material.Filled.FlashOn"
Color="Color.Warning"
Variant="Variant.Filled"
Size="Size.Small"
Title="Schnellzuweisung"
AnchorOrigin="Origin.BottomRight"
TransformOrigin="Origin.TopRight">
@foreach (var expense in OneClickExpenses.OrderBy(e => e.Name))
{
<MudMenuItem OnClick="@(() => QuickAssignExpense(expense))">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Style="min-width: 200px">
<MudText>@expense.Name</MudText>
<MudText Color="Color.Secondary">@expense.Price.ToString("C")</MudText>
</MudStack>
</MudMenuItem>
}
</MudMenu>
}
<MudButton Variant="Variant.Text"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddExpenseDialog"
Size="Size.Small"
Disabled="@(Day.Participants.Count == 0)">
Strafe hinzufügen
</MudButton>
</MudStack>
}
</MudStack>
@ -331,6 +373,10 @@ else
private DayDto? Day => DayState.Value.SelectedDay;
private IReadOnlyList<PersonExpenseDto> Expenses => DayState.Value.SelectedDayExpenses;
private Guid? _selectedParticipantId;
private DayParticipantDto? SelectedParticipant => Day?.Participants.FirstOrDefault(p => p.PersonId == _selectedParticipantId);
private IReadOnlyList<ExpenseDto> OneClickExpenses => DayState.Value.AvailableExpenses.Where(e => e.IsOneClick && !e.IsVariable).ToList();
private decimal TotalExpenseAmount => Expenses.Sum(e => e.Price);
private decimal OpenExpenseAmount => Expenses.Where(e => e.PersonExpenseStatus == PersonExpenseStatus.Open).Sum(e => e.Price);
private decimal PaidExpenseAmount => Expenses.Where(e => e.PersonExpenseStatus == PersonExpenseStatus.Done).Sum(e => e.Price);
@ -359,6 +405,16 @@ else
Dispatcher.Dispatch(new ClearDayErrorAction());
}
private void ToggleParticipantSelection(Guid personId)
{
_selectedParticipantId = _selectedParticipantId == personId ? null : personId;
}
private void ClearParticipantSelection()
{
_selectedParticipantId = null;
}
private void NavigateBack()
{
NavigationManager.NavigateTo("/days");
@ -546,7 +602,8 @@ else
{
{ "AvailableExpenses", availableExpenses },
{ "Participants", Day.Participants },
{ "DayId", Day.Id }
{ "DayId", Day.Id },
{ "PreselectedPersonId", _selectedParticipantId }
};
var dialog = await DialogService.ShowAsync<AddPersonExpenseDialog>("Strafe hinzufügen", parameters);
@ -567,6 +624,37 @@ else
}
}
private void QuickAssignExpense(ExpenseDto expense)
{
if (Day is null || !_selectedParticipantId.HasValue) return;
if (expense.IsInverse)
{
var dto = new CreateInversePersonExpenseDto
{
ExcludedPersonId = _selectedParticipantId.Value,
ExpenseId = expense.Id,
DayId = Day.Id,
GameId = null
};
Dispatcher.Dispatch(new CreateInversePersonExpenseAction(dto));
Snackbar.Add($"{expense.Name} für alle außer {SelectedParticipant?.PersonName}...", Severity.Info);
}
else
{
var dto = new CreatePersonExpenseDto
{
PersonId = _selectedParticipantId.Value,
ExpenseId = expense.Id,
DayId = Day.Id,
GameId = null,
Price = null
};
Dispatcher.Dispatch(new CreatePersonExpenseAction(dto));
Snackbar.Add($"{expense.Name} für {SelectedParticipant?.PersonName}...", Severity.Info);
}
}
private void MarkAsPaid(PersonExpenseDto expense)
{
var dto = new UpdatePersonExpenseStatusDto

View File

@ -0,0 +1,21 @@
.participant-list ::deep .mud-list-item {
cursor: pointer;
transition: background-color 0.2s ease;
}
.selectable-participant {
border-left: 3px solid transparent;
}
.selectable-participant:hover {
background-color: var(--mud-palette-action-default-hover);
}
.selected-participant {
background-color: color-mix(in srgb, var(--mud-palette-success) 15%, transparent);
border-left: 3px solid var(--mud-palette-success);
}
.selected-participant:hover {
background-color: color-mix(in srgb, var(--mud-palette-success) 25%, transparent);
}