'use client'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import BarChart from '@/components/admin/BarChart'; import { adminFetch } from '@/lib/adminFetch'; type Period = 'day' | 'week' | 'month'; interface CostData { period: string; since: string; summary: { totalTokens: number; inputTokens: number; outputTokens: number; estimatedUsd: number; recordCount: number; }; bySister: Array<{ name: string; totalTokens: number; estimatedUsd: number }>; byModel: Array<{ model: string; totalTokens: number; estimatedUsd: number; count: number }>; timeline: Array<{ date: string; totalTokens: number; estimatedUsd: number }>; } const Controls = styled.div` display: flex; gap: 8px; margin-bottom: 24px; `; const PeriodBtn = styled.button<{ $active: boolean }>` padding: 6px 16px; border-radius: 7px; font-size: 13px; font-weight: ${({ $active }) => $active ? '600' : '400'}; background: ${({ $active }) => $active ? 'rgba(88,166,255,0.15)' : 'transparent'}; border: 1px solid ${({ $active }) => $active ? 'rgba(88,166,255,0.4)' : 'var(--border-color)'}; color: ${({ $active }) => $active ? '#58A6FF' : 'var(--text-secondary)'}; cursor: pointer; transition: all 0.15s; &:hover { color: #58A6FF; border-color: rgba(88,166,255,0.3); } `; const SummaryGrid = styled.div` display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 14px; margin-bottom: 28px; `; const SummaryCard = styled.div` background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: 10px; padding: 16px 18px; `; const SummaryLabel = styled.div` font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px; `; const SummaryValue = styled.div` font-size: 20px; font-weight: 700; color: var(--text-primary); `; const SummaryUnit = styled.span` font-size: 12px; color: var(--text-secondary); margin-left: 4px; `; const ChartsGrid = styled.div` display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 28px; @media (max-width: 900px) { grid-template-columns: 1fr; } `; const Card = styled.div` background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: 12px; padding: 20px 22px; `; const RecordBtn = styled.button` padding: 8px 16px; border-radius: 7px; font-size: 12px; font-weight: 600; background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25); color: #58A6FF; cursor: pointer; transition: background 0.15s; &:hover { background: rgba(88,166,255,0.18); } &:disabled { opacity: 0.5; cursor: not-allowed; } `; const EmptyNote = styled.div` padding: 32px; text-align: center; color: var(--text-secondary); font-size: 13px; line-height: 1.6; `; function formatTokens(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; return String(n); } export default function CostsPage() { const [period, setPeriod] = useState('week'); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [recording, setRecording] = useState(false); const load = async (p: Period) => { setLoading(true); try { const res = await adminFetch(`/api/admin/costs?period=${p}`); if (res.ok) setData(await res.json()); } catch { // silent } finally { setLoading(false); } }; useEffect(() => { load(period); }, [period]); const recordAll = async () => { setRecording(true); try { await Promise.all(['harang', 'narang', 'darang', 'erang'].map((name) => adminFetch(`/api/admin/costs/record/${name}`, { method: 'POST' }), )); await load(period); } finally { setRecording(false); } }; const sisterChartData = data?.bySister.map((s) => ({ label: { harang: '하랑이', narang: '나랑이', darang: '다랑이', erang: '이랑이' }[s.name] ?? s.name, value: s.estimatedUsd, })) ?? []; const modelChartData = data?.byModel.map((m) => ({ label: m.model.split('/').pop() ?? m.model, value: m.totalTokens, })) ?? []; const timelineChartData = data?.timeline.map((t) => ({ label: t.date.slice(5), value: t.estimatedUsd, })) ?? []; return ( <> {(['day', 'week', 'month'] as Period[]).map((p) => ( setPeriod(p)}> {p === 'day' ? '오늘' : p === 'week' ? '7일' : '30일'} ))} {recording ? '수집 중...' : '📊 토큰 수집'} {loading ? (
로딩 중...
) : !data || data.summary.recordCount === 0 ? ( 아직 비용 데이터가 없어.
위 "토큰 수집" 버튼으로 자매들의 세션 토큰을 수집해.
) : ( <> 총 토큰 {formatTokens(data.summary.totalTokens)}tokens 예상 비용 ${data.summary.estimatedUsd.toFixed(4)}USD 입력 토큰 {formatTokens(data.summary.inputTokens)} 출력 토큰 {formatTokens(data.summary.outputTokens)} {timelineChartData.length > 0 && ( )} )} ); }