- backend apps/stats: GET /api/v1/admin/stats?from&to → totals / byUser / byDay. ChatMessage(assistant) 집계만, is_superuser 아니면 403. 테스트 3개 - frontend features/admin: 요약 카드 4개(요청·토큰·비용·평균 응답), 일별 토큰 막대(div), 사용자별 표, 기간 프리셋(오늘/7/30/90일). 헤더에 ADMIN 만 아이콘. 테스트 2개 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
53 lines
2.2 KiB
TypeScript
53 lines
2.2 KiB
TypeScript
import { lazy, Suspense } from "react"
|
|
import { CenterSpinner } from "@/shared/components/CenterSpinner"
|
|
import { Navigate, type RouteObject } from "react-router-dom"
|
|
import Layout from "@/shared/components/Layout"
|
|
import ProtectedRoute from "@/shared/components/ProtectedRoute"
|
|
import { SnapLayout } from "@/features/snap/components/SnapLayout"
|
|
import { PaletteShell } from "@/shared/components/PaletteShell"
|
|
import { PATHS } from "@/config/routes"
|
|
|
|
const LoginPage = lazy(() => import("@/features/auth/pages/LoginPage"))
|
|
const SessionListPage = lazy(() => import("@/features/snap/pages/SessionListPage"))
|
|
const NewChatPage = lazy(() => import("@/features/snap/pages/NewChatPage"))
|
|
const SessionChatPage = lazy(() => import("@/features/snap/pages/SessionChatPage"))
|
|
const SnippetPalettePage = lazy(() => import("@/features/snippets/pages/SnippetPalettePage"))
|
|
const AdminStatsPage = lazy(() => import("@/features/admin/pages/AdminStatsPage"))
|
|
|
|
const fallback = <CenterSpinner />
|
|
|
|
const wrap = (el: React.ReactNode) => <Suspense fallback={fallback}>{el}</Suspense>
|
|
|
|
export const routes: RouteObject[] = [
|
|
{
|
|
element: <Layout />,
|
|
children: [{ path: PATHS.LOGIN.slice(1), element: wrap(<LoginPage />) }],
|
|
},
|
|
{
|
|
element: <ProtectedRoute />,
|
|
children: [
|
|
{
|
|
// 챗봇(snap)·스니펫 공통 래퍼 — 붙여넣기 대상 배지 등 공통 UI 를 여기 한 곳에.
|
|
element: <PaletteShell />,
|
|
children: [
|
|
{
|
|
element: <SnapLayout />,
|
|
children: [
|
|
{ path: PATHS.SNAP.slice(1), element: wrap(<SessionListPage />) },
|
|
{ path: PATHS.SNAP_NEW.slice(1), element: wrap(<NewChatPage />) },
|
|
{ path: PATHS.SNAP_SESSION.slice(1), element: wrap(<SessionChatPage />) },
|
|
{ path: PATHS.ADMIN.slice(1), element: wrap(<AdminStatsPage />) },
|
|
],
|
|
},
|
|
{
|
|
// snap 헤더·Ctrl+N 없이 풀윈도우(Raycast). h-full 체인은 PaletteShell 의 h-screen 에 기댐.
|
|
path: PATHS.SNIPPET.slice(1),
|
|
element: wrap(<SnippetPalettePage />),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{ path: "*", element: <Navigate to={PATHS.SNAP} replace /> },
|
|
]
|