From bbedd58acf1973c73a20faf487965ede25ed3620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Wed, 18 Mar 2026 13:21:48 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20[plans]=20=EC=9D=B4=EC=9A=A9=ED=98=84?= =?UTF-8?q?=ED=99=A9=20React=20=EA=B5=AC=ED=98=84=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 응답 구조 (ai_tokens 신규, api_calls 제거) - TypeScript 타입 정의 (UsageApiData, AiTokenByModel) - 화면 와이어프레임 및 Progress Bar 색상 규칙 - 컴포넌트 구조 및 파일 변경 목록 - 체크리스트 11항목 --- INDEX.md | 1 + plans/usage-react-request.md | 440 +++++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100644 plans/usage-react-request.md diff --git a/INDEX.md b/INDEX.md index ecfb3ad..1efc45e 100644 --- a/INDEX.md +++ b/INDEX.md @@ -297,6 +297,7 @@ DB 도메인별: | [ai-token-usage-service-migration.md](plans/ai-token-usage-service-migration.md) | AI 토큰사용량 서비스 이관 기획 (MNG→API+React, GCS→R2 저장소 차이) | | [notification-sound-react-request.md](plans/notification-sound-react-request.md) | 알림설정 soundType React 구현 요청 (음원 배치, 미리듣기 실제 재생, API 연동 완료) | | [usage-subscription-unification.md](plans/usage-subscription-unification.md) | 이용현황+구독관리 통합 계획 (AI 토큰 사용량, 저장공간, 메뉴 통합) | +| [usage-react-request.md](plans/usage-react-request.md) | 이용현황 React 구현 요청서 (API 완료, 타입/컴포넌트/와이어프레임 포함) | ### frontend/integration/ — 프론트엔드 개발 가이드 diff --git a/plans/usage-react-request.md b/plans/usage-react-request.md new file mode 100644 index 0000000..ead6549 --- /dev/null +++ b/plans/usage-react-request.md @@ -0,0 +1,440 @@ +# 이용현황 페이지 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` + +```json +{ + "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` 타입 (변경) + +```typescript +// 기존 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` 타입 (변경) + +```typescript +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 토큰 경고 표시 + +```typescript +// percentage >= warning_threshold (80%)이면 경고 배지 표시 +{aiTokens.percentage >= aiTokens.warningThreshold && ( + + 기본 제공량의 {aiTokens.percentage}% 사용 중 + +)} + +// is_over_limit이면 초과 배지 표시 +{aiTokens.isOverLimit && ( + + 한도 초과 — 초과분 실비 과금 + +)} +``` + +### 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 변경 + +```typescript +// actions.ts — buildApiUrl 사용으로 변경 +import { buildApiUrl } from '@/lib/api/query-params'; + +export async function getUsage(): Promise> { + return executeServerAction({ + url: buildApiUrl('/api/v1/subscriptions/usage'), + errorMessage: '사용량 정보를 불러오는데 실패했습니다.', + }); +} +``` + +> `getSubscriptionData()` 함수에서 `getCurrentSubscription()` + `getUsage()` 병렬 호출 유지. 단, `transformApiToFrontend()`에서 `ai_tokens` 필드를 매핑해야 한다. + +### 5.3 `/usage/page.tsx` (신규) + +```typescript +'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(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + getSubscriptionData() + .then(result => setData(result.data)) + .finally(() => setIsLoading(false)); + }, []); + + if (isLoading) return ; + return ; +} +``` + +### 5.4 `/subscription/page.tsx` (리다이렉트) + +```typescript +'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. 포맷팅 유틸 + +```typescript +// 토큰 수 포맷: 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 파랑, 60~80 노랑, 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