@page "/tables" @rendermode InteractiveServer @using System @using System.Collections.Generic @using System.Diagnostics.CodeAnalysis @inject IJSRuntime JSRuntime @inject LookupService LookupService Critical Tables
Critical Tables

Browse critical results by table

Switch tables to read the full roll matrix, compare outcomes, and use the affix legend as quick play help.

@if (referenceData is null) {

Loading reference data...

} else if (!referenceData.CriticalTables.Any()) {

No critical tables are available yet.

} else if (isDetailLoading) {

Loading the selected table...

} else if (!string.IsNullOrWhiteSpace(detailError)) {

@detailError

} else if (tableDetail is null) {

The selected table could not be loaded.

} else if (tableDetail is { } detail) {

@detail.DisplayName

@if (detail.Groups.Count > 0) { @foreach (var group in detail.Groups) { } @foreach (var group in detail.Groups) { foreach (var column in detail.Columns) { } } } else { @foreach (var column in detail.Columns) { } } @foreach (var rollBand in detail.RollBands) { @if (detail.Groups.Count > 0) { foreach (var group in detail.Groups) { foreach (var column in detail.Columns) { @if (TryGetCell(rollBand.Label, group.Key, column.Key, out var groupedCell)) { } else { } } } } else { foreach (var column in detail.Columns) { @if (TryGetCell(rollBand.Label, null, column.Key, out var cell)) { } else { } } } }
@group.Label
@column.Label
@column.Label
@rollBand.Label
@{ var legendEntries = detail.Legend ?? Array.Empty(); } @if (legendEntries.Count > 0) {

Affix legend

@foreach (var entry in legendEntries) {
@entry.Symbol
@entry.Label @entry.Description
}
}
}
@if (isEditorOpen) { } @code { private LookupReferenceData? referenceData; private CriticalTableDetail? tableDetail; private string selectedTableSlug = string.Empty; private bool isDetailLoading; private bool isReferenceDataLoading = true; private string? detailError; private Dictionary<(string RollBand, string? GroupKey, string ColumnKey), CriticalTableCellDetail>? cellIndex; private int tableLayoutVersion; private int appliedLayoutVersion = -1; private bool IsTableSelectionDisabled => isReferenceDataLoading || (referenceData?.CriticalTables.Count ?? 0) == 0; private bool isEditorOpen; private bool isEditorLoading; private bool isEditorSaving; private string? editorLoadError; private string? editorSaveError; private int? editingResultId; private CriticalCellEditorModel? editorModel; protected override async Task OnInitializedAsync() { referenceData = await LookupService.GetReferenceDataAsync(); isReferenceDataLoading = false; selectedTableSlug = referenceData?.CriticalTables.FirstOrDefault()?.Key ?? string.Empty; await LoadTableDetailAsync(); } private async Task HandleTableChanged(ChangeEventArgs args) { selectedTableSlug = args.Value?.ToString() ?? string.Empty; await LoadTableDetailAsync(); } private async Task LoadTableDetailAsync() { if (string.IsNullOrWhiteSpace(selectedTableSlug)) { tableDetail = null; cellIndex = null; tableLayoutVersion++; return; } isDetailLoading = true; detailError = null; tableDetail = null; cellIndex = null; try { tableDetail = await LookupService.GetCriticalTableAsync(selectedTableSlug); if (tableDetail is null) { detailError = "The selected table could not be loaded."; } } catch (Exception exception) { detailError = exception.Message; } finally { isDetailLoading = false; BuildCellIndex(); tableLayoutVersion++; } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (tableDetail is null || appliedLayoutVersion == tableLayoutVersion) { return; } await JSRuntime.InvokeVoidAsync("rolemasterTables.alignCriticalCells"); appliedLayoutVersion = tableLayoutVersion; } private void BuildCellIndex() { if (tableDetail?.Cells is null) { cellIndex = null; return; } cellIndex = new Dictionary<(string, string?, string), CriticalTableCellDetail>(); foreach (var cell in tableDetail.Cells) { cellIndex[(cell.RollBand, cell.GroupKey, cell.ColumnKey)] = cell; } } private bool TryGetCell(string rollBand, string? groupKey, string columnKey, [NotNullWhen(true)] out CriticalTableCellDetail? cell) { if (cellIndex is null) { cell = null; return false; } return cellIndex.TryGetValue((rollBand, groupKey, columnKey), out cell); } private async Task OpenCellEditorAsync(int resultId) { if (string.IsNullOrWhiteSpace(selectedTableSlug)) { return; } editorLoadError = null; editorSaveError = null; editorModel = null; editingResultId = resultId; isEditorSaving = false; isEditorLoading = true; isEditorOpen = true; try { var response = await LookupService.GetCriticalCellEditorAsync(selectedTableSlug, resultId); if (response is null) { editorLoadError = "The selected cell could not be loaded for editing."; editorModel = null; return; } editorModel = CriticalCellEditorModel.FromResponse(response); } catch (Exception exception) { editorLoadError = exception.Message; editorModel = null; } finally { isEditorLoading = false; } } private async Task CloseCellEditorAsync() { isEditorOpen = false; isEditorLoading = false; isEditorSaving = false; editorLoadError = null; editorSaveError = null; editingResultId = null; editorModel = null; await InvokeAsync(StateHasChanged); } private async Task SaveCellEditorAsync() { if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null) { return; } isEditorSaving = true; editorSaveError = null; try { var response = await LookupService.UpdateCriticalCellAsync(selectedTableSlug, editingResultId.Value, editorModel.ToRequest()); if (response is null) { editorSaveError = "The selected cell could not be saved."; return; } await LoadTableDetailAsync(); await CloseCellEditorAsync(); } catch (Exception exception) { editorSaveError = exception.Message; } finally { isEditorSaving = false; } } private async Task HandleCellKeyDownAsync(KeyboardEventArgs args, int resultId) { if (args.Key is "Enter" or " ") { await OpenCellEditorAsync(resultId); } } }