[FE-42] 연락 검토 목록
작업 내용 (설계 의도)
근거: 지원 관리 확장 FE 웹 설계 S-26 연락 검토 목록 — 3단계 · 상태 표기 규칙 · 컴포넌트 트리 · Query 규약 · API 연동 > 3단계 · Single Writer per File 검증 > 단계 3 wave 2, 지원 관리 확장 TDD 3단계 — 연락 검토 화면 (FR-90)의 GET /api/contact-events · ContactEventListItem.
변경 사항
- 인박스형 목록 화면(S-26)을 만듭니다. 미처리 항목이 위, 처리 완료가 아래로 접히는 토스 인박스 패턴이며, 각 행은 “누가·무엇을·언제” 한 줄 요약 + 신뢰도 신호 1개로 구성합니다.
- 조회는
useInfiniteQuery(['contact-events', { reviewStatus, days }])로 하고PageResponse.hasNext로[더 보기]를 제어합니다. 페이지 조회 로직은 훅이 소유하고 페이지는 컨테이너로서 훅만 호출합니다. - 기본 세그먼트는
PENDING(검토 대기) 입니다 — 이 화면에 오는 이유가 그것이기 때문입니다. 세그먼트 상태는useSearchParams(URL)로 두어 뒤로가기·링크 공유가 동작하게 합니다. - 세그먼트 라벨에
PageResponse.totalCount를 붙입니다(검토 대기 3). 건수를 클라이언트에서 배열 길이로 세지 않습니다 — 무한 쿼리는 로드된 페이지만 갖고 있어 전체 건수와 다릅니다. topCandidateConfidence === null(후보 0건)은[후보 없음]중립 칩으로 표시해LOW와 구분합니다. 전자는 계산 자체가 불가했던 상태이고 후자는 계산했으나 낮은 상태라, 같은 칩으로 묶으면 사용자가 “낮게 판단됐다”로 오해합니다.- 신뢰도 칩
ConfidenceChip은 wave 1의 FE-40이 소유합니다 — FE-42·FE-43이 같은 wave에서 둘 다 쓰므로 정의를 선행 wave로 올렸습니다. 이 티켓은 import만 하고 수정하지 않습니다. 다만topCandidateConfidence === null(후보 0건)을[후보 없음]중립 칩으로 렌더해LOW와 구분하는 목록 화면의 판단은 이 티켓이 소유합니다. - 빈 상태를 3종으로 구분합니다 — ① 검토 대기 0건은 “검토할 연락이 없어요 / 새 연락이 오면 디스코드로 알려드려요”로 긍정 상태라 CTA를 두지 않습니다, ② 반영됨·무시됨 0건은 “아직 없어요” 1줄, ③ 최근 30일 전체 0건은 Gmail 연동 안내 +
[운영 열기]. 세 경우를 한 문구로 합치면 정상 상태가 문제처럼 보입니다. - 신뢰도 칩·상태 표기는 기존 토큰 매핑만 씁니다(높음 filled positive / 보통 outlined neutral / 낮음 filled warning / 후보 없음 중립). 신규 색 토큰 0건입니다.
- 카드 탭 시
/contact-events/:contactEventId로 이동합니다. 라우트는 FE-41이 이미 선언했으므로 이 티켓은 링크만 겁니다.
의존
- FE-40 —
ContactEventListItem·ContactConfidenceLevel타입,['contact-events', …]queryKey, 목록 MSW 목(세그먼트·페이지 반영)을 사용합니다. - FE-41 —
/contact-events라우트와 스텁 페이지를 이 티켓이 구현으로 대체합니다. - BE-77 —
GET /api/contact-events구현. 계약이 SSOT이므로 MSW로 선행 착수하고 통합 검증만 BE-77 이후에 합니다.
다이어그램
처리 흐름
sequenceDiagram participant User as 사용자 participant Page as ContactEventListPage participant Hook as useContactEvents participant API as GET /api/contact-events User->>Page: 연락 검토 열기 (기본 PENDING) Page->>Hook: reviewStatus·days 로 무한 조회 Hook->>API: page=0 API-->>Hook: PageResponse + totalCount Hook-->>Page: items · hasNext · totalCount Page-->>User: 세그먼트 건수 + 카드 목록 User->>Page: 더 보기 Page->>Hook: 다음 페이지 요청
컴포넌트 의존
flowchart LR Page[ContactEventListPage] --> Hook[useContactEvents] Hook --> Api[fetchContactEvents] Api --> Types[ContactEventListItem] Page --> Segment[Segment 기존 UI] Page --> Card[ContactEventCard] Card --> Chip[ConfidenceChip · FE-40 소유] Chip --> Util[utils/contactEvent] Page --> Empty[EmptyState 3종] Page --> Error[ErrorState]
테스트 케이스
- 검토 대기 연락 3건이 있으면 카드 3장과 세그먼트 라벨 “검토 대기 3”이 보이고, 건수는
totalCount에서 온다. - 화면 진입 시 별도 조작 없이
PENDING세그먼트가 선택돼 있고 검토 대기 목록이 조회된다. - 세그먼트를
반영됨으로 바꾸면 URL이 갱신되고reviewStatus=APPLIED로 재조회되며, 뒤로가기로PENDING으로 돌아온다. topCandidateConfidence === null인 카드에는[후보 없음]중립 칩이 보이고LOW(낮음) 칩과 다른 라벨로 표시된다.- 검토 대기 0건이면 “검토할 연락이 없어요” 긍정 빈 상태가 보이고 CTA 버튼이 렌더되지 않는다.
- 반영됨 세그먼트가 0건이면 “아직 없어요” 1줄만 보이고 검토 대기용 긍정 문구는 보이지 않는다.
- 최근 30일 전체가 0건이면 Gmail 연동 안내와
[운영 열기]가 보인다. - 목록 조회가 5xx로 실패하면
ErrorState와[다시 시도]가 보이고, 재시도하면 다시 조회한다. hasNext=true면[더 보기]가 보이고 누르면 다음 페이지가 append되며,hasNext=false면 버튼이 렌더되지 않는다.- 카드를 누르면
/contact-events/{contactEventId}로 이동한다. - 로딩 중에는 카드 스켈레톤 3개가 보이고 빈 상태 문구는 보이지 않는다.
- 목록 화면을 다크 모드로 렌더하면 시맨틱 토큰 class만 사용하고 하드코딩 색이 0건이다.