[FE-15] 면접 회차 시트 — 추가 · 수정

작업 내용 (설계 의도)

근거 설계: 20260722-공고알림앱-design-fe-web.md — “S-09 면접 회차 시트” 근거 요구: FR-46(면접 회차는 상태값이 아닌 별도 기록 — 회차 번호·사용자 지정 레이블·일정·결과)

변경 사항

한 지원 건에 여러 면접 회차를 추가·수정하는 시트입니다.

핵심 설계 의도:

  • 면접 회차는 지원 상태값이 아닙니다(FR-46). INTERVIEWING이라는 단일 상태 아래에 1차·2차·3차가 독립 레코드로 존재합니다. UI도 이 구조를 그대로 반영해 상태 변경 시트(FE-14)와 완전히 분리합니다.
  • 레이블은 자유 입력입니다(“1차 기술면접”·“임원면접”·“컬처핏”). 회사마다 부르는 이름이 달라 셀렉트로 고정할 수 없습니다.
  • 회차 번호를 자동으로 채웁니다 — 기존 최대 회차 + 1. 사용자가 매번 세지 않게 하는 Minimum Input 장치이며, 수정은 가능합니다.
  • 일정 미정 상태로 먼저 등록할 수 있습니다(scheduledAt=null). “면접 잡혔는데 날짜는 조율 중”이 흔한 상황이라 일정을 필수로 두면 기록을 미루게 됩니다.
  • 결과 기록이 지원 상태를 자동 전이시키지 않습니다(BE-15 설계 의도). 1차 합격이 곧 다음 단계 진입은 아니며(대기 기간 존재), 사용자가 명시적으로 상태를 전이합니다. 이 사실을 시트 하단에 **“결과를 기록해도 지원 상태는 바뀌지 않아요. 상태는 직접 변경해 주세요”**로 명시합니다 — 명시하지 않으면 사용자는 상태가 자동으로 바뀌길 기다립니다.
  • 수정 모드에서는 회차·레이블이 읽기 전용이 되고 일정·결과·메모만 편집합니다. 회차 번호 변경은 유니크 제약(unique(application_id, round_number)) 충돌을 유발하므로 애초에 허용하지 않습니다.
  • 종료된 지원에는 회차 추가가 거부됩니다(409) — 호출부가 CTA를 숨기지만, 시트도 409를 처리해 시트를 닫고 상세를 refetch합니다.

범위: src/components/application/InterviewSheet.tsx(추가·수정 모드 공용), src/api/application/interview.ts, src/hooks/application/useAddInterview.ts, useUpdateInterview.ts.

조립은 FE-16이 담당합니다.

의존

  • FE-05, FE-03
  • BE 의존: BE-15 (면접 회차 API)

다이어그램

처리 흐름

sequenceDiagram
    participant U as 사용자
    participant S as InterviewSheet
    participant M as useAddInterview
    participant Q as Query 캐시
    participant Sv as 서버
    S->>S: 회차 기본값 = 기존 최대 + 1
    U->>S: 레이블 입력 · 일정 미정으로 제출
    S->>M: mutate(roundNumber, label, scheduledAt=null)
    M->>Sv: POST /api/applications/{id}/interviews
    alt 409 회차 중복
        Sv-->>M: 409
        S-->>U: 회차 필드 인라인 에러
    else 409 종료된 지원
        Sv-->>M: 409
        M->>Q: 상세 무효화
        S-->>U: 시트 닫힘 + 토스트
    else 성공
        Sv-->>M: {interviewId}
        M->>Q: 상세 무효화
        S-->>U: 시트 닫힘 + 토스트
    end

클래스 의존

flowchart LR
    subgraph Sheet["components/application"]
        I[InterviewSheet]
    end
    subgraph Hooks["hooks/application"]
        HA[useAddInterview]
        HU[useUpdateInterview]
    end
    subgraph Api["api/application"]
        A[interview.ts]
    end
    subgraph Ui["components/ui"]
        BS[BottomSheet]
        TF[TextField]
        DF[DateField]
        Sel[Select]
        Btn[Button]
    end
    subgraph Utils["utils"]
        V[isPositiveInteger]
    end
    I --> HA
    I --> HU
    I --> BS
    I --> TF
    I --> DF
    I --> Sel
    I --> Btn
    I --> V
    HA --> A
    HU --> A

테스트 케이스

  • 추가 모드에서 회차 기본값이 기존 최대 회차 + 1로 채워진다
  • 기존 회차가 없으면 회차 기본값이 1이다
  • 사용자 지정 레이블(“임원면접”)을 입력하고 제출하면 그대로 전송된다
  • 일정 없이(scheduledAt=null) 제출할 수 있다
  • 회차 번호가 0 이하이면 인라인 에러가 보이고 요청이 전송되지 않는다
  • 레이블이 비어 있으면 제출 버튼이 비활성이다
  • 회차 중복(409) 시 회차 필드에 “이미 있는 회차예요” 인라인 에러가 보인다
  • 종료된 지원(409) 시 시트가 닫히고 토스트가 표시되며 상세가 refetch된다
  • 수정 모드에서 회차와 레이블이 읽기 전용으로 렌더된다
  • 수정 모드에서 일정만 변경해 제출하면 결과 필드가 덮어써지지 않는다
  • 결과를 기록해도 지원 상태 변경 요청이 전송되지 않는다
  • “결과를 기록해도 지원 상태는 바뀌지 않아요” 안내가 시트에 렌더된다
  • 추가 성공 시 시트가 닫히고 상세 쿼리가 무효화된다
  • 500 수신 시 입력값이 보존된 채 에러 토스트가 표시된다
  • 제출 중 폼이 비활성화되고 CTA에 로딩이 표시된다