[FE-11] 공고 상세 화면 · 지원 기록 생성 시트
작업 내용 (설계 의도)
근거 설계: 20260722-공고알림앱-design-fe-web.md — “S-04 공고 상세”
근거 요구: FR-29(근거·확신도 함께 저장) · FR-34 · FR-42(미지원 = 기록 미존재) · FR-21(수동 공고도 동일 지원 관리) · 시나리오 6
변경 사항
공고 하나의 판단 근거를 모두 보여주고, “지원 기록을 남길 것인가”라는 하나의 결론으로 이어지는 화면입니다(토스 Clear CTA).
핵심 설계 의도:
- CTA는 언제나 하나입니다. 지원 기록이 없으면
[지원 기록 남기기], 있으면[지원 현황 보기]로 바뀝니다. 버튼을 2개로 늘리지 않습니다. - “미지원”을 상태로 표현하지 않습니다(FR-42). 응답의
application이null이면 그냥 CTA가 생성 모드일 뿐이고,NOT_APPLIED같은 배지를 만들지 않습니다. - 근무형태 근거를 스니펫과 함께 노출합니다(FR-29). 목록에서는 라벨만 보이지만 상세에서는 “왜 그렇게 판정했는가”(근거 단계 + 원문 스니펫)를 볼 수 있어야 사용자가 확신도를 신뢰하거나 의심할 수 있습니다.
- 매칭 결과도 근거와 함께 표시합니다 — 매칭됨이면 매칭된 그룹, 매칭 안 됨이면 사유(제외어 등). 매칭 실패 공고도 조회 대상이므로(FR-25) “왜 알림이 안 왔는가”를 여기서 답합니다.
accessRestricted면 상단 warning 배너로 “로그인이 필요한 공고예요”를 알리고 공고 바로가기를 강조합니다(시나리오 6 — P0에서는 알림 대신 배지·배너로 표현).- 지원 기록 생성 시트는 Minimum Input입니다 — 지원일 기본값을 오늘로 채우고 메모는 선택입니다. “기록하면 ‘지원 완료’ 상태로 시작해요”로 결과를 사전 고지합니다.
- 시트 제출 실패 시 입력값을 보존합니다. 409(이미 지원 기록 존재)는 시트를 유지한 채 인라인 에러 + 지원 현황 링크를 제공합니다.
- 자동 수집 공고와 수동 등록 공고의 화면·흐름이 동일합니다(FR-21) — origin에 따른 분기를 만들지 않습니다.
범위: src/pages/posting/JobPostingDetailPage.tsx(스텁 대체), 전용 컴포넌트(WorkArrangementEvidenceBox·MatchResultBox·CreateApplicationSheet), src/api/posting/detail.ts, src/api/application/create.ts, src/hooks/posting/useJobPosting.ts, src/hooks/application/useCreateApplication.ts.
의존
- FE-05 (WorkArrangementLabel·DeadlineText·ApplicationStatusChip)
- BE 의존: BE-18 (공고 상세), BE-14 (지원 기록 생성)
다이어그램
처리 흐름
sequenceDiagram participant U as 사용자 participant P as JobPostingDetailPage participant H as useJobPosting participant Sheet as CreateApplicationSheet participant M as useCreateApplication participant S as 서버 P->>H: GET /api/job-postings/{id} alt application 이 null P-->>U: CTA "지원 기록 남기기" U->>Sheet: CTA 클릭 Sheet->>M: mutate(jobPostingId, appliedAt, memo?) M->>S: POST /api/applications alt 409 이미 지원함 S-->>Sheet: 409 Sheet-->>U: 인라인 에러 + 지원 현황 링크 else 성공 S-->>M: {applicationId} M-->>P: 상세 무효화 후 지원 상세로 이동 end else application 존재 P-->>U: 상태 칩 + CTA "지원 현황 보기" end
클래스 의존
flowchart LR subgraph Page["pages/posting"] Detail[JobPostingDetailPage] Evi[WorkArrangementEvidenceBox] Match[MatchResultBox] Sheet[CreateApplicationSheet] end subgraph Domain["components/domain"] WA[WorkArrangementLabel] DT[DeadlineText] SC[ApplicationStatusChip] end subgraph Hooks["hooks"] HP[useJobPosting] HA[useCreateApplication] end Detail --> Evi Detail --> Match Detail --> Sheet Detail --> WA Detail --> DT Detail --> SC Detail --> HP Sheet --> HA
테스트 케이스
- 공고 제목·회사·플랫폼·근무형태 라벨·마감 표기가 렌더된다
- 근무형태 근거 박스에 확신도와 원문 스니펫이 렌더된다
- 확신도가
UNKNOWN이면 “근무형태 정보 없음”이 표기되고 근거 박스가 렌더되지 않는다 - 매칭된 공고에 매칭된 키워드 그룹이 표시된다
- 매칭 안 된 공고에 제외 사유가 표시된다
- 지원 기록이 없으면 CTA가 “지원 기록 남기기”로 렌더된다
- 지원 기록이 있으면 CTA가 “지원 현황 보기”로 바뀌고 상태 칩이 상단에 렌더된다
- 지원 기록이 없어도
NOT_APPLIED같은 상태 배지가 렌더되지 않는다 accessRestricted공고에 상단 warning 배너가 렌더된다- 수동 등록 공고도 자동 수집 공고와 동일한 CTA·흐름으로 렌더된다
- 지원 기록 생성 시트의 지원일 기본값이 오늘 날짜다
- 시트 제출 성공 시 지원 상세로 이동하고 토스트가 표시된다
- 시트 제출이 409면 시트가 유지되고 인라인 에러와 지원 현황 링크가 보인다
- 시트 제출이 500이면 입력값이 보존된 채 에러 토스트가 표시된다
- 404 응답 시 “공고를 찾을 수 없어요”와 회사 목록 이동 수단이 렌더된다
- 로딩 중 스켈레톤이 렌더되고 CTA는 비활성 상태로 보인다
- 공고 바로가기 링크가 새 탭으로 열리는 안전한 속성(
rel="noopener noreferrer")을 갖는다
2차 갱신 반영 (2026-07-22) — 크로스 소스 출처 (FR-64)
AlternateSourcesBox 추가
- 상세 응답의
alternateSources[]가 비어 있지 않으면 “이 공고는 N곳에 게재됐어요” 박스를 렌더합니다(AlternateSourcesBox— 이 화면 전용, 공용화하지 않음). - 대표 출처를 “(대표)“로 표시하고(
isRepresentative), 각 출처는 외부 링크(새 탭,rel="noopener noreferrer")로 렌더합니다. - “중복”·“dedup” 같은 내부 용어를 쓰지 않고 “게재됐어요”로 표현합니다(토스 Casual Concept). 지원·마감·매칭은 대표 공고 기준이므로 사용자는 대표 하나만 신경 쓰면 됩니다.
alternateSources[]가 비면 박스를 렌더하지 않습니다(단독 공고).- 지원 여부는 상세 응답의
application(없으면 null) 기준으로 기존과 동일하게 처리합니다.
계약 (요청 #11 확정 대기)
alternateSources[]아이템 shape({ jobSourceId, platform, postingUrl, isRepresentative })는 요청안 기준으로 MSW 목·타입에 선언하고, BE 확정 시 FE-17이 동기화합니다.
추가 테스트 케이스
alternateSources[]가 3건이면 대표 1건이 “(대표)“로, 나머지가 외부 링크로 렌더된다alternateSources[]가 비면 크로스 소스 박스가 렌더되지 않는다- 대체 출처 링크가 새 탭 안전 속성(
rel="noopener noreferrer")을 갖는다
3차 갱신 반영 (2026-07-22, BE 최종 확정)
alternateSources[] shape 확정
- shape =
{ jobSourceId, platform, postingUrl, isRepresentative }, 대표 자신을 포함한 그룹 전체가 반환됩니다(기존 “확정 대기” 해소). isRepresentative === true인 항목을 “(대표)“로 강조하고 나머지는 외부 링크로 렌더합니다.sourceLabel필드가 없으므로platform을 FE-03platformLabel로 매핑해 표시합니다(예:SARAMIN→ “사람인”).alternateSources[]가 비면(단독 공고) 박스를 렌더하지 않습니다.
정정 테스트 케이스
alternateSources[]에서isRepresentative=true인 항목이 “(대표)“로 강조된다- 대체 출처의
platform이 한글 라벨로 렌더된다