Add some local storage to save settings between

page reloads.
This commit is contained in:
2025-12-12 12:48:44 -05:00
parent aeafdcee1a
commit b465235096
9 changed files with 379 additions and 21 deletions
@@ -6,6 +6,7 @@
@inject IConfiguration Configuration
@inject AppDbContext Context
@inject ClipboardService ClipboardService
@inject LocalStorageService LocalStorage
<PageTitle>@Configuration["ChapterSettings:Shortname"] TSA Schedule @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
@@ -18,7 +19,8 @@
<MudPaper Class="pa-2 ma-2" Elevation="3">
<MudGrid>
<MudItem xs="6" sm="3" lg="2">
<MudNumericField @bind-Value="_parameters.TimeSlots"
<MudNumericField Value="_parameters.TimeSlots"
ValueChanged="async (int val) => await OnTimeSlotCountChanged(val)"
Label="Time Slots" Min="1" Max="4">
</MudNumericField>
</MudItem>
@@ -83,13 +85,15 @@
<MudItem xs="5" sm="4" lg="3">
<MudStack>
<StudentTextBoxSelector Students="@_students"
@bind-SelectedStudents="_absentStudents"
SelectedStudents="_absentStudents"
SelectedStudentsChanged="OnAbsentStudentsChanged"
Title="Absent Students"
Label="Search for absent students"
ShowFullName="true"/>
<MudDivider Class="my-4"/>
<TeamToggleSelector Teams="@_teams"
@bind-SelectedTeams="_scheduledTeams"
SelectedTeams="_scheduledTeams"
SelectedTeamsChanged="OnScheduledTeamsChanged"
Title="Scheduled Teams"
ShowEventAttributes="true" />
</MudStack>
@@ -109,43 +113,67 @@
private IEnumerable<Student> _absentStudents = [];
private IEnumerable<Team> _possibleAdditions = [];
private void AddRegionals()
private async Task OnScheduledTeamsChanged(IEnumerable<Team> teams)
{
_scheduledTeams = teams;
await SaveScheduledTeams();
}
private async Task OnAbsentStudentsChanged(IEnumerable<Student> students)
{
_absentStudents = students;
await SaveAbsentStudents();
}
private async Task OnTimeSlotCountChanged(int timeSlots)
{
_parameters.TimeSlots = timeSlots;
await SaveTimeSlotCount();
}
private async void AddRegionals()
{
_scheduledTeams
= _teams.Where(e => e.Event.RegionalEvent).Concat(_scheduledTeams).Distinct();
await SaveScheduledTeams();
}
private void AddHighLevelOfEffort()
private async void AddHighLevelOfEffort()
{
_scheduledTeams
= _teams.Where(e => e.Event.LevelOfEffort >= 3).Concat(_scheduledTeams).Distinct();
await SaveScheduledTeams();
}
private void RemoveIndividual()
private async void RemoveIndividual()
{
_scheduledTeams
= _scheduledTeams.Where(t => t.Event.EventFormat != EventFormat.Individual);
await SaveScheduledTeams();
}
private void RemoveLowLevelOfEffort()
private async void RemoveLowLevelOfEffort()
{
_scheduledTeams
= _scheduledTeams.Where(t => t.Event.LevelOfEffort > 1);
await SaveScheduledTeams();
}
private void Invert()
private async void Invert()
{
var rt = _scheduledTeams.ToArray();
_scheduledTeams
= _teams.Where(t => !rt.Contains(t));
await SaveScheduledTeams();
}
private void Reset()
private async void Reset()
{
_scheduledTeams = [];
await SaveScheduledTeams();
}
private void ToggleRequiredTeam(Team unassignedTeam)
private async void ToggleRequiredTeam(Team unassignedTeam)
{
if (_scheduledTeams.Contains(unassignedTeam))
_scheduledTeams = _scheduledTeams.Where(t => t != unassignedTeam);
@@ -153,6 +181,7 @@
{
_scheduledTeams = _scheduledTeams.Concat(new[] { unassignedTeam });
}
await SaveScheduledTeams();
}
protected override async Task OnInitializedAsync()
@@ -201,6 +230,55 @@
.Include(e => e.EventRankings)
.ThenInclude(e => e.EventDefinition)
.OrderBy(e => e.FirstName).ToArrayAsync();
// Load saved selections from localStorage
await LoadScheduledTeams();
await LoadAbsentStudents();
await LoadTimeSlotCount();
}
private async Task SaveScheduledTeams()
{
var teamIds = _scheduledTeams.Select(t => t.Id).ToArray();
await LocalStorage.SetIntArrayAsync("MeetingSchedule_ScheduledTeams", teamIds);
}
private async Task LoadScheduledTeams()
{
var teamIds = await LocalStorage.GetIntArrayAsync("MeetingSchedule_ScheduledTeams");
if (teamIds.Length > 0)
{
_scheduledTeams = _teams.Where(t => teamIds.Contains(t.Id)).ToArray();
}
}
private async Task SaveAbsentStudents()
{
var studentIds = _absentStudents.Select(s => s.Id).ToArray();
await LocalStorage.SetIntArrayAsync("MeetingSchedule_AbsentStudents", studentIds);
}
private async Task LoadAbsentStudents()
{
var studentIds = await LocalStorage.GetIntArrayAsync("MeetingSchedule_AbsentStudents");
if (studentIds.Length > 0)
{
_absentStudents = _students.Where(s => studentIds.Contains(s.Id)).ToArray();
}
}
private async Task SaveTimeSlotCount()
{
await LocalStorage.SetIntAsync("MeetingSchedule_TimeSlotCount", _parameters.TimeSlots);
}
private async Task LoadTimeSlotCount()
{
var timeSlots = await LocalStorage.GetIntAsync("MeetingSchedule_TimeSlotCount", defaultValue: 2);
if (timeSlots > 0)
{
_parameters.TimeSlots = timeSlots;
}
}
private async Task<TableData<TeamScheduleTimeSlot>> SolveSchedule(TableState arg1, CancellationToken arg2)
@@ -64,8 +64,8 @@ else
</MudPaper>
<div class="mt-4">
<MudButton StartIcon="@Icons.Material.Filled.Edit" Href="@($"/students/edit?id={student.Id}")" Variant="Variant.Filled" Color="Color.Primary">Edit</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="/students" Variant="Variant.Text">Back to List</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Edit" Href="@($"/students/edit?id={student.Id}&returnUrl={ReturnUrl ?? "/students"}")" Variant="Variant.Filled" Color="Color.Primary">Edit</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="@(ReturnUrl ?? "/students")" Variant="Variant.Text">Back to List</MudButton>
</div>
}
@@ -75,6 +75,9 @@ else
[SupplyParameterFromQuery]
private int Id { get; set; }
[SupplyParameterFromQuery]
private string? ReturnUrl { get; set; }
protected override async Task OnInitializedAsync()
{
student = await context.Students.FirstOrDefaultAsync(m => m.Id == Id);
@@ -45,7 +45,7 @@ else
</MudPaper>
</MudItem>
</MudGrid>
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students">Back</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="@(ReturnUrl ?? "/students")">Back</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Save" OnClick="UpdateStudent">Save</MudButton>
</EditForm>
}
@@ -54,6 +54,9 @@ else
[SupplyParameterFromQuery]
private int Id { get; set; }
[SupplyParameterFromQuery]
private string? ReturnUrl { get; set; }
[SupplyParameterFromForm]
private Student? Student { get; set; }
@@ -92,7 +95,7 @@ else
}
}
NavigationManager.NavigateTo("/students");
NavigationManager.NavigateTo(ReturnUrl ?? "/students");
}
private bool StudentExists(int id)
@@ -32,8 +32,8 @@
</PropertyColumn>
<TemplateColumn>
<CellTemplate>
<CrudActions DetailsHref="@($"/students/details?id={context.Item!.Id}")"
EditHref="@($"/students/edit?id={context.Item!.Id}")"
<CrudActions DetailsHref="@($"/students/details?id={context.Item!.Id}&returnUrl=/students")"
EditHref="@($"/students/edit?id={context.Item!.Id}&returnUrl=/students")"
DeleteOnClick="() => DeleteStudent(context.Item!)">
</CrudActions>
</CellTemplate>
@@ -3,6 +3,7 @@
@using Microsoft.EntityFrameworkCore
@using WebApp.Models
@inject AppDbContext Context
@inject WebApp.LocalStorageService LocalStorage
<PageTitle>Registration - TSA Chapter Organizer</PageTitle>
@@ -23,10 +24,10 @@
<MudText Typo="Typo.body2" Style="margin-bottom: 8px;">
<strong>Show Columns:</strong>
<MudCheckBox Value="_showGrade" ValueChanged="(bool val) => { _showGrade = val; _gridKey++; StateHasChanged(); }" Label="Grade" Dense="true" Size="Size.Small" />
<MudCheckBox Value="_showRegionalId" ValueChanged="(bool val) => { _showRegionalId = val; _gridKey++; StateHasChanged(); }" Label="Regional ID" Dense="true" Size="Size.Small" />
<MudCheckBox Value="_showStateId" ValueChanged="(bool val) => { _showStateId = val; _gridKey++; StateHasChanged(); }" Label="State ID" Dense="true" Size="Size.Small" />
<MudCheckBox Value="_showNationalId" ValueChanged="(bool val) => { _showNationalId = val; _gridKey++; StateHasChanged(); }" Label="National ID" Dense="true" Size="Size.Small" />
<MudCheckBox Value="_showGrade" ValueChanged="async (bool val) => await OnColumnToggle(nameof(_showGrade), val, v => _showGrade = v)" Label="Grade" Dense="true" Size="Size.Small" />
<MudCheckBox Value="_showRegionalId" ValueChanged="async (bool val) => await OnColumnToggle(nameof(_showRegionalId), val, v => _showRegionalId = v)" Label="Regional ID" Dense="true" Size="Size.Small" />
<MudCheckBox Value="_showStateId" ValueChanged="async (bool val) => await OnColumnToggle(nameof(_showStateId), val, v => _showStateId = v)" Label="State ID" Dense="true" Size="Size.Small" />
<MudCheckBox Value="_showNationalId" ValueChanged="async (bool val) => await OnColumnToggle(nameof(_showNationalId), val, v => _showNationalId = v)" Label="National ID" Dense="true" Size="Size.Small" />
</MudText>
<MudDataGrid T="StudentTeamInfo" ServerData="ServerReload" @ref="_dataGrid" @key="_gridKey" Filterable="true" RowsPerPage="35">
@@ -34,6 +35,12 @@
<PropertyColumn Property="@(e => e.Student.LastName)" Title="Student" Sortable="true">
<CellTemplate>
@context.Item.Student.LastNameFirstName
<MudTooltip Text="Edit">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Href="@($"/students/edit?id={context.Item.Student.Id}&returnUrl=/students/teams")"
Style="margin-left: 4px;" />
</MudTooltip>
</CellTemplate>
</PropertyColumn>
@if (_showGrade)
@@ -94,6 +101,7 @@
private bool _showRegionalId;
private bool _showStateId;
private bool _showNationalId;
private bool _preferencesLoaded = false;
// TODO: Remove this workaround once MudBlazor fixes dynamic column ordering
// https://dev.to/the_real_slim_janey/get-in-line-customizing-column-order-in-mudblazor-3ail
@@ -103,6 +111,39 @@
// 3. Remove "@key="_gridKey"" attribute from MudDataGrid (line 32)
private int _gridKey = 0;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!_preferencesLoaded)
{
await LoadColumnPreferences();
_preferencesLoaded = true;
_gridKey++; // Force grid recreation with loaded preferences
StateHasChanged();
}
}
protected override void OnInitialized()
{
// Reset flag when component is initialized/re-initialized
_preferencesLoaded = false;
}
private async Task LoadColumnPreferences()
{
_showGrade = await LocalStorage.GetBoolAsync("Registration_ShowGrade", false);
_showRegionalId = await LocalStorage.GetBoolAsync("Registration_ShowRegionalId", false);
_showStateId = await LocalStorage.GetBoolAsync("Registration_ShowStateId", false);
_showNationalId = await LocalStorage.GetBoolAsync("Registration_ShowNationalId", false);
}
private async Task OnColumnToggle(string columnName, bool value, Action<bool> setter)
{
setter(value);
_gridKey++;
await LocalStorage.SetBoolAsync($"Registration_{columnName}", value);
StateHasChanged();
}
private async Task ToggleRegionalFilter()
{
_showRegionalOnly = !_showRegionalOnly;
@@ -16,7 +16,7 @@
@student.FirstName
@if (captain)
{
<span> (Cpt)</span>
<MudIcon Icon="@AppIcons.Captain" Size="Size.Small" Style="margin-left: 4px;" />
}
</MudPaper>
}