@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 additional-fields 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 additional 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; } }