feat(admin): Phase V2-6 Admin CRM 고도화 — CRUD + 차트 + 결제 + 감사
- DB: audit_log 테이블(diff 포함) + subscriptions.admin_note + super_admin role - Edge Functions 4개: admin-users, admin-subscriptions, admin-payments, admin-audit-log - 공유 유틸: admin-auth.ts(권한 검증), audit.ts(감사로그 기록) - Swagger UI: 독립 정적 페이지 + OpenAPI 3.0 spec - CRUD 페이지: 구독 생성/수정/삭제, role 변경, 감사로그 목록/상세 - recharts: feature별 StackedBar + DAU Line + Top Users HorizontalBar - 결제 이력: DB + Payple API 병행 조회 - 권한: super_admin만 위험 작업, admin은 조회 전용 - RLS: admin/super_admin IN 정책 + super_admin 쓰기 정책 - SQL RPC: admin_usage_by_feature, admin_top_users, admin_dau
This commit is contained in:
parent
f7c50eb2ed
commit
dca1b90faa
34 changed files with 3142 additions and 45 deletions
88
apps/admin/src/components/charts/feature-usage-chart.tsx
Normal file
88
apps/admin/src/components/charts/feature-usage-chart.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
'use client'
|
||||
|
||||
// apps/admin/src/components/charts/feature-usage-chart.tsx
|
||||
// feature별 일간 API 호출 — StackedBar
|
||||
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'recharts'
|
||||
import { Box } from '@mui/material'
|
||||
import { d3roPalette, d3roFontMono } from '@d3ro/ui/theme'
|
||||
|
||||
interface FeatureUsageRow {
|
||||
date: string
|
||||
feature: string
|
||||
total_count: number
|
||||
unique_users: number
|
||||
}
|
||||
|
||||
interface FeatureUsageChartProps {
|
||||
data: FeatureUsageRow[]
|
||||
}
|
||||
|
||||
const FEATURE_COLORS: Record<string, string> = {
|
||||
llm_haiku: d3roPalette.accent.amber,
|
||||
llm_sonnet: d3roPalette.tag.green,
|
||||
llm_opus: d3roPalette.tag.purple,
|
||||
stt_transcribe: d3roPalette.tag.blue,
|
||||
translate: d3roPalette.tag.blue,
|
||||
}
|
||||
|
||||
const DEFAULT_COLOR = d3roPalette.text.secondary
|
||||
|
||||
export function FeatureUsageChart({ data }: FeatureUsageChartProps): React.ReactElement {
|
||||
// Pivot: group by date, features as columns
|
||||
const features = [...new Set(data.map(d => d.feature))]
|
||||
const dateMap = new Map<string, Record<string, string | number>>()
|
||||
|
||||
for (const row of data) {
|
||||
const existing = dateMap.get(row.date) ?? { date: row.date }
|
||||
existing[row.feature] = row.total_count
|
||||
dateMap.set(row.date, existing)
|
||||
}
|
||||
|
||||
const chartData = [...dateMap.values()].sort((a, b) =>
|
||||
String(a.date).localeCompare(String(b.date))
|
||||
)
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', height: 300 }}>
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke={d3roPalette.border.subtle} />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tick={{ fill: d3roPalette.text.muted, fontFamily: d3roFontMono, fontSize: 10 }}
|
||||
tickFormatter={(val: string) => val.slice(5)}
|
||||
/>
|
||||
<YAxis tick={{ fill: d3roPalette.text.muted, fontFamily: d3roFontMono, fontSize: 10 }} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
background: d3roPalette.bg.card,
|
||||
border: `1px solid ${d3roPalette.border.default}`,
|
||||
fontFamily: d3roFontMono,
|
||||
fontSize: 11,
|
||||
color: d3roPalette.text.primary,
|
||||
}}
|
||||
/>
|
||||
<Legend wrapperStyle={{ fontFamily: d3roFontMono, fontSize: 11 }} />
|
||||
{features.map((feature) => (
|
||||
<Bar
|
||||
key={feature}
|
||||
dataKey={feature}
|
||||
stackId="a"
|
||||
fill={FEATURE_COLORS[feature] ?? DEFAULT_COLOR}
|
||||
/>
|
||||
))}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue