"use client"; import * as React from "react"; import Link from "next/link"; import { useAdminQuery } from "@/components/admin/use-query"; import type { GameAnalyticsResponse, GameAnalyticsRow } from "@/components/admin/types"; import { PageHeader, RefreshButton, Panel, DataTable, ErrorState, TableSkeleton, ChartSkeleton, SegmentedControl, useSort, type Column } from "@/components/admin/primitives"; import { Bars, ChartFrame, CHART } from "@/components/admin/charts"; import { compact, int, ms, multiplier, num, pct, sc } from "@/components/admin/format"; import { cn } from "@/lib/utils"; const DAYS = [7, 30, 90] as const; const cols: Column[] = [ { key: "game", header: "Game", sortValue: (r) => r.game_slug, render: (r) => {r.game_slug} }, { key: "spins", header: "Spins", align: "right", sortValue: (r) => num(r.spins), render: (r) => int(r.spins) }, { key: "players", header: "Players", align: "right", sortValue: (r) => num(r.players), render: (r) => int(r.players) }, { key: "launches", header: "Launches", align: "right", sortValue: (r) => num(r.launches), render: (r) => int(r.launches) }, { key: "wagered", header: "Wagered", align: "right", sortValue: (r) => num(r.wagered), render: (r) => sc(r.wagered) }, { key: "won", header: "Won", align: "right", sortValue: (r) => num(r.won), render: (r) => sc(r.won) }, { key: "rtp", header: "RTP", align: "right", sortValue: (r) => r.rtp, render: (r) => 1.02 || r.rtp < 0.9) && "text-[#ffc46b]")}>{pct(r.rtp)} }, { key: "avg_bet", header: "Avg bet", align: "right", sortValue: (r) => num(r.avg_bet), render: (r) => sc(Math.round(num(r.avg_bet))) }, { key: "bonuses", header: "Bonus rounds", align: "right", sortValue: (r) => num(r.bonuses), render: (r) => {int(r.bonuses)} ({pct(num(r.spins) ? num(r.bonuses) / num(r.spins) : null, 1)}) }, { key: "big_wins", header: "Big wins ≥20×", align: "right", sortValue: (r) => num(r.big_wins), render: (r) => int(r.big_wins) }, { key: "max", header: "Max win", align: "right", sortValue: (r) => num(r.max_multiplier), render: (r) => {multiplier(r.max_multiplier ?? 0)} · {sc(r.max_win)} }, { key: "favorites", header: "♥", align: "right", sortValue: (r) => num(r.favorites), render: (r) => int(r.favorites) }, { key: "avg_ms", header: "Avg ms", align: "right", sortValue: (r) => num(r.avg_ms), render: (r) => ms(num(r.avg_ms)) }, ]; export default function AdminGameAnalyticsPage() { const [days, setDays] = React.useState<(typeof DAYS)[number]>(30); const q = useAdminQuery(`/api/admin/analytics/games?days=${days}`); const d = q.data; const { sorted, sort, toggle } = useSort(d?.games, cols, { key: "spins", dir: "desc" }); const chartData = React.useMemo(() => (d?.games ?? []).slice().sort((a, b) => num(b.spins) - num(a.spins)).map((g) => ({ game: g.game_slug, spins: num(g.spins), players: num(g.players) })), [d]); return ( <> ({ value: v, label: `${v}d` }))} /> void q.refresh()} loading={q.refreshing} /> } /> {q.error && !d ? ( void q.refresh()} /> ) : (
{!d ? ( ) : ( compact(v)} tooltipLabel={(l, row) => `${String(l)} · ${int(row?.players ?? 0)} players`} /> )} {!d ? : r.game_slug} dense sort={sort} onSort={toggle} empty={`No rounds recorded in the last ${days} days.`} />}
)} ); }