File size: 4,671 Bytes
df56b50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { useState } from "preact/hooks";
import { useT } from "../../../shared/i18n/context";
import { useUsageSummary, useUsageHistory, type Granularity } from "../../../shared/hooks/use-usage-stats";
import { UsageChart, formatNumber } from "../components/UsageChart";
import type { TranslationKey } from "../../../shared/i18n/translations";

const granularityOptions: Array<{ value: Granularity; label: TranslationKey }> = [
  { value: "hourly", label: "granularityHourly" },
  { value: "daily", label: "granularityDaily" },
];

const rangeOptions: Array<{ hours: number; label: TranslationKey }> = [
  { hours: 24, label: "last24h" },
  { hours: 72, label: "last3d" },
  { hours: 168, label: "last7d" },
];

export function UsageStats() {
  const t = useT();
  const { summary, loading: summaryLoading } = useUsageSummary();
  const [granularity, setGranularity] = useState<Granularity>("hourly");
  const [hours, setHours] = useState(24);
  const { dataPoints, loading: historyLoading } = useUsageHistory(granularity, hours);

  return (
    <div class="min-h-screen bg-slate-50 dark:bg-bg-dark flex flex-col">
      {/* Header */}
      <header class="sticky top-0 z-50 bg-white dark:bg-card-dark border-b border-gray-200 dark:border-border-dark px-4 py-3">
        <div class="max-w-[1100px] mx-auto flex items-center gap-3">
          <a
            href="#/"
            class="text-sm text-slate-500 dark:text-text-dim hover:text-primary transition-colors"
          >
            &larr; {t("backToDashboard")}
          </a>
          <h1 class="text-base font-semibold text-slate-800 dark:text-text-main">
            {t("usageStats")}
          </h1>
        </div>
      </header>

      <main class="flex-grow px-4 md:px-8 py-6 max-w-[1100px] mx-auto w-full">
        {/* Summary cards */}
        <div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6">
          <SummaryCard
            label={t("totalInputTokens")}
            value={summaryLoading ? "—" : formatNumber(summary?.total_input_tokens ?? 0)}
          />
          <SummaryCard
            label={t("totalOutputTokens")}
            value={summaryLoading ? "—" : formatNumber(summary?.total_output_tokens ?? 0)}
          />
          <SummaryCard
            label={t("totalRequestCount")}
            value={summaryLoading ? "—" : formatNumber(summary?.total_request_count ?? 0)}
          />
          <SummaryCard
            label={t("activeAccounts")}
            value={summaryLoading ? "—" : `${summary?.active_accounts ?? 0} / ${summary?.total_accounts ?? 0}`}
          />
        </div>

        {/* Controls */}
        <div class="flex flex-wrap gap-2 mb-4">
          {granularityOptions.map(({ value, label }) => (
            <button
              key={value}
              onClick={() => setGranularity(value)}
              class={`px-3 py-1 text-xs font-medium rounded-full border transition-colors ${
                granularity === value
                  ? "bg-primary text-white border-primary"
                  : "bg-white dark:bg-card-dark border-gray-200 dark:border-border-dark text-slate-600 dark:text-text-dim hover:border-primary/50"
              }`}
            >
              {t(label)}
            </button>
          ))}
          <div class="w-px h-5 bg-gray-200 dark:bg-border-dark self-center" />
          {rangeOptions.map(({ hours: h, label }) => (
            <button
              key={h}
              onClick={() => setHours(h)}
              class={`px-3 py-1 text-xs font-medium rounded-full border transition-colors ${
                hours === h
                  ? "bg-primary text-white border-primary"
                  : "bg-white dark:bg-card-dark border-gray-200 dark:border-border-dark text-slate-600 dark:text-text-dim hover:border-primary/50"
              }`}
            >
              {t(label)}
            </button>
          ))}
        </div>

        {/* Chart */}
        <div class="bg-white dark:bg-card-dark rounded-xl border border-gray-200 dark:border-border-dark p-4">
          {historyLoading ? (
            <div class="text-center py-12 text-slate-400 dark:text-text-dim text-sm">Loading...</div>
          ) : (
            <UsageChart data={dataPoints} />
          )}
        </div>
      </main>
    </div>
  );
}

function SummaryCard({ label, value }: { label: string; value: string }) {
  return (
    <div class="bg-white dark:bg-card-dark rounded-xl border border-gray-200 dark:border-border-dark p-4">
      <div class="text-xs text-slate-500 dark:text-text-dim mb-1">{label}</div>
      <div class="text-lg font-semibold text-slate-800 dark:text-text-main">{value}</div>
    </div>
  );
}