[FE-45] 추천도 가중치 설정

작업 내용 (설계 의도)

근거: 지원 관리 확장 FE 웹 설계 S-28 추천도 가중치 설정 — 3단계 · 컴포넌트 트리 · Query 규약(무효화 매핑) · API 연동 > 3단계 · 라우팅 · 내비게이션 · Testing Plan > 반드시 커버할 실패·엣지 경로 #43, 지원 관리 확장 TDD 3단계 — 가중치 수정·상한 해제 (FR-97)GET·PUT /api/recommendations/weights.

변경 사항

  • 추천도 4축 가중치 편집 화면(S-28)을 만듭니다. 4축은 고정이며 축 추가·삭제 UI를 두지 않습니다 — 계약이 4축 전량 전송을 요구하고, 축 자체는 서버 도메인이 정의합니다.
  • 입력은 숫자 스텝퍼(0~100 정수)입니다. 슬라이더는 미채택입니다 — 4축 연동 조정(하나를 올리면 나머지를 줄이는) 로직이 붙어 복잡도가 커지고, 합계 검증을 사용자가 직접 하기 어려워집니다.
  • 합계 100 검증을 클라이언트에서 실시간으로 합니다. 합계 ≠ 100이면 저장 CTA를 disabled 처리하고 “합계가 100이 되어야 해요 (현재 {N})“를 현재 합계와 함께 보여 줍니다. 서버 400을 기다리지 않습니다 — 왕복 후에야 틀렸다고 알려 주면 편집 리듬이 끊깁니다. 서버 400 RECOMMENDATION_WEIGHT_INVALID는 클라이언트 선검증을 뚫은 경우의 최후 방어로 합계 행에 인라인 표시합니다.
  • 기본값 되돌리기(45/20/20/15)를 부차 액션으로 둡니다. 실수로 망친 배분을 복구할 수 있어야 하고, 기본값은 서버 응답이 아니라 화면이 아는 상수입니다.
  • 저장은 낙관적 업데이트를 하지 않습니다 — 서버가 criteriaRevision을 새로 발급하므로 응답 없이 화면이 새 버전을 표시하면 거짓말이 됩니다. 성공 시 ['recommendations','weights']['job-postings'] 접두사 전체를 무효화합니다(모든 공고의 추천도가 새 기준 대상이 됩니다).
  • 저장 성공 토스트에 [지금 다시 평가] 액션을 붙여 FE-39의 재평가 훅을 재사용합니다. 가중치만 바꾸고 재평가를 하지 않으면 화면의 점수가 옛 기준이라 사용자가 변경 효과를 확인할 수 없습니다. 재평가 훅을 이 티켓에서 새로 만들지 않습니다.
  • 화면 상단에 현재 criteriaRevision을, 안내 문구에 “저장하면 기준 버전이 올라가고 관심 공고를 다시 평가해야 해요”를 노출해 저장의 파급을 미리 알립니다.
  • 진입 동선은 매칭 설정 화면(S-10) 하단의 [추천도 가중치] 링크입니다 — 매칭 탭 하위이므로 탭을 늘리지 않습니다. 링크 추가 외에 매칭 설정 화면의 기존 동작은 건드리지 않습니다.
  • 롤백: 가중치 저장은 되돌리는 API가 없으므로, 이전 배분을 다시 입력해 저장하는 것이 원복 절차입니다. 화면이 criteriaRevision을 항상 노출해 어느 기준이 적용 중인지 확인할 수 있게 합니다.

의존

  • FE-40 — 가중치 조회·저장 타입, ['recommendations','weights'] queryKey, 가중치 MSW 목(400 RECOMMENDATION_WEIGHT_INVALID 포함)을 사용합니다.
  • FE-41 — /settings/recommendation 라우트와 스텁 페이지를 이 티켓이 구현으로 대체합니다.
  • FE-39 (단계 2) — 저장 성공 토스트의 [지금 다시 평가]가 재평가 훅을 재사용합니다. 단계 2가 이미 닫혀 있으므로 대기 없이 착수합니다.
  • BE-72 — GET·PUT /api/recommendations/weights 구현.

다이어그램

처리 흐름

sequenceDiagram
    participant User as 사용자
    participant Page as RecommendationWeightPage
    participant Hook as useRecommendationWeights
    participant API as GET·PUT weights
    Page->>Hook: 현재 가중치 조회
    Hook->>API: GET weights
    API-->>Page: criteriaRevision + 4축
    User->>Page: 축 값 수정
    Page-->>User: 합계 실시간 표시 · 100 아니면 CTA 비활성
    User->>Page: 저장
    Page->>API: PUT weights (합계 100)
    API-->>Page: 새 criteriaRevision
    Page-->>User: 토스트 + [지금 다시 평가]

컴포넌트 의존

flowchart LR
    Matching[MatchingSettingsPage] --> Page[RecommendationWeightPage]
    Page --> Hook[useRecommendationWeights]
    Hook --> Api[fetch·saveRecommendationWeights]
    Page --> Row[WeightRow x4]
    Page --> Sum[합계 검증]
    Page --> Toast[토스트 · 지금 다시 평가]
    Toast --> Reeval[useReevaluateRecommendations · FE-39]

테스트 케이스

  • 조회 성공 시 4축 값과 현재 criteriaRevision이 보이고 합계 100이 표시된다.
  • 합계가 100이면 저장 CTA가 활성이고, 저장하면 새 criteriaRevision이 토스트에 보인다.
  • 한 축 값을 바꿔 합계가 95가 되면 서버 요청 없이 CTA가 비활성되고 “현재 95”가 보인다.
  • 합계가 100을 넘어도 CTA가 비활성되고 현재 합계가 보인다.
  • 기본값으로 되돌리기를 누르면 45/20/20/15가 입력되고 합계 100으로 CTA가 활성된다.
  • 서버가 400 RECOMMENDATION_WEIGHT_INVALID를 반환하면 합계 행에 인라인 에러가 보이고 입력값이 유지된다.
  • 저장 성공 토스트의 [지금 다시 평가]를 누르면 재평가 요청이 발생한다.
  • 저장 성공 후 공고 관련 쿼리가 무효화돼 추천도가 새 기준으로 재조회된다.
  • 축 입력에 0 또는 100 같은 경계값을 넣어도 오류 없이 처리되고 음수·비정수 입력은 반영되지 않는다.
  • 조회가 실패하면 ErrorState[다시 시도]가 보인다.
  • 로딩 중에는 4행 스켈레톤이 보이고 저장 CTA가 비활성이다.
  • 가중치 화면을 다크 모드로 렌더하면 시맨틱 토큰 class만 사용하고 하드코딩 색이 0건이다.