[FE-35] 이력서 프로필 검토 · 확정

작업 내용 (설계 의도)

근거: 지원 관리 확장 FE 웹 설계 S-23 이력서 프로필 검토·확정·컴포넌트 트리·상태 관리·API 연동 > 2단계·Testing Plan > 반드시 커버할 실패·엣지 경로(21·24·25·26), 지원 관리 확장 TDD 2단계 — 이력서 프로필 API 계약.

변경 사항

  • 이력서 프로필 검토·확정 화면(S-23)을 구현합니다. 자동 추출된 값을 사용자가 확인·수정하고 하단 CTA로 확정하는 “검토 후 확정” 흐름입니다. 자동 추출값과 사용자 수정값을 시각적으로 구분하지 않습니다 — 확정하면 똑같이 사용자의 값이기 때문입니다.
  • 3개 섹션(기술 스택·경력·근무 선호)을 각각 추가·삭제·수정 가능한 목록으로 구성합니다. 섹션마다 0건일 때 “아직 등록한 {기술/경력/근무 선호}가 없어요” + [+ 추가]를 보여 줍니다.
  • 폼 상태는 로컬 useReducer 드래프트입니다. 이는 “서버 데이터 복사”가 아니라 아직 저장되지 않은 사용자 입력이므로 Query 캐시 SSOT 원칙과 충돌하지 않습니다. 저장이 성공하면 드래프트를 폐기하고 Query 캐시를 재조회해 서버 값을 다시 SSOT로 만듭니다.
  • 리듀서를 hooks/resume/resumeProfileDraftReducer.ts순수 함수로 분리합니다. 3배열의 추가·삭제·수정 규칙은 컴포넌트 렌더와 무관한 로직이며, 단위 테스트 대상입니다(no-logic-in-component).
  • 임시 저장PUT /api/resume-profiles/{id} 단독이고, 프로필 확정PUTPOST .../confirmations 2단계입니다. 확정 전에 반드시 저장해야 편집 내용이 확정 스냅샷에 반영됩니다 — 저장을 건너뛰고 확정하면 사용자가 방금 고친 값이 아닌 이전 값으로 관심 공고가 재평가됩니다.
  • 확정 전에 확인 다이얼로그를 1회 띄웁니다(“확정하면 관심 공고가 다시 평가돼요 · 최대 5분 걸릴 수 있어요”). 파괴적 동작은 아니지만 5분 대기를 유발하므로 사전 고지가 필요합니다.
  • 확정 실행 중에는 진행 안내 카드에 “최대 5분 걸릴 수 있어요 · 화면을 닫아도 계속 진행돼요” 를 표시하고 CTA를 disabled로 둡니다. 서버가 동기 재평가를 수행하므로(NFR-13) 사용자가 응답 없음으로 오해하지 않게 하는 것이 목적입니다.
  • extractionFailed: true는 에러가 아니라 빈 상태로 렌더합니다 — 이력서 등록 자체는 201로 성공했고 텍스트 추출만 실패한 상황입니다(스캔 이미지 PDF·hwp). warning-subtle 배너로 “이력서에서 텍스트를 읽을 수 없어요 / 항목을 직접 입력해 주세요”를 안내하고 섹션별 빈 상태 + 추가 버튼을 그대로 제공합니다. ErrorState를 띄우면 사용자가 업로드 자체가 실패한 줄 알고 다시 올립니다.
  • GET /api/resume-profiles/current의 404도 에러가 아니라 빈 상태입니다 — “확정 프로필이 아직 없다”는 정상 상태이므로 “아직 프로필이 없어요 / 이력서를 올리면 기술·경력을 자동으로 읽어 초안을 만들어요” + [이력서 올리기](documentType=RESUME 고정)를 보여 줍니다.
  • 저장 실패 409(이미 확정된 프로필 수정)는 “이미 확정된 프로필이에요 / 새 이력서를 올려 다시 만들어 주세요” + [서류 보관함]으로 안내합니다. 확정 실패는 토스트만 띄우고 폼 상태를 유지해 입력 손실을 막습니다.
  • 확정 응답의 elapsedMillis(밀리초)를 결과 카드에 소요 시간으로 표시합니다(E 확정). 스키마에 확정된 필수 필드이므로 “없으면 미표시” 분기를 두지 않습니다. 표시 자체는 선택 사항이나, 표시하면 “최대 5분”이라는 사전 고지가 실제로 얼마나 걸렸는지로 닫혀 사용자가 다음 확정의 대기 시간을 가늠할 수 있습니다. 밀리초를 그대로 노출하지 않고 4.2초 형태로 변환해 보여 줍니다.
  • 헤더에 원본 표시(sourceDocumentVersionId → 계열 링크)와 미확정 안내 배너(“확정해야 추천도 계산에 쓰여요”)를 둡니다. 확정 결과 요약·재평가 실패 목록 영역은 wave 3의 FE-39가 이 페이지에 덧붙입니다 — 이 티켓은 그 자리를 남겨 둡니다.

롤백: 확정은 서버 상태 전이를 유발하므로 FE 롤백만으로 되돌릴 수 없습니다. FE를 이전 빌드로 되돌리면 확정 화면 진입 경로가 사라지고 기존 화면은 그대로 동작합니다.

의존

  • FE-30 — ResumeProfileResponse·SaveResumeProfileRequest 타입, 현재 프로필 queryKey, MSW 목(추출 실패·404·409 시나리오 포함).
  • FE-31 — /resume-profile 라우트와 스텁 페이지. 이 티켓이 스텁을 대체합니다.
  • BE-62 — 프로필 초안·저장·확정·현재 조회 API 구현.

다이어그램

처리 흐름

sequenceDiagram
    participant User as 사용자
    participant Page as ResumeProfilePage
    participant Draft as 드래프트 리듀서
    participant Save as PUT profiles/{id}
    participant Confirm as POST confirmations
    User->>Page: 항목 수정
    Page->>Draft: 추가·삭제·수정 액션
    User->>Page: 프로필 확정(다이얼로그 승인)
    Page->>Save: 드래프트 저장
    Save-->>Page: 200
    Page->>Confirm: 확정 요청
    Confirm-->>Page: reevaluatedCount

컴포넌트 의존

flowchart LR
    Page[ResumeProfilePage] --> Current[useCurrentResumeProfile]
    Page --> Reducer[resumeProfileDraftReducer]
    Page --> SaveHook[useSaveResumeProfile]
    Page --> ConfirmHook[useConfirmResumeProfile]
    Page --> Skill[ResumeSkillSection]
    Page --> Exp[ResumeExperienceSection]
    Page --> Pref[ResumePreferenceSection]
    Page --> Notice[ExtractionFailedNotice]
    SaveHook --> Api[api/resume]
    ConfirmHook --> Api

테스트 케이스

  • 확정 프로필이 있으면 기술·경력·근무 선호 3섹션에 서버 값이 채워져 렌더된다.
  • 기술 항목을 추가·삭제하면 화면 목록이 즉시 반영되고 서버 요청은 발생하지 않는다(드래프트 단계).
  • 임시 저장을 누르면 드래프트 3배열이 PUT 요청 바디로 전송된다.
  • 프로필 확정을 누르면 확인 다이얼로그가 1회 뜨고, 승인 시 PUTPOST confirmations 순서로 요청이 나간다.
  • 확인 다이얼로그를 취소하면 저장·확정 요청이 모두 발생하지 않는다.
  • 확정 진행 중에는 “최대 5분 걸릴 수 있어요 · 화면을 닫아도 계속 진행돼요” 안내가 보이고 CTA가 비활성된다.
  • extractionFailed: true이고 항목이 전부 빈 배열이면 빈 상태 + 수동 입력 안내가 보이고 ErrorState는 보이지 않는다.
  • .hwp에서 만들어진 초안이 extractionFailed: true로 내려오면 추출 실패 안내가 보이고 등록 실패로 표현되지 않는다.
  • GET current가 404이면 “아직 프로필이 없어요” + [이력서 올리기]가 보이고 ErrorState는 보이지 않는다.
  • [이력서 올리기]documentType=RESUME가 고정된 업로드 경로로 이동한다.
  • 저장이 409이면 “이미 확정된 프로필이에요”와 [서류 보관함] 링크가 보인다.
  • 확정 요청이 실패하면 토스트가 뜨고 폼 입력이 그대로 유지된다.
  • 확정 응답의 elapsedMillis가 결과 카드에 소요 시간(4.2초 형태)으로 표시되고 밀리초 원값이 그대로 노출되지 않는다.
  • 3섹션이 각각 0건이면 섹션별 “아직 등록한 …이 없어요” + [+ 추가]가 보인다.
  • resumeProfileDraftReducer가 기술 추가·삭제·수정, 경력 추가·삭제, 근무 선호 required 토글을 각각 순수하게 처리하고 원본 배열을 변형하지 않는다.
  • resumeProfileDraftReducer가 존재하지 않는 인덱스 삭제 액션을 받아도 상태를 깨뜨리지 않는다(경계값).
  • 화면과 3섹션이 .dark 클래스 환경에서 렌더되고 하드코딩 색을 0건 사용한다.