mirror of
https://github.com/the-programmers-hangout/Hermes.git
synced 2026-09-04 01:06:00 +02:00
bottom chart and responsive changes
This commit is contained in:
@@ -61,6 +61,8 @@ class HomepageController
|
||||
'top_movers' => $topMovers,
|
||||
'top_static' => $aggregate['top_static'] ?? collect(),
|
||||
'top_animated' => $aggregate['top_animated'] ?? collect(),
|
||||
'bottom_static' => $aggregate['bottom_static'] ?? collect(),
|
||||
'bottom_animated' => $aggregate['bottom_animated'] ?? collect(),
|
||||
'top_unicode' => $aggregate['top_unicode'] ?? collect(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Concerns;
|
||||
|
||||
trait BuildsEmoteAggregates
|
||||
{
|
||||
protected static function aggregateSummaryAndUsageByType(
|
||||
$baseQuery,
|
||||
string $usageCountColumn,
|
||||
string $emoteIdColumn,
|
||||
?string $userIdColumn = null
|
||||
): array {
|
||||
$summaryQuery = (clone $baseQuery)
|
||||
->selectRaw("COALESCE(SUM({$usageCountColumn}), 0) as total_usage")
|
||||
->selectRaw("COUNT(DISTINCT {$emoteIdColumn}) as unique_emotes");
|
||||
|
||||
if ($userIdColumn !== null) {
|
||||
$summaryQuery->selectRaw("COUNT(DISTINCT {$userIdColumn}) as unique_users");
|
||||
}
|
||||
|
||||
$summary = $summaryQuery->first();
|
||||
|
||||
$usageByType = (clone $baseQuery)
|
||||
->select('emotes.type')
|
||||
->selectRaw("COALESCE(SUM({$usageCountColumn}), 0) as total_usage")
|
||||
->groupBy('emotes.type')
|
||||
->pluck('total_usage', 'type');
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'usage_by_type' => $usageByType,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Concerns;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait BuildsEmoteUsageLists
|
||||
{
|
||||
protected static function emotesByType(
|
||||
$baseQuery,
|
||||
string $type,
|
||||
string $usageCountColumn,
|
||||
string $emoteIdColumn,
|
||||
string $direction = 'desc'
|
||||
): Collection {
|
||||
$query = (clone $baseQuery)
|
||||
->where('emotes.type', $type)
|
||||
->select(
|
||||
$emoteIdColumn.' as emote_id',
|
||||
'emotes.emote_name',
|
||||
'emotes.type',
|
||||
'emotes.image'
|
||||
)
|
||||
->selectRaw("COALESCE(SUM({$usageCountColumn}), 0) as total_usage")
|
||||
->groupBy(
|
||||
$emoteIdColumn,
|
||||
'emotes.emote_name',
|
||||
'emotes.type',
|
||||
'emotes.image'
|
||||
);
|
||||
|
||||
if ($direction === 'asc') {
|
||||
$query->orderBy('total_usage');
|
||||
} else {
|
||||
$query->orderByDesc('total_usage');
|
||||
}
|
||||
|
||||
return $query
|
||||
->limit(10)
|
||||
->get();
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Concerns\BuildsEmoteAggregates;
|
||||
use App\Models\Concerns\BuildsEmoteUsageLists;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class EmoteGuildStat extends Model
|
||||
{
|
||||
use BuildsEmoteAggregates;
|
||||
use BuildsEmoteUsageLists;
|
||||
|
||||
protected $fillable = [
|
||||
'emote_id',
|
||||
'guild_id',
|
||||
@@ -27,24 +31,19 @@ class EmoteGuildStat extends Model
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build aggregate stats for all users.
|
||||
*/
|
||||
public static function dashboardAggregate(): array
|
||||
{
|
||||
$baseQuery = self::query()
|
||||
->join('emotes', 'emote_guild_stats.emote_id', '=', 'emotes.emote_id');
|
||||
|
||||
$summary = (clone $baseQuery)
|
||||
->selectRaw('COALESCE(SUM(emote_guild_stats.usage_count), 0) as total_usage')
|
||||
->selectRaw('COUNT(DISTINCT emote_guild_stats.emote_id) as unique_emotes')
|
||||
->first();
|
||||
$aggregate = self::aggregateSummaryAndUsageByType(
|
||||
$baseQuery,
|
||||
'emote_guild_stats.usage_count',
|
||||
'emote_guild_stats.emote_id'
|
||||
);
|
||||
|
||||
$usageByType = (clone $baseQuery)
|
||||
->select('emotes.type')
|
||||
->selectRaw('COALESCE(SUM(emote_guild_stats.usage_count), 0) as total_usage')
|
||||
->groupBy('emotes.type')
|
||||
->pluck('total_usage', 'type');
|
||||
$summary = $aggregate['summary'];
|
||||
$usageByType = $aggregate['usage_by_type'];
|
||||
|
||||
$unicodeUsage = (int) EmoteLog::query()
|
||||
->join('emotes', 'emote_logs.emote_id', '=', 'emotes.emote_id')
|
||||
@@ -88,48 +87,35 @@ class EmoteGuildStat extends Model
|
||||
'ANIMATED' => (int) ($usageByType->get('ANIMATED') ?? 0),
|
||||
'UNICODE' => $unicodeUsage,
|
||||
],
|
||||
'top_static' => self::topEmotesByType(
|
||||
'top_static' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'STATIC',
|
||||
'emote_guild_stats.usage_count',
|
||||
'emote_guild_stats.emote_id'
|
||||
'emote_guild_stats.emote_id',
|
||||
'desc'
|
||||
),
|
||||
'top_animated' => self::topEmotesByType(
|
||||
'top_animated' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'ANIMATED',
|
||||
'emote_guild_stats.usage_count',
|
||||
'emote_guild_stats.emote_id'
|
||||
'emote_guild_stats.emote_id',
|
||||
'desc'
|
||||
),
|
||||
'bottom_static' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'STATIC',
|
||||
'emote_guild_stats.usage_count',
|
||||
'emote_guild_stats.emote_id',
|
||||
'asc'
|
||||
),
|
||||
'bottom_animated' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'ANIMATED',
|
||||
'emote_guild_stats.usage_count',
|
||||
'emote_guild_stats.emote_id',
|
||||
'asc'
|
||||
),
|
||||
'top_unicode' => $topUnicode,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a top-10 emote list by type.
|
||||
*/
|
||||
protected static function topEmotesByType(
|
||||
$baseQuery,
|
||||
string $type,
|
||||
string $usageCountColumn,
|
||||
string $emoteIdColumn
|
||||
): Collection {
|
||||
return (clone $baseQuery)
|
||||
->where('emotes.type', $type)
|
||||
->select(
|
||||
$emoteIdColumn.' as emote_id',
|
||||
'emotes.emote_name',
|
||||
'emotes.type',
|
||||
'emotes.image'
|
||||
)
|
||||
->selectRaw("COALESCE(SUM({$usageCountColumn}), 0) as total_usage")
|
||||
->groupBy(
|
||||
$emoteIdColumn,
|
||||
'emotes.emote_name',
|
||||
'emotes.type',
|
||||
'emotes.image'
|
||||
)
|
||||
->orderByDesc('total_usage')
|
||||
->limit(10)
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Concerns\BuildsEmoteAggregates;
|
||||
use App\Models\Concerns\BuildsEmoteUsageLists;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class UserGuildStat extends Model
|
||||
{
|
||||
use BuildsEmoteAggregates;
|
||||
use BuildsEmoteUsageLists;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'guild_id',
|
||||
@@ -25,26 +29,21 @@ class UserGuildStat extends Model
|
||||
return $this->belongsTo(User::class, 'user_id', 'discord_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build aggregate stats for a single user.
|
||||
*/
|
||||
public static function dashboardAggregateForUser(string $userId): array
|
||||
{
|
||||
$baseQuery = self::query()
|
||||
->join('emotes', 'user_guild_stats.emote_id', '=', 'emotes.emote_id')
|
||||
->where('user_guild_stats.user_id', $userId);
|
||||
|
||||
$summary = (clone $baseQuery)
|
||||
->selectRaw('COALESCE(SUM(user_guild_stats.usage_count), 0) as total_usage')
|
||||
->selectRaw('COUNT(DISTINCT user_guild_stats.emote_id) as unique_emotes')
|
||||
->selectRaw('COUNT(DISTINCT user_guild_stats.user_id) as unique_users')
|
||||
->first();
|
||||
$aggregate = self::aggregateSummaryAndUsageByType(
|
||||
$baseQuery,
|
||||
'user_guild_stats.usage_count',
|
||||
'user_guild_stats.emote_id',
|
||||
'user_guild_stats.user_id'
|
||||
);
|
||||
|
||||
$usageByType = (clone $baseQuery)
|
||||
->select('emotes.type')
|
||||
->selectRaw('COALESCE(SUM(user_guild_stats.usage_count), 0) as total_usage')
|
||||
->groupBy('emotes.type')
|
||||
->pluck('total_usage', 'type');
|
||||
$summary = $aggregate['summary'];
|
||||
$usageByType = $aggregate['usage_by_type'];
|
||||
|
||||
return [
|
||||
'total_usage' => (int) ($summary->total_usage ?? 0),
|
||||
@@ -55,53 +54,41 @@ class UserGuildStat extends Model
|
||||
'ANIMATED' => (int) ($usageByType->get('ANIMATED') ?? 0),
|
||||
'UNICODE' => (int) ($usageByType->get('UNICODE') ?? 0),
|
||||
],
|
||||
'top_static' => self::topEmotesByType(
|
||||
'top_static' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'STATIC',
|
||||
'user_guild_stats.usage_count',
|
||||
'user_guild_stats.emote_id'
|
||||
'user_guild_stats.emote_id',
|
||||
'desc'
|
||||
),
|
||||
'top_animated' => self::topEmotesByType(
|
||||
'top_animated' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'ANIMATED',
|
||||
'user_guild_stats.usage_count',
|
||||
'user_guild_stats.emote_id'
|
||||
'user_guild_stats.emote_id',
|
||||
'desc'
|
||||
),
|
||||
'top_unicode' => self::topEmotesByType(
|
||||
'bottom_static' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'STATIC',
|
||||
'user_guild_stats.usage_count',
|
||||
'user_guild_stats.emote_id',
|
||||
'asc'
|
||||
),
|
||||
'bottom_animated' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'ANIMATED',
|
||||
'user_guild_stats.usage_count',
|
||||
'user_guild_stats.emote_id',
|
||||
'asc'
|
||||
),
|
||||
'top_unicode' => self::emotesByType(
|
||||
$baseQuery,
|
||||
'UNICODE',
|
||||
'user_guild_stats.usage_count',
|
||||
'user_guild_stats.emote_id'
|
||||
'user_guild_stats.emote_id',
|
||||
'desc'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a top-10 emote list by type.
|
||||
*/
|
||||
protected static function topEmotesByType(
|
||||
$baseQuery,
|
||||
string $type,
|
||||
string $usageCountColumn,
|
||||
string $emoteIdColumn
|
||||
): Collection {
|
||||
return (clone $baseQuery)
|
||||
->where('emotes.type', $type)
|
||||
->select(
|
||||
$emoteIdColumn.' as emote_id',
|
||||
'emotes.emote_name',
|
||||
'emotes.type',
|
||||
'emotes.image'
|
||||
)
|
||||
->selectRaw("COALESCE(SUM({$usageCountColumn}), 0) as total_usage")
|
||||
->groupBy(
|
||||
$emoteIdColumn,
|
||||
'emotes.emote_name',
|
||||
'emotes.type',
|
||||
'emotes.image'
|
||||
)
|
||||
->orderByDesc('total_usage')
|
||||
->limit(10)
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,29 @@
|
||||
'values' => data_get($stats, 'usage_over_time.values', []),
|
||||
],
|
||||
];
|
||||
|
||||
$customUsageTables = [
|
||||
[
|
||||
'title' => 'Top 10 Static Emotes',
|
||||
'list' => $stats['top_static'] ?? collect(),
|
||||
'empty' => 'No static emote usage found.',
|
||||
],
|
||||
[
|
||||
'title' => 'Top 10 Animated Emotes',
|
||||
'list' => $stats['top_animated'] ?? collect(),
|
||||
'empty' => 'No animated emote usage found.',
|
||||
],
|
||||
[
|
||||
'title' => 'Bottom 10 Static Emotes',
|
||||
'list' => $stats['bottom_static'] ?? collect(),
|
||||
'empty' => 'No static emote usage found.',
|
||||
],
|
||||
[
|
||||
'title' => 'Bottom 10 Animated Emotes',
|
||||
'list' => $stats['bottom_animated'] ?? collect(),
|
||||
'empty' => 'No animated emote usage found.',
|
||||
],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<x-layouts.app>
|
||||
@@ -69,36 +92,36 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mb-8 overflow-x-auto lg:overflow-visible">
|
||||
<div class="flex gap-3 pb-1 min-w-max lg:min-w-0 lg:w-full lg:justify-between">
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-[180px] lg:min-w-0 lg:flex-1">
|
||||
<div class="mb-8">
|
||||
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-0">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-[#b5bac1]">Total Emoji Usage</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white">{{ $stats['total_usage'] ?? 0 }}</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white break-words">{{ $stats['total_usage'] ?? 0 }}</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-[180px] lg:min-w-0 lg:flex-1">
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-0">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-[#b5bac1]">Unique Emotes</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white">{{ $stats['unique_emotes'] ?? 0 }}</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white break-words">{{ $stats['unique_emotes'] ?? 0 }}</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-[180px] lg:min-w-0 lg:flex-1">
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-0">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-[#b5bac1]">Users Included</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white">{{ $stats['unique_users'] ?? 0 }}</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white break-words">{{ $stats['unique_users'] ?? 0 }}</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-[180px] lg:min-w-0 lg:flex-1">
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-0">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-[#b5bac1]">Static Usage</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white">{{ $stats['usage_by_type']['STATIC'] ?? 0 }}</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white break-words">{{ $stats['usage_by_type']['STATIC'] ?? 0 }}</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-[180px] lg:min-w-0 lg:flex-1">
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-0">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-[#b5bac1]">Animated Usage</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white">{{ $stats['usage_by_type']['ANIMATED'] ?? 0 }}</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white break-words">{{ $stats['usage_by_type']['ANIMATED'] ?? 0 }}</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-[180px] lg:min-w-0 lg:flex-1">
|
||||
<div class="p-4 border rounded-lg bg-[#2b2d31] border-[#3f4147] min-w-0">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-[#b5bac1]">Unicode Usage</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white">{{ $stats['usage_by_type']['UNICODE'] ?? 0 }}</p>
|
||||
<p class="mt-2 text-2xl font-semibold text-white break-words">{{ $stats['usage_by_type']['UNICODE'] ?? 0 }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,91 +130,84 @@
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-xl font-semibold text-white">Emote Usage Over Time (Last 30 Days)</h3>
|
||||
|
||||
<div class="p-6 border rounded-lg bg-[#2b2d31] border-[#3f4147] md:p-7">
|
||||
<canvas id="emoji-usage-over-time-chart"></canvas>
|
||||
<div class="p-4 overflow-hidden border rounded-lg bg-[#2b2d31] border-[#3f4147] md:p-5">
|
||||
<div class="h-44 min-w-0 sm:h-56 md:h-64 lg:h-72">
|
||||
<canvas id="emoji-usage-over-time-chart" class="w-full h-full"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-8 lg:grid-cols-2">
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-xl font-semibold text-white">Top Movers (Last 7 Days vs Previous 7 Days)</h3>
|
||||
|
||||
<div class="overflow-hidden border rounded-lg bg-[#2b2d31] border-[#3f4147]">
|
||||
@forelse (($stats['top_movers'] ?? collect()) as $emote)
|
||||
<div class="flex flex-col items-start gap-2 px-4 py-3.5 border-b border-[#3f4147] sm:flex-row sm:items-center sm:justify-between sm:gap-3 last:border-b-0">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
@if (($emote->type ?? null) === 'UNICODE')
|
||||
<span class="font-medium truncate text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
@else
|
||||
<img src="{{ $emoteImageSrc($emote->image) }}" alt="{{ $emote->emote_name }}" class="object-cover w-9 h-9 border rounded border-[#3f4147]" />
|
||||
<span class="font-medium truncate text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="text-left sm:text-right sm:shrink-0">
|
||||
<p class="text-sm text-[#b5bac1]">{{ $emote->previous_count }} → {{ $emote->current_count }}</p>
|
||||
<p class="text-sm font-medium {{ $emote->delta >= 0 ? 'text-[#57f287]' : 'text-[#ed4245]' }}">
|
||||
{{ $emote->delta >= 0 ? '+' : '' }}{{ $emote->delta }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="px-4 py-3 text-sm text-[#b5bac1]">No mover data available yet.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-xl font-semibold text-white">Top 10 Unicode Emotes</h3>
|
||||
|
||||
<div class="overflow-hidden border rounded-lg bg-[#2b2d31] border-[#3f4147]">
|
||||
@forelse (($stats['top_unicode'] ?? collect()) as $emote)
|
||||
<div class="flex flex-col items-start gap-2 px-4 py-3.5 border-b border-[#3f4147] sm:flex-row sm:items-center sm:justify-between sm:gap-3 last:border-b-0">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<span class="w-8 text-sm font-semibold text-[#b5bac1]">#{{ $loop->iteration }}</span>
|
||||
<span class="font-medium truncate text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-[#b5bac1] sm:shrink-0">{{ $emote->total_usage }}</span>
|
||||
</div>
|
||||
@empty
|
||||
<p class="px-4 py-3 text-sm text-[#b5bac1]">No unicode emote usage found.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-xl font-semibold text-white">Top Movers (Last 7 Days vs Previous 7 Days)</h3>
|
||||
<h3 class="text-xl font-semibold text-white">Custom Emote Usage (Top & Bottom)</h3>
|
||||
|
||||
<div class="overflow-hidden border rounded-lg bg-[#2b2d31] border-[#3f4147]">
|
||||
@forelse (($stats['top_movers'] ?? collect()) as $emote)
|
||||
<div class="flex items-center justify-between px-4 py-3.5 border-b border-[#3f4147] last:border-b-0">
|
||||
<div class="flex items-center gap-3">
|
||||
@if (($emote->type ?? null) === 'UNICODE')
|
||||
<span class="font-medium text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
@else
|
||||
<img src="{{ $emoteImageSrc($emote->image) }}" alt="{{ $emote->emote_name }}" class="object-cover w-9 h-9 border rounded border-[#3f4147]" />
|
||||
<span class="font-medium text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-sm text-[#b5bac1]">{{ $emote->previous_count }} → {{ $emote->current_count }}</p>
|
||||
<p class="text-sm font-medium {{ $emote->delta >= 0 ? 'text-[#57f287]' : 'text-[#ed4245]' }}">
|
||||
{{ $emote->delta >= 0 ? '+' : '' }}{{ $emote->delta }}
|
||||
</p>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
@foreach ($customUsageTables as $table)
|
||||
<div class="overflow-hidden border rounded-lg bg-[#2b2d31] border-[#3f4147]">
|
||||
<div class="px-4 py-3 border-b border-[#3f4147]">
|
||||
<h4 class="text-sm font-semibold text-white">{{ $table['title'] }}</h4>
|
||||
</div>
|
||||
|
||||
@forelse ($table['list'] as $emote)
|
||||
<div class="flex flex-col items-start gap-2 px-4 py-3.5 border-b border-[#3f4147] sm:flex-row sm:items-center sm:justify-between sm:gap-3 last:border-b-0">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<span class="w-8 text-sm font-semibold text-[#b5bac1]">#{{ $loop->iteration }}</span>
|
||||
<img src="{{ $emoteImageSrc($emote->image) }}" alt="{{ $emote->emote_name }}" class="object-cover w-9 h-9 border rounded border-[#3f4147]" />
|
||||
<span class="font-medium truncate text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-[#b5bac1] sm:shrink-0">{{ $emote->total_usage }}</span>
|
||||
</div>
|
||||
@empty
|
||||
<p class="px-4 py-3 text-sm text-[#b5bac1]">{{ $table['empty'] }}</p>
|
||||
@endforelse
|
||||
</div>
|
||||
@empty
|
||||
<p class="px-4 py-3 text-sm text-[#b5bac1]">No mover data available yet.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-xl font-semibold text-white">Top 10 Static Emotes</h3>
|
||||
|
||||
<div class="overflow-hidden border rounded-lg bg-[#2b2d31] border-[#3f4147]">
|
||||
@forelse (($stats['top_static'] ?? collect()) as $emote)
|
||||
<div class="flex items-center justify-between px-4 py-3.5 border-b border-[#3f4147] last:border-b-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-8 text-sm font-semibold text-[#b5bac1]">#{{ $loop->iteration }}</span>
|
||||
<img src="{{ $emoteImageSrc($emote->image) }}" alt="{{ $emote->emote_name }}" class="object-cover w-9 h-9 border rounded border-[#3f4147]" />
|
||||
<span class="font-medium text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-[#b5bac1]">{{ $emote->total_usage }}</span>
|
||||
</div>
|
||||
@empty
|
||||
<p class="px-4 py-3 text-sm text-[#b5bac1]">No static emote usage found.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-xl font-semibold text-white">Top 10 Animated Emotes</h3>
|
||||
|
||||
<div class="overflow-hidden border rounded-lg bg-[#2b2d31] border-[#3f4147]">
|
||||
@forelse (($stats['top_animated'] ?? collect()) as $emote)
|
||||
<div class="flex items-center justify-between px-4 py-3.5 border-b border-[#3f4147] last:border-b-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-8 text-sm font-semibold text-[#b5bac1]">#{{ $loop->iteration }}</span>
|
||||
<img src="{{ $emoteImageSrc($emote->image) }}" alt="{{ $emote->emote_name }}" class="object-cover w-9 h-9 border rounded border-[#3f4147]" />
|
||||
<span class="font-medium text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-[#b5bac1]">{{ $emote->total_usage }}</span>
|
||||
</div>
|
||||
@empty
|
||||
<p class="px-4 py-3 text-sm text-[#b5bac1]">No animated emote usage found.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-xl font-semibold text-white">Top 10 Unicode Emotes</h3>
|
||||
|
||||
<div class="overflow-hidden border rounded-lg bg-[#2b2d31] border-[#3f4147]">
|
||||
@forelse (($stats['top_unicode'] ?? collect()) as $emote)
|
||||
<div class="flex items-center justify-between px-4 py-3.5 border-b border-[#3f4147] last:border-b-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-8 text-sm font-semibold text-[#b5bac1]">#{{ $loop->iteration }}</span>
|
||||
<span class="font-medium text-[#dbdee1]">{{ $emote->emote_name }}</span>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-[#b5bac1]">{{ $emote->total_usage }}</span>
|
||||
</div>
|
||||
@empty
|
||||
<p class="px-4 py-3 text-sm text-[#b5bac1]">No unicode emote usage found.</p>
|
||||
@endforelse
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -237,6 +253,7 @@
|
||||
|
||||
const usageOverTimeCanvas = document.getElementById('emoji-usage-over-time-chart');
|
||||
const usageOverTime = payload.usage_over_time || { labels: [], values: [] };
|
||||
const isMobileViewport = window.matchMedia('(max-width: 639px)').matches;
|
||||
|
||||
if (usageOverTimeCanvas) {
|
||||
hermesDestroyChart('usageOverTime');
|
||||
@@ -250,15 +267,23 @@
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
borderColor: '#5865f2',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#5865f2',
|
||||
pointBorderColor: '#5865f2'
|
||||
pointBorderColor: '#5865f2',
|
||||
pointRadius: isMobileViewport ? 0 : 2,
|
||||
pointHoverRadius: isMobileViewport ? 2 : 4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: !isMobileViewport,
|
||||
labels: {
|
||||
color: '#dbdee1'
|
||||
}
|
||||
@@ -267,17 +292,23 @@
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: '#b5bac1'
|
||||
color: '#b5bac1',
|
||||
autoSkip: true,
|
||||
maxTicksLimit: isMobileViewport ? 4 : 8,
|
||||
maxRotation: 0,
|
||||
minRotation: 0
|
||||
},
|
||||
grid: {
|
||||
color: '#3f4147'
|
||||
color: '#3f4147',
|
||||
display: !isMobileViewport
|
||||
}
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
precision: 0,
|
||||
color: '#b5bac1'
|
||||
color: '#b5bac1',
|
||||
maxTicksLimit: isMobileViewport ? 5 : 8
|
||||
},
|
||||
grid: {
|
||||
color: '#3f4147'
|
||||
@@ -291,6 +322,10 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
hermesRenderOverTimeChart();
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
hermesRenderOverTimeChart();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endonce
|
||||
|
||||
Reference in New Issue
Block a user