< Summary

Line coverage
100%
Covered lines: 5
Uncovered lines: 0
Coverable lines: 5
Total lines: 260
Line coverage: 100%
Branch coverage
100%
Covered branches: 10
Total branches: 10
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: BuildRenderTree(...)100%1010100%
File 2: .ctor()100%11100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Quests/ArcQuestList.razor

#LineLine coverage
 1@using Chronicis.Shared.DTOs.Quests
 2@using Chronicis.Shared.Enums
 3
 4<div class="arc-quest-list">
 5    <div class="d-flex align-center justify-space-between mb-3">
 6        <MudText Typo="Typo.h6" Style="color: var(--chronicis-beige-gold);">
 7            <MudIcon Icon="@Icons.Material.Filled.Flag" Size="Size.Small" Class="mr-2" />
 8            Quests
 9        </MudText>
 4210        @if (_isGm)
 11        {
 12            <MudButton Variant="Variant.Filled"
 13                       Color="Color.Primary"
 14                       Size="Size.Small"
 15                       StartIcon="@Icons.Material.Filled.Add"
 16                       OnClick="CreateQuest"
 17                       Disabled="_isLoading || _isCreating">
 18                @if (_isCreating)
 19                {
 20                    <MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-1" />
 21                }
 22                <span>New Quest</span>
 23            </MudButton>
 24        }
 25    </div>
 26
 4227    @if (_loadingError != null)
 28    {
 29        <MudAlert Severity="Severity.Error" Class="mb-4">
 30            <div class="d-flex align-center justify-space-between">
 31                <div>
 32                    <strong>Failed to load quests</strong><br />
 33                    <span style="font-size: 0.875rem;">@_loadingError</span>
 34                </div>
 35                <MudButton Variant="Variant.Text"
 36                           Color="Color.Error"
 37                           OnClick="LoadQuestsAsync">
 38                    Retry
 39                </MudButton>
 40            </div>
 41        </MudAlert>
 42    }
 4143    else if (_isLoading)
 44    {
 45        <div class="d-flex align-center gap-2 mb-4">
 46            <MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Small" />
 47            <MudText Typo="Typo.body2" Style="opacity: 0.7;">Loading quests...</MudText>
 48        </div>
 49    }
 3950    else if (_quests.Any())
 51    {
 52        <MudList T="QuestDto" Dense="true" Class="mb-4">
 53            @foreach (var quest in _quests.OrderByDescending(q => q.UpdatedAt))
 54            {
 55                <MudListItem T="QuestDto" Class="quest-list-item">
 56                    <div class="d-flex align-center justify-space-between">
 57                        <div class="flex-grow-1">
 58                            <div class="d-flex align-center gap-2">
 59                                <MudText Typo="Typo.body1" Class="quest-title">@quest.Title</MudText>
 60                                <QuestStatusChip Status="@quest.Status" />
 61                                @if (quest.IsGmOnly)
 62                                {
 63                                    <MudChip Size="Size.Small" Color="Color.Warning" Variant="Variant.Text">
 64                                        GM Only
 65                                    </MudChip>
 66                                }
 67                            </div>
 68                            <MudText Typo="Typo.caption" Class="text-muted">
 69                                Updated @quest.UpdatedAt.ToString("MMM d, yyyy")
 70                                @if (quest.UpdateCount > 0)
 71                                {
 72                                    <span> · @quest.UpdateCount update@(quest.UpdateCount == 1 ? "" : "s")</span>
 73                                }
 74                            </MudText>
 75                        </div>
 76                        @if (_isGm)
 77                        {
 78                            <div class="d-flex gap-2">
 79                                <MudIconButton Icon="@Icons.Material.Filled.Edit"
 80                                               Size="Size.Small"
 81                                               Color="Color.Primary"
 82                                               OnClick="@(() => EditQuest(quest))"
 83                                               title="Edit quest" />
 84                                <MudIconButton Icon="@Icons.Material.Filled.Delete"
 85                                               Size="Size.Small"
 86                                               Color="Color.Error"
 87                                               OnClick="@(() => DeleteQuest(quest))"
 88                                               Disabled="_isDeleting"
 89                                               title="Delete quest" />
 90                            </div>
 91                        }
 92                    </div>
 93                </MudListItem>
 94            }
 95        </MudList>
 96    }
 97    else
 98    {
 99        <MudAlert Severity="Severity.Info" Class="mb-4">
 100            No quests yet. @(_isGm ? "Create your first quest to track objectives and goals!" : "The GM hasn't created a
 101        </MudAlert>
 102    }
 103</div>

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Quests/ArcQuestList.razor.cs

#LineLine coverage
 1using Chronicis.Client.Components.Dialogs;
 2using Chronicis.Client.Services;
 3using Chronicis.Shared.DTOs.Quests;
 4using Microsoft.AspNetCore.Components;
 5using MudBlazor;
 6
 7namespace Chronicis.Client.Components.Quests;
 8
 9public partial class ArcQuestList : ComponentBase
 10{
 11    [Parameter, EditorRequired]
 12    public Guid ArcId { get; set; }
 13
 14    [Parameter]
 15    public bool IsGm { get; set; }
 16
 17    [Parameter]
 18    public EventCallback<QuestDto> OnEditQuest { get; set; }
 19
 20    [Inject] private IQuestApiService QuestApi { get; set; } = default!;
 21    [Inject] private IDialogService DialogService { get; set; } = default!;
 22    [Inject] private ISnackbar Snackbar { get; set; } = default!;
 23    [Inject] private ILogger<ArcQuestList> Logger { get; set; } = default!;
 24
 2125    private List<QuestDto> _quests = new();
 26    private bool _isLoading;
 27    private bool _isCreating;
 28    private bool _isDeleting;
 29    private bool _isGm;
 30    private string? _loadingError;
 31
 32    protected override async Task OnInitializedAsync()
 33    {
 34        _isGm = IsGm;
 35        await LoadQuestsAsync();
 36    }
 37
 38    protected override async Task OnParametersSetAsync()
 39    {
 40        _isGm = IsGm;
 41
 42        // Reload if ArcId changes
 43        if (ArcId != Guid.Empty)
 44        {
 45            await LoadQuestsAsync();
 46        }
 47    }
 48
 49    private async Task LoadQuestsAsync()
 50    {
 51        _isLoading = true;
 52        _loadingError = null;
 53        StateHasChanged();
 54
 55        try
 56        {
 57            _quests = await QuestApi.GetArcQuestsAsync(ArcId);
 58        }
 59        catch (Exception ex)
 60        {
 61            Logger.LogError(ex, "Failed to load quests for arc {ArcId}", ArcId);
 62            _loadingError = ex.Message;
 63            _quests = new();
 64        }
 65        finally
 66        {
 67            _isLoading = false;
 68            StateHasChanged();
 69        }
 70    }
 71
 72    private async Task CreateQuest()
 73    {
 74        if (!_isGm || _isCreating)
 75            return;
 76
 77        _isCreating = true;
 78        StateHasChanged();
 79
 80        try
 81        {
 82            var parameters = new DialogParameters
 83            {
 84                { "ArcId", ArcId }
 85            };
 86
 87            var dialog = await DialogService.ShowAsync<CreateQuestDialog>("Create Quest", parameters);
 88            var result = await dialog.Result;
 89
 90            if (result != null && !result.Canceled)
 91            {
 92                await LoadQuestsAsync();
 93                Snackbar.Add("Quest created successfully", Severity.Success);
 94            }
 95        }
 96        catch (Exception ex)
 97        {
 98            Logger.LogError(ex, "Failed to create quest");
 99            Snackbar.Add($"Failed to create quest: {ex.Message}", Severity.Error);
 100        }
 101        finally
 102        {
 103            _isCreating = false;
 104            StateHasChanged();
 105        }
 106    }
 107
 108    private async Task EditQuest(QuestDto quest)
 109    {
 110        if (!_isGm)
 111            return;
 112
 113        await OnEditQuest.InvokeAsync(quest);
 114    }
 115
 116    private async Task DeleteQuest(QuestDto quest)
 117    {
 118        if (!_isGm || _isDeleting)
 119            return;
 120
 121        var confirmed = await DialogService.ShowMessageBox(
 122            "Delete Quest",
 123            $"Are you sure you want to delete '{quest.Title}'? This will also delete all quest updates. This action cann
 124            yesText: "Delete",
 125            cancelText: "Cancel");
 126
 127        if (confirmed != true)
 128            return;
 129
 130        _isDeleting = true;
 131        StateHasChanged();
 132
 133        try
 134        {
 135            var success = await QuestApi.DeleteQuestAsync(quest.Id);
 136            if (success)
 137            {
 138                Snackbar.Add("Quest deleted successfully", Severity.Success);
 139                await LoadQuestsAsync();
 140            }
 141            else
 142            {
 143                Snackbar.Add("Failed to delete quest", Severity.Error);
 144            }
 145        }
 146        catch (Exception ex)
 147        {
 148            Logger.LogError(ex, "Failed to delete quest {QuestId}", quest.Id);
 149            Snackbar.Add($"Error deleting quest: {ex.Message}", Severity.Error);
 150        }
 151        finally
 152        {
 153            _isDeleting = false;
 154            StateHasChanged();
 155        }
 156    }
 157}