[FE-07] 매칭 설정 화면 — 직무 키워드 그룹 · 제외어 · 근무형태 키워드 · 재평가

작업 내용 (설계 의도)

근거 설계: 20260722-공고알림앱-design-fe-web.md — “S-10 매칭 설정”, “낙관적 업데이트 적용 지점” 근거 요구: FR-22(동의어 그룹) · FR-23(제외어) · FR-26·33(근무형태 = 라벨·정렬 전용) · FR-25(재매칭)

변경 사항

매칭 기준을 등록·삭제하고, 기준이 바뀌면 저장된 공고를 다시 확인(재평가)하는 화면입니다.

핵심 설계 의도:

  • 내부 용어를 그대로 노출하지 않습니다(토스 Casual Concept). “동의어 그룹” → “같은 뜻으로 볼 단어”, “criteriaRevision 증가” → “조건이 바뀌었어요 / 저장된 공고를 다시 확인해 보세요”. 사용자는 revision이라는 개념을 몰라도 됩니다.
  • 근무형태가 필터가 아님을 화면에서 명시합니다(FR-33) — “목록에서 라벨과 정렬에만 쓰고, 공고를 걸러내지는 않아요”. 이 문구가 없으면 사용자는 근무형태 키워드를 등록하면 그 공고만 보인다고 오해합니다.
  • 재평가 배너는 revision이 바뀌었을 때만 노출합니다. 항상 떠 있으면 무시됩니다.
  • 낙관적 업데이트를 키워드 칩 추가·삭제에만 적용합니다. 근거: 조작이 가볍고 결과가 단순(칩 하나)이라 즉시 피드백이 자연스럽습니다. 롤백 흐름: onMutate에서 이전 캐시 스냅샷 → 실패 시 onError에서 복원 + 토스트(“추가하지 못했어요”) → onSettled에서 무효화. 자동 재시도는 하지 않습니다(사용자가 다시 누르는 편이 명확).
  • 재평가에는 낙관적 업데이트를 적용하지 않습니다 — 수 초가 걸리고 결과가 목록 전체에 영향을 미치므로 실제 완료를 기다려 결과 건수를 보여줍니다.
  • 삭제 확인 다이얼로그를 두지 않습니다 — 키워드는 재등록 비용이 낮고, 확인 단계가 잦은 조작을 방해합니다.

계약 리스크 (blocking): 매칭 기준 조회 API가 계약에 없습니다(POST·DELETE만 존재) → 계약 요청 #1. 제외어·근무형태 키워드 삭제 API도 없습니다 → 계약 요청 #2. 이 티켓은 MSW 목으로 개발·테스트를 완주하되, 실 연동은 두 계약 수용이 전제입니다. 미수용 시 이 화면은 등록 전용으로 축소되며 그 경우 사용자가 등록된 기준을 확인할 수 없다는 문제가 남습니다.

범위: src/pages/matching/MatchingSettingsPage.tsx(스텁 대체), 전용 컴포넌트(ReEvaluationBanner·KeywordGroupSection·KeywordChipSection), src/api/matching.ts, src/hooks/matching/**.

의존

  • FE-02, FE-03, FE-04
  • BE 의존: BE-12 (매칭 기준 CRUD), BE-13 (재평가 API)

다이어그램

처리 흐름

sequenceDiagram
    participant U as 사용자
    participant P as MatchingSettingsPage
    participant M as useRegisterExclusionKeyword
    participant C as Query 캐시
    participant S as 서버
    U->>P: 제외어 "인턴" 추가
    P->>M: mutate(keyword)
    M->>C: onMutate — 스냅샷 후 칩 낙관적 추가
    M->>S: POST /api/matching/exclusion-keywords
    alt 실패
        S-->>M: 500
        M->>C: onError — 스냅샷 복원 (칩 제거)
        M-->>U: 토스트 "추가하지 못했어요"
    else 성공
        S-->>M: {id, criteriaRevision}
        M->>C: onSettled — 무효화
        P-->>U: 재평가 배너 노출
    end

클래스 의존

flowchart LR
    subgraph Page["pages/matching"]
        Main[MatchingSettingsPage]
        Banner[ReEvaluationBanner]
        Group[KeywordGroupSection]
        ChipSec[KeywordChipSection]
    end
    subgraph Hooks["hooks/matching"]
        Read[useMatchCriteria]
        Write[키워드 mutation 4종]
        Re[useReEvaluate]
    end
    subgraph Api["api"]
        Client[matching.ts]
    end
    subgraph Ui["components/ui"]
        Chip
        Sheet[BottomSheet]
        Empty[EmptyState]
    end
    Main --> Read
    Main --> Banner
    Main --> Group
    Main --> ChipSec
    Banner --> Re
    Group --> Write
    ChipSec --> Write
    ChipSec --> Chip
    Group --> Sheet
    Group --> Empty
    Read --> Client
    Write --> Client
    Re --> Client

테스트 케이스

  • 등록된 직무 키워드 그룹이 동의어와 함께 렌더된다
  • 제외어·근무형태 키워드가 각각 칩으로 렌더된다
  • 근무형태 섹션에 “공고를 걸러내지는 않아요” 안내 문구가 렌더된다
  • 동의어 4개를 가진 직무 그룹을 추가하면 목록에 반영되고 재평가 배너가 노출된다
  • 제외어를 추가하면 칩이 즉시(서버 응답 전) 추가된다 (낙관적 업데이트)
  • 제외어 추가가 실패하면 낙관적으로 추가된 칩이 사라지고 토스트가 표시된다
  • 키워드 삭제가 실패하면 삭제된 칩이 복원된다
  • 재평가 배너는 criteriaRevision이 변하기 전에는 렌더되지 않는다
  • “다시 확인하기” 클릭 시 CTA에 로딩이 표시되고 완료 후 평가 건수 토스트가 뜬다
  • 재평가 실패 시 배너가 유지되고 에러 토스트가 표시된다
  • 빈 문자열·공백만 있는 키워드는 제출 버튼이 비활성이다
  • 각 섹션이 비어 있으면 섹션별 empty 안내가 렌더된다
  • 세 섹션이 모두 비어도 화면 자체는 정상 렌더된다 (설정 화면)
  • 기준 조회 실패 시 전체 에러 대체와 [다시 시도]가 렌더된다
  • 로딩 중 섹션별 스켈레톤이 렌더된다

2차 갱신 반영 (2026-07-22)

blocking 해소 (계약 응답 #1·#2)

  • GET /api/matching/criteria 신설 → 이 화면은 더 이상 blocking이 아니며 실 API로 완주합니다.
  • 제외어·근무형태 키워드 DELETE 신설(소프트 삭제 — 과거 평가 결과 참조 유지) → 삭제 흐름도 실 API 완주.

재평가 skip 표기 (계약 응답 #6)

  • “다시 확인하기”는 {force?}미전송(기본 false)해 revision이 바뀐 공고만 재평가합니다.
  • 응답 {evaluatedCount, skippedCount}를 토스트로 표기: “42건 재평가 · 1,200건은 이미 최신이라 건너뜀”. skippedCount가 화면 변화 적음을 설명해 “왜 아무 일도 안 일어났지” 오해를 막습니다.

추가 테스트 케이스

  • 매칭 기준 조회가 실 API 엔드포인트(GET /api/matching/criteria)를 사용한다
  • 제외어·근무형태 키워드 삭제가 각각의 DELETE 엔드포인트를 호출한다
  • 재평가 응답의 skippedCount가 토스트에 “이미 최신이라 건너뜀”으로 표기된다
  • 재평가 요청에 force가 기본 미전송된다