< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 78
Coverable lines: 78
Total lines: 248
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 62
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

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

#LineLine coverage
 1@using Chronicis.Shared.DTOs.Quests
 2@using Chronicis.Shared.Enums
 3
 4<div class="arc-quest-timeline">
 05    @if (_quest != null)
 6    {
 7        <div class="d-flex align-center justify-space-between mb-3">
 8            <MudText Typo="Typo.h6" Style="color: var(--chronicis-beige-gold);">
 9                <MudIcon Icon="@Icons.Material.Filled.Timeline" Size="Size.Small" Class="mr-2" />
 10                Quest Updates
 11            </MudText>
 12
 013            @if (_quest.UpdateCount > 0)
 14            {
 15                <MudText Typo="Typo.caption" Class="text-muted">
 016                    @_quest.UpdateCount update@(_quest.UpdateCount == 1 ? "" : "s")
 17                </MudText>
 18            }
 19        </div>
 20
 021        @if (_isLoading && !_updates.Any())
 22        {
 23            <MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Small" />
 24        }
 025        else if (_updates.Any())
 26        {
 27            <MudTimeline TimelinePosition="TimelinePosition.Start" TimelineOrientation="TimelineOrientation.Vertical">
 028                @foreach (var update in _updates)
 29                {
 30                    <MudTimelineItem Color="Color.Info" Size="Size.Small">
 31                        <ItemOpposite>
 32                            <MudText Typo="Typo.caption" Class="text-muted">
 033                                @update.CreatedAt.ToString("MMM d, yyyy h:mm tt")
 34                            </MudText>
 35                        </ItemOpposite>
 36                        <ItemContent>
 37                            <MudCard Elevation="2" Class="quest-update-card">
 38                                <MudCardContent>
 39                                    <!-- Author and Session Info -->
 40                                    <div class="d-flex align-center justify-space-between mb-2">
 41                                        <div class="d-flex align-center gap-2">
 042                                            @if (!string.IsNullOrEmpty(update.CreatedByAvatarUrl))
 43                                            {
 44                                                <MudAvatar Size="Size.Small">
 45                                                    <MudImage Src="@update.CreatedByAvatarUrl" />
 46                                                </MudAvatar>
 47                                            }
 48                                            else
 49                                            {
 50                                                <MudAvatar Size="Size.Small" Color="Color.Primary">
 051                                                    @update.CreatedByName.FirstOrDefault()
 52                                                </MudAvatar>
 53                                            }
 054                                            <MudText Typo="Typo.body2">@update.CreatedByName</MudText>
 55
 056                                            @if (update.SessionId.HasValue && !string.IsNullOrEmpty(update.SessionTitle)
 57                                            {
 58                                                <MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Colo
 059                                                    Session: @update.SessionTitle
 60                                                </MudChip>
 61                                            }
 62                                        </div>
 63
 064                                        @if (CanDeleteUpdate(update))
 65                                        {
 66                                            <MudIconButton Icon="@Icons.Material.Filled.Delete"
 67                                                           Size="Size.Small"
 68                                                           Color="Color.Error"
 069                                                           OnClick="@(() => DeleteUpdate(update))" />
 70                                        }
 71                                    </div>
 72
 73                                    <!-- Update Body (HTML from TipTap) -->
 74                                    <div class="quest-update-body">
 075                                        @((MarkupString)update.Body)
 76                                    </div>
 77                                </MudCardContent>
 78                            </MudCard>
 79                        </ItemContent>
 80                    </MudTimelineItem>
 81                }
 82            </MudTimeline>
 83
 84            <!-- Load More Button -->
 085            @if (_hasMore)
 86            {
 87                <div class="text-center mt-3">
 88                    <MudButton Variant="Variant.Outlined"
 89                               Color="Color.Primary"
 90                               OnClick="LoadMoreAsync"
 91                               Disabled="_isLoadingMore">
 092                        @if (_isLoadingMore)
 93                        {
 94                            <MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
 95                        }
 96                        Load More
 97                    </MudButton>
 98                </div>
 99            }
 100        }
 101        else
 102        {
 103            <MudAlert Severity="Severity.Info">
 104                No updates yet. Quest updates will appear here as progress is made.
 105            </MudAlert>
 106        }
 107    }
 108    else
 109    {
 110        <MudAlert Severity="Severity.Info">
 111            Select a quest to view updates
 112        </MudAlert>
 113    }
 114</div>

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

#LineLine coverage
 1using Chronicis.Client.Services;
 2using Chronicis.Shared.DTOs.Quests;
 3using Microsoft.AspNetCore.Components;
 4using MudBlazor;
 5
 6namespace Chronicis.Client.Components.Quests;
 7
 8public partial class ArcQuestTimeline : ComponentBase
 9{
 10    [Parameter]
 011    public QuestDto? Quest { get; set; }
 12
 13    [Parameter]
 014    public bool IsGm { get; set; }
 15
 16    [Parameter]
 017    public Guid CurrentUserId { get; set; }
 18
 019    [Inject] private IQuestApiService QuestApi { get; set; } = default!;
 020    [Inject] private IDialogService DialogService { get; set; } = default!;
 021    [Inject] private ISnackbar Snackbar { get; set; } = default!;
 22
 23    private QuestDto? _quest;
 024    private List<QuestUpdateEntryDto> _updates = new();
 25    private bool _isLoading;
 26    private bool _isLoadingMore;
 27    private bool _hasMore;
 28    private int _skip = 0;
 29    private const int _pageSize = 20;
 30
 31    protected override async Task OnParametersSetAsync()
 32    {
 33        // Quest changed - reset and reload
 034        if (_quest?.Id != Quest?.Id)
 35        {
 036            _quest = Quest;
 037            _updates.Clear();
 038            _skip = 0;
 039            _hasMore = false;
 40
 041            if (_quest != null)
 42            {
 043                await LoadUpdatesAsync();
 44            }
 45        }
 046    }
 47
 48    private async Task LoadUpdatesAsync()
 49    {
 050        if (_quest == null)
 051            return;
 52
 053        _isLoading = true;
 054        StateHasChanged();
 55
 56        try
 57        {
 058            var result = await QuestApi.GetQuestUpdatesAsync(_quest.Id, _skip, _pageSize);
 59
 060            _updates.AddRange(result.Items);
 061            _hasMore = result.TotalCount > _updates.Count;
 062            _skip = _updates.Count;
 063        }
 064        catch (Exception ex)
 65        {
 066            Snackbar.Add($"Failed to load quest updates: {ex.Message}", Severity.Error);
 067        }
 68        finally
 69        {
 070            _isLoading = false;
 071            StateHasChanged();
 72        }
 073    }
 74
 75    private async Task LoadMoreAsync()
 76    {
 077        if (_quest == null || _isLoadingMore)
 078            return;
 79
 080        _isLoadingMore = true;
 081        StateHasChanged();
 82
 83        try
 84        {
 085            var result = await QuestApi.GetQuestUpdatesAsync(_quest.Id, _skip, _pageSize);
 86
 087            _updates.AddRange(result.Items);
 088            _hasMore = result.TotalCount > _updates.Count;
 089            _skip = _updates.Count;
 090        }
 091        catch (Exception ex)
 92        {
 093            Snackbar.Add($"Failed to load more updates: {ex.Message}", Severity.Error);
 094        }
 95        finally
 96        {
 097            _isLoadingMore = false;
 098            StateHasChanged();
 99        }
 0100    }
 101
 102    private bool CanDeleteUpdate(QuestUpdateEntryDto update)
 103    {
 104        // GM can delete any update, Player can delete own updates only
 0105        return IsGm || update.CreatedBy == CurrentUserId;
 106    }
 107
 108    private async Task DeleteUpdate(QuestUpdateEntryDto update)
 109    {
 0110        if (_quest == null)
 0111            return;
 112
 0113        var confirmed = await DialogService.ShowMessageBox(
 0114            "Delete Update",
 0115            "Are you sure you want to delete this quest update?",
 0116            yesText: "Delete",
 0117            cancelText: "Cancel");
 118
 0119        if (confirmed == true)
 120        {
 0121            var success = await QuestApi.DeleteQuestUpdateAsync(_quest.Id, update.Id);
 0122            if (success)
 123            {
 0124                _updates.Remove(update);
 0125                Snackbar.Add("Quest update deleted", Severity.Success);
 0126                StateHasChanged();
 127            }
 128            else
 129            {
 0130                Snackbar.Add("Failed to delete quest update", Severity.Error);
 131            }
 132        }
 0133    }
 134}