< Summary

Information
Class: Chronicis.Client.Pages.CampaignDetail
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Pages/CampaignDetail.razor
Line coverage
0%
Covered lines: 0
Uncovered lines: 101
Coverable lines: 101
Total lines: 320
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 38
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
BuildRenderTree(...)0%2040%
get_CampaignId()100%210%
.ctor()100%210%
OnParametersSetAsync()100%210%
LoadCampaignAsync()0%4260%
OnNameChanged()100%210%
OnDescriptionChanged()100%210%
OnActiveToggle()0%7280%
SaveCampaign()0%7280%
CreateArc()0%4260%
NavigateToArc(...)100%210%

File(s)

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

#LineLine coverage
 1@page "/campaign/{CampaignId:guid}"
 2@attribute [Authorize]
 3@using Chronicis.Shared.DTOs
 4@using Chronicis.Client.Components.Dialogs
 5@using Chronicis.Client.Components.Shared
 6@using Chronicis.Client.Utilities
 7@inject ICampaignApiService CampaignApi
 8@inject IArcApiService ArcApi
 9@inject IWorldApiService WorldApi
 10@inject ITreeStateService TreeState
 11@inject IBreadcrumbService BreadcrumbService
 12@inject ISnackbar Snackbar
 13@inject IDialogService DialogService
 14@inject NavigationManager Navigation
 15@inject IJSRuntime JSRuntime
 16
 017@if (_isLoading)
 18{
 19    <LoadingSkeleton />
 20}
 021else if (_campaign != null)
 22{
 23    <MudPaper Elevation="2" Class="chronicis-article-card chronicis-fade-in">
 24        <DetailPageHeader Breadcrumbs="_breadcrumbs"
 25                          Icon="@Icons.Material.Filled.AutoStories"
 26                          @bind-Title="_editName"
 27                          Placeholder="Campaign Name"
 28                          OnTitleEdited="OnNameChanged"
 29                          OnEnterPressed="SaveCampaign" />
 30
 31        <!-- Description -->
 32        <MudTextField @bind-Value="_editDescription"
 33                      Label="Description"
 34                      Variant="Variant.Outlined"
 35                      Lines="3"
 36                      Placeholder="Describe your campaign..."
 37                      Class="mb-4"
 38                      Immediate="true"
 39                      @onkeyup="OnDescriptionChanged" />
 40
 41        <!-- Arcs Section -->
 42        <div class="d-flex align-center justify-space-between mb-3">
 43            <MudText Typo="Typo.h6" Style="color: var(--chronicis-beige-gold);">
 44                <MudIcon Icon="@Icons.Material.Filled.Bookmark" Size="Size.Small" Class="mr-2" />
 45                Arcs / Acts
 46            </MudText>
 47            <MudButton Variant="Variant.Filled"
 48                       Color="Color.Primary"
 49                       Size="Size.Small"
 50                       StartIcon="@Icons.Material.Filled.Add"
 51                       OnClick="CreateArc">
 52                New Arc
 53            </MudButton>
 54        </div>
 55
 056        @if (_arcs.Any())
 57        {
 58            <MudList T="ArcDto" Dense="true" Class="mb-4">
 059                @foreach (var arc in _arcs.OrderBy(a => a.SortOrder))
 60                {
 61                    <EntityListItem Icon="@Icons.Material.Filled.Bookmark"
 62                                    Title="@arc.Name"
 063                                    OnClick="@(() => NavigateToArc(arc.Id))">
 64                        <MudChip T="string" Size="Size.Small" Variant="Variant.Outlined">
 065                            @arc.SessionCount sessions
 66                        </MudChip>
 67                    </EntityListItem>
 68                }
 69            </MudList>
 70        }
 71        else
 72        {
 73            <MudAlert Severity="Severity.Info" Class="mb-4">
 74                No arcs yet. Create your first arc to organize your sessions!
 75            </MudAlert>
 76        }
 77
 78        <!-- Campaign Info -->
 79        <MudText Typo="Typo.h6" Class="mb-3" Style="color: var(--chronicis-beige-gold);">
 80            Campaign Info
 81        </MudText>
 82
 83        <MudSimpleTable Dense="true" Hover="true" Class="mb-4">
 84            <tbody>
 85                <tr>
 86                    <td><MudIcon Icon="@Icons.Material.Filled.PlayCircle" Size="Size.Small" Class="mr-2" />Active Campai
 87                    <td>
 88                        <MudSwitch T="bool"
 89                                   Value="_campaign.IsActive"
 90                                   ValueChanged="OnActiveToggle"
 91                                   Color="Color.Success"
 92                                   Size="Size.Small"
 93                                   Disabled="_isTogglingActive" />
 94                    </td>
 95                </tr>
 96                <tr>
 97                    <td><MudIcon Icon="@Icons.Material.Filled.Layers" Size="Size.Small" Class="mr-2" />Arcs</td>
 098                    <td>@_campaign.ArcCount</td>
 99                </tr>
 100                <tr>
 101                    <td><MudIcon Icon="@Icons.Material.Filled.Person" Size="Size.Small" Class="mr-2" />Owner</td>
 0102                    <td>@_campaign.OwnerName</td>
 103                </tr>
 104                <tr>
 105                    <td><MudIcon Icon="@Icons.Material.Filled.CalendarToday" Size="Size.Small" Class="mr-2" />Created</t
 0106                    <td>@_campaign.CreatedAt.ToString("MMMM d, yyyy")</td>
 107                </tr>
 0108                @if (_campaign.StartedAt.HasValue)
 109                {
 110                    <tr>
 111                        <td><MudIcon Icon="@Icons.Material.Filled.PlayArrow" Size="Size.Small" Class="mr-2" />Started</t
 0112                        <td>@_campaign.StartedAt.Value.ToString("MMMM d, yyyy")</td>
 113                    </tr>
 114                }
 115            </tbody>
 116        </MudSimpleTable>
 117
 118        <!-- AI Summary Section -->
 119        <AISummarySection EntityId="CampaignId"
 120                          EntityType="Campaign"
 121                          @bind-IsExpanded="_summaryExpanded" />
 122
 123        <!-- Save Status -->
 124        <div class="chronicis-flex-between mt-4">
 125            <SaveStatusIndicator IsSaving="_isSaving" HasUnsavedChanges="_hasUnsavedChanges" />
 126
 127            <MudButton Variant="Variant.Filled"
 128                       Color="Color.Primary"
 129                       OnClick="SaveCampaign"
 130                       Disabled="_isSaving"
 131                       StartIcon="@Icons.Material.Filled.Save">
 132                Save
 133            </MudButton>
 134        </div>
 135    </MudPaper>
 136}
 137
 138@code {
 139    [Parameter]
 0140    public Guid CampaignId { get; set; }
 141
 142    private CampaignDetailDto? _campaign;
 0143    private List<ArcDto> _arcs = new();
 144    private WorldDto? _world;
 0145    private string _editName = string.Empty;
 0146    private string _editDescription = string.Empty;
 0147    private bool _isLoading = true;
 148    private bool _isSaving = false;
 149    private bool _isTogglingActive = false;
 150    private bool _hasUnsavedChanges = false;
 151    private bool _summaryExpanded = false;
 0152    private List<BreadcrumbItem> _breadcrumbs = new();
 153
 154    protected override async Task OnParametersSetAsync()
 155    {
 0156        await LoadCampaignAsync();
 0157    }
 158
 159    private async Task LoadCampaignAsync()
 160    {
 0161        _isLoading = true;
 0162        StateHasChanged();
 163
 164        try
 165        {
 0166            _campaign = await CampaignApi.GetCampaignAsync(CampaignId);
 0167            if (_campaign == null)
 168            {
 0169                Navigation.NavigateTo("/dashboard", replace: true);
 0170                return;
 171            }
 172            else
 173            {
 0174                _editName = _campaign.Name;
 0175                _editDescription = _campaign.Description ?? string.Empty;
 0176                _hasUnsavedChanges = false;
 177
 178                // Load arcs
 0179                _arcs = await ArcApi.GetArcsByCampaignAsync(CampaignId);
 180
 181                // Load world for breadcrumbs
 0182                _world = (await WorldApi.GetWorldAsync(_campaign.WorldId)) as WorldDto;
 183
 0184                if (_world != null)
 185                {
 0186                    _breadcrumbs = BreadcrumbService.ForCampaign(_campaign, _world);
 187                }
 188                else
 189                {
 190                    // Fallback if world not loaded
 0191                    _breadcrumbs = new List<BreadcrumbItem>
 0192                    {
 0193                        new("Dashboard", href: "/dashboard"),
 0194                        new(_campaign.Name, href: null, disabled: true)
 0195                    };
 196                }
 197
 0198                await JSRuntime.InvokeVoidAsync("eval", $"document.title = '{JsUtilities.EscapeForJs(_campaign.Name)} - 
 199
 200                // Highlight in tree
 0201                TreeState.ExpandPathToAndSelect(CampaignId);
 202            }
 0203        }
 0204        catch (Exception ex)
 205        {
 0206            Snackbar.Add($"Failed to load campaign: {ex.Message}", Severity.Error);
 0207        }
 208        finally
 209        {
 0210            _isLoading = false;
 211        }
 0212    }
 213
 0214    private void OnNameChanged() => _hasUnsavedChanges = true;
 0215    private void OnDescriptionChanged() => _hasUnsavedChanges = true;
 216
 217    private async Task OnActiveToggle(bool isActive)
 218    {
 0219        if (_campaign == null || _isTogglingActive) return;
 220
 0221        _isTogglingActive = true;
 0222        StateHasChanged();
 223
 224        try
 225        {
 0226            if (isActive)
 227            {
 0228                var success = await CampaignApi.ActivateCampaignAsync(CampaignId);
 0229                if (success)
 230                {
 0231                    _campaign.IsActive = true;
 0232                    Snackbar.Add("Campaign set as active", Severity.Success);
 233                }
 234                else
 235                {
 0236                    Snackbar.Add("Failed to activate campaign", Severity.Error);
 237                }
 238            }
 239            else
 240            {
 241                // To deactivate, we'd need a separate endpoint or just not support it
 242                // For now, show a message that you need to activate another campaign
 0243                Snackbar.Add("To deactivate, set another campaign as active", Severity.Info);
 244            }
 0245        }
 0246        catch (Exception ex)
 247        {
 0248            Snackbar.Add($"Error: {ex.Message}", Severity.Error);
 0249        }
 250        finally
 251        {
 0252            _isTogglingActive = false;
 0253            StateHasChanged();
 254        }
 0255    }
 256
 257    private async Task SaveCampaign()
 258    {
 0259        if (_campaign == null || _isSaving) return;
 260
 0261        _isSaving = true;
 0262        StateHasChanged();
 263
 264        try
 265        {
 0266            var updateDto = new CampaignUpdateDto
 0267            {
 0268                Name = _editName.Trim(),
 0269                Description = string.IsNullOrWhiteSpace(_editDescription) ? null : _editDescription.Trim()
 0270            };
 271
 0272            var updated = await CampaignApi.UpdateCampaignAsync(CampaignId, updateDto);
 0273            if (updated != null)
 274            {
 0275                _campaign.Name = updated.Name;
 0276                _campaign.Description = updated.Description;
 0277                _hasUnsavedChanges = false;
 278
 0279                await TreeState.RefreshAsync();
 0280                await JSRuntime.InvokeVoidAsync("eval", $"document.title = '{JsUtilities.EscapeForJs(_editName)} - Chron
 281
 0282                Snackbar.Add("Campaign saved", Severity.Success);
 283            }
 0284        }
 0285        catch (Exception ex)
 286        {
 0287            Snackbar.Add($"Failed to save: {ex.Message}", Severity.Error);
 0288        }
 289        finally
 290        {
 0291            _isSaving = false;
 0292            StateHasChanged();
 293        }
 0294    }
 295
 296    private async Task CreateArc()
 297    {
 0298        var parameters = new DialogParameters
 0299        {
 0300            { "CampaignId", CampaignId }
 0301        };
 302
 0303        var dialog = await DialogService.ShowAsync<CreateArcDialog>("New Arc", parameters);
 0304        var result = await dialog.Result;
 305
 0306        if (result != null && !result.Canceled && result.Data is ArcDto arc)
 307        {
 0308            await TreeState.RefreshAsync();
 0309            await LoadCampaignAsync();
 0310            Navigation.NavigateTo($"/arc/{arc.Id}");
 0311            Snackbar.Add("Arc created", Severity.Success);
 312        }
 0313    }
 314
 315    private void NavigateToArc(Guid arcId)
 316    {
 0317        Navigation.NavigateTo($"/arc/{arcId}");
 0318    }
 319
 320}