< Summary

Information
Class: Chronicis.Client.Pages.Dashboard
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Pages/Dashboard.razor
Line coverage
100%
Covered lines: 12
Uncovered lines: 0
Coverable lines: 12
Total lines: 228
Line coverage: 100%
Branch coverage
100%
Covered branches: 12
Total branches: 12
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
BuildRenderTree(...)100%1212100%
OnViewModelChanged(...)100%11100%
Dispose()100%11100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Pages/Dashboard.razor

#LineLine coverage
 1@page "/dashboard"
 2@attribute [Authorize]
 3@implements IDisposable
 4@using Chronicis.Client.Components.Shared
 5@using Chronicis.Client.Components.Dialogs
 6@inject DashboardViewModel ViewModel
 7
 8<PageTitle>Dashboard - Chronicis</PageTitle>
 9
 510@if (ViewModel.IsLoading)
 11{
 12    <MudPaper Elevation="2" Class="dashboard-container">
 13        <div class="dashboard-loading">
 14            <MudProgressCircular Color="Color.Primary" Size="Size.Large" Indeterminate="true" />
 15            <MudText Typo="Typo.body1" Class="mt-4">Loading your chronicle...</MudText>
 16        </div>
 17    </MudPaper>
 18}
 419else if (ViewModel.Dashboard == null)
 20{
 21    <MudPaper Elevation="2" Class="dashboard-container">
 22        <div class="dashboard-error">
 23            <MudIcon Icon="@Icons.Material.Filled.Error" Size="Size.Large" Color="Color.Error" />
 24            <MudText Typo="Typo.h6" Class="mt-4">Unable to load dashboard</MudText>
 25            <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(() => ViewModel.LoadDashboardAsync())" 
 26                Try Again
 27            </MudButton>
 28        </div>
 29    </MudPaper>
 30}
 31else
 32{
 33    <div class="dashboard-wrapper">
 34        <!-- Hero Section -->
 35        <div class="hero-section">
 36            <div class="hero-content">
 37                <div class="hero-text">
 38                    <MudText Typo="Typo.h3" Class="hero-title">
 39                        Welcome back, <span class="user-name">@ViewModel.Dashboard.UserDisplayName</span>
 40                    </MudText>
 41                    <MudText Typo="Typo.body1" Class="hero-subtitle">
 42                        Your chronicle awaits. What story will you tell today?
 43                    </MudText>
 344                    @if (ViewModel.Quote != null)
 45                    {
 46                        <div class="hero-quote">
 47                            <MudText Typo="Typo.body2" Class="quote-text">
 48                                "@ViewModel.Quote.Content"
 49                            </MudText>
 50                            <MudText Typo="Typo.caption" Class="quote-author">
 51                                — @ViewModel.Quote.Author
 52                            </MudText>
 53                        </div>
 54                    }
 55                </div>
 56                <div class="hero-actions">
 57                    <MudButton Variant="Variant.Filled"
 58                               Color="Color.Primary"
 59                               Size="Size.Large"
 60                               OnClick="@(() => ViewModel.CreateNewWorldAsync())"
 61                               Class="hero-button">
 62                        Create New World
 63                    </MudButton>
 64                    <MudButton Variant="Variant.Outlined"
 65                               Color="Color.Primary"
 66                               Size="Size.Large"
 67                               OnClick="@(() => ViewModel.JoinWorldAsync())"
 68                               Class="hero-button">
 69                        Join a World
 70                    </MudButton>
 71                </div>
 72            </div>
 73
 74            <!-- Prompts in Hero -->
 375            @if (ViewModel.Dashboard.Prompts.Any())
 76            {
 77                <div class="hero-prompts">
 678                    @foreach (var prompt in ViewModel.Dashboard.Prompts.Take(3))
 79                    {
 80                        <div class="hero-prompt-card @DashboardViewModel.GetCategoryClass(prompt.Category)" @onclick="()
 81                            <div class="prompt-icon">
 82                                <IconDisplay Icon="@prompt.Icon" DefaultIcon="💡" />
 83                            </div>
 84                            <div class="prompt-content">
 285                                <span class="prompt-title">@prompt.Title</span>
 286                                <span class="prompt-message">@prompt.Message</span>
 87                            </div>
 288                            @if (!string.IsNullOrEmpty(prompt.ActionUrl))
 89                            {
 90                                <MudIcon Icon="@Icons.Material.Filled.ChevronRight" Class="prompt-arrow" />
 91                            }
 92                        </div>
 93                    }
 94                </div>
 95            }
 96        </div>
 97
 98        <!-- Main Content -->
 99        <MudPaper Elevation="2" Class="dashboard-container">
 100            <div class="chronicis-dashboard">
 101                <MudGrid>
 102                    <!-- Main Content: World Panels -->
 103                    <MudItem xs="12" md="8">
 104                        @if (ViewModel.OrderedWorlds.Any())
 105                        {
 106                            <!-- Primary World -->
 107                            <WorldPanel World="@ViewModel.OrderedWorlds.First()"
 108                                        IsExpanded="true" />
 109
 110                            <!-- Other Worlds -->
 111                            @if (ViewModel.OrderedWorlds.Count > 1)
 112                            {
 113                                <MudText Typo="Typo.overline" Class="other-worlds-header">
 114                                    Other Worlds
 115                                </MudText>
 116                                @foreach (var world in ViewModel.OrderedWorlds.Skip(1))
 117                                {
 118                                    <WorldPanel World="@world"
 119                                                IsExpanded="false"
 120                                                Class="mb-3" />
 121                                }
 122                            }
 123                        }
 124                        else
 125                        {
 126                            <!-- Empty State: No Worlds -->
 127                            <MudPaper Elevation="0" Class="empty-state-panel">
 128                                <div class="empty-state-content">
 129                                    <div class="empty-icon">🌍</div>
 130                                    <MudText Typo="Typo.h5" Class="empty-title">
 131                                        Begin Your Chronicle
 132                                    </MudText>
 133                                    <MudText Typo="Typo.body1" Class="empty-message">
 134                                        Create your first world to start organizing your campaign knowledge.
 135                                        Every great adventure needs a home.
 136                                    </MudText>
 137                                    <MudButton Variant="Variant.Filled"
 138                                               Color="Color.Primary"
 139                                               Size="Size.Large"
 140                                               StartIcon="@Icons.Material.Filled.Add"
 141                                               OnClick="@(() => ViewModel.CreateNewWorldAsync())"
 142                                               Class="mt-4">
 143                                        Create Your First World
 144                                    </MudButton>
 145                                </div>
 146                            </MudPaper>
 147                        }
 148                    </MudItem>
 149
 150                    <!-- Sidebar -->
 151                    <MudItem xs="12" md="4">
 152                        <!-- My Characters Summary -->
 153                        @if (ViewModel.Dashboard.ClaimedCharacters.Any())
 154                        {
 155                            <MudPaper Elevation="1" Class="sidebar-panel mb-4">
 156                                <MudText Typo="Typo.h6" Class="panel-title">
 157                                    <MudIcon Icon="@Icons.Material.Filled.People" Class="mr-2" />
 158                                    My Characters
 159                                </MudText>
 160                                <div class="characters-summary">
 161                                    @foreach (var character in ViewModel.Dashboard.ClaimedCharacters.Take(5))
 162                                    {
 163                                        <div class="character-item" @onclick="async () => await ViewModel.NavigateToChar
 164                                            <IconDisplay Icon="@character.IconEmoji" DefaultIcon="👤" CssClass="characte
 165                                            <div class="character-info">
 166                                                <span class="character-name">@character.Title</span>
 167                                                <span class="character-world">@character.WorldName</span>
 168                                            </div>
 169                                        </div>
 170                                    }
 171                                    @if (ViewModel.Dashboard.ClaimedCharacters.Count > 5)
 172                                    {
 173                                        <MudText Typo="Typo.caption" Class="more-characters">
 174                                            +@(ViewModel.Dashboard.ClaimedCharacters.Count - 5) more characters
 175                                        </MudText>
 176                                    }
 177                                </div>
 178                            </MudPaper>
 179                        }
 180
 181                        <!-- Quick Stats -->
 182                        <MudPaper Elevation="1" Class="sidebar-panel stats-panel">
 183                            <MudText Typo="Typo.h6" Class="panel-title">
 184                                <MudIcon Icon="@Icons.Material.Filled.Analytics" Class="mr-2" />
 185                                Your Chronicle
 186                            </MudText>
 187                            <div class="stats-grid">
 188                                <div class="stat-box">
 189                                    <span class="stat-number">@ViewModel.Dashboard.Worlds.Count</span>
 190                                    <span class="stat-label">Worlds</span>
 191                                </div>
 192                                <div class="stat-box">
 193                                    <span class="stat-number">@ViewModel.Dashboard.Worlds.Sum(w => w.Campaigns.Count)</s
 194                                    <span class="stat-label">Campaigns</span>
 195                                </div>
 196                                <div class="stat-box">
 197                                    <span class="stat-number">@ViewModel.Dashboard.Worlds.Sum(w => w.ArticleCount)</span
 198                                    <span class="stat-label">Articles</span>
 199                                </div>
 200                                <div class="stat-box">
 201                                    <span class="stat-number">@ViewModel.Dashboard.ClaimedCharacters.Count</span>
 202                                    <span class="stat-label">Characters</span>
 203                                </div>
 204                            </div>
 205                        </MudPaper>
 206                    </MudItem>
 207                </MudGrid>
 208            </div>
 209        </MudPaper>
 210    </div>
 211}
 212
 213@code {
 214    protected override async Task OnInitializedAsync()
 215    {
 216        ViewModel.PropertyChanged += OnViewModelChanged;
 217        await ViewModel.InitializeAsync();
 218    }
 219
 220    private void OnViewModelChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
 12221        => InvokeAsync(StateHasChanged);
 222
 223    public void Dispose()
 224    {
 5225        ViewModel.PropertyChanged -= OnViewModelChanged;
 5226        ViewModel.Dispose();
 5227    }
 228}