Files

8.2 KiB

Phase 10. 마에스트로 정보 수정 및 학생 목록 고도화

목표

/maestros/[maestroId] 상세 화면에서 마에스트로 정보를 직접 수정할 수 있는 폼을 추가하고, 학생 목록을 서버 사이드 페이지네이션과 이름 검색이 가능한 형태로 완성한다.


1. 마에스트로 상세 화면 수정 폼 구현

사전 조사 결과 (기존 mouse-typing 코드 확인 완료)

조사 항목 파일 위치 결과
이름 중복 검사 기준 server/maestro/update_maestro_info.php 중복 검사 없음 — 이름을 그대로 UPDATE. chocoadmin에서 신규 추가.
이메일 변경 처리 server/maestro/update_maestro_info.php 변경 시 이메일 안내 메일 발송 + maestro_log 기록. 메일은 Phase 11에서 구현.
입장코드 수정 컬럼 server/maestro/set_allow_edit_entercode.php maestro.AllowEditEnterCode: 0(비허용) / 1(허용). 로그 기록 없음.
상태/계정유형 변경 로그 server/admin/ 전체 기존 관리자에서 상태/계정유형/사용가능일을 직접 수정하는 기능 없음. 신규 기능이므로 신규 로그 타입 정의.

정의한 신규 로그 타입 (기존 코드에 없음):

Type 대상 필드 Remark 형식
update_maestro_status ActivateStatus {prev} -> {new} (숫자)
update_maestro_account_type AccountType {prev} -> {new} (숫자)
update_maestro_available_date AvailableActivateDateTime {YYYY-MM-DD} -> {YYYY-MM-DD}
update_maestro_allow_enter_code AllowEditEnterCode {prev} -> {new} (0 or 1)

API 구현: PATCH /api/maestros/[id]

파일: app/api/maestros/[id]/route.ts (기존 GET과 동일 파일에 PATCH 추가) 서비스 레이어: lib/maestros.tsupdateMaestroSchema, UpdateMaestroPayload, updateMaestro() 추가

요청 스키마 (Zod)

필드 타입 규칙
name string 1자 이상, trim
email string 이메일 형식
activateStatus 0 | 1 | 2 | 100 ACTIVATE_STATUSES 상수
accountType 1 | 2 | 3 | 4 | 5 유료 타입만 허용 (PaidAccountType)
availableActivateDateTime string ISO datetime 또는 YYYY-MM-DD 형식
allowEditEnterCode 0 | 1 정수

모든 필드는 optional — 전달된 필드만 업데이트한다.

처리 순서

  1. withApiHandler로 인증 자동 검사
  2. 대상 maestro 존재 확인 (findUnique) — 없으면 404
  3. name이 포함된 경우: 동일 이름의 다른 마에스트로 존재 여부 확인 — 있으면 409
  4. 변경 필드 추출 (전달값 vs 현재값 비교, 동일한 값이면 변경 대상에서 제외)
  5. 변경 필드가 없으면 200 + { changed: false } 반환 (DB 업데이트 및 로그 기록 생략)
  6. 트랜잭션: maestro 업데이트 + maestro_log INSERT (변경된 필드별로 각각 로그 기록)
  7. 성공 응답: 200 + { changed: true }

응답 정책

상황 HTTP 비고
변경 없음 200 { changed: false }
성공 200 { changed: true }
이름 중복 409 자기 자신은 중복으로 처리하지 않음
존재하지 않는 마에스트로 404
유효하지 않은 값 400 Zod 파싱 실패
인증 없음 401 withApiHandler 처리

UI 구현: 수정 폼

파일: app/(admin)/maestros/[maestroId]/MaestroEditForm.tsx (신규 Client Component)

현재 상세 페이지(page.tsx)의 "기본 정보" 섹션을 읽기 전용 표시에서 편집 가능한 폼으로 교체한다.

필드 구성

필드 컴포넌트 비고
이름 <Input type="text">
이메일 <Input type="email">
상태 <Select> ACTIVATE_STATUS_LABELS 기반
계정유형 <Select> ACCOUNT_TYPE_LABELS 기반, 유료 타입만
사용 가능일 <Input type="datetime-local">
입장코드 수정 <Select> 허용(1) / 비허용(0)

제출 흐름

  1. 저장 버튼 클릭 → 확인 다이얼로그 표시
  2. 확인 → PATCH /api/maestros/[id] 호출, 저장 버튼 비활성화
  3. 성공 → 성공 토스트 + router.refresh()로 화면 갱신
  4. 실패 → 실패 토스트 (이름 중복 409 응답 시 "이미 사용 중인 이름입니다." 별도 메시지)

2. 학생 목록 구현

API 구현: GET /api/maestros/[id]/students

파일: app/api/maestros/[id]/students/route.ts (신규) 서비스 레이어: lib/maestros.tsstudentSearchSchema, MaestroStudentsResult, getMaestroStudents() 추가

쿼리 파라미터

파라미터 타입 기본값 설명
page number 1 페이지 번호
pageSize 10 | 20 | 50 10 페이지 크기
q string "" 이름 검색 (LIKE %q%)

응답 형식

{
  items: Array<{
    playerID: number;
    name: string;
    enterCode: string;
    accountType: number | null;
  }>;
  page: number;
  pageSize: number;
  totalCount: number;
  totalPages: number;
  q: string;
}

처리 순서

  1. withApiHandler로 인증 검사
  2. maestro 존재 확인 — 없으면 404
  3. 쿼리 파라미터 Zod 파싱
  4. player 테이블에서 MaestroID 조건 + 이름 LIKE 검색 + 페이지네이션 조회
  5. 정렬: PlayerID DESC

UI 구현: 학생 목록

현재 "학생 목록 요약" 섹션(최근 20명 고정)을 서버 사이드 페이지네이션 목록으로 교체한다.

파일: app/(admin)/maestros/[maestroId]/StudentsSection.tsx (신규 Client Component)

구성 요소

  • 이름 검색 입력 (<Input>) + 검색 버튼
  • 전체 학생 수 표시 (전체 N명)
  • 학생 목록 테이블 (PlayerID, 이름, 입장코드)
  • 페이지네이션 컨트롤 — 마에스트로 목록과 동일한 슬라이딩 윈도우 형식 (현재 페이지 ±2, 처음/끝 이동, ±5 그룹 이동)
  • 빈 목록 상태: "등록된 학생이 없습니다."
  • 검색 결과 없음 상태: "검색 결과가 없습니다."

동작

  • 검색어 입력 후 Enter 또는 검색 버튼 클릭으로 조회 (실시간 debounce 없음)
  • 검색어 변경 시 page를 1로 초기화
  • 페이지 이동은 URL query string 없이 컴포넌트 state로 관리

3. 검증

항목 방법 기대 결과 실행 결과
이름 중복 저장 실패 다른 마에스트로와 동일한 이름으로 저장 시도 409, "이미 사용 중인 이름입니다." 409 + {"message":"이미 사용 중인 이름입니다."}
이름 자기 자신과 동일 현재 이름 그대로 저장 변경 없음 처리 또는 중복 아님으로 정상 처리 200 + {"changed":false}
정보 수정 성공 후 DB 반영 이름 / 이메일 / 상태 / 계정유형 / 사용 가능일 / 입장코드 수정 변경 후 DB 직접 조회 변경값 반영 확인 모든 필드 DB 반영 확인
maestro_log 기록 확인 정보 수정 성공 후 maestro_log 조회 변경 필드별 Type, Remark 형식이 §1 정의와 일치 6개 Type 모두 정확한 형식으로 기록됨
변경 없는 저장 현재 값 그대로 저장 시도 { changed: false } 응답, maestro_log 미기록 200 + {"changed":false}, 로그 없음
학생 목록 페이지네이션 학생 수가 pageSize 초과인 마에스트로에서 다음 페이지 이동 정확한 레코드 표시, totalCount 일치 823명 마에스트로: totalPages=83, page=2 정상
학생 목록 이름 검색 존재하는 이름 일부로 검색 해당 학생만 필터링 q=최건 → totalCount=1, 해당 학생만 반환
학생 목록 검색 결과 없음 존재하지 않는 이름으로 검색 빈 상태 메시지 표시 totalCount=0, items=[]
비인증 접근 차단 세션 없이 PATCH /api/maestros/[id], GET /api/maestros/[id]/students 호출 401 응답 401 + {"message":"Unauthorized"}

부가 수정 사항

  • proxy.ts matcher 수정: api/authapi/ (API 라우트 전체를 미들웨어 제외 대상으로 변경)
    • 기존: API 라우트에 미들웨어가 개입하여 307 리다이렉트 반환
    • 수정 후: API 라우트는 withApiHandler가 직접 401 처리