16 KiB
16 KiB
이용현황(구독관리 통합) React 구현 요청서
작성일: 2026-03-18 상태: API 구현 완료, React 구현 대기 대상: sam/react 메뉴 위치: 사이드바
이용현황(/usage)
1. 개요
1.1 목적
기존 구독관리(/subscription)와 이용현황(/usage)을 하나의 페이지로 통합한다. 구독 정보, 리소스 사용량(사용자/저장공간), AI 토큰 사용량을 한 화면에 표시한다.
1.2 변경 요약
| 항목 | 변경 전 | 변경 후 |
|---|---|---|
/usage |
EmptyPage (미구현) | 통합 이용현황 페이지 |
/subscription |
SubscriptionManagement | /usage로 리다이렉트 |
| 메뉴 | 구독관리 + 이용현황 2개 | 이용현황 1개로 통합 |
API 호출 수 (api_calls) |
하드코딩 10,000 | 제거 → AI 토큰으로 대체 |
| AI 토큰 | 없음 | 신규 추가 (한도/비용/모델별) |
| 저장공간 한도 | 10GB | 100GB |
1.3 API 상태
| API | 엔드포인트 | 상태 |
|---|---|---|
| 구독 정보 | GET /api/v1/subscriptions/current |
기존 유지 |
| 사용량 조회 | GET /api/v1/subscriptions/usage |
개선 완료 |
| 구독 취소 | POST /api/v1/subscriptions/{id}/cancel |
기존 유지 |
| 데이터 내보내기 | POST /api/v1/subscriptions/export |
기존 유지 |
2. API 응답 구조 (변경된 부분)
2.1 GET /api/v1/subscriptions/usage
{
"success": true,
"data": {
"users": {
"used": 24,
"limit": 10,
"percentage": 240.0
},
"storage": {
"used": 22808833,
"used_formatted": "21.75 MB",
"limit": 107374182400,
"limit_formatted": "100 GB",
"percentage": 0.0
},
"ai_tokens": {
"period": "2026-03",
"total_requests": 156,
"total_tokens": 620000,
"prompt_tokens": 412000,
"completion_tokens": 208000,
"limit": 1000000,
"percentage": 62.0,
"cost_usd": 0.95,
"cost_krw": 1234,
"warning_threshold": 80,
"is_over_limit": false,
"by_model": [
{
"model": "gemini-2.0-flash",
"requests": 120,
"total_tokens": 496000,
"cost_krw": 987
},
{
"model": "claude-3-haiku",
"requests": 36,
"total_tokens": 124000,
"cost_krw": 247
}
]
},
"subscription": {
"plan": "스탠다드",
"monthly_fee": 79000,
"status": "active",
"started_at": "2026-01-01",
"ended_at": "2026-12-31",
"remaining_days": 288
}
}
}
2.2 변경점 (기존 대비)
| 필드 | 변경 |
|---|---|
api_calls |
삭제됨 |
ai_tokens |
신규 추가 |
ai_tokens.limit |
월별 토큰 한도 (테넌트별, 기본 100만) |
ai_tokens.percentage |
한도 대비 사용율 |
ai_tokens.warning_threshold |
경고 기준 (80%) |
ai_tokens.is_over_limit |
한도 초과 여부 |
ai_tokens.by_model |
모델별 사용량 내역 |
subscription.plan |
Plan 테이블의 name (한글) |
subscription.monthly_fee |
Plan 테이블의 price (원화) |
subscription.started_at |
start_date → started_at 변경 |
subscription.ended_at |
next_billing → ended_at 변경 |
storage.limit |
기본값 10GB → 100GB |
3. TypeScript 타입 정의
3.1 UsageApiData 타입 (변경)
// 기존 api_calls 제거, ai_tokens 추가
export interface UsageApiData {
users: {
used: number;
limit: number;
percentage: number;
};
storage: {
used: number;
used_formatted: string;
limit: number;
limit_formatted: string;
percentage: number;
};
ai_tokens: {
period: string; // "2026-03"
total_requests: number;
total_tokens: number;
prompt_tokens: number;
completion_tokens: number;
limit: number; // 월별 한도 (기본 100만)
percentage: number; // 한도 대비 사용율
cost_usd: number;
cost_krw: number;
warning_threshold: number; // 경고 기준 (80)
is_over_limit: boolean;
by_model: AiTokenByModel[];
};
subscription: {
plan: string | null; // "스탠다드", "프로페셔널" 등
monthly_fee: number; // 원화 금액
status: string;
started_at: string | null;
ended_at: string | null;
remaining_days: number | null;
};
}
export interface AiTokenByModel {
model: string; // "gemini-2.0-flash"
requests: number;
total_tokens: number;
cost_krw: number;
}
3.2 SubscriptionInfo 타입 (변경)
export interface SubscriptionInfo {
// 구독
id?: number;
plan: string | null;
planName: string;
monthlyFee: number;
status: SubscriptionStatus;
startedAt: string | null;
endedAt: string | null;
remainingDays: number | null;
// 사용자
userCount: number;
userLimit: number;
// 저장공간
storageUsed: number;
storageLimit: number;
storageUsedFormatted: string;
storageLimitFormatted: string;
storagePercentage: number;
// AI 토큰 (신규)
aiTokens: {
period: string;
totalTokens: number;
limit: number;
percentage: number;
costKrw: number;
warningThreshold: number;
isOverLimit: boolean;
byModel: AiTokenByModel[];
};
}
4. 화면 구성
4.1 전체 레이아웃
이용현황
┌─────────────────────────────────────────────────────────────┐
│ │
│ [섹션 1] 구독 정보 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 요금제 │ │ 구독 상태 │ │ 구독 금액 │ │
│ │ 스탠다드 │ │ ● 활성 │ │ ₩79,000/월 │ │
│ │ │ │ │ │ │ │
│ │ 시작: 01-01 │ │ 남은일: 288일│ │ 종료: 12-31 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ [섹션 2] 리소스 사용량 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 사용자 │ │
│ │ ████████████████████████░░░░░░ 24 / 10명 │ │
│ │ │ │
│ │ 저장 공간 │ │
│ │ █░░░░░░░░░░░░░░░░░░░░░░░░░░░ 21.75 MB / 100 GB │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ [섹션 3] AI 토큰 사용량 — 2026년 3월 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ ██████████████████░░░░░░░░░░░ 620,000 / 1,000,000│ │
│ │ 62.0% │ │
│ │ │ │
│ │ 총 비용: ₩1,234 │ │
│ │ │ │
│ │ 모델별 사용량: │ │
│ │ ┌────────────────────┬────────┬──────┬───────┐ │ │
│ │ │ 모델 │ 호출수 │ 토큰 │ 비용 │ │ │
│ │ ├────────────────────┼────────┼──────┼───────┤ │ │
│ │ │ gemini-2.0-flash │ 120 │ 496K │ ₩987 │ │ │
│ │ │ claude-3-haiku │ 36 │ 124K │ ₩247 │ │ │
│ │ └────────────────────┴────────┴──────┴───────┘ │ │
│ │ │ │
│ │ ※ 기본 제공: 월 100만 토큰. 초과 시 실비 과금 │ │
│ │ ※ 매월 1일 리셋, 잔여 토큰 이월 불가 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ [섹션 4] 서비스 관리 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ [ 자료 내보내기 ] [ 서비스 해지 ] │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 Progress Bar 색상 규칙
| 사용율 | 색상 | 비고 |
|---|---|---|
| 0~60% | bg-blue-500 (기본) |
정상 |
| 60~80% | bg-yellow-500 (주의) |
|
| 80~100% | bg-orange-500 (경고) |
warning_threshold 기준 |
| 100%+ | bg-red-500 (초과) |
is_over_limit: true |
4.3 AI 토큰 경고 표시
// percentage >= warning_threshold (80%)이면 경고 배지 표시
{aiTokens.percentage >= aiTokens.warningThreshold && (
<Badge variant="warning">
기본 제공량의 {aiTokens.percentage}% 사용 중
</Badge>
)}
// is_over_limit이면 초과 배지 표시
{aiTokens.isOverLimit && (
<Badge variant="destructive">
한도 초과 — 초과분 실비 과금
</Badge>
)}
4.4 구독 정보가 없는 경우
subscription.plan이 null이면 구독 카드 영역에 안내 메시지 표시:
구독 정보가 없습니다. 관리자에게 문의하세요.
5. 컴포넌트 구조
5.1 파일 구조
src/app/[locale]/(protected)/
├── usage/page.tsx ← 신규: 이용현황 페이지
└── subscription/page.tsx ← 수정: /usage로 리다이렉트
src/components/settings/SubscriptionManagement/
├── types.ts ← 수정: UsageApiData에 ai_tokens 추가
├── actions.ts ← 수정: API_URL → buildApiUrl
├── utils.ts ← 수정: transformApiToFrontend 개선
├── SubscriptionManagement.tsx ← 수정 또는 교체: AI 토큰 섹션 추가
├── SubscriptionClient.tsx ← 수정 또는 교체
└── index.ts ← 기존 유지
5.2 Server Actions 변경
// actions.ts — buildApiUrl 사용으로 변경
import { buildApiUrl } from '@/lib/api/query-params';
export async function getUsage(): Promise<ActionResult<UsageApiData>> {
return executeServerAction({
url: buildApiUrl('/api/v1/subscriptions/usage'),
errorMessage: '사용량 정보를 불러오는데 실패했습니다.',
});
}
getSubscriptionData()함수에서getCurrentSubscription()+getUsage()병렬 호출 유지. 단,transformApiToFrontend()에서ai_tokens필드를 매핑해야 한다.
5.3 /usage/page.tsx (신규)
'use client';
import { useEffect, useState } from 'react';
import { getSubscriptionData } from '@/components/settings/SubscriptionManagement/actions';
import type { SubscriptionInfo } from '@/components/settings/SubscriptionManagement/types';
// 기존 SubscriptionManagement 컴포넌트 재사용
import { SubscriptionManagement } from '@/components/settings/SubscriptionManagement';
export default function UsagePage() {
const [data, setData] = useState<SubscriptionInfo | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
getSubscriptionData()
.then(result => setData(result.data))
.finally(() => setIsLoading(false));
}, []);
if (isLoading) return <UsageSkeleton />;
return <SubscriptionManagement data={data} />;
}
5.4 /subscription/page.tsx (리다이렉트)
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function SubscriptionRedirect() {
const router = useRouter();
useEffect(() => {
router.replace('/usage');
}, [router]);
return null;
}
6. 포맷팅 유틸
// 토큰 수 포맷: 1,000,000 → "1,000K" 또는 "1M"
export function formatTokenCount(tokens: number): string {
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
if (tokens >= 1_000) return `${Math.round(tokens / 1_000).toLocaleString()}K`;
return tokens.toLocaleString();
}
// 원화 포맷: 1234 → "₩1,234"
export function formatKrw(amount: number): string {
return `₩${Math.round(amount).toLocaleString()}`;
}
// 월 표시: "2026-03" → "2026년 3월"
export function formatPeriod(period: string): string {
const [year, month] = period.split('-');
return `${year}년 ${parseInt(month)}월`;
}
7. 제거 대상
기존 코드에서 제거하거나 대체할 항목:
| 파일 | 제거 대상 | 사유 |
|---|---|---|
types.ts |
api_calls 인터페이스 |
ai_tokens로 대체 |
types.ts |
apiCallsUsed, apiCallsLimit |
제거 |
SubscriptionManagement.tsx |
API 호출 수 Progress Bar | AI 토큰 Progress Bar로 대체 |
utils.ts |
apiCallsUsed/Limit 변환 |
aiTokens 변환으로 대체 |
8. 체크리스트
types.ts—UsageApiData에서api_calls제거,ai_tokens+subscription변경utils.ts—transformApiToFrontend()에 AI 토큰 매핑 추가SubscriptionManagement.tsx— AI 토큰 섹션 추가, API 호출 수 섹션 제거- Progress Bar — 사용율별 색상 변경 (0
60 파랑, 6080 노랑, 80~100 주황, 100+ 빨강) by_model테이블 — 모델별 호출수/토큰/비용 표시- 한도 초과 경고 배지 —
is_over_limit/warning_threshold기반 /usage/page.tsx신규 생성/subscription/page.tsx→/usage리다이렉트actions.ts—buildApiUrl()사용으로 변경- 구독 정보 null 처리 (미가입 테넌트)
- 로딩 스켈레톤 표시
관련 문서
| 문서 | 경로 |
|---|---|
| 통합 계획 (내부) | plans/usage-subscription-unification.md |
| 과금 정책 | rules/customer-pricing.md (6장 사용량 기반 추가 과금) |
| AI 토큰 기능 | features/ai/README.md |
| AI 토큰 API 명세 | frontend/api-specs/ai-token-usage-api.md |
최종 업데이트: 2026-03-18