style(admin): 비용 표시 제거 — 사내 LLM 은 단가 없어 항상 0. 토큰·응답시간만

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
lee-hyeon-cheol
2026-09-21 13:49:39 +09:00
co-authored by Claude Fable 5.1
parent d4a5f20787
commit 9fc93ffbce
2 changed files with 5 additions and 9 deletions
@@ -91,7 +91,6 @@ describe("AdminStatsPage", () => {
asUser("ADMIN") asUser("ADMIN")
mount() mount()
expect(await screen.findByText("1,500")).toBeTruthy() // 총 토큰 expect(await screen.findByText("1,500")).toBeTruthy() // 총 토큰
expect(screen.getByText("$0.1234")).toBeTruthy()
expect(screen.getByText("2.5s")).toBeTruthy() expect(screen.getByText("2.5s")).toBeTruthy()
expect(screen.getByText("김개발")).toBeTruthy() expect(screen.getByText("김개발")).toBeTruthy()
expect(client.apiGet).toHaveBeenCalledTimes(1) expect(client.apiGet).toHaveBeenCalledTimes(1)
@@ -9,7 +9,6 @@ import { ApiError } from "@/lib/api/errors"
import { useAdminStats } from "../api/admin.api" import { useAdminStats } from "../api/admin.api"
const fmtInt = (n: number) => n.toLocaleString("ko-KR") const fmtInt = (n: number) => n.toLocaleString("ko-KR")
const fmtUsd = (n: number) => `$${n.toFixed(n < 1 ? 4 : 2)}`
const fmtSec = (ms: number | null) => (ms === null ? "" : `${(ms / 1000).toFixed(1)}s`) const fmtSec = (ms: number | null) => (ms === null ? "" : `${(ms / 1000).toFixed(1)}s`)
const isoDay = (d: Date) => d.toISOString().slice(0, 10) const isoDay = (d: Date) => d.toISOString().slice(0, 10)
@@ -20,7 +19,8 @@ const PRESETS: { label: string; days: number }[] = [
{ label: "90일", days: 90 }, { label: "90일", days: 90 },
] ]
/** 관리자 대시보드 — 총 토큰·비용·응답시간 + 사용자별·일별. 서버는 ChatMessage 집계만(새로 모으는 것 없음). */ /** 관리자 대시보드 — 총 토큰·응답시간 + 사용자별·일별. 서버는 ChatMessage 집계만(새로 모으는 것 없음).
* 비용은 안 보여줌 — 사내 LLM(FabriX) 은 단가가 없어 OpenCode 가 0 으로 줌. 단가 정해지면 그때 카드 추가. */
export default function AdminStatsPage() { export default function AdminStatsPage() {
const navigate = useNavigate() const navigate = useNavigate()
const isAdmin = useAuthStore((s) => s.user?.role === "ADMIN") const isAdmin = useAuthStore((s) => s.user?.role === "ADMIN")
@@ -86,7 +86,7 @@ export default function AdminStatsPage() {
{t && ( {t && (
<> <>
{/* 요약 카드 4개 */} {/* 요약 카드 4개 */}
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4"> <div className="grid grid-cols-3 gap-2">
<Stat <Stat
label="요청" label="요청"
value={fmtInt(t.requests)} value={fmtInt(t.requests)}
@@ -97,7 +97,6 @@ export default function AdminStatsPage() {
value={fmtInt(t.totalTokens)} value={fmtInt(t.totalTokens)}
sub={`입력 ${fmtInt(t.inputTokens)} · 출력 ${fmtInt(t.outputTokens)}`} sub={`입력 ${fmtInt(t.inputTokens)} · 출력 ${fmtInt(t.outputTokens)}`}
/> />
<Stat label="비용" value={fmtUsd(t.costUsd)} sub="OpenCode 집계 기준" />
<Stat label="평균 응답" value={fmtSec(t.avgElapsedMs)} sub="첫 요청→완료" /> <Stat label="평균 응답" value={fmtSec(t.avgElapsedMs)} sub="첫 요청→완료" />
</div> </div>
@@ -115,7 +114,7 @@ export default function AdminStatsPage() {
{data.byDay.map((d) => ( {data.byDay.map((d) => (
<div <div
key={d.day} key={d.day}
title={`${d.day}: ${fmtInt(d.totalTokens)} 토큰 · ${d.requests} · ${fmtUsd(d.costUsd)}`} title={`${d.day}: ${fmtInt(d.totalTokens)} 토큰 · ${d.requests}`}
className="bg-primary/80 hover:bg-primary min-w-[4px] flex-1 rounded-sm transition-colors" className="bg-primary/80 hover:bg-primary min-w-[4px] flex-1 rounded-sm transition-colors"
style={{ height: `${Math.max(2, (d.totalTokens / maxDayTokens) * 100)}%` }} style={{ height: `${Math.max(2, (d.totalTokens / maxDayTokens) * 100)}%` }}
/> />
@@ -133,14 +132,13 @@ export default function AdminStatsPage() {
<th className="px-3 py-2 text-right font-semibold"></th> <th className="px-3 py-2 text-right font-semibold"></th>
<th className="px-3 py-2 text-right font-semibold"></th> <th className="px-3 py-2 text-right font-semibold"></th>
<th className="px-3 py-2 text-right font-semibold"></th> <th className="px-3 py-2 text-right font-semibold"></th>
<th className="px-3 py-2 text-right font-semibold"></th>
<th className="px-3 py-2 text-right font-semibold"> </th> <th className="px-3 py-2 text-right font-semibold"> </th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-border/70 divide-y"> <tbody className="divide-border/70 divide-y">
{data.byUser.length === 0 && ( {data.byUser.length === 0 && (
<tr> <tr>
<td colSpan={6} className="text-muted-foreground py-6 text-center text-xs"> <td colSpan={5} className="text-muted-foreground py-6 text-center text-xs">
</td> </td>
</tr> </tr>
@@ -154,7 +152,6 @@ export default function AdminStatsPage() {
<td className="px-3 py-2 text-right tabular-nums">{fmtInt(u.requests)}</td> <td className="px-3 py-2 text-right tabular-nums">{fmtInt(u.requests)}</td>
<td className="px-3 py-2 text-right tabular-nums">{fmtInt(u.sessions)}</td> <td className="px-3 py-2 text-right tabular-nums">{fmtInt(u.sessions)}</td>
<td className="px-3 py-2 text-right tabular-nums">{fmtInt(u.totalTokens)}</td> <td className="px-3 py-2 text-right tabular-nums">{fmtInt(u.totalTokens)}</td>
<td className="px-3 py-2 text-right tabular-nums">{fmtUsd(u.costUsd)}</td>
<td className="px-3 py-2 text-right tabular-nums">{fmtSec(u.avgElapsedMs)}</td> <td className="px-3 py-2 text-right tabular-nums">{fmtSec(u.avgElapsedMs)}</td>
</tr> </tr>
))} ))}