Skip to main content

Media

Media Stats code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/media-stats.blade.php

@props([
    'title' => 'Performance',
    'description' => null,
    'views' => null,
    'watchTime' => null,
    'completion' => null,
    'avgViewDuration' => null,
    'trend' => [],
    'items' => [],
])

@php
    /*
    | Media performance stats: KPI strip plus an optional sparkline trend.
    |
    | Usage:
    |   <x-ui.media-stats
    |       views="12.4k"
    |       watch-time="186h"
    |       completion="64%"
    |       :trend="[12, 18, 16, 24, 30, 28, 36]"
    |   />
    */
    $stats = collect([
        ['key' => 'views', 'label' => 'Views', 'icon' => 'eye', 'value' => $views],
        ['key' => 'watchTime', 'label' => 'Watch time', 'icon' => 'clock', 'value' => $watchTime],
        ['key' => 'completion', 'label' => 'Completion', 'icon' => 'chart-line-up', 'value' => $completion],
        ['key' => 'avgViewDuration', 'label' => 'Avg. view', 'icon' => 'timer', 'value' => $avgViewDuration],
    ])
        ->filter(fn (array $stat) => filled($stat['value']))
        ->values();

    $extra = collect($items)
        ->map(function ($item) {
            return [
                'key' => (string) ($item['key'] ?? $item['label'] ?? uniqid('stat_', true)),
                'label' => $item['label'] ?? 'Metric',
                'icon' => $item['icon'] ?? 'chart-bar',
                'value' => $item['value'] ?? null,
            ];
        })
        ->filter(fn (array $item) => filled($item['value']))
        ->values();

    $all = $stats->merge($extra)->values()->all();

    $spark = collect($trend)
        ->map(fn ($point) => max(0, (float) $point))
        ->values()
        ->all();

    $sparkMax = max(1, ...( $spark ?: [1] ));
@endphp

<section
    {{ $attributes->merge(['class' => 'min-w-0 max-w-full rounded-2xl border border-gray-200 bg-white p-3 shadow-sm dark:border-gray-800 dark:bg-gray-900 sm:p-5']) }}
    aria-label="{{ $title }}"
>
    <div class="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
        <div class="min-w-0">
            <h3 class="text-sm font-semibold text-gray-900 dark:text-white">{{ $title }}</h3>
            <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-400">
                {{ $description ?? 'Audience engagement for this media item.' }}
            </p>
        </div>

        @if ($spark !== [])
            <div class="flex h-12 w-full items-end gap-1 sm:w-40" aria-hidden="true">
                @foreach ($spark as $point)
                    <span
                        class="min-w-[4px] flex-1 rounded-t bg-brand-500/80 dark:bg-brand-400/80"
                        style="height: {{ max(12, (int) round(($point / $sparkMax) * 100)) }}%"
                    ></span>
                @endforeach
            </div>
        @endif
    </div>

    @if ($all !== [])
        <div class="mt-4 grid grid-cols-2 gap-2 sm:grid-cols-4 sm:gap-3">
            @foreach ($all as $stat)
                <div class="min-w-0 rounded-xl border border-gray-200 bg-gray-50 px-3 py-3 dark:border-gray-800 dark:bg-gray-950/50">
                    <div class="flex items-center gap-1.5 text-gray-400 dark:text-gray-500">
                        <x-ui.icon :name="$stat['icon']" size="sm" />
                        <p class="truncate text-[11px] font-semibold uppercase tracking-wide">{{ $stat['label'] }}</p>
                    </div>
                    <p class="mt-2 truncate text-lg font-semibold tabular-nums text-gray-900 dark:text-white">{{ $stat['value'] }}</p>
                </div>
            @endforeach
        </div>
    @else
        <p class="mt-4 text-sm text-gray-500 dark:text-gray-400">No stats available yet.</p>
    @endif
</section>