Skip to content

[feat] 사장님 구인구직 UI 구현 및 API 연동 - #60

Open
limtjdghks wants to merge 23 commits into
devfrom
feat/ALT-274
Open

[feat] 사장님 구인구직 UI 구현 및 API 연동#60
limtjdghks wants to merge 23 commits into
devfrom
feat/ALT-274

Conversation

@limtjdghks

@limtjdghks limtjdghks commented Jul 29, 2026

Copy link
Copy Markdown
Member

ID

  • ALT-274

변경 내용

  • 사장님(매니저) 구인구직 플로우 화면 6종 신규 구현 (내 공고 목록 / 공고 등록 / 공고 수정 / 공고 상세 / 지원자 목록 / 지원자 상세)
  • 위 화면 전체를 MANAGER - 업장 관리자 공고 관리 API에 연동 (조회·등록·수정·마감·채용 결정)
  • Docbar에 사장님 전용 내 공고 / 지원자 탭 추가 및 라우트 등록
  • Toast / SelectDropdown / Skeleton 공용 컴포넌트, 커서 페이징·시간 포맷 유틸을 shared 계층에 추가
  • 매니저 홈의 "진행 중인 공고"가 항상 0건으로 표시되던 버그 수정

구현 사항

화면 · 라우팅

  • features/manager/posting 슬라이스 신규 구성 (api / types / lib / hooks / ui)
  • 정적 세그먼트가 :postingId보다 먼저 매칭되도록 라우트 순서 배치
  • 지원자 목록은 Docbar 탭과 공고 상세 두 경로로 진입하며, 진입 경로에 따라 뒤로가기 노출을 분기
  • 근무일정의 시간 입력을 네이티브 type="time" 대신 기존 휠 픽커(WorkTimePickerDrawer)로 통일

API 연동

  • 공고 목록 / 지원자 목록은 useInfiniteQuery + IntersectionObserver 커서 무한스크롤
  • 공고 수정은 폼의 단일 스케줄 배열을 원본 스케줄 ID와 비교해 createSchedules / updateSchedules / deleteScheduleIds 3분할로 변환 (lib/buildPostingRequest.ts)
  • 서버 DTO → UI 모델 변환을 types/dto.ts의 어댑터로 일원화. DescribedEnumDto 래퍼(status.value) 해제, contact→phoneNumber·birthday→birthDate·GENDER_MALE→남성·자격증 필드명 매핑 처리
  • 뮤테이션 실패 시 에러 코드(B007/B012/B017/B018/B019/B020)를 사용자 문구로 변환해 토스트 노출
  • 쿼리 키를 queryKeys.posting 하위로 통일해, 공고 변경 시 목록·상세·홈 카드를 한 번에 무효화

shared 승격 / 버그 수정

  • 커서 페이징 응답의 CommonApiResponse 래핑 여부가 엔드포인트마다 달라, 두 형태를 모두 정규화하는 unwrapCursorPage 추가
  • 매니저 홈이 위 응답을 wrapped로 단정해 목록을 항상 빈 배열로 읽던 문제 수정 (해당 업장에 공고 3건이 있어도 "진행 중인 공고 0건"으로 표시됨)
  • 서버가 근무 시간을 HH:mm:ss로 내려주어 화면에 07:00:00~12:00:00으로 노출되고 시간 픽커 초기값이 깨지던 문제를 toTimeOfDay로 정규화
  • 요일·급여·상대시간 포맷터와 ApplicationApiStatus 타입을 shared로 추출해 유저/매니저 양측에서 재사용

구현 시연 (필요 시)

480.mov

Summary by CodeRabbit

  • 새로운 기능

    • 매니저가 공고를 등록·수정·조회하고 모집을 마감할 수 있습니다.
    • 공고 목록과 지원자 목록을 필터링하고 무한 스크롤로 확인할 수 있습니다.
    • 지원자 상세 정보 확인 및 채용 상태 변경을 지원합니다.
    • 근무 일정, 급여, 지원 상태를 편리하게 입력하고 확인할 수 있습니다.
    • 작업 결과와 오류를 토스트 알림으로 안내합니다.
  • 개선

    • 매니저 전용 내비게이션 탭과 공고·지원자 화면 경로가 추가되었습니다.
    • 공고 및 지원자 상태가 배지와 안내 문구로 명확하게 표시됩니다.

@limtjdghks
limtjdghks requested a review from kim3360 July 29, 2026 11:21
@limtjdghks limtjdghks self-assigned this Jul 29, 2026
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
alter-client Ready Ready Preview, Comment Jul 29, 2026 12:01pm

Request Review

@limtjdghks limtjdghks changed the title Feat/alt 274 [feat] 사장님 구인구직 UI 구현 및 API 연동 Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@limtjdghks, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a9db220-55d0-4e1f-bd2a-a1fa6357e480

📥 Commits

Reviewing files that changed from the base of the PR and between b7f14d4 and f7c8516.

📒 Files selected for processing (2)
  • src/features/manager/posting/types/dto.ts
  • src/features/manager/posting/types/posting.ts
📝 Walkthrough

Walkthrough

매니저용 공고·지원자 관리 기능이 추가됐다. 커서 페이지네이션 API, 생성·수정·마감 및 지원자 상태 변경 흐름, 목록·상세·폼 화면, 매니저 전용 라우팅과 Docbar 탭, 토스트·공통 입력 UI가 구현됐다.

Changes

매니저 공고·지원자 관리

Layer / File(s) Summary
공유 타입과 페이지네이션 계약
src/shared/constants/*, src/shared/lib/*, src/features/manager/home/...
결제·요일·시간·상태 타입과 커서 페이지 응답 및 포맷 유틸이 추가되고 기존 공고 응답 해석이 커서 구조에 맞게 변경됐다.
공고·지원자 API와 도메인 변환
src/features/manager/posting/api/*, src/features/manager/posting/types/*, src/features/manager/posting/lib/*
공고·지원자 목록/상세/상태 변경 API, DTO 계약, 도메인 어댑터, 상태 배지와 오류 메시지 매핑이 추가됐다.
쿼리·뮤테이션·공고 폼 상태
src/features/manager/posting/hooks/*
무한 스크롤 목록·상세 조회, 워크스페이스 필터, 생성·수정·마감·지원 상태 변경 뮤테이션, 공고 폼 검증과 요청 변환이 구현됐다.
공고·지원자 공통 UI
src/features/manager/posting/ui/*, src/shared/ui/common/SelectDropdown.tsx, src/shared/ui/common/WheelPicker.tsx
목록 카드, 필터, 상태 배지, 채용 액션, 공고 입력 및 일정·시간 편집 UI가 추가됐다.
토스트와 공통 상태 표시
src/shared/stores/useToastStore.ts, src/shared/ui/common/Toast*.tsx, src/shared/ui/common/Skeleton.tsx, tailwind.config.js
토스트 상태·표시 컴포넌트와 스켈레톤 UI가 추가되고 앱 루트 및 애니메이션 설정에 연결됐다.
매니저 페이지와 라우팅
src/pages/manager/*, src/app/App.tsx, src/shared/constants/routes.ts
공고·지원자 목록, 생성·수정·상세 페이지와 MANAGER 보호 라우트 및 경로 빌더가 추가됐다.
매니저 탭과 공통 타입 정리
src/shared/ui/common/Docbar.tsx, src/shared/stores/useDocStore.ts, src/shared/types/*, storybook/stories/Docbar.stories.tsx, src/pages/manager/worker-schedule/*
지원자 탭과 매니저 라벨 오버라이드가 추가됐고, 근무시간 타입·유틸 import가 shared 경로로 통합됐다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: kim3360, dohy-eon

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.60% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 주요 변경인 사장님 구인구직 UI와 API 연동을 간결하게 요약해 제목이 적절합니다.
Description check ✅ Passed ID, 변경 내용, 구현 사항, 구현 시연까지 템플릿 요구를 대부분 채워 형식에 맞습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ALT-274

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/app/App.tsx (1)

33-38: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

신규 매니저 공고 페이지 6종에 lazy loading 미적용.

SignupPage는 이미 Suspense+lazy 패턴을 쓰고 있는데, 이번에 추가된 매니저 공고·지원자 페이지 6개(ManagerPostingListPage, ManagerPostingCreatePage, ManagerPostingDetailPage, ManagerPostingEditPage, ManagerApplicationListPage, ManagerApplicationDetailPage)는 정적 import라 초기 번들에 그대로 포함됩니다. 매니저 전용 대규모 기능이므로 코드 스플리팅 대상으로 적합합니다.

♻️ 제안: React.lazy + Suspense 적용
-import { ManagerPostingListPage } from '`@/pages/manager/posting-list`'
-import { ManagerPostingCreatePage } from '`@/pages/manager/posting-create`'
-import { ManagerPostingDetailPage } from '`@/pages/manager/posting-detail`'
-import { ManagerPostingEditPage } from '`@/pages/manager/posting-edit`'
-import { ManagerApplicationListPage } from '`@/pages/manager/application-list`'
-import { ManagerApplicationDetailPage } from '`@/pages/manager/application-detail`'
+const ManagerPostingListPage = lazy(() => import('`@/pages/manager/posting-list`'))
+const ManagerPostingCreatePage = lazy(() => import('`@/pages/manager/posting-create`'))
+const ManagerPostingDetailPage = lazy(() => import('`@/pages/manager/posting-detail`'))
+const ManagerPostingEditPage = lazy(() => import('`@/pages/manager/posting-edit`'))
+const ManagerApplicationListPage = lazy(() => import('`@/pages/manager/application-list`'))
+const ManagerApplicationDetailPage = lazy(() => import('`@/pages/manager/application-detail`'))

<HomeRouteGuard> 내부를 <Suspense fallback={null}>로 감싸주세요.

As per path instructions, src/app/**: "라우팅 구조가 lazy loading을 활용하는지 확인" 항목에 따라 지적합니다.

Also applies to: 171-211, 256-271

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/App.tsx` around lines 33 - 38, Replace the six static manager page
imports with React.lazy imports for ManagerPostingListPage,
ManagerPostingCreatePage, ManagerPostingDetailPage, ManagerPostingEditPage,
ManagerApplicationListPage, and ManagerApplicationDetailPage, following the
existing SignupPage pattern. Wrap each corresponding HomeRouteGuard route
element in Suspense with a null fallback, preserving the current route
configuration and guards.

Source: Path instructions

src/pages/manager/posting-list/index.tsx (1)

50-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

무한스크롤 sentinel 로직이 두 페이지에 중복 구현됨. sentinelRef + IntersectionObserver 셋업/해제 로직이 두 파일에서 변수명까지 동일하게 반복됩니다. 근본 원인은 공유 훅 부재이므로 하나로 추출하는 것을 권장합니다.

  • src/pages/manager/posting-list/index.tsx#L50-L69: 이 블록을 useInfiniteScrollSentinel({ hasNextPage, isFetchingNextPage, fetchNextPage, isLoading, count: postings.length }) 같은 공유 훅으로 추출하고 sentinelRef만 반환받아 사용.
  • src/pages/manager/application-list/index.tsx#L56-L75: 동일한 공유 훅을 재사용해 중복 블록 제거(count: applications.length로 전달).
♻️ 제안: 공유 훅 추출 예시
// src/shared/lib/useInfiniteScrollSentinel.ts
export function useInfiniteScrollSentinel({
  hasNextPage,
  isFetchingNextPage,
  fetchNextPage,
  isLoading,
  count,
}: {
  hasNextPage: boolean
  isFetchingNextPage: boolean
  fetchNextPage: () => void
  isLoading: boolean
  count: number
}) {
  const sentinelRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const el = sentinelRef.current
    if (!el) return

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
          fetchNextPage()
        }
      },
      { threshold: 0.1 }
    )

    observer.observe(el)
    return () => observer.disconnect()
  }, [hasNextPage, isFetchingNextPage, fetchNextPage, isLoading, count])

  return sentinelRef
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/manager/posting-list/index.tsx` around lines 50 - 69, Extract the
duplicated sentinelRef and IntersectionObserver setup from
src/pages/manager/posting-list/index.tsx lines 50-69 into a shared
useInfiniteScrollSentinel hook, accepting hasNextPage, isFetchingNextPage,
fetchNextPage, isLoading, and count, then return the ref for rendering. Replace
the duplicate block in src/pages/manager/application-list/index.tsx lines 56-75
with the same hook, passing applications.length as count; both sites should
otherwise preserve the existing fetch and cleanup behavior.
src/shared/ui/common/WheelPicker.tsx (1)

87-129: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

선택값이 스크린리더에 노출되지 않아요.

컨테이너에 role="listbox"만 있고 개별 항목엔 role/aria-selected가 없으며, 값 변경 시(ArrowUp/ArrowDown) 포커스나 aria-activedescendant 갱신도 없습니다. 근무시간처럼 필수 값을 고르는 컨트롤인데, 스크린리더 사용자는 현재 선택값을 전혀 알 수 없어 폼 작성을 완료하기 어렵습니다. listbox 대신 값-휠 특성에 맞는 slider/spinbutton + aria-valuenow/aria-valuetext 패턴이 더 적합합니다.

♿ 제안: spinbutton 시맨틱 추가
     <div
       data-vaul-no-drag
       className={cn(
         'relative touch-none select-none overflow-hidden focus:outline-none',
         className
       )}
       style={{ height: itemHeight * visibleCount }}
       aria-label={ariaLabel}
-      role="listbox"
+      role="slider"
+      aria-valuemin={0}
+      aria-valuemax={items.length - 1}
+      aria-valuenow={selectedIndex}
+      aria-valuetext={items[selectedIndex]}
       tabIndex={0}

**/*.tsx: "a11y: 서비스를 막는 수준(의미 있는 버튼/폼/이미지, 키보드 트랩 등)만" 지침에 근거해 필수 값-선택 컨트롤의 선택값 미노출을 지적합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/common/WheelPicker.tsx` around lines 87 - 129, Update the
WheelPicker container’s accessibility semantics from a bare listbox to a
value-selection control such as spinbutton, exposing the current selection
through aria-valuenow and aria-valuetext. Ensure handleKeyDown and any
pointer-driven value updates keep these attributes synchronized so screen
readers announce the selected item, while preserving the existing interaction
and visual behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/manager/posting/types/dto.ts`:
- Around line 231-240: Update adaptApplicant so a missing dto.gender does not
default to ‘남성’; preserve ‘여성’ for GENDER_FEMALE and map absent gender to the
same placeholder convention used for other missing applicant fields, such as
‘-’.

In `@src/features/manager/posting/ui/PostingFormFields.tsx`:
- Around line 77-88: 공고 제목, 급여, 상세내용 입력 요소에 접근 가능한 이름을 추가하세요. PostingFormFields의
해당 input/textarea 각각에 화면에 표시되는 Section 라벨과 일치하는 aria-label을 지정하고, 기존 값·변경 처리·오류
스타일은 유지하세요.

---

Nitpick comments:
In `@src/app/App.tsx`:
- Around line 33-38: Replace the six static manager page imports with React.lazy
imports for ManagerPostingListPage, ManagerPostingCreatePage,
ManagerPostingDetailPage, ManagerPostingEditPage, ManagerApplicationListPage,
and ManagerApplicationDetailPage, following the existing SignupPage pattern.
Wrap each corresponding HomeRouteGuard route element in Suspense with a null
fallback, preserving the current route configuration and guards.

In `@src/pages/manager/posting-list/index.tsx`:
- Around line 50-69: Extract the duplicated sentinelRef and IntersectionObserver
setup from src/pages/manager/posting-list/index.tsx lines 50-69 into a shared
useInfiniteScrollSentinel hook, accepting hasNextPage, isFetchingNextPage,
fetchNextPage, isLoading, and count, then return the ref for rendering. Replace
the duplicate block in src/pages/manager/application-list/index.tsx lines 56-75
with the same hook, passing applications.length as count; both sites should
otherwise preserve the existing fetch and cleanup behavior.

In `@src/shared/ui/common/WheelPicker.tsx`:
- Around line 87-129: Update the WheelPicker container’s accessibility semantics
from a bare listbox to a value-selection control such as spinbutton, exposing
the current selection through aria-valuenow and aria-valuetext. Ensure
handleKeyDown and any pointer-driven value updates keep these attributes
synchronized so screen readers announce the selected item, while preserving the
existing interaction and visual behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0826f7f3-5fd7-49f8-8037-ab543b407274

📥 Commits

Reviewing files that changed from the base of the PR and between 2859c2d and b7f14d4.

⛔ Files ignored due to path filters (11)
  • src/assets/icons/doc/Applicant.svg is excluded by !**/*.svg
  • src/assets/icons/posting/Alert.svg is excluded by !**/*.svg
  • src/assets/icons/posting/Calendar.svg is excluded by !**/*.svg
  • src/assets/icons/posting/CheckCircle.svg is excluded by !**/*.svg
  • src/assets/icons/posting/ChevronRight.svg is excluded by !**/*.svg
  • src/assets/icons/posting/Clock.svg is excluded by !**/*.svg
  • src/assets/icons/posting/Close.svg is excluded by !**/*.svg
  • src/assets/icons/posting/Lock.svg is excluded by !**/*.svg
  • src/assets/icons/posting/Person.svg is excluded by !**/*.svg
  • src/assets/icons/posting/Plus.svg is excluded by !**/*.svg
  • src/assets/icons/posting/Write.svg is excluded by !**/*.svg
📒 Files selected for processing (67)
  • src/app/App.tsx
  • src/features/manager/api/posting.ts
  • src/features/manager/home/hooks/useManagedPostingsViewModel.ts
  • src/features/manager/home/types/posting.ts
  • src/features/manager/posting/api/application.ts
  • src/features/manager/posting/api/posting.ts
  • src/features/manager/posting/hooks/mutation/useClosePostingMutation.ts
  • src/features/manager/posting/hooks/mutation/useCreatePostingMutation.ts
  • src/features/manager/posting/hooks/mutation/useUpdateApplicationStatusMutation.ts
  • src/features/manager/posting/hooks/mutation/useUpdatePostingMutation.ts
  • src/features/manager/posting/hooks/query/useManagerPostingDetailQuery.ts
  • src/features/manager/posting/hooks/query/useManagerPostingsQuery.ts
  • src/features/manager/posting/hooks/query/usePostingApplicationDetailQuery.ts
  • src/features/manager/posting/hooks/query/usePostingApplicationsQuery.ts
  • src/features/manager/posting/hooks/query/useWorkspaceFilterOptions.ts
  • src/features/manager/posting/hooks/useApplicationDetailViewModel.ts
  • src/features/manager/posting/hooks/useApplicationListViewModel.ts
  • src/features/manager/posting/hooks/usePostingDetailViewModel.ts
  • src/features/manager/posting/hooks/usePostingForm.ts
  • src/features/manager/posting/hooks/usePostingListViewModel.ts
  • src/features/manager/posting/lib/applicationStatus.ts
  • src/features/manager/posting/lib/buildPostingRequest.ts
  • src/features/manager/posting/lib/postingErrorMessage.ts
  • src/features/manager/posting/lib/postingStatus.ts
  • src/features/manager/posting/types/dto.ts
  • src/features/manager/posting/types/posting.ts
  • src/features/manager/posting/ui/ApplicantCard.tsx
  • src/features/manager/posting/ui/FilterBar.tsx
  • src/features/manager/posting/ui/HiringActionBar.tsx
  • src/features/manager/posting/ui/ManagerApplicationStatusBadge.tsx
  • src/features/manager/posting/ui/ManagerPostingStatusBadge.tsx
  • src/features/manager/posting/ui/PostingFormFields.tsx
  • src/features/manager/posting/ui/PostingListCard.tsx
  • src/features/manager/posting/ui/ScheduleEditor.tsx
  • src/features/user/home/applied-stores/types/application.ts
  • src/pages/manager/application-detail/index.tsx
  • src/pages/manager/application-list/index.tsx
  • src/pages/manager/posting-create/index.tsx
  • src/pages/manager/posting-detail/index.tsx
  • src/pages/manager/posting-edit/index.tsx
  • src/pages/manager/posting-list/index.tsx
  • src/pages/manager/worker-schedule/components/FixedScheduleDateSection.tsx
  • src/pages/manager/worker-schedule/components/GeneralScheduleDateSection.tsx
  • src/pages/manager/worker-schedule/components/WorkTimeRangeField.tsx
  • src/shared/constants/payment.ts
  • src/shared/constants/routes.ts
  • src/shared/constants/workingDays.ts
  • src/shared/lib/cursorPage.ts
  • src/shared/lib/formatKoreanWorkTime.ts
  • src/shared/lib/formatRelativeTime.ts
  • src/shared/lib/queryKeys.ts
  • src/shared/lib/toTimeOfDay.ts
  • src/shared/stores/useDocStore.ts
  • src/shared/stores/useToastStore.ts
  • src/shared/types/applicationStatus.ts
  • src/shared/types/tab.ts
  • src/shared/types/workTime.ts
  • src/shared/ui/common/Docbar.tsx
  • src/shared/ui/common/Navbar.tsx
  • src/shared/ui/common/SelectDropdown.tsx
  • src/shared/ui/common/Skeleton.tsx
  • src/shared/ui/common/Toast.tsx
  • src/shared/ui/common/ToastViewport.tsx
  • src/shared/ui/common/WheelPicker.tsx
  • src/shared/ui/common/WorkTimePickerDrawer.tsx
  • storybook/stories/Docbar.stories.tsx
  • tailwind.config.js

Comment thread src/features/manager/posting/types/dto.ts
Comment on lines +77 to +88
<Section label="공고 제목" required error={errors.title}>
<input
type="text"
value={values.title}
placeholder="예: 주말 홀서빙 구합니다"
onChange={e => form.setTitle(e.target.value)}
className={cn(
inputClassName,
errors.title ? 'border-error' : 'border-line-1 focus:border-main'
)}
/>
</Section>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

필수 입력 필드에 접근성 이름이 없어요.

공고 제목, 급여, 상세내용 섹션의 input/textarea는 Section의 시각적 <h2> 라벨과 프로그램적으로 연결되어 있지 않습니다(htmlFor/id 없음, aria-label도 없음). 스크린리더 사용자는 어떤 값을 입력해야 하는지 알 수 없어 공고 등록/수정을 완료하지 못할 수 있습니다. 반면 업장 선택SelectDropdownariaLabel이 있어 문제없습니다.

♿ 최소 수정안 (각 input에 aria-label 추가)
       <Section label="공고 제목" required error={errors.title}>
         <input
           type="text"
+          aria-label="공고 제목"
           value={values.title}
           <input
             type="text"
             inputMode="numeric"
+            aria-label="급여"
             value={
         <textarea
           value={values.description}
           rows={5}
+          aria-label="상세내용"
           placeholder="근무 조건, 우대 사항 등을 자유롭게 작성해 주세요"

**/*.tsx: "a11y: 서비스를 막는 수준(의미 있는 버튼/폼/이미지, 키보드 트랩 등)만." 지침에 근거해 필수 폼 입력의 이름 부재를 지적합니다.

Also applies to: 116-139, 141-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manager/posting/ui/PostingFormFields.tsx` around lines 77 - 88,
공고 제목, 급여, 상세내용 입력 요소에 접근 가능한 이름을 추가하세요. PostingFormFields의 해당 input/textarea
각각에 화면에 표시되는 Section 라벨과 일치하는 aria-label을 지정하고, 기존 값·변경 처리·오류 스타일은 유지하세요.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant