회기 보고서 — 회기 템플릿 보고서 항목 추가 및 보고서 페이지 신설
마지막 업데이트 2026-07-22
TL;DR
회기 템플릿(LessonTemplate)에 "회기 보고서" 필드(sessionReport)를 추가했다. 회기마다 오늘 수업에서는 · 이번 회기에서 연습한 대화 기술 · 함께 해볼 활동 3개 항목을 회기 템플릿 생성/수정 화면에서 입력·저장한다(1부).
이어서 회기 보고서 페이지(/main/report)를 신설했다. 학습플랜(Curriculum)을 선택하면 그 학습플랜에 묶인 회기들의 보고서 항목을 표로 모아 보여준다. 페이지는 admin/developer 역할만 접근 가능하다(2부).
데이터 관계 한눈에 보기
Curriculum (학습플랜)
└─ items: ContentItem[] ← 회기 목록 (순서 있는 배열)
└─ ContentItem.id === LessonTemplate.id (값 복사 참조 · 역참조 없음)
LessonTemplate (회기 템플릿)
├─ activityTemplates: string[] ← 활동 템플릿 id
├─ jumpConfigs ← 활동 간 분기
└─ sessionReport ★신규 ← { todayLesson, conversationSkills, homeActivity }
보고서 페이지(/main/report)
학습플랜 선택 → items 순회 → item.id로 LessonTemplate 매칭 → sessionReport 표로 출력
참조는 단방향이다. 학습플랜이 items[].id로 회기 템플릿을 가리킬 뿐, 회기 템플릿에는 "어느 학습플랜에 속하는지"를 가리키는 필드가 없다. 보고서 페이지는 학습플랜을 기준으로 회기 템플릿을 역으로 끌어와 합친다.
회기 템플릿에 회기 보고서(sessionReport) 항목 추가
회기 템플릿 생성·수정 화면에서 3개 보고서 항목을 입력·저장한다.
1.1 타입 — SessionReport
apps/web/types/db/lesson-template.types.ts에 SessionReport 인터페이스를 신설하고 LessonTemplate에 선택 필드로 붙였다.
export interface SessionReport {
todayLesson: string; // 오늘 수업에서는
conversationSkills: string; // 이번 회기에서 연습한 대화 기술
homeActivity: string; // 함께 해볼 활동
}
export interface LessonTemplate {
id: string;
title: string;
activityTemplates: string[];
jumpConfigs?: Record<string, JumpConfig>;
sessionReport?: SessionReport; // ★신규
createdAt: number;
updatedAt: number;
}
필드를 optional로 둔 이유: 이미 저장된 기존 회기 템플릿에는 sessionReport가 없으므로, 폼/조회 양쪽에서 빈 값으로 폴백 처리한다.
1.2 입력 UI — 회기 템플릿 폼
lesson-template-form.tsx의 제목과 활동 템플릿 사이에 "회기 보고서" 섹션을 넣었다. 3개 textarea로 구성된다.
| UI 라벨 | 필드 | placeholder |
|---|---|---|
| 오늘 수업에서는 | todayLesson | 오늘 수업에서 한 내용을 입력하세요 |
| 이번 회기에서 연습한 대화 기술 | conversationSkills | 연습한 대화 기술을 입력하세요 |
| 함께 해볼 활동 | homeActivity | 가정에서 함께 해볼 활동을 입력하세요 |
상태는 sessionReport 객체 하나로 관리하며, 기존 템플릿 로드 시 template.sessionReport || {빈 값}으로 초기화하고 신규 생성 시 resetForm()에서 빈 값으로 리셋한다. 제출 시 lessonTemplate 객체에 그대로 포함한다.
1.3 저장 경로 — API · DynamoDB
저장 테이블은 ppi-lesson-template-{STAGE}다.
| 동작 | 경로 | 처리 |
|---|---|---|
| 생성 | POST /api/lesson-templates | body.sessionReport를 객체에 포함 → setLessonTemplate(PutCommand, 객체 전체 저장) |
| 수정 | PUT /api/lesson-templates/[id] | body.sessionReport ?? existing.sessionReport 전달 |
| DB 갱신 | updateLessonTemplate (db-queries.ts) | UpdateExpression에 sessionReport = :sessionReport 추가 |
함정 — UpdateExpression 누락 주의. 생성은 PutCommand라 객체 전체가 저장되지만, 수정은 updateLessonTemplate의 set 구문에 명시한 필드만 반영된다. sessionReport를 UpdateExpression에 넣지 않으면 폼에서 입력해도 수정 시 DB에 반영되지 않는다. 값이 없을 때를 대비해 빈 객체 폴백(:sessionReport = 빈 3필드)도 함께 넣었다.
회기 보고서 페이지 신설 (/main/report)
학습플랜 선택 → 회기별 보고서 항목을 표로 조회. admin/developer 전용.
2.1 네비게이션 메뉴
navigation.tsx의 adminOrDeveloperLinks에서 학습플랜 바로 우측에 { href: "/main/report", label: "보고서" }를 추가했다. 학습플랜 메뉴 자체가 admin/developer 전용 배열에만 있으므로 진행자(manager)에게는 보고서 메뉴도 노출되지 않는다.
2.2 페이지 구성 — report-page.tsx
- 학습플랜 선택 —
useCurriculums로 불러온 학습플랜을FilterableSelect드롭다운으로 선택(회기 템플릿 페이지와 동일 UI). 드롭다운은h-10고정 높이로 디자인 가이드에 맞춤. - 회기별 표시 — 선택한 학습플랜의
items[]를 순회하며 각item.id로 회기 템플릿(useLessonTemplates)을 매칭해 그 템플릿의sessionReport를 표로 출력. - 컬럼 — 회차 · 회기 제목 · 오늘 수업에서는 · 이번 회기에서 연습한 대화 기술 · 함께 해볼 활동.
엣지 케이스
| 상황 | 표시 |
|---|---|
| 학습플랜 미선택 | "학습플랜을 선택하세요" (로딩 중엔 로딩 메시지) |
| 회기 없음 | "이 학습플랜에는 회기가 없습니다" |
| 빈 보고서 항목 | - 표시 |
| 삭제된 회기 템플릿(dangling id) | "(삭제된 회기 템플릿)" 라벨 + 학습플랜에 박힌 스냅샷 제목으로 폴백 |
2.3 접근 권한 — 서버 사이드 역할 가드
루트 middleware.ts가 없어 라우트 단위 가드가 기본 제공되지 않는다. class-mgmt/schedule 페이지가 쓰던 패턴을 그대로 따라, 서버 컴포넌트 app/main/report/page.tsx에서 렌더 전에 역할을 검사한다.
const currentMember = await getCurrentMember();
if (!currentMember) redirect("/blocked"); // 미로그인
if (role !== "admin" && role !== "developer")
redirect("/main"); // 진행자 등 → 차단
| 역할 | 메뉴 노출 | 페이지 접근 |
|---|---|---|
| admin / developer | O | O |
| manager(진행자) | X | X (URL 직접 입력해도 /main 리다이렉트) |
| 미로그인 | — | X (/blocked 리다이렉트) |
데이터 조회 API(/api/curriculums GET, /api/lesson-templates GET)는 withAuthMember라 로그인 멤버면 호출 자체는 가능하다. 단, 보고서 페이지 진입이 서버에서 막히므로 진행자의 실질 접근은 차단된다. API 레벨까지 조이는 것은 다른 페이지의 조회에도 영향을 주어 이번 범위에서는 제외했다.
주요 변경 파일
| 파일 | 변경 |
|---|---|
types/db/lesson-template.types.ts | SessionReport 인터페이스 + sessionReport 필드 |
components/sections/lesson-template-form.tsx | 회기 보고서 입력 UI(3 textarea) · 상태 · 초기화 · 제출 |
app/api/lesson-templates/route.ts (POST) | 생성 시 sessionReport 저장 |
app/api/lesson-templates/[id]/route.ts (PUT) | 수정 시 sessionReport 반영 |
lib/db-queries.ts | updateLessonTemplate UpdateExpression에 sessionReport |
components/ui/navigation.tsx | 학습플랜 우측 "보고서" 메뉴 |
app/main/report/page.tsx | 보고서 라우트 + 역할 가드 (신규) |
components/pages/report-page.tsx | 보고서 페이지 본문 (신규) |
브랜치 PPI-1025 · PR Dobraindev/ppi#708