Skip to content

[feat] 알바생 대타요청 UI 구현 및 API 연동#31

Merged
dohy-eon merged 16 commits into
devfrom
feat/ALT-218
May 14, 2026
Merged

[feat] 알바생 대타요청 UI 구현 및 API 연동#31
dohy-eon merged 16 commits into
devfrom
feat/ALT-218

Conversation

@dohy-eon
Copy link
Copy Markdown
Member

@dohy-eon dohy-eon commented May 11, 2026

ID

  • ALT-218

변경 내용

  • 근무지 상세에서 대타 요청 플로우를 API와 연동했습니다. 교환 가능 근무자 조회, 특정 근무자 지정 대타 생성(SPECIFIC), 성공 시 스케줄 관련 쿼리 무효화까지 포함합니다.

구현 사항

  • GET /app/schedules/{scheduleId}/exchangeable-workers로 4단계에서 목록을 불러오고,
  • POST /app/schedules/{scheduleId}/substitute-requests에 requestType: SPECIFIC, targetId(workerId), requestReason을 보냅니다.
  • 검증·로딩·에러(getAxiosErrorMessage에 B001/B020) 처리를 넣었으며 성공 시 workspace 스케줄·해당 스케줄의 교환 근무자 쿼리를 무효화한 뒤 모달을 닫습니다.

구현 시연 (필요 시)

2026-05-11.12.51.16.mov

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 근로자 대체 요청 5단계 모달 플로우 추가
    • 교환 가능한 근로자 조회 및 대체 요청 API/모듈 추가
    • 캘린더용 대체 날짜 선택 패널 추가
  • 개선 사항

    • 일/주 캘린더에 예상 수익 표시 추가
    • 월 캘린더 제목에 우측 커스텀 액션 추가 가능
    • 무한 스크롤 "더보기" 표시 로직(총건수 반영) 개선
    • 캘린더 모달 z-index 상향 및 일정 데이터 정규화 강화
    • 네비게이션바에 우측 커스텀 액션 지원
  • 버그 수정

    • 특정 에러 코드에 대한 사용자 메시지 추가

Review Change Stack

@dohy-eon dohy-eon requested review from kim3360 and limtjdghks May 11, 2026 03:52
@dohy-eon dohy-eon self-assigned this May 11, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

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

Project Deployment Actions Updated (UTC)
alter-client Ready Ready Preview, Comment May 14, 2026 2:37am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@dohy-eon has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 30 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f4b5a4c7-e653-40f6-8b0e-737274f9cd67

📥 Commits

Reviewing files that changed from the base of the PR and between 30660bc and 6e7e0f2.

📒 Files selected for processing (17)
  • src/app/App.tsx
  • src/features/home/common/schedule/ui/MonthlyCalendar.tsx
  • src/features/manager/home/api/workerFixedSchedule.ts
  • src/features/manager/home/constants/managerWeekdayKo.ts
  • src/features/manager/home/hooks/useManagerHomeViewModel.ts
  • src/features/manager/home/hooks/useWorkerScheduleManageViewModel.ts
  • src/features/manager/home/lib/mapWorkerFixedScheduleSlots.ts
  • src/features/manager/home/types/workerFixedSchedule.ts
  • src/features/user/home/workspace/api/workspaceSchedule.ts
  • src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts
  • src/features/user/index.ts
  • src/pages/manager/home/index.tsx
  • src/pages/manager/worker-schedule/LegacyEntryRedirect.tsx
  • src/pages/manager/worker-schedule/index.tsx
  • src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx
  • src/shared/constants/routes.ts
  • src/shared/lib/queryKeys.ts
📝 Walkthrough

Walkthrough

이 PR은 월간/주간/일일 캘린더에 estimated earnings 표시, 대체 근로자 요청을 위한 5단계 모달 플로우, 무한 스크롤 페이지네이션의 totalCount 기반 개선을 포함합니다.


Changes

Calendar UI & Substitute Request Feature Integration

Layer / File(s) Summary
API & View Model 타입
src/features/user/home/schedule/types/dailyCalendar.ts, weeklyCalendar.ts, src/features/user/home/workspace/types/exchangeableWorkers.ts, substituteRequests.ts, workspaceSchedule.ts
Estimated earnings 필드, exchangeable workers DTO, substitute request 페이로드, 정규화된 workspace schedule 페이로드 타입 추가.
API 계층
src/features/user/home/workspace/api/exchangeableWorkers.ts, substituteRequests.ts, workspaceSchedule.ts
새로운 exchangeableWorkers/substituteRequests 엔드포인트, schedule 정규화 헬퍼(normalizeWorkspaceShifts, extractWorkspaceScheduleTotals), multi-month 통합 로직.
View Models
src/features/user/home/schedule/hooks/useDailyCalendarViewModel.ts, useWeeklyCalendarViewModel.ts, src/features/user/home/workspace/hooks/useWorkspaceWorkersViewModel.ts, useWorkspaceManagersViewModel.ts, useWorkspacesViewModel.ts, useAppliedStoresViewModel.ts, src/features/manager/home/hooks/useManagerHomeViewModel.ts
Earnings 텍스트 및 totalCount 메트릭 추가.
Pagination 유틸리티
src/shared/lib/listLoadMoreVisibility.ts, queryKeys.ts, getAxiosErrorMessage.ts
shouldShowInfiniteListLoadMore 헬퍼, exchangeableWorkers 쿼리 키, error fallback codes.
Calendar UI
src/features/home/common/schedule/ui/MonthYearPickerModal.tsx, MonthlyCalendar.tsx, MonthlyDateCell.tsx, src/features/user/home/schedule/ui/DailyCalendar.tsx, WeeklyCalendar.tsx, HomeScheduleCalendar.tsx
Month picker z-index 업데이트, interactive day selection, earnings 우측 표시, titleRightAction 지원.
Substitute Request Modal
src/pages/user/workspace-detail/components/SubstituteCalendarPickerPanel.tsx, SubstituteRequestModalFlow.tsx
5단계 모달: 날짜 선택 → 시간 입력 → 요약 → 근로자 선택 → 이유 입력.
Navbar 우측 액션
src/shared/ui/common/Navbar.tsx
detail variant에서 커스텀 rightAction 렌더링.
Page 통합
src/pages/manager/home/index.tsx, src/pages/user/applied-stores/index.tsx, src/pages/user/workspace/index.tsx, src/pages/user/workspace-detail/index.tsx, src/pages/user/workspace-members/hooks/useWorkspaceMembers.ts
shouldShowInfiniteListLoadMore 기반 load-more 표시 로직, substitute request modal 연결.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes


시니어 검토 포인트 (핵심 리스크)

🔴 1. workspaceSchedule 정규화의 안전성

문제: normalizeWorkspaceShifts/ extractWorkspaceScheduleTotals가 비정상 payload에 대해 조용히 빈값으로 처리해 데이터 손실을 일으킬 수 있음.
권장수정: 입력 타입 검증과 실패 시 로깅/경고를 추가하고, 필요시 호출자에 오류를 전파해 상위에서 폴백 UI를 보이게 할 것.

🔴 2. Substitute Request Flow 유효성 검증 부족

문제: 시간/일자/선택 근로자/사유에 대한 클라이언트 검증이 완전하지 않음(예: start >= end, 빈 reason, 유효한 scheduleId 확인).
권장수정: step 전후로 다음 검증 추가:

  • startTime < endTime 검사
  • reason.trim() 비어있음 차단
  • scheduleId 존재·유효성 확인
  • 선택된 candidate가 실제 fetch 결과에 존재하는지 확인

🔴 3. Day cell 접근성/행동 일관성

문제: 비활성(다음/이전 월) 셀은 시각적으로 버튼처럼 보일 수 있으나 클릭 핸들러가 없음 → 혼동/접근성 문제. MonthlyDateCell은 클릭 가능 여부에 따라 button/div로 바뀜.
권장수정: 비활성 셀은 명확히 non-interactive 요소(span/div, aria-disabled)로 렌더링하거나 클릭 시 월 전환 등 기대 동작을 제공해 일관성 유지.

🟡 4. totalCount 기반 load-more 정책 불투명

문제: LIST_LOAD_MORE_MIN_TOTAL_COUNT = 4 하드코딩. totalCount 추출이 항상 첫 페이지만 참고하는 구현은 페이징 흐름에 따라 부정확할 수 있음.
권장수정: 상수화 근거 문서화 또는 설정화, totalCount를 신뢰할 수 있도록 쿼리 최신화/의존성 명확화.

🟡 5. 통화 포맷 일관성

문제: estimatedEarningsText 생성에 toLocaleString('ko-KR') 사용이 환경/로케일에 의존적이고 음수/0 처리 미정의.
권장수정: 음수/0 처리 명확화, 필요 시 i18n 유틸 사용으로 포맷 일원화.


Possibly related PRs


Suggested reviewers

  • kim3360
  • limtjdghks
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.36% 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
Title check ✅ Passed 제목은 PR의 핵심 기능(대타요청 UI 구현 및 API 연동)을 명확하고 간결하게 요약하고 있습니다.
Description check ✅ Passed ID, 변경 내용, 구현 사항을 포함하여 템플릿 요구사항을 충족하며, API 엔드포인트, 요청 페이로드, 무효화 로직이 상세히 기술되어 있습니다.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ALT-218

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/pages/user/workspace-detail/index.tsx (1)

17-17: ⚡ Quick win

조건부 모달을 정적 import해서 초기 번들에 포함되고 있습니다

SubstituteRequestModalFlow는 열릴 때만 필요한데 정적 import라 초기 로드 비용이 증가합니다. 페이지 가이드대로 React.lazy + Suspense로 분리하는 게 좋습니다.

코드 스플리팅 예시
-import { useEffect, useMemo, useState } from 'react'
+import { Suspense, lazy, useEffect, useMemo, useState } from 'react'
 ...
-import { SubstituteRequestModalFlow } from './components/SubstituteRequestModalFlow'
+const SubstituteRequestModalFlow = lazy(
+  () => import('./components/SubstituteRequestModalFlow')
+)
 ...
-        {substituteFlowOpen ? (
-          <SubstituteRequestModalFlow
-            key={substituteFlowSession}
-            onClose={() => setSubstituteFlowOpen(false)}
-            storeName={(storeDisplayName ?? '근무 업장').trim()}
-            calendarData={calendarData}
-            initialMonth={baseDate}
-            workspaceId={Number.isFinite(id) && id > 0 ? id : undefined}
-          />
-        ) : null}
+        {substituteFlowOpen ? (
+          <Suspense fallback={null}>
+            <SubstituteRequestModalFlow
+              key={substituteFlowSession}
+              onClose={() => setSubstituteFlowOpen(false)}
+              storeName={(storeDisplayName ?? '근무 업장').trim()}
+              calendarData={calendarData}
+              initialMonth={baseDate}
+              workspaceId={Number.isFinite(id) && id > 0 ? id : undefined}
+            />
+          </Suspense>
+        ) : null}

As per coding guidelines "src/pages/**: React.lazy / Suspense를 통한 코드 스플리팅 적용 여부".

Also applies to: 128-137

🤖 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/user/workspace-detail/index.tsx` at line 17,
SubstituteRequestModalFlow is statically imported and should be code-split:
replace the top-level import of SubstituteRequestModalFlow with a React.lazy
dynamic import (e.g. const SubstituteRequestModalFlow = React.lazy(() =>
import('./components/SubstituteRequestModalFlow'))), and wrap where the modal is
rendered with <Suspense fallback={...}> so it only loads when opened; apply the
same React.lazy + Suspense pattern to any other conditionally-used components
referenced around the 128-137 block to avoid including them in the initial
bundle.
🤖 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/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx`:
- Around line 92-192: This component (SubstituteRequestModalFlow) contains too
much business logic (state machine, API queries/mutations, error mapping, and
query invalidation); extract that logic into a reusable hook (e.g.,
useSubstituteRequestFlow) or feature component and leave
SubstituteRequestModalFlow as a composition-only layer. Move state variables
(step, substituteReason, selfIntroduction, substituteCalendarBaseDate,
selectedCalendarDate, startHour/startMin/endHour/endMin, selectedCandidateKeys),
the useMemo selectors (selectedWeekdayLabel, summarySelectedTimeLabel,
substituteScheduleId), the useQuery call for exchangeable workers
(queryKeys.workspace.exchangeableWorkers / getExchangeableWorkers /
refetchExchangeable), and the useMutation substituteRequestMutation
(createSubstituteRequest + onSuccess invalidations) into the new hook, expose
necessary setters and derived values, and update SubstituteRequestModalFlow to
call useSubstituteRequestFlow and only render UI and pass handlers.
- Around line 57-75: pickScheduleIdForSelectedDate currently picks the first
event of the selected day and ignores the selected time, causing wrong
scheduleId for days with multiple shifts; update pickScheduleIdForSelectedDate
to use the full selected Date (including time) to find the matching event in
calendarData.events (e.g., match by comparing selected.getTime() against new
Date(event.startDateTime).getTime() and new Date(event.endDateTime).getTime(),
or by matching formatted time strings used where the selection time is built),
return that event.shiftId if numeric and finite (preserve the existing
Number.isFinite check), and only fall back to the first-event behavior if no
time-match is found; references: pickScheduleIdForSelectedDate,
CalendarViewData.events, DATE_KEY_FORMAT, and the code path that builds the
selection time string.

In `@src/pages/user/workspace-detail/index.tsx`:
- Around line 43-54: The current useEffect that calls fetchNextPage() (watching
storeDisplayName, hasNextPage, isFetchingNextPage, fetchNextPage, id) can
repeatedly trigger continuous page fetches when storeDisplayName is missing;
change it to perform a single supplemental fetch instead of repeated
auto-pagination by adding a one-time guard (e.g., a local ref or state like
hasPerformedSupplementalFetch) or split the logic to call a dedicated
single-item lookup path when id is present but storeDisplayName is missing;
update the effect around useEffect/fetchNextPage to check and set that guard so
fetchNextPage() runs at most once for the missing storeDisplayName case and
preserve existing checks for hasNextPage/isFetchingNextPage/id.

---

Nitpick comments:
In `@src/pages/user/workspace-detail/index.tsx`:
- Line 17: SubstituteRequestModalFlow is statically imported and should be
code-split: replace the top-level import of SubstituteRequestModalFlow with a
React.lazy dynamic import (e.g. const SubstituteRequestModalFlow = React.lazy(()
=> import('./components/SubstituteRequestModalFlow'))), and wrap where the modal
is rendered with <Suspense fallback={...}> so it only loads when opened; apply
the same React.lazy + Suspense pattern to any other conditionally-used
components referenced around the 128-137 block to avoid including them in the
initial bundle.
🪄 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

Run ID: a0b6022d-0a0a-4383-b86b-a9579cd45b39

📥 Commits

Reviewing files that changed from the base of the PR and between f13663e and f63f1f3.

⛔ Files ignored due to path filters (1)
  • src/assets/icons/catppuccin_changelog.svg is excluded by !**/*.svg
📒 Files selected for processing (33)
  • src/features/home/common/schedule/ui/MonthYearPickerModal.tsx
  • src/features/home/common/schedule/ui/MonthlyCalendar.tsx
  • src/features/home/common/schedule/ui/MonthlyDateCell.tsx
  • src/features/manager/home/hooks/useManagerHomeViewModel.ts
  • src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts
  • src/features/user/home/applied-stores/hooks/useAppliedStoresViewModel.ts
  • src/features/user/home/schedule/hooks/useDailyCalendarViewModel.ts
  • src/features/user/home/schedule/hooks/useWeeklyCalendarViewModel.ts
  • src/features/user/home/schedule/types/dailyCalendar.ts
  • src/features/user/home/schedule/types/weeklyCalendar.ts
  • src/features/user/home/schedule/ui/DailyCalendar.tsx
  • src/features/user/home/schedule/ui/HomeScheduleCalendar.tsx
  • src/features/user/home/schedule/ui/WeeklyCalendar.tsx
  • src/features/user/home/workspace/api/exchangeableWorkers.ts
  • src/features/user/home/workspace/api/substituteRequests.ts
  • src/features/user/home/workspace/api/workspaceSchedule.ts
  • src/features/user/home/workspace/hooks/useWorkspaceManagersViewModel.ts
  • src/features/user/home/workspace/hooks/useWorkspaceWorkersViewModel.ts
  • src/features/user/home/workspace/hooks/useWorkspacesViewModel.ts
  • src/features/user/home/workspace/types/exchangeableWorkers.ts
  • src/features/user/home/workspace/types/substituteRequests.ts
  • src/features/user/home/workspace/types/workspaceSchedule.ts
  • src/pages/manager/home/index.tsx
  • src/pages/user/applied-stores/index.tsx
  • src/pages/user/workspace-detail/components/SubstituteCalendarPickerPanel.tsx
  • src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx
  • src/pages/user/workspace-detail/index.tsx
  • src/pages/user/workspace-members/hooks/useWorkspaceMembers.ts
  • src/pages/user/workspace/index.tsx
  • src/shared/lib/getAxiosErrorMessage.ts
  • src/shared/lib/listLoadMoreVisibility.ts
  • src/shared/lib/queryKeys.ts
  • src/shared/ui/common/Navbar.tsx

Comment thread src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx Outdated
Comment thread src/pages/user/workspace-detail/index.tsx
Comment thread src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts
Comment thread src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts Outdated
Comment thread src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts
Comment thread src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/pages/user/workspace-detail/hooks/useSubstituteRequestFlow.ts`:
- Around line 71-302: The hook useSubstituteRequestFlow contains business logic
and must be moved out of the pages layer into the feature layer's hooks (create
a hooks file under the appropriate feature and copy useSubstituteRequestFlow
there), then update the feature's index.ts to re-export useSubstituteRequestFlow
as the public API; update all imports in the pages code to import
useSubstituteRequestFlow from the feature index, and fix any relative import
paths inside the moved file (queries, mutations, constants like WEEKDAY_LABELS,
queryKeys, getExchangeableWorkers, createSubstituteRequest, etc.) so they
reference the correct feature/shared modules; run the app/tests to ensure
query/mutation behavior and query invalidation still work after the move.
🪄 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

Run ID: 2397bcc3-8018-4dde-947f-c8f86e2f57a9

📥 Commits

Reviewing files that changed from the base of the PR and between f63f1f3 and 30660bc.

📒 Files selected for processing (7)
  • src/features/home/common/schedule/lib/date.ts
  • src/features/manager/home/hooks/useTodaySchedulesViewModel.ts
  • src/features/manager/home/hooks/useWorkerScheduleManageViewModel.ts
  • src/pages/manager/worker-schedule/index.tsx
  • src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx
  • src/pages/user/workspace-detail/hooks/useSubstituteRequestFlow.ts
  • src/pages/user/workspace-detail/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/user/workspace-detail/index.tsx

Comment thread src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts
@dohy-eon dohy-eon merged commit 87d467e into dev May 14, 2026
4 checks passed
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.

3 participants