@* The accessible alternative to the chart (brief §7.2): one row per bucket with the period label, each series' value with its unit, the status in words (availability, reason, provenance), the optional cost and price coverage, the optional comparison (with the bucket it is compared with) and the change, a total row, and a drill-down per row. A wide table scrolls inside its own container, never the page. The rows read newest first with the total above them (A-42); the chart beside it stays chronological. *@ @if (_model is not null && _model.Rows.Count > 0) {
@if (!string.IsNullOrWhiteSpace(Caption)) { @Caption } @S.AnalysisTable_Period @foreach (var column in _model.Columns) { @column.Header @if (column.SubHeader is not null) {
@column.SubHeader
} } @if (HasDrill) { @S.Common_Actions } @foreach (var row in _model.Rows) { @row.Label @for (var i = 0; i < row.Cells.Count; i++) { var cell = row.Cells[i]; var numeric = _model.Columns[i].IsNumeric; @cell.Text @if (cell.Secondary is not null) {
@cell.Secondary
} } @if (HasDrill) { @if (row.Bucket is { } bucket) { var href = DrillHref?.Invoke(bucket); var label = Loc.F(S.AnalysisTable_DrillLabel, row.Label); if (href is not null) { } else if (OnDrill.HasDelegate) { } } } }
@if (Series.Any(s => !s.IsAdditive)) { @S.AnalysisTable_NotAdditive } } @code { /// The buckets of the plan, oldest first; the table turns them round itself (A-42). [Parameter, EditorRequired] public IReadOnlyList Buckets { get; set; } = []; /// The series (, , …). [Parameter, EditorRequired] public IReadOnlyList Series { get; set; } = []; /// The comparison buckets paired with : each row names the bucket it is compared with. [Parameter] public IReadOnlyList? ComparisonPairs { get; set; } [Parameter] public bool IncludeTotal { get; set; } = true; /// The drill-down address of a row ( written as a URL); null for none. [Parameter] public Func? DrillHref { get; set; } /// A row's drill-down as an action, when no address is given. [Parameter] public EventCallback OnDrill { get; set; } /// The table's accessible name (also its caption for screen readers). [Parameter] public string? Caption { get; set; } private AnalysisTableModel? _model; private object? _builtFrom; private bool HasDrill => DrillHref is not null || OnDrill.HasDelegate; protected override void OnParametersSet() { var source = (Buckets, Series, ComparisonPairs, IncludeTotal, System.Globalization.CultureInfo.CurrentCulture.Name); if (!Equals(_builtFrom, source)) { _builtFrom = source; _model = AnalysisTableModel.Build(Buckets, Series, ComparisonPairs, IncludeTotal); } } private static string? RowClass(AnalysisTableRow row) => row.IsTotal ? "mv-row-total" : row.IsQualified ? "mv-row-qualified" : null; private static string? CellClass(AnalysisTableCell cell, bool numeric) { var classes = string.Join(' ', new[] { numeric ? "mv-num" : null, cell.CssClass, cell.IsUnknown ? "mv-unknown" : null }.Where(c => c is not null)); return classes.Length > 0 ? classes : null; } }