마지막 업데이트 2026-08-04
scheduled-lessons 응답 구조는 모니터 대시보드가 그대로 소비해요. 시간대·그룹 변환의 중심 파일은 lib/class-group-utils.ts이고, 나머지는 아래 "함정 · 주의" 섹션에 정리돼 있습니다.치료사-아동 배정, 수업(class) 생성/관리, 그룹(시간대별 다중 세션) 편성, 예약 수업(scheduled-lessons) 조회를 다루는 관리자 영역. 대부분 Next API가 BFF로 외부 ppi-api를 프록시하고, 클라이언트는 시간대/그룹 구조로 변환해 표시한다.
| 영역 | 경로 |
|---|---|
| 클래스 관리 | /main/class-mgmt |
| 스케줄 | /main/schedule |
| 커리큘럼(학습플랜) | /main/curriculum |
| 수업 CRUD | api/class-mgmt/classes, classes/[childId]/[date](+status/videos), batch-delete, counts |
| 그룹 | groups, groups/[groupId]/{assign,unassign,members,check-unassign}, auto-assign |
| 아동 | children/[childId](+class-type, auth/convert·expire), children/search |
| 배정 현황 | assignment-matrix, assignment-drilldown, members/available |
| 예약 수업 | api/scheduled-lessons (모니터 대시보드 #13가 소비) |
평면적인 class 목록을 시간대(timeslot) → 그룹/1:1 2단 구조로 변환해 UI에 표시한다.
TimeslotSection { timeslot, endTime(+20분), groups[], individualClasses[] }time으로 묶고, groupId 있으면 그룹 섹션·없으면 1:1로 분류.
endTime = timeslot + 20분(수업 길이 20분 고정 가정).isGroupClass(groupId 유무), formatGroupId(uuid[:8] 표시).groups/[groupId]/assign|unassign(check-unassign으로 해제 가능 여부 사전 확인), auto-assign(자동 편성).children/[childId]/class-type — 아동의 수업 유형(1:1/그룹 등) 설정, check로 변경 가능 여부 확인.children/[childId]/auth/convert·[orderNo]/expire(+preview) — 계정/주문 관련.assignment-matrix(치료사×시간대 배정표), assignment-drilldown(상세).이 영역은 7월 말~8월 초에 운영자 조회/편집 UX 중심으로 5건이 들어왔다. BFF 프록시 구조 자체는 그대로다.
type="date" 입력으로 조회일을 바꾼다. 내부 저장 포맷은 YYYYMMDD이고
formatDateForDateInput()/formatDateFromDateInput()가 - 삽입·제거만 담당한다. 선택일의 요일도 함께 표시한다.isAdmin)가 status = SCHEDULED 행을 클릭하면 ClassModifyModal이 열려
날짜·시간·진행자를 수정한다. 날짜/시간이 바뀌면 fetchAvailableMembers(date, time)로 배정 가능 진행자를 다시 조회하고,
확정은 updateClass(childId, originalDate, updates) → loadClasses() 순으로 반영한다.onDateTimeChange는 모달 내부 useEffect dependency이므로
handleModifyDateTimeChange를 useCallback([fetchAvailableMembers])로 고정해야 한다.
인라인 함수로 되돌리면 모달이 매 렌더마다 재조회하며 무한 루프에 가까워진다.
today-class-list.regression.test.ts가 소스 문자열로 이 형태를 검사한다
(pnpm exec tsx apps/web/components/sections/today-class-list.regression.test.ts).dateFilterMode: "all" | "date" 2모드. 전체는 실제 날짜 범위를
19000101 ~ 99991231(ALL_STOPPED_CLASSES_START_DATE/END_DATE) 상수로 넓혀 조회한다 —
별도 "전체" API 파라미터가 아니라 범위 트릭이라는 점이 함정.selectedChild)와 진행자 필터를 함께 적용하며, 관리자/개발자 권한에서만 일부 조작이 열린다.getChildDisplayName(child, candidates) — 후보 목록에 같은 이름이 2명 이상일 때만
이름 (생년월일)로 표기하고, 유일하면 이름만 쓴다. 생년월일이 없으면 이름만.
오늘 수업·중단 관리·전체 수업·수동 바우처 모달이 이 입력을 공유하므로, 표시 규칙 변경은 4개 화면에 동시 반영된다.
buildGroupUnassignAuditHeaders(request) → ppi-api로 전달되는 헤더 3종X-PPI-Bulk-Operation-ID(UUID 형식일 때만 통과) · X-PPI-UI-Surface: today_class_list ·
X-PPI-UI-Action: bulk_auto_to_individual | single_group_unassign
unassign 엔드포인트라도 일괄 변환(자동→개별)과 단건 해제를 서버 로그에서 구분하기 위한 장치.bulk-auto-to-individual-modal.tsx · lib/bulk-group-convert.ts)가 operation id를 생성해 보내야 한다.classes/[childId]/[date], groups/[groupId]/unassign, groups/[groupId]/check-unassign.SaveFixRulesResult(success/failure 판별 유니온)로 모델링하고,
실패는 mapSaveFixRulesFailure(status, data)가 응답의 code·message·error를 골라
사용자에게 보여줄 문장 하나로 환원한다(우선순위: message → error → 기본 문구).code로 분기하므로, ppi-api가 새 충돌 코드를 추가하면 모달 분기도 함께 늘려야 한다.19000101~99991231로 범위를 넓히는 방식. 페이지네이션·성능 가정이 여기에 묶여 있다.ClassModifyModal의 onDateTimeChange는 useEffect dependency다(회귀 테스트로 고정됨).| 파일 | 역할 |
|---|---|
| app/main/class-mgmt/page.tsx, schedule, curriculum | 관리 페이지 |
| lib/class-group-utils.ts | 시간대·그룹 변환 |
| api/class-mgmt/{classes,groups,children,assignment-*}/... | BFF 프록시 |
| api/scheduled-lessons/route.ts | 예약 수업(모니터 소비) |
| components/sections/today-class-list.tsx (+regression.test.ts) | 오늘 수업 목록 · 날짜 필터 · 예정 수업 변경 |
| components/sections/stopped-class-list.tsx | 중단 관리 목록 · 전체/날짜 필터 |
| components/ui/child-search-input.tsx, lib/child-display.ts | 아동 검색 · 동명이인 생년월일 표기 |
| api/class-mgmt/groups/audit-headers.ts, lib/bulk-group-convert.ts | 일괄 변경 감사 헤더 |
| lib/fix-rule-save-result.ts, types/db/member-fix-rule.types.ts | 고정 시간표 저장 결과·충돌 메시지 |