[feat] 사장님 구인구직 UI 구현 및 API 연동 - #60
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 20 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough매니저용 공고·지원자 관리 기능이 추가됐다. 커서 페이지네이션 API, 생성·수정·마감 및 지원자 상태 변경 흐름, 목록·상세·폼 화면, 매니저 전용 라우팅과 Docbar 탭, 토스트·공통 입력 UI가 구현됐다. Changes매니저 공고·지원자 관리
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (11)
src/assets/icons/doc/Applicant.svgis excluded by!**/*.svgsrc/assets/icons/posting/Alert.svgis excluded by!**/*.svgsrc/assets/icons/posting/Calendar.svgis excluded by!**/*.svgsrc/assets/icons/posting/CheckCircle.svgis excluded by!**/*.svgsrc/assets/icons/posting/ChevronRight.svgis excluded by!**/*.svgsrc/assets/icons/posting/Clock.svgis excluded by!**/*.svgsrc/assets/icons/posting/Close.svgis excluded by!**/*.svgsrc/assets/icons/posting/Lock.svgis excluded by!**/*.svgsrc/assets/icons/posting/Person.svgis excluded by!**/*.svgsrc/assets/icons/posting/Plus.svgis excluded by!**/*.svgsrc/assets/icons/posting/Write.svgis excluded by!**/*.svg
📒 Files selected for processing (67)
src/app/App.tsxsrc/features/manager/api/posting.tssrc/features/manager/home/hooks/useManagedPostingsViewModel.tssrc/features/manager/home/types/posting.tssrc/features/manager/posting/api/application.tssrc/features/manager/posting/api/posting.tssrc/features/manager/posting/hooks/mutation/useClosePostingMutation.tssrc/features/manager/posting/hooks/mutation/useCreatePostingMutation.tssrc/features/manager/posting/hooks/mutation/useUpdateApplicationStatusMutation.tssrc/features/manager/posting/hooks/mutation/useUpdatePostingMutation.tssrc/features/manager/posting/hooks/query/useManagerPostingDetailQuery.tssrc/features/manager/posting/hooks/query/useManagerPostingsQuery.tssrc/features/manager/posting/hooks/query/usePostingApplicationDetailQuery.tssrc/features/manager/posting/hooks/query/usePostingApplicationsQuery.tssrc/features/manager/posting/hooks/query/useWorkspaceFilterOptions.tssrc/features/manager/posting/hooks/useApplicationDetailViewModel.tssrc/features/manager/posting/hooks/useApplicationListViewModel.tssrc/features/manager/posting/hooks/usePostingDetailViewModel.tssrc/features/manager/posting/hooks/usePostingForm.tssrc/features/manager/posting/hooks/usePostingListViewModel.tssrc/features/manager/posting/lib/applicationStatus.tssrc/features/manager/posting/lib/buildPostingRequest.tssrc/features/manager/posting/lib/postingErrorMessage.tssrc/features/manager/posting/lib/postingStatus.tssrc/features/manager/posting/types/dto.tssrc/features/manager/posting/types/posting.tssrc/features/manager/posting/ui/ApplicantCard.tsxsrc/features/manager/posting/ui/FilterBar.tsxsrc/features/manager/posting/ui/HiringActionBar.tsxsrc/features/manager/posting/ui/ManagerApplicationStatusBadge.tsxsrc/features/manager/posting/ui/ManagerPostingStatusBadge.tsxsrc/features/manager/posting/ui/PostingFormFields.tsxsrc/features/manager/posting/ui/PostingListCard.tsxsrc/features/manager/posting/ui/ScheduleEditor.tsxsrc/features/user/home/applied-stores/types/application.tssrc/pages/manager/application-detail/index.tsxsrc/pages/manager/application-list/index.tsxsrc/pages/manager/posting-create/index.tsxsrc/pages/manager/posting-detail/index.tsxsrc/pages/manager/posting-edit/index.tsxsrc/pages/manager/posting-list/index.tsxsrc/pages/manager/worker-schedule/components/FixedScheduleDateSection.tsxsrc/pages/manager/worker-schedule/components/GeneralScheduleDateSection.tsxsrc/pages/manager/worker-schedule/components/WorkTimeRangeField.tsxsrc/shared/constants/payment.tssrc/shared/constants/routes.tssrc/shared/constants/workingDays.tssrc/shared/lib/cursorPage.tssrc/shared/lib/formatKoreanWorkTime.tssrc/shared/lib/formatRelativeTime.tssrc/shared/lib/queryKeys.tssrc/shared/lib/toTimeOfDay.tssrc/shared/stores/useDocStore.tssrc/shared/stores/useToastStore.tssrc/shared/types/applicationStatus.tssrc/shared/types/tab.tssrc/shared/types/workTime.tssrc/shared/ui/common/Docbar.tsxsrc/shared/ui/common/Navbar.tsxsrc/shared/ui/common/SelectDropdown.tsxsrc/shared/ui/common/Skeleton.tsxsrc/shared/ui/common/Toast.tsxsrc/shared/ui/common/ToastViewport.tsxsrc/shared/ui/common/WheelPicker.tsxsrc/shared/ui/common/WorkTimePickerDrawer.tsxstorybook/stories/Docbar.stories.tsxtailwind.config.js
| <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> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
필수 입력 필드에 접근성 이름이 없어요.
공고 제목, 급여, 상세내용 섹션의 input/textarea는 Section의 시각적 <h2> 라벨과 프로그램적으로 연결되어 있지 않습니다(htmlFor/id 없음, aria-label도 없음). 스크린리더 사용자는 어떤 값을 입력해야 하는지 알 수 없어 공고 등록/수정을 완료하지 못할 수 있습니다. 반면 업장 선택은 SelectDropdown에 ariaLabel이 있어 문제없습니다.
♿ 최소 수정안 (각 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
ID
변경 내용
MANAGER - 업장 관리자 공고 관리 API에 연동 (조회·등록·수정·마감·채용 결정)내 공고/지원자탭 추가 및 라우트 등록구현 사항
화면 · 라우팅
features/manager/posting슬라이스 신규 구성 (api/types/lib/hooks/ui):postingId보다 먼저 매칭되도록 라우트 순서 배치type="time"대신 기존 휠 픽커(WorkTimePickerDrawer)로 통일API 연동
useInfiniteQuery+ IntersectionObserver 커서 무한스크롤createSchedules/updateSchedules/deleteScheduleIds3분할로 변환 (lib/buildPostingRequest.ts)types/dto.ts의 어댑터로 일원화.DescribedEnumDto래퍼(status.value) 해제,contact→phoneNumber·birthday→birthDate·GENDER_MALE→남성·자격증 필드명 매핑 처리queryKeys.posting하위로 통일해, 공고 변경 시 목록·상세·홈 카드를 한 번에 무효화shared 승격 / 버그 수정
CommonApiResponse래핑 여부가 엔드포인트마다 달라, 두 형태를 모두 정규화하는unwrapCursorPage추가HH:mm:ss로 내려주어 화면에07:00:00~12:00:00으로 노출되고 시간 픽커 초기값이 깨지던 문제를toTimeOfDay로 정규화ApplicationApiStatus타입을 shared로 추출해 유저/매니저 양측에서 재사용구현 시연 (필요 시)
480.mov
Summary by CodeRabbit
새로운 기능
개선