회기 보고서 — 회기 템플릿 보고서 항목 추가 및 보고서 페이지 신설

마지막 업데이트 2026-07-22

회기 보고서 sessionReport 회기 템플릿 학습플랜 보고서 페이지 역할 가드(admin/developer) DynamoDB PPI-1025 2026-06-11

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로 회기 템플릿을 가리킬 뿐, 회기 템플릿에는 "어느 학습플랜에 속하는지"를 가리키는 필드가 없다. 보고서 페이지는 학습플랜을 기준으로 회기 템플릿을 역으로 끌어와 합친다.

1부

회기 템플릿에 회기 보고서(sessionReport) 항목 추가

회기 템플릿 생성·수정 화면에서 3개 보고서 항목을 입력·저장한다.

1.1 타입 — SessionReport

apps/web/types/db/lesson-template.types.tsSessionReport 인터페이스를 신설하고 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-templatesbody.sessionReport를 객체에 포함 → setLessonTemplate(PutCommand, 객체 전체 저장)
수정PUT /api/lesson-templates/[id]body.sessionReport ?? existing.sessionReport 전달
DB 갱신updateLessonTemplate (db-queries.ts)UpdateExpression에 sessionReport = :sessionReport 추가

함정 — UpdateExpression 누락 주의. 생성은 PutCommand라 객체 전체가 저장되지만, 수정은 updateLessonTemplateset 구문에 명시한 필드만 반영된다. sessionReport를 UpdateExpression에 넣지 않으면 폼에서 입력해도 수정 시 DB에 반영되지 않는다. 값이 없을 때를 대비해 빈 객체 폴백(:sessionReport = 빈 3필드)도 함께 넣었다.

2부

회기 보고서 페이지 신설 (/main/report)

학습플랜 선택 → 회기별 보고서 항목을 표로 조회. admin/developer 전용.

2.1 네비게이션 메뉴

navigation.tsxadminOrDeveloperLinks에서 학습플랜 바로 우측{ href: "/main/report", label: "보고서" }를 추가했다. 학습플랜 메뉴 자체가 admin/developer 전용 배열에만 있으므로 진행자(manager)에게는 보고서 메뉴도 노출되지 않는다.

2.2 페이지 구성 — report-page.tsx

엣지 케이스

상황표시
학습플랜 미선택"학습플랜을 선택하세요" (로딩 중엔 로딩 메시지)
회기 없음"이 학습플랜에는 회기가 없습니다"
빈 보고서 항목- 표시
삭제된 회기 템플릿(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 / developerOO
manager(진행자)XX (URL 직접 입력해도 /main 리다이렉트)
미로그인X (/blocked 리다이렉트)

데이터 조회 API(/api/curriculums GET, /api/lesson-templates GET)는 withAuthMember라 로그인 멤버면 호출 자체는 가능하다. 단, 보고서 페이지 진입이 서버에서 막히므로 진행자의 실질 접근은 차단된다. API 레벨까지 조이는 것은 다른 페이지의 조회에도 영향을 주어 이번 범위에서는 제외했다.

주요 변경 파일

파일변경
types/db/lesson-template.types.tsSessionReport 인터페이스 + 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.tsupdateLessonTemplate UpdateExpression에 sessionReport
components/ui/navigation.tsx학습플랜 우측 "보고서" 메뉴
app/main/report/page.tsx보고서 라우트 + 역할 가드 (신규)
components/pages/report-page.tsx보고서 페이지 본문 (신규)

브랜치 PPI-1025 · PR Dobraindev/ppi#708