@page "/settings/chapter" @attribute [Authorize(Roles = AuthRoles.Administrator)] @implements IAsyncDisposable @using WebApp.Authentication @using WebApp.Models @using WebApp.Components.Shared.Components @using WebApp.Services @using Core.Models @inject IConfiguration Configuration @inject IChapterSettingsWriter ChapterSettingsWriter @inject INotesService NotesService @rendermode InteractiveServer @if (_settings != null) { Basic Information Both (MS and HS) Middle School (MS) High School (HS) Chapter IDs Student Index Columns Field names from each student's imported notes table to show as extra Students index columns. Click a field found in notes to add or remove it, or type names (one per line). @if (_availableFields.Count > 0) { Fields in student notes @foreach (var field in _availableFields) { var selected = IsFieldSelected(field); @field } } else { No imported fields found in student notes yet. } @if (_isSaving) { Saving... } else { Save Settings } @if (!string.IsNullOrEmpty(_statusMessage)) { @_statusMessage } } else { } @code { private Models.ChapterSettings? _settings; private string _noteFieldsText = string.Empty; private IReadOnlyList _availableFields = []; private bool _isSaving; private string? _statusMessage; private Severity _statusSeverity = Severity.Success; private CancellationTokenSource? _cancellationTokenSource; private bool _isDisposed; protected override void OnInitialized() { _cancellationTokenSource = new CancellationTokenSource(); _settings = Models.ChapterSettings.FromConfiguration(Configuration); _noteFieldsText = string.Join(Environment.NewLine, _settings.StudentIndexNoteFields); } protected override async Task OnInitializedAsync() { try { var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; _availableFields = await NotesService.GetImportedFieldNamesAsync(cancellationToken); } catch (OperationCanceledException) { } } private IReadOnlyList SelectedFields() => _noteFieldsText .Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Where(line => !string.IsNullOrWhiteSpace(line)) .ToList(); private bool IsFieldSelected(string field) => SelectedFields().Any(selected => selected.Equals(field, StringComparison.OrdinalIgnoreCase)); private void ToggleField(string field) { var selected = SelectedFields().ToList(); var index = selected.FindIndex(name => name.Equals(field, StringComparison.OrdinalIgnoreCase)); if (index >= 0) selected.RemoveAt(index); else selected.Add(field); _noteFieldsText = string.Join(Environment.NewLine, selected); } private async Task SaveSettings() { if (_settings == null) return; _isSaving = true; _statusMessage = null; try { _settings.StudentIndexNoteFields = [.. SelectedFields()]; var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; await ChapterSettingsWriter.WriteAsync(_settings, cancellationToken); if (_isDisposed) return; _statusMessage = "Settings saved. Open Students again to see the updated index columns."; _statusSeverity = Severity.Success; } catch (OperationCanceledException) { } catch (Exception ex) { if (_isDisposed) return; _statusMessage = $"Error saving settings: {ex.Message}"; _statusSeverity = Severity.Error; } finally { if (!_isDisposed) _isSaving = false; } } public async ValueTask DisposeAsync() { if (!_isDisposed) { _isDisposed = true; _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } await ValueTask.CompletedTask; } }