[FE-10] 공고 목록 화면 (회사 상세) — 상태 세그먼트 · 정렬 · 매칭 강등 섹션
작업 내용 (설계 의도)
근거 설계: 20260722-공고알림앱-design-fe-web.md — “S-03 회사 상세”, “방안 2 매칭/비매칭”
근거 요구: FR-50 · FR-25(매칭 실패 공고도 저장) · FR-30·33(근무형태는 라벨·정렬 전용, 필터 아님) · FR-34 · FR-18(CLOSED 소프트 삭제)
변경 사항
정보 밀도와 절제의 균형이 이 화면의 과제입니다. “아무것도 제외하면 안 되는데(FR-25·33) 다 똑같이 보여주면 밀도가 무너진다”를 세 가지 장치로 해결합니다.
- 상태는 세그먼트 탭(
진행 중/마감) — 서버postingStatus파라미터를 사용합니다.CLOSED공고는 삭제되지 않고(FR-18, NFR-5) 마감 탭에서 마감 사유와 함께 조회됩니다. 진행 중 탭에 섞지 않는 근거: 이 화면의 목적은 “지금 지원 가능한 것”의 판단이며, 섞으면 그 목적이 흐려집니다. - 매칭 여부는 필터가 아니라 강등 — 기본 목록은 매칭된 공고, 하단에 “매칭되지 않은 공고 N건” 접힘 섹션(기본 닫힘, 건수는 항상 노출)을 둡니다. 제외가 아니라 위계 분리라 FR-25를 지키면서 밀도를 얻습니다. 건수를 상시 노출하는 이유: 사용자가 키워드 조건을 바꿔야 할 시점을 인지할 수 있어야 합니다.
- 근무형태는 정렬 셀렉트의 한 옵션일 뿐입니다(FR-33). 어떤 확신도에서도 목록에서 제외하지 않습니다. 정렬 라벨은 “근무형태 확실한 순”으로 표기해 확신도 기준임을 드러냅니다(FR-30).
그 외:
- 필터·정렬 상태는 URL search params가 SSOT입니다(
?postingStatus=&sort=). 전역 스토어에 두지 않는 근거: 뒤로가기·새로고침·링크 공유가 자연히 동작해야 합니다. - 지원 기록이 있는 공고에
[지원함]배지를 표시하며, 마감 탭의 CLOSED 공고에서도 배지를 유지합니다(지원 이력은 공고 마감과 무관하게 보존). accessRestricted공고에 접근 제한 배지를 표시합니다(시나리오 6의 P0 표현).- 하단에
[공고 직접 추가]보조 CTA(outline) — 주 CTA와 경쟁하지 않게 secondary variant를 씁니다. - empty가 3종으로 갈립니다: 진행 중 0건(마감 탭 유도) / 마감 0건 / 회사 전체 0건(시딩 전 — “오늘 자정에 첫 수집이 진행돼요” + 직접 추가 유도).
계약 리스크 (blocking): 위 화면은 목록 응답의 matched·applied·workArrangement·closedReason·accessRestricted 필드에 의존하는데 TDD 계약이 {jobPostings:[...]}로만 명시돼 있습니다 → 계약 요청 #4. 미수용 시 폴백은 “접힘 섹션 제거(전건 단일 목록) + 지원함 배지 제거”이며 FR-25는 지키되 밀도가 떨어집니다.
범위: src/pages/posting/JobPostingListPage.tsx(스텁 대체), 전용 컴포넌트(PostingStatusSegment·PostingSortSelect·UnmatchedSection), src/api/posting/list.ts, src/hooks/posting/useJobPostings.ts.
의존
- FE-05 (JobPostingCard·WorkArrangementLabel·DeadlineText)
- BE 의존: BE-18 (공고 조회 API)
다이어그램
처리 흐름
sequenceDiagram participant U as 사용자 participant P as JobPostingListPage participant R as useSearchParams participant H as useJobPostings participant S as 서버 U->>P: /companies/1 진입 P->>R: postingStatus 기본 OPEN · sort 기본 DISCOVERED P->>H: (companyId, postingStatus, sort) H->>S: GET /api/companies/1/job-postings S-->>P: 공고 전건 (매칭·비매칭 모두) P-->>U: 매칭 공고 목록 + "매칭되지 않은 공고 N건" 접힘 U->>P: 마감 탭 클릭 P->>R: postingStatus=CLOSED (URL 갱신) P->>H: 재조회 S-->>P: 마감 공고 + closedReason
클래스 의존
flowchart LR subgraph Page["pages/posting"] List[JobPostingListPage] Seg[PostingStatusSegment] Sort[PostingSortSelect] Un[UnmatchedSection] end subgraph Domain["components/domain"] JC[JobPostingCard] end subgraph Hooks["hooks/posting"] H[useJobPostings] end subgraph Api["api/posting"] L[list.ts] end List --> Seg List --> Sort List --> Un List --> JC Un --> JC List --> H H --> L
테스트 케이스
- 매칭된 공고가 기본 목록에 렌더되고 비매칭 공고는 접힘 섹션에 들어간다
- 접힘 섹션 헤더에 비매칭 공고 건수가 항상 노출된다 (닫힌 상태에서도)
- 접힘 섹션을 펼치면 비매칭 공고가 렌더된다 (목록에서 제외되지 않음)
- 마감 탭으로 전환하면 URL의
postingStatus가 갱신되고 CLOSED 공고가 마감 사유와 함께 렌더된다 - 진행 중 탭에 CLOSED 공고가 렌더되지 않는다
- 정렬을 “근무형태 확실한 순”으로 바꾸면 URL의
sort가 갱신되고 재조회된다 - 근무형태 확신도가
UNKNOWN인 공고도 목록에서 제외되지 않는다 (필터 아님) - 확신도
UNKNOWN공고에 “근무형태 정보 없음”이 표기된다 - 지원 기록이 있는 공고에
[지원함]배지가 렌더된다 - 마감 탭의 CLOSED 공고에도 지원함 배지가 유지된다
accessRestricted공고에 접근 제한 배지가 렌더된다- 마감일이
null인 공고가 “상시채용”으로 표기된다 - 공고 카드 클릭 시
/job-postings/{id}로 이동한다 - 진행 중 0건이면 마감 탭으로 유도하는 empty가 렌더된다
- 회사 전체 공고가 0건이면 “오늘 자정에 첫 수집” 안내 empty가 렌더된다
- 조회 실패 시 목록 영역만 에러 대체되고 세그먼트·CTA는 유지된다
[공고 직접 추가]클릭 시/job-postings/new?companyId=1로 이동한다- URL에
postingStatus=CLOSED를 직접 넣고 진입하면 마감 탭이 선택된 상태로 렌더된다
2차 갱신 반영 (2026-07-22)
지원 여부 판정 변경 (계약 응답 #3)
- 목록 아이템의
applied: boolean이 **applicationId: number | null**로 확정됐습니다.[지원함]배지는applicationId != null조건으로 렌더하고, 배지 클릭 시applicationId로 지원 상세로 바로 이동합니다.
크로스 소스 표기 (FR-64)
- 목록은 대표 공고만 나옵니다(서버가
representative_id IS NULL로 거름).alternateSourceCount > 0인 공고에🔗 N곳 게재칩(FE-05CrossSourceChip)을 렌더합니다. sourceType(COMPANY_BOUND/AGGREGATOR)은 목록에서 별도 라벨로 강조하지 않습니다 — 대표 공고 기준이라 사용자는 출처 유형을 신경 쓸 필요가 없고, 구체 출처는 상세의alternateSources[]에 있습니다.
추가 테스트 케이스
applicationId != null인 공고에 지원함 배지가 렌더되고null이면 없다applicationId가 있는 공고의 지원함 배지 클릭 시 지원 상세로 이동한다alternateSourceCount가 2 이상인 공고에 ”🔗 N곳 게재” 칩이 렌더된다alternateSourceCount가 0인 공고에 크로스 소스 칩이 렌더되지 않는다