< Summary

Line coverage
100%
Covered lines: 89
Uncovered lines: 0
Coverable lines: 89
Total lines: 170
Line coverage: 100%
Branch coverage
100%
Covered branches: 24
Total branches: 24
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Components/History/TimeDistributionChart.razor

#LineLine coverage
 1<div class="time-distribution-chart">
 2    <h4 class="chart-title">Time Distribution</h4>
 3
 1264    @if (HasData)
 125    {
 6        <div class="chart-container">
 7            <canvas id="@CanvasId"></canvas>
 8        </div>
 9        <div class="chart-summary">
 1210            <span class="total-time">Total: @FormattedTotalMinutes</span>
 11        </div>
 1212    }
 13    else
 11414    {
 15        <div class="empty-chart-state">
 16            <span class="empty-icon">📭</span>
 17            <p>No activities for this day</p>
 18        </div>
 11419    }
 20</div>

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Components/History/TimeDistributionChart.razor.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Components;
 2using Microsoft.JSInterop;
 3using Pomodoro.Web.Services;
 4using Pomodoro.Web.Services.Formatters;
 5
 6namespace Pomodoro.Web.Components.History;
 7
 8/// <summary>
 9/// Displays a doughnut chart showing time distribution across tasks and breaks
 10/// </summary>
 11public partial class TimeDistributionChart : IDisposable
 12{
 32513    [Inject] private IJSRuntime JS { get; set; } = default!;
 52514    [Inject] private IActivityService ActivityService { get; set; } = default!;
 41815    [Inject] private ILogger<TimeDistributionChart> Logger { get; set; } = default!;
 23716    [Inject] private TimeFormatter TimeFormatter { get; set; } = default!;
 17
 18    [Parameter]
 33619    public DateTime SelectedDate { get; set; }
 20
 21    /// <summary>
 22    /// Static canvas ID for the chart (single instance)
 23    /// </summary>
 224    private static readonly string CanvasId = Constants.Charts.TimeDistributionCanvasId;
 25
 26    private DateTime _lastRenderedDate;
 27
 28    /// <summary>
 29    /// Total minutes displayed in the chart
 30    /// </summary>
 5331    public int TotalMinutes { get; private set; }
 32
 33    /// <summary>
 34    /// Formatted total time for display (safe from null reference)
 35    /// </summary>
 36    public string FormattedTotalMinutes
 37    {
 38        get
 1639        {
 40            try
 1641            {
 1642                return TimeFormatter?.FormatTime(TotalMinutes) ?? TotalMinutes.ToString();
 43            }
 244            catch (Exception ex)
 245            {
 246                Logger?.LogError(ex, "Error formatting total minutes in TimeDistributionChart");
 247                return TotalMinutes.ToString();
 48            }
 1649        }
 50    }
 51
 52    /// <summary>
 53    /// Whether there is data to display
 54    /// </summary>
 14655    public bool HasData { get; private set; }
 56
 57    private bool _isRendered;
 58    private bool _isDisposed;
 59
 60    protected override void OnInitialized()
 10361    {
 10362        ActivityService.OnActivityChanged += OnActivityChanged;
 10363    }
 64
 65    protected override async Task OnAfterRenderAsync(bool firstRender)
 12666    {
 12667        if (firstRender)
 10368        {
 10369            _isRendered = true;
 10370            await UpdateChartAsync();
 10371        }
 12672    }
 73
 74    protected override async Task OnParametersSetAsync()
 10675    {
 76        // Only update if the date has changed and we've already rendered
 10677        if (_isRendered && _lastRenderedDate != SelectedDate.Date)
 378        {
 379            await UpdateChartAsync();
 380        }
 10681    }
 82
 83    private void OnActivityChanged()
 884    {
 885        if (_isRendered && !_isDisposed)
 786        {
 787            SafeTaskRunner.RunAndForget(async () =>
 788            {
 789                await InvokeAsync(async () =>
 790                {
 791                    await UpdateChartAsync();
 792                    StateHasChanged();
 1493                });
 1494            }, Logger, "OnActivityChanged");
 795        }
 896    }
 97
 98    private async Task UpdateChartAsync()
 11499    {
 115100        if (_isDisposed) return;
 101
 102        try
 113103        {
 113104            var distribution = ActivityService.GetTimeDistribution(SelectedDate);
 113105            _lastRenderedDate = SelectedDate.Date;
 106
 113107            if (distribution.Count == 0)
 4108            {
 4109                TotalMinutes = 0;
 4110                    HasData = false;
 111                    // Destroy existing chart when no data
 4112                    await JS.InvokeVoidAsync(Constants.ChartJsFunctions.DestroyChart, CanvasId);
 3113                    StateHasChanged();
 3114                    return;
 115                }
 116
 14117                var labels = distribution.Keys.ToList();
 14118                var data = distribution.Values.ToList();
 14119                TotalMinutes = data.Sum();
 14120                HasData = true;
 121
 14122                var centerText = TimeFormatter.FormatTime(TotalMinutes);
 123
 12124                await JS.InvokeVoidAsync(Constants.ChartJsFunctions.CreateDoughnutChart,
 12125                    CanvasId,
 12126                    labels,
 12127                    data,
 12128                    centerText);
 129
 11130                StateHasChanged();
 11131            }
 99132            catch (Exception ex)
 99133            {
 99134                Logger.LogError(ex, Constants.Messages.ErrorUpdatingTimeDistributionChart);
 99135            }
 114136        }
 137
 138    public void Dispose()
 109139    {
 115140        if (_isDisposed) return;
 103141        _isDisposed = true;
 142
 103143        ActivityService.OnActivityChanged -= OnActivityChanged;
 144
 103145        SafeTaskRunner.RunAndForget(
 307146            async () => { await JS.InvokeVoidAsync(Constants.ChartJsFunctions.DestroyChart, CanvasId); },
 103147            Logger,
 103148            "DestroyChart");
 109149    }
 150}