ui dashboard cleanup

This commit is contained in:
2026-06-26 18:07:02 +09:00
parent 1e6bf702bc
commit 74a83f94fb
5 changed files with 218 additions and 355 deletions
@@ -1,28 +1,8 @@
<MudNavMenu>
<MudNavMenu>
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard">
Dashboard
</MudNavLink>
<MudNavLink Href="/portfolio" Icon="@Icons.Material.Filled.Inventory2">
Portfolio
</MudNavLink>
<MudNavLink Href="/analytics" Icon="@Icons.Material.Filled.Analytics">
Analytics
</MudNavLink>
<MudNavLink Href="/reports" Icon="@Icons.Material.Filled.DocumentScanner">
Reports
</MudNavLink>
<MudDivider Class="my-2" />
<MudNavLink Href="/settings" Icon="@Icons.Material.Filled.Settings">
Settings
</MudNavLink>
<MudNavLink Href="/" Icon="@Icons.Material.Filled.Help">
Help
<MudNavLink Href="/operations" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Assessment">
Operations
</MudNavLink>
</MudNavMenu>
@@ -1,19 +0,0 @@
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
@@ -1,310 +1,138 @@
@page "/"
@using QuantEngine.Core.Models
@using QuantEngine.Core.Interfaces
@inject NavigationManager NavManager
@inject ISnackbar Snackbar
@inject IPostgresqlHistorySnapshotReader HistoryReader
@using QuantEngine.Core.Infrastructure
@inject IWebHostEnvironment Environment
<PageTitle>Quant Engine - Dashboard</PageTitle>
<MudText Typo="Typo.h4" Class="mb-4">Quant Engine Dashboard</MudText>
<MudText Typo="Typo.h4" Class="mb-2">Quant Engine</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-4">
루트 화면은 운영 진입점입니다. 가짜 성과 수치 없이 현재 스냅샷 상태와 리포트 경로만 보여줍니다.
</MudText>
<!-- KPI Cards -->
<MudGrid Class="mb-4">
<MudItem xs="12" sm="6" md="3">
<MudItem xs="12" sm="6" md="4">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">Active Positions</MudText>
<MudText Typo="Typo.h5" Class="mt-2">@activePositions</MudText>
<MudText Typo="Typo.caption" Class="mt-1">from history snapshot</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary">Operational Report</MudText>
<MudText Typo="Typo.h6" Class="mt-2">@ReportStateLabel</MudText>
<MudText Typo="Typo.caption">@ReportPath</MudText>
</MudCardContent>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudItem xs="12" sm="6" md="4">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">Portfolio Value</MudText>
<MudText Typo="Typo.h5" Class="mt-2">@portfolioValueLabel</MudText>
<MudText Typo="Typo.caption" Class="mt-1">PostgreSQL snapshot</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary">Sections</MudText>
<MudText Typo="Typo.h6" Class="mt-2">@SectionCountLabel</MudText>
<MudText Typo="Typo.caption">Temp/operational_report.json</MudText>
</MudCardContent>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudItem xs="12" sm="6" md="4">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">Signal Quality</MudText>
<MudText Typo="Typo.h5" Class="mt-2">@signalQualityLabel</MudText>
<MudText Typo="Typo.caption" Class="mt-1">decision_result_history</MudText>
</MudCardContent>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">System Status</MudText>
<MudChip Color="Color.Success" Icon="@Icons.Material.Filled.Check" Class="mt-2">@dbStatusLabel</MudChip>
<MudText Typo="Typo.body2" Color="Color.Secondary">Primary Route</MudText>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Href="/operations" Class="mt-2">
Open Operations
</MudButton>
</MudCardContent>
</MudCard>
</MudItem>
</MudGrid>
<!-- Market Overview -->
<MudGrid Class="mb-4">
<MudItem xs="12" md="6">
<MudCard>
<MudItem xs="12" md="7">
<MudCard Class="h-100">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Market Status</MudText>
<MudText Typo="Typo.h6">Current State</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudStack Spacing="2">
<MudText Typo="Typo.body2">
<strong>Market Regime:</strong> <MudChip Size="Size.Small" Color="Color.Warning">@marketRegimeLabel</MudChip>
</MudText>
<MudText Typo="Typo.body2">
<strong>Volatility:</strong> @volatilityLabel
</MudText>
<MudText Typo="Typo.body2">
<strong>Cash Position:</strong> @cashPositionLabel
</MudText>
<MudText Typo="Typo.body2">
<strong>Last Updated:</strong> @lastUpdatedLabel
</MudText>
<MudText Typo="Typo.body2"><strong>Status:</strong> <MudChip Size="Size.Small" Color="@ReportChipColor">@ReportChipLabel</MudChip></MudText>
<MudText Typo="Typo.body2"><strong>Generated:</strong> @GeneratedAtLabel</MudText>
<MudText Typo="Typo.body2"><strong>Source:</strong> @SourceLabel</MudText>
<MudText Typo="Typo.body2"><strong>Decision feed:</strong> @DecisionFeedLabel</MudText>
<MudText Typo="Typo.body2"><strong>Factor feed:</strong> @FactorFeedLabel</MudText>
<MudText Typo="Typo.body2"><strong>Raw feed:</strong> @RawFeedLabel</MudText>
</MudStack>
</MudCardContent>
</MudCard>
</MudItem>
<MudItem xs="12" md="6">
<MudCard>
<MudItem xs="12" md="5">
<MudCard Class="h-100">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">System Health</MudText>
<MudText Typo="Typo.h6">Routing Notes</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudStack Spacing="2">
<MudText Typo="Typo.body2">
<strong>Database:</strong>
<MudChip Size="Size.Small" Color="Color.Success">@databaseLabel</MudChip>
</MudText>
<MudText Typo="Typo.body2">
<strong>DB History Feed:</strong>
<MudChip Size="Size.Small" Color="Color.Success">@historyFeedLabel</MudChip>
</MudText>
<MudText Typo="Typo.body2">
<strong>Signal Generator:</strong>
<MudChip Size="Size.Small" Color="Color.Info">@signalGeneratorLabel</MudChip>
</MudText>
<MudText Typo="Typo.body2">
<strong>API Uptime:</strong> 99.8%
</MudText>
<MudText Typo="Typo.body2">- 운영 데이터는 snapshot 우선입니다.</MudText>
<MudText Typo="Typo.body2">- Excel/GAS 의존 문구는 운영 경로에서 제거 대상입니다.</MudText>
<MudText Typo="Typo.body2">- 숫자는 provenance 없으면 표시하지 않습니다.</MudText>
</MudStack>
</MudCardContent>
</MudCard>
</MudItem>
</MudGrid>
<!-- Performance Metrics -->
<MudCard Class="mb-4">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Performance Metrics</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudStack Spacing="3">
<MudStack Row="true" Spacing="3">
<MudItem xs="12" sm="6" md="4">
<MudText Typo="Typo.body2"><strong>YTD Return</strong></MudText>
<MudText Typo="Typo.h6" Color="Color.Success">@ytdReturnLabel</MudText>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudText Typo="Typo.body2"><strong>Sharpe Ratio</strong></MudText>
<MudText Typo="Typo.h6">@sharpeLabel</MudText>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudText Typo="Typo.body2"><strong>Max Drawdown</strong></MudText>
<MudText Typo="Typo.h6" Color="Color.Warning">@maxDrawdownLabel</MudText>
</MudItem>
</MudStack>
<MudStack Row="true" Spacing="3">
<MudItem xs="12" sm="6" md="4">
<MudText Typo="Typo.body2"><strong>Win Rate</strong></MudText>
<MudText Typo="Typo.h6" Color="Color.Success">@winRateLabel</MudText>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudText Typo="Typo.body2"><strong>Profit Factor</strong></MudText>
<MudText Typo="Typo.h6">@profitFactorLabel</MudText>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudText Typo="Typo.body2"><strong>Trades This Month</strong></MudText>
<MudText Typo="Typo.h6">@tradesThisMonthLabel</MudText>
</MudItem>
</MudStack>
</MudStack>
</MudCardContent>
</MudCard>
<!-- Algorithm Status -->
<MudCard Class="mb-4">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Algorithm Status (v9 Hardening)</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudTable Items="algorithmPhases" Hover="true" Striped="true" Dense="true">
<HeaderContent>
<MudTh>Phase</MudTh>
<MudTh>Name</MudTh>
<MudTh>Status</MudTh>
<MudTh>Progress</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context["Phase"]</MudTd>
<MudTd>@context["Name"]</MudTd>
<MudTd>
@{
var status = context["Status"].ToString();
var chipColor = "Calibrated".Equals(status) ? Color.Success : Color.Info;
}
<MudChip Size="Size.Small" Color="@chipColor">@status</MudChip>
</MudTd>
<MudTd>
@{
var progress = context.TryGetValue("Progress", out var p) ? p?.ToString() ?? string.Empty : string.Empty;
var progressValue = int.TryParse(progress.Replace("%", ""), out var val) ? val : 0;
}
<MudProgressLinear Value="@progressValue" Size="Size.Small" />
</MudTd>
</RowTemplate>
</MudTable>
</MudCardContent>
</MudCard>
<!-- Live Signal Feed -->
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Recent Signals (Live Feed)</MudText>
<MudText Typo="Typo.h6">Coverage Summary</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudTable Items="recentSignals" Hover="true" Striped="true" Dense="true">
<HeaderContent>
<MudTh>Timestamp</MudTh>
<MudTh>Ticker</MudTh>
<MudTh>Signal</MudTh>
<MudTh>Score</MudTh>
<MudTh>Style</MudTh>
<MudTh>Status</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context["Timestamp"]</MudTd>
<MudTd><strong>@context["Ticker"]</strong></MudTd>
<MudTd>
@{
var signal = context["Signal"].ToString();
var signalColor = "BUY".Equals(signal) ? Color.Success : Color.Warning;
}
<MudChip Size="Size.Small" Color="@signalColor">@signal</MudChip>
</MudTd>
<MudTd>@context["Score"]</MudTd>
<MudTd>@context["Style"]</MudTd>
<MudTd>
<MudChip Size="Size.Small" Variant="Variant.Text">@context["Status"]</MudChip>
</MudTd>
</RowTemplate>
</MudTable>
@if (Sections.Count == 0)
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined">
DATA_MISSING: operational_report.json이 비어 있거나 아직 생성되지 않았습니다.
</MudAlert>
}
else
{
<MudTable Items="Sections" Hover="true" Striped="true" Dense="true">
<HeaderContent>
<MudTh>Name</MudTh>
<MudTh>Title</MudTh>
<MudTh>Preview</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Name</MudTd>
<MudTd>@context.Title</MudTd>
<MudTd>@context.Preview</MudTd>
</RowTemplate>
</MudTable>
}
</MudCardContent>
</MudCard>
@code {
private List<Dictionary<string, object>> algorithmPhases = new();
private List<Dictionary<string, object>> recentSignals = new();
private string activePositions = "0";
private string portfolioValueLabel = "n/a";
private string signalQualityLabel = "n/a";
private string dbStatusLabel = "Pending";
private string marketRegimeLabel = "PENDING";
private string volatilityLabel = "n/a";
private string cashPositionLabel = "n/a";
private string lastUpdatedLabel = "n/a";
private string databaseLabel = "Pending";
private string historyFeedLabel = "Pending";
private string signalGeneratorLabel = "Pending";
private string ytdReturnLabel = "n/a";
private string sharpeLabel = "n/a";
private string maxDrawdownLabel = "n/a";
private string winRateLabel = "n/a";
private string profitFactorLabel = "n/a";
private string tradesThisMonthLabel = "0";
private readonly List<OperationalReportSection> Sections = new();
private string ReportStateLabel = "DATA_MISSING";
private string ReportChipLabel = "DATA_MISSING";
private Color ReportChipColor = Color.Warning;
private string SectionCountLabel = "0";
private string GeneratedAtLabel = "n/a";
private string SourceLabel = "n/a";
private string DecisionFeedLabel = "DISCONNECTED";
private string FactorFeedLabel = "DISCONNECTED";
private string RawFeedLabel = "DISCONNECTED";
private string ReportPath = "n/a";
protected override async Task OnInitializedAsync()
protected override void OnInitialized()
{
await LoadHistoryAsync();
}
private async Task LoadHistoryAsync()
{
try
{
var decisions = await HistoryReader.ReadAsync("decision_result_history", 5);
activePositions = decisions.Count.ToString();
signalQualityLabel = decisions.Count > 0 ? "snapshot" : "n/a";
dbStatusLabel = decisions.Count > 0 ? "Connected" : "Empty";
databaseLabel = dbStatusLabel;
historyFeedLabel = decisions.Count > 0 ? "Active" : "Pending";
signalGeneratorLabel = decisions.Count > 0 ? "Snapshot-driven" : "Pending";
marketRegimeLabel = decisions.Count > 0 ? "SNAPSHOT" : "PENDING";
volatilityLabel = decisions.Count > 0 ? "Snapshot-derived" : "n/a";
cashPositionLabel = decisions.Count > 0 ? "Snapshot-derived" : "n/a";
lastUpdatedLabel = decisions.Count > 0
? (decisions[0].TryGetValue("decided_at", out var decidedAt) ? decidedAt?.ToString() ?? "n/a" : "n/a")
: "n/a";
ytdReturnLabel = decisions.Count > 0 ? "snapshot" : "n/a";
sharpeLabel = decisions.Count > 0 ? "snapshot" : "n/a";
maxDrawdownLabel = decisions.Count > 0 ? "snapshot" : "n/a";
winRateLabel = decisions.Count > 0 ? "snapshot" : "n/a";
profitFactorLabel = decisions.Count > 0 ? "snapshot" : "n/a";
tradesThisMonthLabel = decisions.Count.ToString();
recentSignals = decisions.Select(row => new Dictionary<string, object>
{
{ "Timestamp", row.TryGetValue("decided_at", out var decidedAt) ? decidedAt?.ToString() ?? "" : "" },
{ "Ticker", row.TryGetValue("instrument_id", out var ticker) ? ticker?.ToString() ?? "" : "" },
{ "Signal", row.TryGetValue("action", out var action) ? action?.ToString() ?? "" : "" },
{ "Score", row.TryGetValue("score", out var score) ? score?.ToString() ?? "" : "" },
{ "Style", row.TryGetValue("source_version", out var sourceVersion) ? sourceVersion?.ToString() ?? "" : "" },
{ "Status", row.TryGetValue("gate", out var gate) ? gate?.ToString() ?? "" : "" }
}).ToList();
var rawCount = (await HistoryReader.ReadAsync("market_raw_history", 1)).Count;
var factorCount = (await HistoryReader.ReadAsync("factor_output_history", 1)).Count;
var gapCount = (await HistoryReader.ReadAsync("market_vs_engine_gap_history", 1)).Count;
portfolioValueLabel = rawCount > 0 ? "snapshot" : "n/a";
algorithmPhases = new()
{
new() { { "Phase", "P0" }, { "Name", "History Contract" }, { "Status", "Calibrated" }, { "Progress", "100%" } },
new() { { "Phase", "P1" }, { "Name", "PostgreSQL Store" }, { "Status", rawCount > 0 ? "Active" : "Pending" }, { "Progress", rawCount > 0 ? "100%" : "0%" } },
new() { { "Phase", "P2" }, { "Name", "Factor Output History" }, { "Status", factorCount > 0 ? "Active" : "Pending" }, { "Progress", factorCount > 0 ? "100%" : "0%" } },
new() { { "Phase", "P3" }, { "Name", "Decision Result History" }, { "Status", recentSignals.Count > 0 ? "Active" : "Pending" }, { "Progress", recentSignals.Count > 0 ? "100%" : "0%" } },
new() { { "Phase", "P4" }, { "Name", "Gap History" }, { "Status", gapCount > 0 ? "Active" : "Pending" }, { "Progress", gapCount > 0 ? "100%" : "0%" } }
};
}
catch
{
algorithmPhases = new()
{
new() { { "Phase", "P0" }, { "Name", "History Contract" }, { "Status", "Pending" }, { "Progress", "0%" } }
};
recentSignals = new();
}
ReportPath = Path.GetFullPath(Path.Combine(Environment.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
var report = OperationalReportLoader.Load(ReportPath);
Sections.AddRange(report.Sections);
SectionCountLabel = report.SectionCount.ToString();
GeneratedAtLabel = report.GeneratedAt;
SourceLabel = report.SourceJson;
ReportStateLabel = Sections.Count > 0 ? "READY" : "DATA_MISSING";
ReportChipLabel = Sections.Count > 0 ? "READY" : "DATA_MISSING";
ReportChipColor = Sections.Count > 0 ? Color.Success : Color.Warning;
}
}
@@ -0,0 +1,138 @@
@page "/operations"
@using QuantEngine.Core.Infrastructure
@inject IWebHostEnvironment Environment
<PageTitle>Quant Engine - Operations</PageTitle>
<MudText Typo="Typo.h4" Class="mb-2">Operational Report</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-4">
이 페이지는 `Temp/operational_report.json`만 읽습니다. DB 연결과 무관하게 동일한 결과를 보여주는 운영 고정 화면입니다.
</MudText>
<MudGrid Class="mb-4">
<MudItem xs="12" sm="6" md="3">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">Schema</MudText>
<MudText Typo="Typo.h6" Class="mt-2">@SchemaVersion</MudText>
</MudCardContent>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">Sections</MudText>
<MudText Typo="Typo.h6" Class="mt-2">@SectionCountLabel</MudText>
</MudCardContent>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">Source</MudText>
<MudText Typo="Typo.h6" Class="mt-2">@SourceJson</MudText>
</MudCardContent>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">Generated</MudText>
<MudText Typo="Typo.h6" Class="mt-2">@GeneratedAt</MudText>
</MudCardContent>
</MudCard>
</MudItem>
</MudGrid>
<MudGrid Class="mb-4">
@foreach (var section in HighlightSections)
{
<MudItem xs="12" sm="6" lg="3">
<MudCard Class="h-100">
<MudCardContent>
<MudText Typo="Typo.body2" Color="Color.Secondary">@(section.Name)</MudText>
<MudText Typo="Typo.subtitle1" Class="mt-1">@(section.Title)</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">@(section.Preview)</MudText>
</MudCardContent>
</MudCard>
</MudItem>
}
</MudGrid>
<MudCard Class="mb-4">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Report Health</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudStack Spacing="2">
<MudText Typo="Typo.body2"><strong>Status:</strong> <MudChip Size="Size.Small" Color="@HealthColor">@HealthLabel</MudChip></MudText>
<MudText Typo="Typo.body2"><strong>Path:</strong> @ReportPath</MudText>
<MudText Typo="Typo.body2"><strong>Sections rendered:</strong> @RenderedSectionCountLabel</MudText>
</MudStack>
</MudCardContent>
</MudCard>
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Sections</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
@if (Sections.Count == 0)
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined">
DATA_MISSING: operational_report.json에 표시할 섹션이 없습니다.
</MudAlert>
}
else
{
<MudTable Items="Sections" Hover="true" Striped="true" Dense="true">
<HeaderContent>
<MudTh>Name</MudTh>
<MudTh>Title</MudTh>
<MudTh>Preview</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Name</MudTd>
<MudTd>@context.Title</MudTd>
<MudTd>@context.Preview</MudTd>
</RowTemplate>
</MudTable>
}
</MudCardContent>
</MudCard>
@code {
private readonly List<OperationalReportSection> Sections = new();
private readonly List<OperationalReportSection> HighlightSections = new();
private string SchemaVersion = "n/a";
private string SourceJson = "n/a";
private string GeneratedAt = "n/a";
private string SectionCountLabel = "0";
private string RenderedSectionCountLabel = "0";
private string HealthLabel = "DATA_MISSING";
private Color HealthColor = Color.Warning;
private string ReportPath = "n/a";
protected override async Task OnInitializedAsync()
{
ReportPath = Path.GetFullPath(Path.Combine(Environment.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
var report = OperationalReportLoader.Load(ReportPath);
SchemaVersion = report.SchemaVersion;
SourceJson = report.SourceJson;
GeneratedAt = report.GeneratedAt;
Sections.AddRange(report.Sections);
HighlightSections.Clear();
HighlightSections.AddRange(Sections.Take(4));
SectionCountLabel = report.SectionCount.ToString();
RenderedSectionCountLabel = Sections.Count.ToString();
HealthLabel = Sections.Count > 0 ? "PASS" : "DATA_MISSING";
HealthColor = Sections.Count > 0 ? Color.Success : Color.Warning;
}
}
@@ -1,64 +0,0 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th aria-label="Temperature in Celsius">Temp. (C)</th>
<th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}