docs: Sync documentation from private repository
This commit is contained in:
parent
ef47bf9645
commit
efd2889fb6
264
API_SPEC.md
264
API_SPEC.md
@ -2063,3 +2063,267 @@ export async function createTeam(data: CreateTeamRequest): Promise<ApiResponse<C
|
||||
5. ⏳ Next.js API Routes 또는 Server Actions 구현
|
||||
6. ⏳ Redis 캐싱 구현 (선택적)
|
||||
7. ⏳ Rate Limiting 구현 (선택적)
|
||||
|
||||
---
|
||||
|
||||
## Curriculum & Lesson API
|
||||
|
||||
### GET /api/curriculum
|
||||
|
||||
시스템 또는 팀 커리큘럼 목록 조회
|
||||
|
||||
**Query Parameters**:
|
||||
- `type`: "system" | "team" (선택)
|
||||
- `teamId`: 팀 ID (type=team 시 필수)
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
curriculums: Curriculum[]
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `curriculumManager.getSystemCurriculums()`
|
||||
- `curriculumManager.getTeamCurriculums(teamId)`
|
||||
- `curriculumManager.getAvailableCurriculums(teamId?)`
|
||||
|
||||
**캐싱**: 클라이언트 캐싱 (TTL 10분)
|
||||
|
||||
---
|
||||
|
||||
### GET /api/curriculum/[curriculumId]
|
||||
|
||||
커리큘럼 상세 조회
|
||||
|
||||
**Query Parameters**:
|
||||
- `includeLessons`: boolean (레슨 포함 여부)
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
curriculum: Curriculum,
|
||||
lessons?: Lesson[]
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `curriculumManager.getCurriculumById(curriculumId, includeLessons)`
|
||||
|
||||
**캐싱**: 클라이언트 캐싱 (TTL 10분)
|
||||
|
||||
---
|
||||
|
||||
### POST /api/curriculum
|
||||
|
||||
커리큘럼 생성
|
||||
|
||||
**Request Body**:
|
||||
```typescript
|
||||
{
|
||||
title: string;
|
||||
description: string;
|
||||
ownerType?: "system" | "team";
|
||||
teamId?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
curriculum: Curriculum
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `curriculumManager.createCurriculum(data)`
|
||||
|
||||
**권한**: 인증 필요, 팀 소유자 (ownerType=team 시)
|
||||
|
||||
**캐시 무효화**: `curriculums:*` 패턴
|
||||
|
||||
---
|
||||
|
||||
### PUT /api/curriculum/[curriculumId]
|
||||
|
||||
커리큘럼 수정
|
||||
|
||||
**Request Body**:
|
||||
```typescript
|
||||
{
|
||||
title?: string;
|
||||
description?: string;
|
||||
items?: Array<{type: string; id: string; title?: string}>;
|
||||
isActive?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
curriculum: Curriculum
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `curriculumManager.updateCurriculum(curriculumId, updates)`
|
||||
|
||||
**권한**: 인증 필요, 커리큘럼 소유자
|
||||
|
||||
**캐시 무효화**: `curriculum:${curriculumId}:*`, `curriculums:*` 패턴
|
||||
|
||||
---
|
||||
|
||||
### DELETE /api/curriculum/[curriculumId]
|
||||
|
||||
커리큘럼 삭제
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
message: string
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `curriculumManager.deleteCurriculum(curriculumId)`
|
||||
|
||||
**권한**: 인증 필요, 커리큘럼 소유자
|
||||
|
||||
**캐시 무효화**: `curriculum:${curriculumId}:*`, `curriculums:*` 패턴
|
||||
|
||||
---
|
||||
|
||||
### GET /api/lesson/[lessonId]
|
||||
|
||||
레슨 상세 조회
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
lesson: Lesson
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `lessonManager.getLessonById(lessonId)`
|
||||
|
||||
**캐싱**: 클라이언트 캐싱 (TTL 10분)
|
||||
|
||||
---
|
||||
|
||||
### GET /api/lesson/curriculum/[curriculumId]
|
||||
|
||||
커리큘럼의 레슨 목록 조회
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
lessons: Lesson[]
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `lessonManager.getLessonsByCurriculum(curriculumId)`
|
||||
|
||||
**캐싱**: 클라이언트 캐싱 (TTL 10분)
|
||||
|
||||
---
|
||||
|
||||
### POST /api/lesson
|
||||
|
||||
레슨 생성
|
||||
|
||||
**Request Body**:
|
||||
```typescript
|
||||
{
|
||||
title: string;
|
||||
description: string;
|
||||
ownerType?: "system" | "team";
|
||||
teamId?: string;
|
||||
level?: number;
|
||||
category: string;
|
||||
curriculumId?: string;
|
||||
orderIndex?: number;
|
||||
contents?: LessonContent[];
|
||||
reward?: {experience: number; stickers?: string[]};
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
lesson: Lesson
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `lessonManager.createLesson(data)`
|
||||
|
||||
**권한**: 인증 필요, 팀 소유자 (ownerType=team 시)
|
||||
|
||||
**캐시 무효화**: `lessons:*` 패턴
|
||||
|
||||
---
|
||||
|
||||
### PUT /api/lesson/[lessonId]
|
||||
|
||||
레슨 수정
|
||||
|
||||
**Request Body**:
|
||||
```typescript
|
||||
{
|
||||
title?: string;
|
||||
description?: string;
|
||||
level?: number;
|
||||
category?: string;
|
||||
orderIndex?: number;
|
||||
contents?: LessonContent[];
|
||||
reward?: {experience: number; stickers?: string[]};
|
||||
isActive?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
lesson: Lesson
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `lessonManager.updateLesson(lessonId, updates)`
|
||||
|
||||
**권한**: 인증 필요, 레슨 소유자
|
||||
|
||||
**캐시 무효화**: `lesson:${lessonId}`, `lessons:*` 패턴
|
||||
|
||||
---
|
||||
|
||||
### DELETE /api/lesson/[lessonId]
|
||||
|
||||
레슨 삭제
|
||||
|
||||
**Response**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
message: string
|
||||
}
|
||||
```
|
||||
|
||||
**Manager 메서드**:
|
||||
- `lessonManager.deleteLesson(lessonId)`
|
||||
|
||||
**권한**: 인증 필요, 레슨 소유자
|
||||
|
||||
**캐시 무효화**: `lesson:${lessonId}`, `lessons:*` 패턴
|
||||
|
||||
296
DATA_MODELS.md
296
DATA_MODELS.md
@ -19,7 +19,8 @@ firestore
|
||||
├── comments/ # 🆕 댓글 (계층 구조 지원)
|
||||
├── userReactions/ # 🆕 댓글 반응
|
||||
├── patternAnalyses/ # ✅ 패턴 분석 결과 (contentHash 캐싱)
|
||||
├── lessons/ # 🔜 학습 레슨
|
||||
├── curriculums/ # ✅ 커리큘럼 (시스템 제공 + 팀 생성)
|
||||
├── lessons/ # ✅ 학습 레슨 (theory/quiz/mission/writing_prompt)
|
||||
├── stickers/ # 🔜 스티커 마스터 데이터
|
||||
└── userStickers/ # 🔜 사용자별 스티커 획득 기록
|
||||
```
|
||||
@ -634,62 +635,82 @@ interface UserReaction {
|
||||
|
||||
---
|
||||
|
||||
## 8. Lesson (학습 레슨) 🔜
|
||||
## 8. Lesson (학습 레슨) ✅
|
||||
|
||||
**컬렉션**: `lessons/{lessonId}` (구현 예정)
|
||||
**컬렉션**: `lessons/{lessonId}` (✅ Fork Model로 개편)
|
||||
|
||||
### 스키마
|
||||
|
||||
```typescript
|
||||
interface LessonContent {
|
||||
type: 'theory' | 'mission' | 'quiz' | 'writing_prompt';
|
||||
data: TheoryBlock | MissionBlock | QuizBlock | WritingPromptBlock;
|
||||
}
|
||||
|
||||
interface TheoryBlock {
|
||||
markdown: string;
|
||||
}
|
||||
|
||||
interface MissionBlock {
|
||||
description: string;
|
||||
items: string[]; // 찾아야 할 대상 등
|
||||
}
|
||||
|
||||
interface QuizBlock {
|
||||
question: string;
|
||||
options?: string[]; // 객관식
|
||||
answer: string | number;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
interface WritingPromptBlock {
|
||||
prompt: string;
|
||||
guideLines: string[];
|
||||
}
|
||||
|
||||
type LessonVisibility = 'public' | 'private' | 'system';
|
||||
|
||||
interface Lesson {
|
||||
id: string; // 문서 ID
|
||||
title: string; // 레슨 제목
|
||||
description: string; // 레슨 설명
|
||||
|
||||
// 🆕 Fork Model (2025-12-19)
|
||||
ownerId: string; // 사용자 UID (소유자)
|
||||
visibility: LessonVisibility; // 공개 범위
|
||||
isSystem: boolean; // 시스템 레슨 여부
|
||||
forkedFrom?: string; // 원본 레슨 ID (Fork한 경우)
|
||||
forkCount: number; // Fork 횟수
|
||||
|
||||
// 분류
|
||||
level: number; // 추천 레벨 (1-100)
|
||||
category: string; // 카테고리 (예: "문단 쓰기", "비유 표현")
|
||||
order: number; // 순서 (같은 카테고리 내)
|
||||
|
||||
// 콘텐츠
|
||||
content: {
|
||||
theory: string; // 이론 설명 (마크다운)
|
||||
examples: string[]; // 예시 문장들
|
||||
exercises: Exercise[]; // 연습 문제
|
||||
};
|
||||
// 커리큘럼 연결
|
||||
curriculumId?: string;
|
||||
orderIndex: number;
|
||||
|
||||
// 선행 조건
|
||||
requiredLessons?: string[]; // 선행 레슨 ID 배열
|
||||
// 콘텐츠 (유연한 블록 구조)
|
||||
contents: LessonContent[];
|
||||
|
||||
// 보상
|
||||
reward: {
|
||||
experience: number; // 획득 경험치
|
||||
stickers?: string[]; // 획득 가능한 스티커 ID
|
||||
experience: number;
|
||||
stickers?: string[];
|
||||
};
|
||||
|
||||
// 통계
|
||||
completionCount: number; // 완료한 학생 수
|
||||
averageScore?: number; // 평균 점수 (0-100)
|
||||
completionCount: number;
|
||||
|
||||
// 타임스탬프
|
||||
createdAt: Timestamp;
|
||||
updatedAt: Timestamp;
|
||||
|
||||
// 관리자
|
||||
createdBy: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
interface Exercise {
|
||||
id: string;
|
||||
type: 'multiple_choice' | 'short_answer' | 'writing';
|
||||
question: string;
|
||||
options?: string[]; // 객관식인 경우
|
||||
correctAnswer?: string | number;
|
||||
explanation?: string; // 해설
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 예시 데이터
|
||||
|
||||
```json
|
||||
@ -743,12 +764,82 @@ interface Exercise {
|
||||
|
||||
### 인덱스
|
||||
|
||||
- `category` + `order` (복합 인덱스, 오름차순)
|
||||
- `level` + `order` (복합 인덱스, 오름차순)
|
||||
- `curriculumId` + `orderIndex` (복합 인덱스, 오름차순) -- 커리큘럼별 조회
|
||||
- `ownerId` + `createdAt` (복합 인덱스) -- 사용자별 레슨 조회
|
||||
- `isSystem` + `createdAt` (복합 인덱스) -- 시스템 레슨 조회
|
||||
|
||||
---
|
||||
|
||||
## 9. Sticker (스티커) 🔜
|
||||
## 9. Curriculum (커리큘럼) ✅
|
||||
|
||||
**컬렉션**: `curriculums/{curriculumId}` (✅ Fork Model로 개편)
|
||||
|
||||
### 스키마
|
||||
|
||||
```typescript
|
||||
type CurriculumVisibility = 'public' | 'private' | 'system';
|
||||
|
||||
interface Curriculum {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
// 🆕 Fork Model (2025-12-19)
|
||||
ownerId: string; // 사용자 UID (소유자)
|
||||
visibility: CurriculumVisibility; // 공개 범위
|
||||
isSystem: boolean; // 시스템 커리큘럼 여부
|
||||
forkedFrom?: string; // 원본 커리큘럼 ID (Fork한 경우)
|
||||
forkCount: number; // Fork 횟수 (인기도)
|
||||
|
||||
// 커리큘럼 구성 아이템 (순서 보장)
|
||||
items: Array<{
|
||||
type: 'lesson' | 'topic';
|
||||
id: string; // lessonId or topicId
|
||||
}>;
|
||||
|
||||
// 메타데이터
|
||||
createdAt: Timestamp;
|
||||
updatedAt: Timestamp;
|
||||
createdBy: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Fork Model 워크플로우
|
||||
|
||||
1. **시스템 커리큘럼**: `isSystem: true`, `visibility: 'system'`
|
||||
2. **공개 커리큘럼**: `visibility: 'public'` (모든 사용자가 Fork 가능)
|
||||
3. **Fork 시**: 원본 `forkCount` 증가, 새 커리큘럼에 `forkedFrom` 설정
|
||||
4. **레슨도 함께 복사**: Fork 시 연결된 레슨도 사용자 소유로 복사
|
||||
|
||||
### 예시 데이터
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "curr_magic_art",
|
||||
"title": "마법의 미술 시간",
|
||||
"description": "글로 그림을 그리는 마법사가 되어보세요!",
|
||||
"ownerId": "user_abc123",
|
||||
"visibility": "public",
|
||||
"isSystem": false,
|
||||
"forkedFrom": "curr_system_sensory",
|
||||
"forkCount": 15,
|
||||
"items": [
|
||||
{ "type": "lesson", "id": "lesson_adj_01" },
|
||||
{ "type": "topic", "id": "topic_drawing_01" }
|
||||
],
|
||||
"isActive": true
|
||||
}
|
||||
```
|
||||
|
||||
### 인덱스
|
||||
- `ownerId` + `createdAt` (사용자별 커리큘럼 조회)
|
||||
- `visibility` + `forkCount` (공개 커리큘럼 인기순)
|
||||
- `isSystem` + `createdAt` (시스템 커리큘럼 조회)
|
||||
|
||||
---
|
||||
|
||||
## 10. Sticker (스티커) 🔜
|
||||
|
||||
**컬렉션**: `stickers/{stickerId}` (구현 예정)
|
||||
|
||||
@ -829,7 +920,7 @@ interface Sticker {
|
||||
|
||||
---
|
||||
|
||||
## 10. UserSticker (사용자 스티커) 🔜
|
||||
## 11. UserSticker (사용자 스티커) 🔜
|
||||
|
||||
**컬렉션**: `userStickers/{userStickerId}` (구현 예정)
|
||||
|
||||
@ -862,7 +953,7 @@ interface UserSticker {
|
||||
|
||||
---
|
||||
|
||||
## 11. WritingSession (실시간 글쓰기 모니터링) 🆕
|
||||
## 12. WritingSession (실시간 글쓰기 모니터링) 🆕
|
||||
|
||||
**데이터베이스**: Firebase Realtime Database (휘발성 데이터)
|
||||
|
||||
@ -1189,9 +1280,148 @@ service cloud.firestore {
|
||||
|
||||
---
|
||||
|
||||
## 3.9. Curriculum (커리큘럼) - Fork Model
|
||||
|
||||
**컬렉션**: `curriculums`
|
||||
|
||||
```typescript
|
||||
type CurriculumVisibility = 'public' | 'private' | 'system';
|
||||
|
||||
interface Curriculum {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
// 🆕 Fork Model (2025-12-19)
|
||||
ownerId: string; // 사용자 UID (소유자)
|
||||
visibility: CurriculumVisibility; // 공개 범위
|
||||
isSystem: boolean; // 시스템 커리큘럼 여부
|
||||
forkedFrom?: string; // 원본 커리큘럼 ID (Fork한 경우)
|
||||
forkCount: number; // Fork 횟수 (인기도)
|
||||
|
||||
// 구성 항목
|
||||
items: Array<{
|
||||
type: 'lesson';
|
||||
id: string;
|
||||
title: string;
|
||||
}>;
|
||||
|
||||
// 메타데이터
|
||||
createdAt: Timestamp;
|
||||
updatedAt: Timestamp;
|
||||
createdBy: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**설명**:
|
||||
- `ownerId`: 커리큘럼 소유자 UID
|
||||
- `visibility`: 공개 범위 (public/private/system)
|
||||
- `isSystem`: 시스템 제공 커리큘럼 여부
|
||||
- `forkedFrom`: Fork한 원본 커리큘럼 ID
|
||||
- `forkCount`: 이 커리큘럼이 Fork된 횟수
|
||||
|
||||
---
|
||||
|
||||
## 3.10. Lesson (레슨) - Fork Model
|
||||
|
||||
**컬렉션**: `lessons`
|
||||
|
||||
```typescript
|
||||
// 레슨 콘텐츠 타입
|
||||
type LessonContentType = 'theory' | 'mission' | 'quiz' | 'writing_prompt';
|
||||
type LessonVisibility = 'public' | 'private' | 'system';
|
||||
|
||||
// 1. 이론 설명 블록
|
||||
interface TheoryBlock {
|
||||
markdown: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
// 2. 미션 블록
|
||||
interface MissionBlock {
|
||||
description: string;
|
||||
items: string[];
|
||||
}
|
||||
|
||||
// 3. 퀴즈 블록
|
||||
interface QuizBlock {
|
||||
question: string;
|
||||
type: 'multiple_choice' | 'short_answer';
|
||||
options?: string[];
|
||||
answer: string | number;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
// 4. 글쓰기 프롬프트 블록
|
||||
interface WritingPromptBlock {
|
||||
prompt: string;
|
||||
guideLines: string[];
|
||||
minLength?: number;
|
||||
}
|
||||
|
||||
// 레슨 콘텐츠 아이템
|
||||
interface LessonContent {
|
||||
type: LessonContentType;
|
||||
data: TheoryBlock | MissionBlock | QuizBlock | WritingPromptBlock;
|
||||
}
|
||||
|
||||
interface Lesson {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
// 🆕 Fork Model (2025-12-19)
|
||||
ownerId: string; // 사용자 UID (소유자)
|
||||
visibility: LessonVisibility; // 공개 범위
|
||||
isSystem: boolean; // 시스템 레슨 여부
|
||||
forkedFrom?: string; // 원본 레슨 ID (Fork한 경우)
|
||||
forkCount: number; // Fork 횟수
|
||||
|
||||
// 분류
|
||||
level: number; // 1~100
|
||||
category: string; // "감각", "이야기", "감정" 등
|
||||
|
||||
// 커리큘럼 연결 (선택적)
|
||||
curriculumId?: string;
|
||||
orderIndex: number;
|
||||
|
||||
// 콘텐츠 (순서대로 렌더링)
|
||||
contents: LessonContent[];
|
||||
|
||||
// 보상
|
||||
reward: {
|
||||
experience: number;
|
||||
stickers?: string[];
|
||||
};
|
||||
|
||||
// 메타데이터
|
||||
completionCount: number;
|
||||
createdAt: Timestamp;
|
||||
updatedAt: Timestamp;
|
||||
createdBy: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**설명**:
|
||||
- `contents`: 다양한 콘텐츠 블록을 순서대로 배치 (이론 → 미션 → 퀴즈 → 글쓰기)
|
||||
- `ownerId`: 레슨 소유자 UID
|
||||
- `visibility`: 공개 범위 (public/private/system)
|
||||
- `forkedFrom`: Fork한 원본 레슨 ID
|
||||
- `completionCount`: 완료한 학생 수
|
||||
|
||||
**4가지 콘텐츠 타입**:
|
||||
1. **theory**: Markdown 기반 이론 설명
|
||||
2. **mission**: 활동 지시 (체크리스트)
|
||||
3. **quiz**: 객관식/단답형 퀴즈
|
||||
4. **writing_prompt**: 가벼운 글쓰기 주제
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [API_SPEC.md](./API_SPEC.md) - API 명세서 (35개 엔드포인트)
|
||||
- [API_SPEC.md](./API_SPEC.md) - API 명세서 (43개 엔드포인트)
|
||||
- [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - 프로젝트 구조
|
||||
- [TECH_STACK.md](./TECH_STACK.md) - 기술 스택
|
||||
- [ROADMAP.md](./ROADMAP.md) - 개발 로드맵
|
||||
|
||||
470
LESSON_UX.md
Normal file
470
LESSON_UX.md
Normal file
@ -0,0 +1,470 @@
|
||||
# 레슨 콘텐츠 타입별 UX 정의
|
||||
|
||||
이 문서는 레슨(Lesson) 시스템의 4가지 콘텐츠 타입이 **어떻게 보이고 어떻게 동작해야 하는지** 명확하게 정의합니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. Theory (이론 설명)
|
||||
|
||||
### 목적
|
||||
개념이나 지식을 가르치는 읽기 전용 콘텐츠
|
||||
|
||||
### 데이터 구조
|
||||
```typescript
|
||||
interface TheoryBlock {
|
||||
markdown: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### 렌더링 방법
|
||||
|
||||
**컴포넌트**: `TheoryRenderer.tsx`
|
||||
|
||||
**UI 구성**:
|
||||
- 마크다운을 HTML로 렌더링 (제목, 굵은 글씨, 목록 등 지원)
|
||||
- 이미지가 있으면 하단에 표시
|
||||
- 읽기 전용 (사용자 입력 없음)
|
||||
|
||||
**사용자 액션**:
|
||||
- 읽기만 함
|
||||
- 스크롤 가능
|
||||
- 액션 없음 → 자동으로 "완료" 처리
|
||||
|
||||
**예시**:
|
||||
```markdown
|
||||
## 의성어란?
|
||||
|
||||
소리를 흉내 낸 말이에요.
|
||||
|
||||
**예시:**
|
||||
- 강아지: 멍멍, 왈왈
|
||||
- 비: 주룩주룩, 후두둑
|
||||
|
||||
의성어를 사용하면 글을 읽는 사람이 소리를 상상할 수 있어요!
|
||||
```
|
||||
|
||||
**렌더링 결과**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 의성어란? │
|
||||
│ │
|
||||
│ 소리를 흉내 낸 말이에요. │
|
||||
│ │
|
||||
│ 예시: │
|
||||
│ • 강아지: 멍멍, 왈왈 │
|
||||
│ • 비: 주룩주룩, 후두둑 │
|
||||
│ │
|
||||
│ 의성어를 사용하면... │
|
||||
│ │
|
||||
│ [이미지가 있으면 여기에 표시] │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Quiz (퀴즈)
|
||||
|
||||
### 목적
|
||||
이해도를 확인하는 문제 (객관식 또는 주관식)
|
||||
|
||||
### 데이터 구조
|
||||
```typescript
|
||||
interface QuizBlock {
|
||||
question: string;
|
||||
type: 'multiple_choice' | 'short_answer';
|
||||
options?: string[]; // 객관식인 경우
|
||||
answer: string | number; // 주관식: string, 객관식: 선택지 인덱스 (0부터)
|
||||
explanation: string;
|
||||
}
|
||||
```
|
||||
|
||||
### 렌더링 방법
|
||||
|
||||
**컴포넌트**: `QuizRenderer.tsx`
|
||||
|
||||
**UI 구성**:
|
||||
1. **문제 표시**
|
||||
2. **답변 입력 영역** (타입에 따라)
|
||||
- **객관식**: 라디오 버튼
|
||||
- **주관식**: 텍스트 입력
|
||||
3. **제출 버튼**
|
||||
4. **결과 표시** (제출 후)
|
||||
- 정답/오답 표시
|
||||
- 해설 표시
|
||||
|
||||
### 사용자 액션
|
||||
|
||||
**객관식 플로우**:
|
||||
1. 문제 읽기
|
||||
2. 선택지 중 하나 선택 (Radio button)
|
||||
3. "정답 확인" 버튼 클릭
|
||||
4. 결과 표시:
|
||||
- ✅ **정답**: "정답입니다!" + 해설
|
||||
- ❌ **오답**: "아쉬워요! 정답은 X번입니다" + 해설
|
||||
5. 다음으로 진행 가능
|
||||
|
||||
**주관식 플로우**:
|
||||
1. 문제 읽기
|
||||
2. 텍스트 입력
|
||||
3. "정답 확인" 버튼 클릭
|
||||
4. 정답과 비교 (대소문자 무시, 공백 제거 후 비교)
|
||||
5. 결과 표시
|
||||
6. 다음으로 진행 가능
|
||||
|
||||
### UI 예시
|
||||
|
||||
**객관식 (제출 전)**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 📝 퀴즈 │
|
||||
│ │
|
||||
│ 다음 중 의성어가 아닌 것은? │
|
||||
│ │
|
||||
│ ○ 졸졸 │
|
||||
│ ○ 빨갛다 │
|
||||
│ ○ 윙윙 │
|
||||
│ ○ 톡톡 │
|
||||
│ │
|
||||
│ [정답 확인] │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**객관식 (정답 제출 후)**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 📝 퀴즈 │
|
||||
│ │
|
||||
│ 다음 중 의성어가 아닌 것은? │
|
||||
│ │
|
||||
│ ○ 졸졸 │
|
||||
│ ● 빨갛다 ❌ (선택) │
|
||||
│ ○ 윙윙 │
|
||||
│ ○ 톡톡 │
|
||||
│ │
|
||||
│ ❌ 아쉬워요! │
|
||||
│ 정답은 2번 "빨갛다" 입니다. │
|
||||
│ │
|
||||
│ 💡 '빨갛다'는 색깔을 나타내는 │
|
||||
│ 말이에요. │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**주관식**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 📝 퀴즈 │
|
||||
│ │
|
||||
│ 비유란 무엇일까요? │
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ │
|
||||
│ [정답 확인] │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Mission (미션)
|
||||
|
||||
### 목적
|
||||
학생이 직접 찾거나 수행해야 할 체크리스트
|
||||
|
||||
### 데이터 구조
|
||||
```typescript
|
||||
interface MissionBlock {
|
||||
description: string;
|
||||
items: string[]; // 체크할 항목들
|
||||
}
|
||||
```
|
||||
|
||||
### 렌더링 방법
|
||||
|
||||
**컴포넌트**: `MissionRenderer.tsx`
|
||||
|
||||
**UI 구성**:
|
||||
1. 미션 설명
|
||||
2. 체크박스 리스트
|
||||
3. 완료 상태 표시
|
||||
|
||||
### 사용자 액션
|
||||
|
||||
1. 미션 설명 읽기
|
||||
2. 각 항목 수행 후 체크
|
||||
3. 모든 항목 체크 시 자동 완료
|
||||
|
||||
**중요**: 실제로 "수행했는지"는 검증 불가능 → 신뢰 기반 체크
|
||||
|
||||
### UI 예시
|
||||
|
||||
**초기 상태**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 🎯 미션 │
|
||||
│ │
|
||||
│ 소리 탐정이 되어보자! │
|
||||
│ │
|
||||
│ ☐ 지금 주변에서 들리는 │
|
||||
│ 소리 3가지 찾기 │
|
||||
│ │
|
||||
│ ☐ 각 소리를 의성어로 │
|
||||
│ 표현해보기 │
|
||||
│ │
|
||||
│ 진행률: 0 / 2 │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**일부 완료**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 🎯 미션 │
|
||||
│ │
|
||||
│ 소리 탐정이 되어보자! │
|
||||
│ │
|
||||
│ ☑ 지금 주변에서 들리는 │
|
||||
│ 소리 3가지 찾기 │
|
||||
│ │
|
||||
│ ☐ 각 소리를 의성어로 │
|
||||
│ 표현해보기 │
|
||||
│ │
|
||||
│ 진행률: 1 / 2 │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**전체 완료**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 🎯 미션 │
|
||||
│ │
|
||||
│ 소리 탐정이 되어보자! │
|
||||
│ │
|
||||
│ ☑ 지금 주변에서 들리는 │
|
||||
│ 소리 3가지 찾기 │
|
||||
│ │
|
||||
│ ☑ 각 소리를 의성어로 │
|
||||
│ 표현해보기 │
|
||||
│ │
|
||||
│ ✅ 완료했어요! │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. WritingPrompt (글쓰기 프롬프트)
|
||||
|
||||
### 목적
|
||||
레슨 안에서 간단하게 글을 써보는 연습
|
||||
|
||||
**주의**: 본격적인 글쓰기는 `/write` 페이지의 Topic 사용. WritingPrompt는 배운 내용을 바로 적용해보는 **짧은 연습**용.
|
||||
|
||||
### 데이터 구조
|
||||
```typescript
|
||||
interface WritingPromptBlock {
|
||||
prompt: string;
|
||||
guideLines: string[];
|
||||
minLength?: number; // 최소 글자 수
|
||||
}
|
||||
```
|
||||
|
||||
### 렌더링 방법
|
||||
|
||||
**컴포넌트**: `WritingPromptRenderer.tsx`
|
||||
|
||||
**UI 구성**:
|
||||
1. 글쓰기 주제
|
||||
2. 가이드라인 목록
|
||||
3. 텍스트 에디터 (간단한 Textarea)
|
||||
4. 글자 수 카운터
|
||||
5. 제출 버튼
|
||||
|
||||
### 사용자 액션
|
||||
|
||||
1. 주제와 가이드라인 읽기
|
||||
2. 텍스트 에디터에 글 작성
|
||||
3. 최소 글자 수 도달 확인
|
||||
4. "제출" 버튼 클릭
|
||||
5. 완료 처리
|
||||
|
||||
**저장 위치**:
|
||||
- LocalStorage 또는 Realtime DB에 임시 저장
|
||||
- 나중에 `/write` 페이지에서 이어 쓸 수 있도록 연동 가능 (선택)
|
||||
|
||||
### UI 예시
|
||||
|
||||
**초기 상태**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ ✍️ 글쓰기 │
|
||||
│ │
|
||||
│ 소리가 가득한 아침 │
|
||||
│ │
|
||||
│ 가이드라인: │
|
||||
│ • 오늘 아침에 들은 소리들을 │
|
||||
│ 떠올려보세요 │
|
||||
│ • 의성어를 3개 이상 사용해보세요 │
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ │
|
||||
│ 0 / 100자 (최소 100자) │
|
||||
│ │
|
||||
│ [제출] (비활성화) │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**작성 중**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ ✍️ 글쓰기 │
|
||||
│ │
|
||||
│ 소리가 가득한 아침 │
|
||||
│ │
|
||||
│ 가이드라인: │
|
||||
│ • 오늘 아침에 들은 소리들을... │
|
||||
│ • 의성어를 3개 이상... │
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ 아침에 일어나니 새들이 │ │
|
||||
│ │ 짹짹거리는 소리가 들렸다. │ │
|
||||
│ │ 부엌에서는 엄마가 후라이팬에 │ │
|
||||
│ │ 계란을 지글지글 굽고 계셨다. │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ │
|
||||
│ 78 / 100자 (거의 다 왔어요!) │
|
||||
│ │
|
||||
│ [제출] (비활성화) │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**제출 가능**:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ ✍️ 글쓰기 │
|
||||
│ │
|
||||
│ 소리가 가득한 아침 │
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ 아침에 일어나니 새들이 │ │
|
||||
│ │ 짹짹거리는 소리가 들렸다... │ │
|
||||
│ │ (글 내용 계속) │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ │
|
||||
│ 125 / 100자 ✅ │
|
||||
│ │
|
||||
│ [제출] │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 레슨 전체 플로우
|
||||
|
||||
### LessonViewer 컴포넌트 구조
|
||||
|
||||
```typescript
|
||||
<LessonViewer lessonId={lessonId}>
|
||||
{/* 헤더 */}
|
||||
<LessonHeader
|
||||
title="1강. 소리를 글로 표현하기"
|
||||
progress={currentIndex / totalContents}
|
||||
/>
|
||||
|
||||
{/* 현재 콘텐츠 블록 */}
|
||||
<ContentRenderer
|
||||
content={lesson.contents[currentIndex]}
|
||||
onComplete={() => handleComplete()}
|
||||
/>
|
||||
|
||||
{/* 네비게이션 */}
|
||||
<LessonNavigation
|
||||
onNext={() => nextContent()}
|
||||
onPrevious={() => previousContent()}
|
||||
canNext={currentContentCompleted}
|
||||
isLastContent={currentIndex === totalContents - 1}
|
||||
/>
|
||||
</LessonViewer>
|
||||
```
|
||||
|
||||
### 진행 상태 관리
|
||||
|
||||
**로컬 상태**:
|
||||
- `currentIndex`: 현재 보고 있는 콘텐츠 인덱스
|
||||
- `completedBlocks`: 완료한 블록들의 Set
|
||||
|
||||
**서버 저장** (나중에 구현):
|
||||
- UserLessonProgress 모델
|
||||
- 레슨 완료 시 경험치 및 스티커 보상
|
||||
|
||||
### 완료 조건
|
||||
|
||||
| 타입 | 완료 조건 |
|
||||
|------|-----------|
|
||||
| Theory | 자동 완료 (읽기만) |
|
||||
| Quiz | 정답 확인 후 |
|
||||
| Mission | 모든 항목 체크 |
|
||||
| WritingPrompt | 최소 글자 수 이상 + 제출 |
|
||||
|
||||
---
|
||||
|
||||
## 구현 우선순위
|
||||
|
||||
### Phase 1: 기본 렌더러
|
||||
1. TheoryRenderer (가장 간단)
|
||||
2. MissionRenderer
|
||||
3. QuizRenderer (주관식만)
|
||||
4. WritingPromptRenderer
|
||||
|
||||
### Phase 2: 고급 기능
|
||||
1. QuizRenderer 객관식 지원
|
||||
2. 마크다운 렌더링 (react-markdown)
|
||||
3. 진행 상태 저장
|
||||
|
||||
### Phase 3: UX 개선
|
||||
1. 애니메이션 (콘텐츠 전환)
|
||||
2. 로딩 상태
|
||||
3. 에러 처리
|
||||
|
||||
---
|
||||
|
||||
## 디자인 토큰
|
||||
|
||||
**색상**:
|
||||
- Theory: `blue` (파란색)
|
||||
- Quiz: `purple` (보라색)
|
||||
- Mission: `orange` (주황색)
|
||||
- WritingPrompt: `green` (초록색)
|
||||
|
||||
**아이콘**:
|
||||
- Theory: `HiOutlineDocumentText`
|
||||
- Quiz: `HiOutlineQuestionMarkCircle`
|
||||
- Mission: `HiOutlineFlag`
|
||||
- WritingPrompt: `HiOutlinePencilAlt`
|
||||
|
||||
---
|
||||
|
||||
## 참고 사항
|
||||
|
||||
### 접근성
|
||||
- 모든 인터랙티브 요소에 키보드 접근 가능
|
||||
- 스크린 리더 지원 (aria-label)
|
||||
- 명확한 포커스 표시
|
||||
|
||||
### 반응형
|
||||
- 모바일 우선 디자인
|
||||
- 터치 친화적 버튼 크기 (최소 44x44px)
|
||||
|
||||
### 성능
|
||||
- 마크다운 렌더링은 메모이제이션
|
||||
- 긴 콘텐츠는 가상화 고려
|
||||
|
||||
---
|
||||
|
||||
© 2024 BlueNovaLab. All rights reserved.
|
||||
@ -1,10 +1,69 @@
|
||||
# 라온누리 - 프로젝트 구조
|
||||
|
||||
> 최종 업데이트: 2025-12-16 (이미지 생성 가능 여부 확인 API, 계획된 기능 문서)
|
||||
> 최종 업데이트: 2025-12-23 (이미지 4:3 비율 표준화)
|
||||
|
||||
초등학생을 위한 창작 글쓰기 교육 플랫폼
|
||||
|
||||
**최신 업데이트** (2025-12-16):
|
||||
**최신 업데이트** (2025-12-23):
|
||||
- 🖼️ **이미지 4:3 비율 표준화**
|
||||
- **AI 이미지 생성**: 16:9 → 4:3 비율 변경 (일관된 이미지 크기)
|
||||
- **ImageCropper 컴포넌트**: react-cropper 기반 이미지 크롭 기능 (4:3 고정)
|
||||
- **필수 크롭 단계**: 이미지 업로드 시 자동으로 크롭 모달 표시
|
||||
- **조작 기능**: 회전, 확대/축소, 리셋 버튼 제공
|
||||
- **최소 크기**: 400x300 (품질 보장)
|
||||
- **최대 크기**: 1600x1200 (4:3 비율, 1920x1080에서 변경)
|
||||
- **Dialog 통합**: Chakra UI v3 Dialog로 구현
|
||||
- **이벤트 버블링 해결**: ImageCropper를 드롭존 외부로 분리
|
||||
- **다국어 지원**: ko/en/ja 크롭 관련 메시지 추가 (7개 키)
|
||||
- **후위 호환성**: 기존 16:9 이미지는 그대로 표시
|
||||
|
||||
**업데이트** (2025-12-19):
|
||||
- 🔀 **Curriculum Fork Model 구현**
|
||||
- **소유권 모델 전환**: 팀 기반 → 사용자 소유(ownerId) + 공개 범위(visibility) 기반
|
||||
- **Fork 기능**: 공개/시스템 커리큘럼을 사용자 라이브러리로 복사
|
||||
- **새로운 필드**: ownerId, visibility, forkedFrom, isSystem, forkCount
|
||||
- **CurriculumFormModal**: 커리큘럼 생성/수정 시 공개 범위 설정 지원
|
||||
- **API 변경**: /api/curriculum, /api/lesson 쿼리 파라미터 및 응답 형식 변경
|
||||
- **Navbar 메뉴**: 'Curriculum' 메뉴 추가
|
||||
- **번역 변경**: 'Curriculum' → 'Learning Course'
|
||||
- **문서 추가**: docs/CURRICULUM_FORK_MODEL.md
|
||||
- **WritingOwnerActions**: 글 작성자 수정/이미지/인터랙션 메뉴 컴포넌트
|
||||
- 📌 **글 상세 페이지 Sticky Header**
|
||||
- **WritingDetailHeader.tsx**: 스크롤 시 상단에 고정되는 헤더 컴포넌트
|
||||
- **표시 정보**: 글 제목, 작성자 정보, 이미지 포함 여부
|
||||
- **WritingOwnerActions 재활용**: 작성자 본인일 때 수정 메뉴 표시
|
||||
- 🔧 **AI 이미지 생성 에러 처리 개선**
|
||||
- **에러 코드별 메시지**: plan_not_supported, limit_exceeded, ai_disabled 등
|
||||
- **다국어 지원**: 한국어/영어/일본어 메시지 추가
|
||||
- ⬆️ **의존성 업데이트**
|
||||
- **@ark-ui/react, @zag-js**: v1.29.1 → v1.31.1
|
||||
|
||||
**업데이트** (2025-12-19 이전):
|
||||
- 📚 **커리큘럼 UX 개편**
|
||||
- **미리보기 모달**: CurriculumPreviewModal (목록 클릭 시 페이지 이동 대신 모달 표시)
|
||||
- **상세 블록 정보**: 레슨별 콘텐츠 블록 상세 표시 (Accordion UI)
|
||||
- **4가지 블록 타입 표시**: theory(이론), quiz(퀴즈), mission(미션), writing_prompt(글쓰기)
|
||||
- **팀 선택 모달**: TeamSelectModal (커리큘럼 가져오기 시 대상 팀 선택)
|
||||
- **커리큘럼 복사 API**: POST /api/curriculum/[curriculumId]/copy (레슨 포함 전체 복사)
|
||||
- **내 커리큘럼 페이지**: /my-curriculums (MyCurriculumsDashboard)
|
||||
- **커리큘럼 수정 모달**: CurriculumEditModal (제목/설명 수정)
|
||||
- **권한 분리**: 시스템/타팀 커리큘럼은 가져오기만, 내 팀 커리큘럼만 편집 가능
|
||||
- **스켈레톤 로딩**: 미리보기 로딩 시 레슨 구조 형태의 Skeleton UI
|
||||
- **다국어 지원**: preview.blockType namespace (theory/quiz/mission/writing_prompt, ko/en/ja)
|
||||
|
||||
**업데이트** (2025-12-17):
|
||||
- 📚 **커리큘럼/레슨 학습 시스템**
|
||||
- **Sandbox 모델**: 시스템 제공 + 팀 자체 생성 커리큘럼
|
||||
- **4가지 콘텐츠 타입**: theory (이론), quiz (퀴즈), mission (미션), writing_prompt (글쓰기)
|
||||
- **API 엔드포인트**: GET/POST/PUT/DELETE /api/curriculum, /api/lesson
|
||||
- **Manager 추가**: CurriculumManager, LessonManager (캐싱 지원)
|
||||
- **초기화 스크립트**: scripts/seed-curriculums.ts (3개 시스템 커리큘럼, 15개 레슨)
|
||||
- **컴포넌트**: CurriculumDashboard, CurriculumList, CurriculumCreateModal, CurriculumDetailView, LessonEditor
|
||||
- **페이지**: /curriculum (목록), /curriculum/[curriculumId] (상세)
|
||||
- **타입 정의**: Curriculum, Lesson, LessonContent, LessonContentType
|
||||
- **다국어 지원**: curriculum, lesson namespace (ko/en/ja, 70+ 키)
|
||||
|
||||
**업데이트** (2025-12-16):
|
||||
- 🖼️ **이미지 생성 가능 여부 확인 API**
|
||||
- **POST /api/check-image-generation**: 글 ID 기반 이미지 생성 권한 확인
|
||||
- **팀/개인 분기 처리**: 팀 글쓰기 → 팀 AI 설정 + 월간/일일 제한, 개인 글쓰기 → 개인 플랜 + 월간 제한
|
||||
@ -658,7 +717,8 @@
|
||||
| **VisibilitySelector** | `VisibilitySelector.tsx` | 🆕 **공개 범위 선택** (PUBLIC/TEAM/PRIVATE, RadioCard 기반) | ✅ 완료 |
|
||||
| **VisibilityBadge** | `VisibilityBadge.tsx` | 🆕 **공개 범위 배지** (아이콘 + 라벨, 색상별 구분) | ✅ 완료 |
|
||||
| **InteractiveImageViewer** | `InteractiveImageViewer.tsx` | 🆕 **인터랙티브 이미지 뷰어** (왜곡 효과, 애니메이션, 오디오 반응) | ✅ 완료 |
|
||||
| **ImageDropzone** | `ImageDropzone.tsx` | 🆕 **이미지 드롭존** (드래그앤드롭 업로드, 미리보기, IndexedDB 저장) | ✅ 완료 |
|
||||
| **ImageDropzone** | `ImageDropzone.tsx` | 🆕 **이미지 드롭존** (드래그앤드롭 업로드, 미리보기, IndexedDB 저장, **4:3 크롭 통합**) | ✅ 완료 |
|
||||
| **ImageCropper** | `ImageCropper.tsx` | 🆕 **이미지 크롭 모달** (react-cropper 기반, 4:3 고정 비율, 회전/확대/축소, 최소 400x300) | ✅ 완료 |
|
||||
| **ImageFirstLayout** | `ImageFirstLayout.tsx` | 🆕 **이미지 우선 레이아웃** (이미지+에디터 2컬럼, 반응형) | ✅ 완료 |
|
||||
| **GenerateImageDialog** | `GenerateImageDialog.tsx` | 🆕 **AI 이미지 생성 Dialog** (4단계 플로우: 장면 추출 → 장면 선택 → 이미지 생성 → 결과 표시) | ✅ 완료 |
|
||||
| ~~**ScoreDisplay**~~ | ~~`ScoreDisplay.tsx`~~ | ~~실시간 피드백 점수 표시~~ | ❌ 삭제됨 (하이라이트로 대체) |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 라온누리 - 개발 로드맵
|
||||
|
||||
> 최종 업데이트: 2025-12-16 (이미지 생성 가능 여부 확인 API, 계획된 기능 문서화)
|
||||
> 최종 업데이트: 2025-12-23 (이미지 4:3 비율 표준화)
|
||||
|
||||
초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획
|
||||
|
||||
@ -98,10 +98,12 @@
|
||||
| **서비스 레이어 i18n 유틸리티** | **src/utils/i18n.ts 생성 (React 훅 없이 번역 사용), detectLocale() 함수 (URL path 우선 → navigator.language fallback), t() 함수 (nested key 지원, 파라미터 치환), messages/*.json import, firebaseAuth.ts 전체 에러 메시지 다국어 처리 (getErrorMessage, loginAsUser, linkEmailPassword, linkGoogleAccount), convertFirebaseUser 기본 이름 다국어, 번역 추가 (errors.auth namespace 11개 + errors.team namespace 6개, ko/en/ja)** | **2025-11-18** |
|
||||
| **AI Delta 전송 로직 개선** | **diff-match-patch 라이브러리 도입, 정확한 diff 계산 (앞/중간/뒤 수정 모두 감지), 5자 미만 변경 누적 (previousText 유지), 완화된 기준 (80% 미만, 200자 미만), 9개 테스트 케이스 통과 (뒷부분 추가, 중간 수정, 앞부분 추가, 삭제, 대량 변경, 누적 변경), skipped 응답 처리 (클라이언트에서 prev 유지), analyze-text API 수정** | **2025-11-19** |
|
||||
| **수정 모드 주제 변경 차단** | **TopicSelector `readonly` prop 추가, 수정 모드에서 Select 드롭다운 완전 숨김, "현재 주제" 라벨만 표시, 주제 생성 Dialog 숨김, 데이터 무결성 보장 (템플릿 덮어쓰기 방지, AI 설정 혼선 방지, 실시간 모니터링 충돌 방지), 다국어 지원 (readonlyNote/currentTopic 키 추가, ko/en/ja)** | **2025-11-21** |
|
||||
| **이미지 4:3 비율 표준화** | **react-cropper + cropperjs 설치 (v2.3.3), AI 이미지 생성 16:9 → 4:3 변경 (imagenService.ts, /api/generate-image), ImageCropper 컴포넌트 생성 (Chakra UI v3 Dialog, 회전/확대/축소/리셋, 최소 400x300, 4:3 고정), ImageDropzone 크롭 통합 (필수 크롭 단계, enableCropping prop, 이벤트 버블링 해결), WritingManager resizeImage 최대 크기 변경 (1920x1080 → 1600x1200), 다국어 지원 (imageUpload.crop namespace 7개 키, ko/en/ja), CSS 통합 (layout.tsx에 cropperjs CSS), 후위 호환성 보장 (기존 16:9 이미지 유지), 타입 체크 통과** | **2025-12-23** |
|
||||
| **수정 모드 UX 대폭 개선** | **Sticky 헤더바 추가 (오렌지/앰버 그라데이션 배경, 상단 고정 z-index:100, blur backdrop, Writing ID 표시), 강조된 테두리 (주제 선택 + 글쓰기 영역 2px solid 오렌지), 섀도우 효과 (글쓰기 영역 오렌지 글로우, 라이트/다크 모드 대응), 색상 시스템 정립 (Light: #FFB366/#FF9800/#E65100, Dark: #8B6B47/#C4A572), 기존 단순 배지 제거, 전체 UI 테마 일관성, globals.css에 pulse/shimmer 애니메이션 추가 (후에 제거됨)** | **2025-11-21** |
|
||||
| **글쓰기 페이지 UI 정리** | **"새 글쓰기" 버튼 제거 (불필요한 기능, 실수 방지, handleNewDraft 함수 제거, LuFilePlus import 제거), 상단 버튼 영역 단순화 ("저장된 글조각" 버튼만 유지), 미사용 i18n 키 정리 (newWriting/discardConfirm 제거, ko/en/ja)** | **2025-11-21** |
|
||||
| **Draft 클라우드 동기화** | **localStorage + Realtime DB 하이브리드 저장, syncStatus 필드 추가 ('local'\|'synced'\|'syncing'), DraftManager 확장 (syncToCloud, loadDraftsFromCloud, mergeDrafts, deleteDraftFromCloud), SavedDraftsDialog 배지 표시 (🟢동기화됨/🟡동기화 중/⚪기기만), write/page.tsx mergeDrafts 호출 (로그인 시), database.rules.json drafts 규칙 추가 (본인만 읽기/쓰기), 기기 간 동기화 (학교 ↔ 집), updatedAt 비교로 최신 버전 선택** | **2025-11-19** |
|
||||
| **팀 코드 예약 반환 로직** | **generate-code API 수정 (previousCode 파라미터 추가), 새 코드 받기 시 이전 예약 자동 해제, team/create page 중복 호출 방지 (isGeneratingCode 체크), 팀 생성 실패 시에도 예약 해제 (catch 블록), GenerateTeamCodeRequest 타입 확장, TeamManager.generateUniqueTeamCode previousCode 파라미터** | **2025-11-19** |
|
||||
| **커리큘럼/레슨 학습 시스템** | **Sandbox 모델 (시스템 제공 + 팀 자체 생성), 4가지 콘텐츠 타입 (theory/quiz/mission/writing_prompt), Curriculum/Lesson Firestore 컬렉션 생성, GET/POST/PUT/DELETE API 엔드포인트 8개, CurriculumManager/LessonManager (싱글톤, 캐싱 지원, 자동 무효화), seed-curriculums.ts 초기화 스크립트 (3개 시스템 커리큘럼, 15개 레슨: 감각 글쓰기/이야기 만들기/감정 표현), /curriculum 목록 페이지, /curriculum/[curriculumId] 상세 페이지, CurriculumDashboard/List/CreateModal/DetailView/LessonEditor 컴포넌트, 레슨 CRUD (생성/수정/삭제), contents 동적 편집 (이론/퀴즈/미션 블록), ownerType 기반 권한 관리 (system/team), 다국어 지원 (curriculum/lesson namespace, 70+ 키, ko/en/ja), TypeScript 타입 정의 (Curriculum/Lesson/LessonContent/LessonContentType), dotenv 설치 (seed 스크립트용)** | **2025-12-17** |
|
||||
| **Firebase Cloud Functions Phase 1** | **functions/ 프로젝트 초기화 (Node.js 22, v2 API), 파일 분리 구조 (index.ts export만, 로직 별도 파일), cleanupExpiredReservations (매 시간, 팀 코드 예약 정리), cleanupExpiredDrafts (매일 새벽 3시, 180일 이상 drafts 정리), onTeamDeleted (Firestore Trigger, cascade 삭제), onWritingCreated (Firestore Trigger, 추후 자동 분석), 한글 로그, asia-northeast1 리전, firebase.json predeploy 수정 (lint 제거), 4개 함수 배포 완료** | **2025-11-19** |
|
||||
| **주제 생성 Dialog 통합** | **CreateTeamTopicDialog 삭제 (674줄), CreateTopicDialog로 통합 (개인/팀 공용), TopicFormData export (데이터 반환 방식), onSubmit 콜백 패턴 (부모가 topicManager 호출), team.manage.teamTopics.dialog 번역 키 제거 (30개), topicSelector.createSuccess 추가, 652줄 코드 감소, 관심사 분리 (Dialog는 UI만, 비즈니스 로직은 부모)** | **2025-11-21** |
|
||||
| **홈 페이지 모듈화** | **HeroSection/QuickActionCard/QuickActionsGrid/ViewAllWritingsCard/EmptyStateCard/RecentActivitySection 컴포넌트 분리 (src/components/home/), 580줄 → 223줄 (62% 감소), Semantic token 적극 활용 (bg/fg/border), Conditional style 객체 최소화, 재사용성 및 유지보수성 향상, JSX 주석 한글화** | **2025-11-21** |
|
||||
@ -153,6 +155,11 @@
|
||||
| **팀 보안 설정 통합** | **TeamSecuritySettingsDialog에 공개설정 통합 (isPublic 스위치, allowPublicWritings, description, coverImage), TeamInfoCard에 검색가능/불가능 태그 추가 (FaGlobe/FaEyeSlash), TeamPublicSettings 제거, SecurityLevelSelector searchable 속성 추가, 다국어 지원 (searchable/notSearchable ko/en/ja)** | **2025-12-12** |
|
||||
| **이미지 생성 가능 여부 확인 API** | **POST /api/check-image-generation (팀/개인 글쓰기 분기 처리), 5가지 비활성화 사유 (PLAN_NOT_SUPPORTED, LIMIT_EXCEEDED, TEAM_AI_DISABLED, TEAM_LIMIT_EXCEEDED, DAILY_LIMIT_EXCEEDED), WritingManager.checkImageGenerationAvailability() 메서드 추가, CheckImageGenerationRequest/Result/DisableReason 타입, 다국어 지원 (imageUpload.disabledReasons namespace ko/en/ja)** | **2025-12-16** |
|
||||
| **계획된 기능 문서화** | **plannedFeature/ 디렉토리 신규 생성, 우선순위별 분류 (high/medium/low-priority.md), implementation-guide.md 구현 가이드, 주요 기능 (부모님 대시보드, 선생님 첨삭 시스템, 글 포트폴리오, 협업 글쓰기, 개인화 글감 AI, 독후감 템플릿, 음성 녹음, PDF 내보내기, 배지/업적 시스템)** | **2025-12-16** |
|
||||
| **커리큘럼 UX 개편** | **CurriculumPreviewModal (목록 클릭→모달로 상세 표시), 레슨별 콘텐츠 블록 상세 표시 (Accordion UI, theory/quiz/mission/writing_prompt), TeamSelectModal (커리큘럼 가져오기 시 팀 선택), POST /api/curriculum/[curriculumId]/copy (레슨 포함 전체 복사), /my-curriculums 페이지 (MyCurriculumsDashboard), CurriculumEditModal (제목/설명 수정), 권한 분리 (시스템/타팀→가져오기만, 내팀→편집 가능), 스켈레톤 로딩 UI, 다국어 지원 (preview.blockType namespace ko/en/ja)** | **2025-12-19** |
|
||||
| **Curriculum Fork Model 구현** | **커리큘럼 소유권 모델을 Fork 기반으로 전환, Curriculum/Lesson 타입에 ownerId/visibility/forkedFrom/isSystem/forkCount 필드 추가, CurriculumFormModal 컴포넌트 (공개 범위 설정), CurriculumPreviewModal 'Import'→'Fork' 변경, 팀 선택 모달 제거 (직접 생성/Fork 로직), navbar에 'Curriculum' 메뉴 추가, 번역 'Curriculum'→'Learning Course', docs/CURRICULUM_FORK_MODEL.md 문서 추가, firestore.indexes.json Fork 인덱스 추가, WritingOwnerActions 컴포넌트 (글 작성자 수정/이미지/인터랙션 메뉴)** | **2025-12-19** |
|
||||
| **글 상세 페이지 Sticky Header** | **WritingDetailHeader.tsx 컴포넌트 추가, 스크롤 시 상단에 고정되는 헤더 기능, 글 제목/작성자 정보/이미지 포함 여부 표시, WritingOwnerActions 컴포넌트 재활용, 기존 헤더 관련 코드 제거 및 구조 변경** | **2025-12-19** |
|
||||
| **AI 이미지 생성 에러 처리 개선** | **에러 코드별 토스트 메시지 처리 로직 개선, plan_not_supported/limit_exceeded/ai_disabled 등 에러 코드 메시지 추가, 에러 발생 시 'selecting' 단계로 되돌아가는 로직 유지, 다국어 지원 (ko/en/ja)** | **2025-12-19** |
|
||||
| **의존성 업데이트** | **@ark-ui/react 및 @zag-js 패키지 버전 업데이트 (v1.29.1 → v1.31.1)** | **2025-12-19** |
|
||||
|
||||
### 🚧 진행 중
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 라온누리 - 기술 스택 및 개발 환경
|
||||
|
||||
> 최종 업데이트: 2025-12-16 (이미지 생성 권한 확인 API)
|
||||
> 최종 업데이트: 2025-12-19 (Curriculum Fork Model + 의존성 업데이트)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user