[FE-14] 지원 상태 변경 시트 — 허용 전이만 제시

작업 내용 (설계 의도)

근거 설계: 20260722-공고알림앱-design-fe-web.md — “S-08 상태 변경 시트”, “방안 3 상태 전이를 UI로 어떻게 드러낼 것인가” 근거 요구: FR-43(전이 표) · FR-44(OFFEREDACCEPTED·OFFER_DECLINED만) · FR-45(WITHDRAWN은 앞 3단계만) · FR-47(전이 이력)

변경 사항

“허용되지 않는 전이는 UI에서 아예 제시하지 않는다”가 이 티켓의 존재 이유입니다.

Kanban 드래그(Huntr 방식)를 미채택한 근거: 드래그는 모든 열을 물리적으로 제시하므로 요구와 충돌합니다. 전 상태를 나열하고 불가한 것을 disabled 처리하는 방식도 미채택입니다 — disabled 항목은 “왜 안 되는지” 설명을 요구하고, 종료 상태에서는 전체가 disabled인 무의미한 화면이 됩니다.

핵심 설계 의도:

  • 현재 상태에서 갈 수 있는 상태만 라디오로 나열합니다. OFFERED에서는 ACCEPTED·OFFER_DECLINED 2개만 보이고 WITHDRAWN·REJECTED목록에 존재하지 않습니다(FR-44).
  • 허용 전이가 0건(종료 상태)이면 이 시트는 열리지 않습니다 — 호출부(FE-16)가 CTA 자체를 렌더하지 않습니다. 시트는 방어적으로 빈 목록을 렌더하지 않고 개발 시 조기 실패하도록 처리합니다.
  • REJECTED 선택 시 확인 문구가 현재 상태에 따라 자동으로 바뀝니다 — “‘면접’ 단계에서 불합격으로 기록돼요”. 탈락 단계를 입력받지 않습니다: BE가 이전 상태로부터 rejectedAtStage를 자동 기록하므로(BE TDD 상태 전이 표) 입력을 늘리지 않고 확인만 제공합니다(Minimum Input).
  • 종료 상태 선택 시 “변경하면 되돌릴 수 없어요” 경고를 CTA 위에 인라인 표시합니다. 별도 확인 다이얼로그를 겹치지 않습니다 — 시트 위 다이얼로그 중첩은 토스 패턴에 반합니다.
  • 409를 최종 방어선으로 신뢰합니다. 클라이언트 전이 맵(FE-03)은 UX 최적화이지 권한 판정이 아닙니다. 409 수신 시 “상태가 이미 바뀌었어요” 안내 + 상세 자동 refetch → 선택지가 갱신됩니다(낙관적 잠금 충돌 포함).
  • 전이 규칙 SSOT: 계약 요청 #7(allowedNextStatuses 응답 포함)이 수용되면 서버 값을 우선 사용하고, 미수용 시 FE-03의 상수 맵을 폴백으로 씁니다. 응답에 값이 있으면 항상 서버 값이 이깁니다.
  • 낙관적 업데이트를 적용하지 않습니다 — 전이 가능 여부 판정이 서버에 있고, 낙관적으로 상태를 바꿨다가 409로 되돌리면 사용자가 “됐다가 안 됐다”를 보게 됩니다.

범위: src/components/application/TransitStatusSheet.tsx, src/api/application/transition.ts, src/hooks/application/useTransitApplicationStatus.ts.

시트는 자기 mutation을 소유합니다(설계 “컨테이너/프레젠테이션 분리”의 명시된 예외) — 트리거 위치와 무관하게 재사용 가능해야 하기 때문입니다. 조립은 FE-16이 담당합니다.

의존

  • FE-05 (ApplicationStatusChip), FE-03 (허용 전이 맵·한글 표기)
  • BE 의존: BE-14 (상태 전이 API)

다이어그램

처리 흐름

sequenceDiagram
    participant U as 사용자
    participant S as TransitStatusSheet
    participant A as allowedNextStatuses
    participant M as useTransitApplicationStatus
    participant Q as Query 캐시
    participant Sv as 서버
    S->>A: currentStatus (서버 값 우선 · 상수 폴백)
    A-->>S: 갈 수 있는 상태만
    U->>S: "불합격" 선택
    S-->>U: "'면접' 단계에서 불합격으로 기록돼요" + 되돌릴 수 없음 경고
    U->>S: 변경하기
    S->>M: mutate(nextStatus, memo?)
    M->>Sv: POST /api/applications/{id}/status-transitions
    alt 409 전이 불가 또는 잠금 충돌
        Sv-->>M: 409
        M->>Q: 상세 무효화 (refetch)
        S-->>U: "상태가 이미 바뀌었어요" + 선택지 갱신
    else 성공
        Sv-->>M: {status, rejectedAtStage?, historyId}
        M->>Q: 상세 무효화
        S-->>U: 시트 닫힘 + 토스트
    end

클래스 의존

flowchart LR
    subgraph Sheet["components/application"]
        T[TransitStatusSheet]
    end
    subgraph Hooks["hooks/application"]
        H[useTransitApplicationStatus]
    end
    subgraph Api["api/application"]
        A[transition.ts]
    end
    subgraph Constants["constants"]
        Map[ALLOWED_TRANSITIONS]
        Label[statusLabel]
    end
    subgraph Ui["components/ui · domain"]
        BS[BottomSheet]
        TF[TextField]
        Btn[Button]
        SC[ApplicationStatusChip]
    end
    T --> H
    T --> Map
    T --> Label
    T --> BS
    T --> TF
    T --> Btn
    T --> SC
    H --> A

테스트 케이스

  • 현재 상태가 APPLIED이면 DOCUMENT_SCREENING·REJECTED·WITHDRAWN 3개만 렌더된다
  • 현재 상태가 INTERVIEWING이면 OFFERED·REJECTED·WITHDRAWN 3개만 렌더된다
  • 현재 상태가 OFFERED이면 ACCEPTED·OFFER_DECLINED 2개만 렌더되고 WITHDRAWN이 목록에 없다
  • 현재 상태가 OFFERED일 때 REJECTED도 목록에 없다
  • 현재 상태와 같은 상태로의 전이 선택지가 렌더되지 않는다
  • REJECTED 선택 시 확인 문구에 현재 상태의 한글 표기가 포함된다 (“‘면접’ 단계에서”)
  • 현재 상태가 DOCUMENT_SCREENING일 때 REJECTED 선택 문구가 “‘서류전형’ 단계에서”로 바뀐다
  • 탈락 시점 단계를 입력받는 필드가 존재하지 않는다
  • 종료 상태를 선택하면 “되돌릴 수 없어요” 경고가 CTA 위에 렌더된다
  • 종료 상태 선택 시 별도 확인 다이얼로그가 열리지 않는다
  • 아무것도 선택하지 않으면 “변경하기” 버튼이 비활성이다
  • 전이 성공 시 시트가 닫히고 상세 쿼리가 무효화된다
  • 409 수신 시 시트가 유지되고 안내 문구가 표시되며 상세가 refetch된다
  • 409 후 갱신된 상태에 맞춰 선택지가 다시 계산된다
  • 서버 응답에 allowedNextStatuses가 있으면 상수 맵보다 우선 사용된다
  • 500 수신 시 선택 상태가 보존된 채 에러 토스트가 표시된다
  • 제출 중 라디오와 CTA가 비활성화된다

2차 갱신 반영 (2026-07-22) — 서버 allowedNextStatuses 우선 (계약 응답 #7)

  • BE가 상세·전이 응답에 allowedNextStatuses[]를 포함하므로, 시트는 서버가 내려준 값을 그대로 선택지로 씁니다. FE-03의 전이 맵 상수는 서버 값이 없을 때만 쓰는 폴백으로 격하됩니다.
  • 전이 맵 동기화 주석·전수 검증 테스트를 삭제합니다 — SSOT가 서버로 이동했습니다.
  • 409 방어선은 유지합니다 — 클라이언트가 서버 값으로 선택지를 그려도, 동시 전이·규칙 변경 시 서버가 409를 반환할 수 있으므로 409 → 안내 + 상세 refetch → 선택지 갱신 흐름은 그대로입니다.

수정·추가 테스트 케이스

  • 서버 응답의 allowedNextStatuses가 시트 선택지로 그대로 렌더된다
  • 서버 값이 없으면 폴백 상수로 선택지를 계산한다
  • 전이 맵 전수 동기화 검증 테스트는 존재하지 않는다 (SSOT가 서버)
  • 409 수신 시 상세 refetch로 서버의 최신 allowedNextStatuses가 다시 반영된다