Skip to content

[feat] 알바생, 사장님 홈 API 연동#13

Open
limtjdghks wants to merge 26 commits intodevfrom
feat/ALT-170
Open

[feat] 알바생, 사장님 홈 API 연동#13
limtjdghks wants to merge 26 commits intodevfrom
feat/ALT-170

Conversation

@limtjdghks
Copy link
Copy Markdown
Member

@limtjdghks limtjdghks commented Apr 16, 2026

ID

  • ALT-170

변경 내용

  • 알바생/사장님 메인 페이지 및 관련 세부 페이지 전체 API 연동
  • 디자인 수정사항 반영하여 기존 컴포넌트 및 페이지 수정
  • 스케줄 캘린더 컴포넌트를 features/home/common으로 이동하여 알바생·사장님 공용으로 리팩토링

구현 사항

알바생 (User)

  • 나의 근무 스케줄 조회 API 연동 (월별/주별/일별 모드)
  • 현재 근무중인 업장 목록 조회 API 연동
  • 업장별 근무 스케줄 및 업장 상세 조회 API 연동
  • 내가 지원한 공고 목록 조회 API 연동
  • 공고 지원 취소 API 연동 (PATCH)

사장님 (Manager)

  • 관리 업장 목록 및 상세 조회 API 연동 (Zustand activeWorkspaceId로 전역 관리)
  • 오늘의 근무자 및 업장 근무자 목록 조회 API 연동 (커서 기반 무한 스크롤)
  • 진행 중인 공고 목록 조회 API 연동 (다중 스케줄 데이터 UI 포맷 가공)
  • 대타 요청 목록 조회 API 연동 (상태값 한글 매핑)
  • 업장 월별 스케줄 조회 API 연동 (연/월 및 업장 변경 시 자동 refetch)

공통 리팩토링

  • MonthlyCalendar, MonthlyDateCell, MonthlyDateGauge 등 캘린더 UI 컴포넌트를 features/home/common/schedule로 이동
  • 기존 user 경로는 하위 호환 re-export로 유지
  • CalendarViewData, CalendarEvent 등 공유 타입 및 날짜 유틸(toDateKey, toTimeLabel, getDurationHours) common으로 통합
  • queryKey를 shared/lib/queryKeys.ts에서 중앙 관리

참고 사항 (필요 시)

  • activeWorkspaceId는 Zustand persist로 관리되며, queryKey에 포함되어 업장 전환 시 모든 관련 쿼리가 자동으로 refetch됩니다
  • 사장님 페이지 금일 스케줄을 조회하는 API를 이후에 추가해야합니다

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Applied Stores page to manage job applications with cancellation support
    • Added Workspace page to browse and manage working locations
    • Added Workspace Detail page displaying schedules and team members
    • Added Manager Worker Schedule management for shift assignments
    • Enhanced Manager Dashboard with workspace overview, worker scheduling, job postings, and substitute request tracking
  • Refactor

    • Reorganized navigation routes under /user/* and /manager/* paths for clearer feature organization
    • Restructured calendar system to support both user and manager schedule views
    • Consolidated applied stores and workspace features into dedicated modules

디자인 완료 이후에 나머지 구현 예정
@limtjdghks limtjdghks self-assigned this Apr 16, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 16, 2026

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

Project Deployment Actions Updated (UTC)
alter-client Ready Ready Preview, Comment Apr 16, 2026 11:45am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

Warning

Rate limit exceeded

@limtjdghks has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 38 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 47 minutes and 38 seconds.

⌛ 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4527faaa-2d04-4181-a130-9fef57642c06

📥 Commits

Reviewing files that changed from the base of the PR and between e4910e6 and 7bac714.

📒 Files selected for processing (2)
  • src/features/home/user/schedule/lib/date.ts
  • src/pages/user/workspace-members/hooks/useWorkspaceMembers.ts

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: String must contain at most 250 character(s) at "tone_instructions"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

This PR restructures the home feature by reorganizing routes under /user/* and /manager/* prefixes, extracting shared calendar utilities to common/schedule, splitting user features into workspace and applied-stores submodules, adding comprehensive manager-side APIs and hooks, and replacing shared stores/hooks with feature-specific view models.

Changes

Cohort / File(s) Summary
Route Organization
src/app/App.tsx, src/pages/login/index.tsx, src/shared/api/auth.ts, src/shared/ui/common/Docbar.tsx, src/shared/stores/useDocStore.ts
Updated route paths to move schedule/workspace/job-lookup pages under /user/* prefix (e.g., /schedule/user/schedule, /job-lookup-map/user/job-lookup-map). Added new routes /user/workspace, /user/workspace/:workspaceId, /user/applied-stores, and /manager/worker-schedule. Updated navigation redirects and tab mapping accordingly.
Common Schedule Utilities
src/features/home/common/schedule/lib/date.ts, src/features/home/common/schedule/types/calendarBase.ts, src/features/home/common/schedule/types/calendarView.ts, src/features/home/common/schedule/types/monthlyCalendar.ts, src/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.ts, src/features/home/common/schedule/hooks/useMonthlyDateCellsState.ts, src/features/home/common/schedule/ui/MonthlyCalendar.tsx, src/features/home/common/schedule/ui/MonthlyDateCell.tsx
Introduced shared calendar utilities including date helpers (toDateKey, toTimeLabel, getDurationHours), calendar type definitions (CalendarEvent, CalendarSummary, CalendarViewData), and reusable calendar UI components with enhanced prop support (hideTitle, rightAction, layout modes).
Manager Feature APIs & Types
src/features/home/manager/api/posting.ts, src/features/home/manager/api/schedule.ts, src/features/home/manager/api/substitute.ts, src/features/home/manager/api/worker.ts, src/features/home/manager/api/workspace.ts, src/features/home/manager/types/posting.ts, src/features/home/manager/types/schedule.ts, src/features/home/manager/types/substitute.ts, src/features/home/manager/types/worker.ts, src/features/home/manager/types/workspace.ts
Added comprehensive manager-side API layer with typed DTOs and adapters for workspaces, workers, postings, substitute requests, and schedules. Includes pagination support and data transformation utilities.
Manager Feature Hooks & UI
src/features/home/manager/hooks/useManagedPostingsViewModel.ts, src/features/home/manager/hooks/useManagedWorkspacesQuery.ts, src/features/home/manager/hooks/useManagerHomeViewModel.ts, src/features/home/manager/hooks/useMonthlySchedulesViewModel.ts, src/features/home/manager/hooks/useSubstituteRequestsViewModel.ts, src/features/home/manager/hooks/useWorkerScheduleManageViewModel.ts, src/features/home/manager/hooks/useWorkspaceDetailQuery.ts, src/features/home/manager/hooks/useWorkspaceWorkersViewModel.ts, src/features/home/manager/ui/WorkspaceChangeCard.tsx, src/pages/manager/worker-schedule/index.tsx, src/pages/manager/home/index.tsx
Added manager view models wiring infinite queries for paginated data with filter support, plus new manager home page rendering workspace/posting/substitute/schedule data with a modal for workspace switching.
User Schedule Reorganization
src/features/home/user/schedule/api/schedule.ts, src/features/home/user/schedule/constants/calendar.ts, src/features/home/user/schedule/lib/date.ts, src/features/home/user/schedule/types/schedule.ts, src/features/home/user/schedule/types/scheduleList.ts, src/features/home/user/schedule/hooks/useHomeScheduleViewModel.ts, src/features/home/user/schedule/hooks/useScheduleListViewModel.ts, src/features/home/user/schedule/ui/*
Reorganized schedule feature under /user/schedule/ with new API structure (getSelfSchedule replacing monthly/weekly/daily variants), new view models, and date utility functions. Preserved backward compatibility via re-exports from user-level paths.
User Workspace Feature
src/features/home/user/workspace/api/workspace.ts, src/features/home/user/workspace/api/workspaceMembers.ts, src/features/home/user/workspace/api/workspaceSchedule.ts, src/features/home/user/workspace/types/workspace.ts, src/features/home/user/workspace/types/workspaceMembers.ts, src/features/home/user/workspace/types/workspaceSchedule.ts, src/features/home/user/workspace/hooks/*, src/pages/user/workspace/index.tsx, src/pages/user/workspace-detail/index.tsx
Introduced new workspace feature with APIs for fetching workspaces, members (workers/managers), and schedules, along with view models for managing pagination and data fetching. Added pages for workspace listing and detail views.
User Applied-Stores Feature
src/features/home/user/applied-stores/api/application.ts, src/features/home/user/applied-stores/types/application.ts, src/features/home/user/applied-stores/types/appliedStore.ts, src/features/home/user/applied-stores/hooks/useAppliedStoresViewModel.ts, src/features/home/user/applied-stores/hooks/useCancelApplication.ts, src/features/home/user/applied-stores/ui/AppliedStoreDetailModal.tsx, src/features/home/user/applied-stores/ui/AppliedStoreListItem.tsx, src/pages/user/applied-stores/index.tsx
Created new applied-stores feature for managing job applications with filtering, pagination, cancellation flow, and detail modal UI.
Shared Store/Hook Refactoring
src/shared/stores/useScheduleStore.ts, src/shared/stores/useWorkspaceStore.ts, src/shared/api/schedule.ts, src/shared/api/workspaceMembers.ts, src/shared/hooks/useSelfScheduleQuery.ts, src/shared/hooks/useWorkspaceWorkersQuery.ts, src/shared/hooks/useWorkspaceManagersQuery.ts, src/shared/hooks/index.ts, src/shared/lib/queryKeys.ts
Removed legacy shared stores/hooks (useScheduleStore, schedule/workspace member queries) and centralized React Query key management in new queryKeys.ts. Added workspace persistence store. Aliased old hook names to new feature-specific view models.
Shared UI Components
src/shared/ui/manager/WorkerListItem.tsx, src/shared/ui/home/WorkerListItem.tsx, src/shared/ui/home/WorkerRoleBadge.tsx, src/shared/ui/manager/OngoingPostingCard.tsx, src/shared/ui/manager/SubstituteApprovalCard.tsx, src/shared/ui/common/Navbar.tsx, src/shared/ui/common/Docbar.tsx
Moved WorkerListItem from manager to shared home UI, updated it with variant prop support and owner role. Enhanced manager cards with icon rendering, parsing utilities, and improved styling. Updated navbar and docbar styling.
Page Updates
src/pages/user/home/index.tsx, src/pages/user/schedule/index.tsx, src/pages/user/workspace-members/*, src/pages/user/schedule/components/ScheduleItem.tsx, src/pages/user/schedule/hooks/useSchedule.ts
Updated pages to use new view models and reorganized feature APIs. Replaced local state management with view-model hooks. Updated type imports to reflect reorganized feature paths. Removed legacy hooks/stores.
Storybook & Config
storybook/stories/*, tailwind.config.js
Updated Storybook import paths to match reorganized features. Added main-900 color to Tailwind palette.
Feature Exports
src/features/home/index.ts
Updated public exports to reflect new feature paths (schedule moved under /user/schedule/, workspace/applied-stores under respective subdirectories). Changed API exports from monthly/weekly/daily schedule functions to unified getSelfSchedule.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ManagerHome as Manager Home Page
    participant ViewModel as useManagerHomeViewModel
    participant WorkspaceAPI as Manager Workspace API
    participant ScheduleAPI as Manager Schedule API
    participant WorkersAPI as Manager Workers API
    participant PostingsAPI as Manager Postings API
    participant SubstituteAPI as Manager Substitute API
    participant UI as UI Components

    User->>ManagerHome: Navigate to manager home
    ManagerHome->>ViewModel: Initialize useManagerHomeViewModel()
    
    ViewModel->>WorkspaceAPI: useManagedWorkspacesQuery()
    WorkspaceAPI-->>ViewModel: List of workspaces
    ViewModel->>ViewModel: Set activeWorkspaceId to first workspace
    
    rect rgba(100, 150, 200, 0.5)
        ViewModel->>ScheduleAPI: useMonthlySchedulesViewModel(activeWorkspaceId)
        ScheduleAPI-->>ViewModel: Calendar data with schedules
    end
    
    rect rgba(100, 150, 200, 0.5)
        ViewModel->>WorkersAPI: useWorkspaceWorkersViewModel(activeWorkspaceId)
        WorkersAPI-->>ViewModel: Paginated workers list
    end
    
    rect rgba(100, 150, 200, 0.5)
        ViewModel->>PostingsAPI: useManagedPostingsViewModel(activeWorkspaceId)
        PostingsAPI-->>ViewModel: Filtered postings (status: OPEN)
    end
    
    rect rgba(100, 150, 200, 0.5)
        ViewModel->>SubstituteAPI: useSubstituteRequestsViewModel(activeWorkspaceId)
        SubstituteAPI-->>ViewModel: Paginated substitute requests
    end
    
    ViewModel-->>ManagerHome: Consolidated view model with all data
    ManagerHome->>UI: Render schedules, workers, postings, substitutes
    User->>UI: Interact (load more, switch workspace, view schedule)
Loading
sequenceDiagram
    participant User
    participant AppliedStoresPage as Applied Stores Page
    participant ViewModel as useAppliedStoresViewModel
    participant API as Job Application API
    participant UI as AppliedStoreList & Modal

    User->>AppliedStoresPage: Navigate to applied stores
    AppliedStoresPage->>ViewModel: Initialize useAppliedStoresViewModel()
    
    ViewModel->>API: useInfiniteQuery(getJobApplications)
    API-->>ViewModel: First page of applications
    
    ViewModel->>ViewModel: Group applications by status
    ViewModel-->>AppliedStoresPage: Grouped applications list
    
    AppliedStoresPage->>UI: Render grouped sections & items
    User->>UI: Click store item
    UI->>AppliedStoresPage: Set selectedStore
    AppliedStoresPage->>UI: Open AppliedStoreDetailModal
    
    rect rgba(150, 100, 200, 0.5)
        User->>UI: Click cancel button
        UI->>ViewModel: useCancelApplication.mutate()
        ViewModel->>API: PATCH /applications/{id}/status
        API-->>ViewModel: Success response
        ViewModel->>API: Invalidate application list cache
    end
    
    ViewModel-->>UI: Close modal, refresh list
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • PR #7: Updates route definitions in src/app/App.tsx to reorganize manager home paths and job-lookup-map routing, directly related to the route reorganization in this PR.
  • PR #6: Introduced workspace-members APIs and hooks that this PR refactors and relocates under new feature paths with updated module structure.
  • PR #9: Previously added schedule/calendar and workspace UI features that this PR further reorganizes by moving shared calendar logic to common/schedule and splitting workspace into separate submodules.

Suggested reviewers

  • kim3360
  • kyeongb-bin

🐰 Hoppity hop, what a refactoring spree!
Routes reorganized, features split with glee,
Manager views bundled, workspace paths run free,
Applied stores nestled—a feature symphony! ✨📅

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

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: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (8)
src/features/home/manager/hooks/useWorkerScheduleManageViewModel.ts-32-35 (1)

32-35: ⚠️ Potential issue | 🟡 Minor

Replace placeholder worker identity before release.

Returning a fixed display name ('이름임') is likely mock data and can surface incorrect user information in production.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/hooks/useWorkerScheduleManageViewModel.ts` around
lines 32 - 35, The worker object inside useWorkerScheduleManageViewModel
currently returns hardcoded mock data (worker.name = '이름임') which must be
removed before release; replace the placeholder by wiring the hook to the real
identity source (props, context, or auth state) so that the worker object
(properties name and role) is populated from the actual user data provider
(e.g., currentUser, auth.getUser(), or incoming props) instead of the fixed
string, and ensure role remains typed as 'manager' only if that value is derived
from the real user record.
src/shared/stores/useWorkspaceStore.ts-6-14 (1)

6-14: ⚠️ Potential issue | 🟡 Minor

Allow resetting activeWorkspaceId to null.

The store state supports null, but the setter API does not. That prevents explicit clearing and can keep stale persisted workspace selection.

Suggested fix
 interface WorkspaceState {
   activeWorkspaceId: number | null
-  setActiveWorkspaceId: (id: number) => void
+  setActiveWorkspaceId: (id: number | null) => void
 }
@@
-      setActiveWorkspaceId: (id: number) => set({ activeWorkspaceId: id }),
+      setActiveWorkspaceId: (id: number | null) =>
+        set({ activeWorkspaceId: id }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/stores/useWorkspaceStore.ts` around lines 6 - 14, The setter type
and implementation for activeWorkspaceId currently only accept a number,
preventing clearing to null; update the setActiveWorkspaceId signature to accept
number | null and change its implementation inside useWorkspaceStore to call
set({ activeWorkspaceId: id }) with id possibly null (so activeWorkspaceId can
be explicitly reset), ensuring the persisted store and initial activeWorkspaceId
remain compatible with null values.
src/features/home/manager/types/posting.ts-84-100 (1)

84-100: ⚠️ Potential issue | 🟡 Minor

Add a fallback when mapped weekday labels are empty.

If workingDays contains only unknown values, this currently returns '' instead of a safe placeholder.

🔧 Proposed fix
 function formatWorkDays(schedules: PostingScheduleDto[]): string {
   if (schedules.length === 0) return '-'
@@
-  return DAY_ORDER.filter(d => daySet.has(d))
-    .map(d => WORKING_DAY_KO[d] ?? d)
-    .join(', ')
+  const labels = DAY_ORDER.filter(d => daySet.has(d)).map(
+    d => WORKING_DAY_KO[d] ?? d
+  )
+  return labels.length > 0 ? labels.join(', ') : '-'
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/types/posting.ts` around lines 84 - 100,
formatWorkDays currently returns an empty string when schedules contain only
unknown weekday values; update the function (formatWorkDays) to compute the
mappedLabels from DAY_ORDER.filter(...).map(d => WORKING_DAY_KO[d] ?? d) and if
mappedLabels is empty return '-' (same placeholder used when schedules.length
=== 0), otherwise join and return the labels, ensuring WORKING_DAY_KO lookup is
used as before.
src/features/home/user/schedule/hooks/useScheduleListViewModel.ts-44-46 (1)

44-46: ⚠️ Potential issue | 🟡 Minor

Replace the placeholder click handler before shipping.

handleScheduleClick only logs the id, so the rows look interactive but don't do anything user-facing. Either wire the intended detail/navigation flow or make the item non-interactive until that exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/schedule/hooks/useScheduleListViewModel.ts` around
lines 44 - 46, handleScheduleClick currently only logs the id making rows appear
interactive without behavior; replace the placeholder by wiring the real
navigation or callback and/or disabling interactivity until implemented. Update
the useScheduleListViewModel hook's handleScheduleClick to call the real
navigation or handler (e.g., invoke the router/navigation method or a passed-in
onSelectSchedule prop) and pass the schedule id, or change the row rendering to
not use onClick/role="button" when handleScheduleClick is a noop so items aren't
interactive; ensure you reference and update the handleScheduleClick function in
useScheduleListViewModel accordingly.
src/features/home/common/schedule/ui/MonthlyCalendar.tsx-53-59 (1)

53-59: ⚠️ Potential issue | 🟡 Minor

Avoid rendering an interactive button with no action.

This is a clickable affordance with no handler; either wire an action or render non-interactive text.

🔧 Suggested fix (non-interactive variant)
-          <button
-            type="button"
-            className="flex items-center gap-1 typography-body01-regular text-text-90"
-          >
+          <div className="flex items-center gap-1 typography-body01-regular text-text-90">
             {monthLabel}
             <DownIcon className="w-4 h-4" />
-          </button>
+          </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/common/schedule/ui/MonthlyCalendar.tsx` around lines 53 -
59, The button rendering the month label in MonthlyCalendar is an interactive
affordance with no handler; either attach an action (e.g., add an
onClick/onKeyDown that toggles the month picker or calls a provided prop like
onToggleMonthMenu) or make it non-interactive by replacing the <button> with a
non-button element (e.g., a <div> or <span>) styled the same and ensuring
DownIcon and monthLabel remain present; update the component to use the chosen
approach (add handler and accessibility attributes if interactive, or use a
semantic non-interactive element if purely decorative) and ensure references to
monthLabel and DownIcon stay intact.
src/features/home/user/workspace/hooks/useWorkspaceManagersViewModel.ts-39-39 (1)

39-39: ⚠️ Potential issue | 🟡 Minor

Guard isLoading by enabled condition to prevent misreporting loading state.

TanStack Query v5 returns isPending: true when a query is disabled (enabled: false) and has no cached data. Since this hook disables the query when workspaceId <= 0, returning raw isPending will incorrectly report loading state even when the query is disabled.

🔧 Suggested fix
-    isLoading: isPending,
+    isLoading: workspaceId > 0 && isPending,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/workspace/hooks/useWorkspaceManagersViewModel.ts` at
line 39, The hook currently returns isLoading: isPending which can be true when
the query is disabled; update the returned isLoading to be guarded by the
query's enabled condition (e.g., use enabled && isPending or enabled ? isPending
: false) inside useWorkspaceManagersViewModel so that when workspaceId <= 0
(query disabled) isLoading is always false; locate the enabled flag and
isPending returned from the useQuery call in useWorkspaceManagersViewModel and
change the returned isLoading accordingly.
src/pages/user/home/index.tsx-24-35 (1)

24-35: ⚠️ Potential issue | 🟡 Minor

Status mapping conflates different application states in the UI.

The mapping s.status === 'cancelled' ? 'rejected' : 'applied' treats both 'submitted' and 'accepted' statuses as 'applied'. While the view model correctly groups by all three backend statuses ('submitted', 'accepted', 'cancelled'), the card display loses this distinction for non-rejected applications.

Consider whether the UI should preserve the distinction between 'submitted' and 'accepted', or update the type definition to reflect that only two display states exist.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/user/home/index.tsx` around lines 24 - 35, The current mapping in
appliedStores collapses 'submitted' and 'accepted' into 'applied' (s.status ===
'cancelled' ? 'rejected' : 'applied'), losing important UI state; update the
mapping in the useMemo (appliedStores) to map s.status explicitly (e.g.,
'cancelled' -> 'rejected', 'accepted' -> 'accepted', 'submitted' -> 'submitted')
and then update the AppliedStoreItem type definition to include those display
states (or, if you intentionally only want two states, change the type to
reflect only 'applied'|'rejected' and keep the explicit mapping). Ensure you
update any consumers of AppliedStoreItem (card components) to handle the newly
preserved 'submitted' and 'accepted' values if you choose the first option.
src/shared/ui/home/WorkerListItem.tsx-81-88 (1)

81-88: ⚠️ Potential issue | 🟡 Minor

Hide the overflow button when no action is provided.

onOptions is optional, but the component always renders a clickable “더보기” button. In the undefined case this becomes a dead control.

Proposed fix
-      <button
-        type="button"
-        onClick={onOptions}
-        className="shrink-0 rounded cursor-pointer"
-        aria-label="더보기"
-      >
-        <EllipsisIcon />
-      </button>
+      {onOptions ? (
+        <button
+          type="button"
+          onClick={onOptions}
+          className="shrink-0 rounded cursor-pointer"
+          aria-label="더보기"
+        >
+          <EllipsisIcon />
+        </button>
+      ) : null}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/home/WorkerListItem.tsx` around lines 81 - 88, The component
WorkerListItem always renders the overflow button even when the optional
onOptions prop is undefined, creating a dead control; update the rendering logic
in WorkerListItem to conditionally render (or return null for) the button
containing EllipsisIcon only when onOptions is a function, and ensure
accessibility attributes are correct (remove aria-label or mark aria-hidden when
not rendered) so no clickable element appears if onOptions is not provided.
🧹 Nitpick comments (19)
src/features/home/manager/hooks/useWorkerScheduleManageViewModel.ts (1)

15-29: Constrain day state/actions to valid workday literals.

selectedDays/toggleDay currently accept any string, so invalid values can be stored. Tightening to the WORKDAY_OPTIONS union improves safety.

Suggested refactor
 const WORKDAY_OPTIONS = ['월', '화', '수', '목', '금', '토', '일'] as const
+type Workday = (typeof WORKDAY_OPTIONS)[number]

-const DEFAULT_SELECTED_DAYS = ['수', '금']
+const DEFAULT_SELECTED_DAYS: Workday[] = ['수', '금']

 export function useWorkerScheduleManageViewModel() {
-  const [selectedDays, setSelectedDays] = useState<string[]>(
+  const [selectedDays, setSelectedDays] = useState<Workday[]>(
     DEFAULT_SELECTED_DAYS
   )
@@
-  function toggleDay(day: string) {
+  function toggleDay(day: Workday) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/hooks/useWorkerScheduleManageViewModel.ts` around
lines 15 - 29, selectedDays and toggleDay accept any string; constrain them to
the WORKDAY_OPTIONS union to prevent invalid values. Update the state
declaration (selectedDays / setSelectedDays) to use the WORKDAY_OPTIONS type (or
a derived Workday type) instead of string[], change toggleDay(day: string) to
toggleDay(day: Workday) and update any callers to pass only valid
WORKDAY_OPTIONS entries, and add a runtime guard inside toggleDay (e.g., check
WORKDAY_OPTIONS.includes(day)) before calling setSelectedDays to be defensive;
reference DEFAULT_SELECTED_DAYS, WORKDAY_OPTIONS, toggleDay, selectedDays, and
setSelectedDays when making these edits.
src/features/home/user/workspace/api/workspaceSchedule.ts (1)

2-11: Use the new common schedule module imports in this new file.

This API adapter still imports calendar types/date utils from user/schedule. For newly added code, importing from the common schedule module keeps boundaries cleaner and avoids legacy path coupling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/workspace/api/workspaceSchedule.ts` around lines 2 -
11, The file imports CalendarEvent, CalendarViewData and the date helpers
toDateKey, toTimeLabel, getDurationHours, formatScheduleTimeRange from the
legacy user/schedule module; update the import statements to pull these types
and functions from the new common schedule module instead (replace the existing
imports that reference CalendarEvent, CalendarViewData and the four date helper
functions), keeping the same symbol names so usages in workspaceSchedule.ts
remain unchanged and run the build to verify there are no unresolved imports.
src/features/home/user/applied-stores/ui/AppliedStoreList.tsx (1)

27-27: Prefer responsive width guard over hard fixed width.

w-[358px] can overflow in narrower layouts. Consider w-full max-w-[358px] to preserve design width while remaining responsive.

💡 Suggested tweak
-    <section className="w-[358px] rounded-2xl bg-white py-6">
+    <section className="w-full max-w-[358px] rounded-2xl bg-white py-6">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/applied-stores/ui/AppliedStoreList.tsx` at line 27,
The section in AppliedStoreList.tsx uses a hard fixed width class "w-[358px]"
which can overflow on narrow screens; update the element (the <section> in the
AppliedStoreList component) to use a responsive guard by replacing the fixed
width with "w-full max-w-[358px]" (i.e., add w-full and max-w-[358px] while
keeping the existing rounded-2xl bg-white py-6 classes) so it preserves the
design width but remains responsive.
src/features/home/user/workspace/ui/WorkingStoresList.tsx (1)

28-28: Prefer responsive width over hard fixed width.

Line 28 uses w-[358px], which can overflow in tighter containers. Consider w-full max-w-[358px] for safer layout behavior.

Suggested change
-    <section className="w-[358px] rounded-2xl bg-white py-6">
+    <section className="w-full max-w-[358px] rounded-2xl bg-white py-6">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/workspace/ui/WorkingStoresList.tsx` at line 28, The
section in the WorkingStoresList component uses a hard fixed width class
"w-[358px]" which can cause overflow; update the section's class to use
responsive sizing (e.g., replace "w-[358px]" with "w-full max-w-[358px]") so the
container can shrink in tight layouts while keeping the intended maximum width.
src/app/App.tsx (1)

65-90: 레거시 경로 리다이렉트 추가를 권장합니다.

Line 65 이후 경로가 /user/*로 이동했는데, 기존 북마크/딥링크('/home', '/schedule' 등) 유입을 위해 alias redirect를 두면 전환이 더 안전합니다.

예시 diff
         <Route element={<MobileRouteLayoutWithoutDocbar />}>
+          <Route path="/schedule" element={<Navigate to="/user/schedule" replace />} />
+          <Route
+            path="/workspaces/:workspaceId/members"
+            element={<Navigate to="/user/workspaces/:workspaceId/members" replace />}
+          />
           <Route path="/user/schedule" element={<SchedulePage />} />
@@
         <Route element={<MobileRouteLayoutWithDocbar />}>
+          <Route path="/home" element={<Navigate to="/user/home" replace />} />
+          <Route
+            path="/job-lookup-map"
+            element={<Navigate to="/user/job-lookup-map" replace />}
+          />
           <Route path="/user/job-lookup-map" element={<JobLookupMapPage />} />
           <Route path="/user/home" element={<UserHomePage />} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/App.tsx` around lines 65 - 90, Add alias redirect Routes for legacy
bookmarked paths by adding Route entries that Navigate to the new /user or
/manager paths; e.g., add Route path="/home" element={<Navigate to="/user/home"
replace />} and path="/schedule" element={<Navigate to="/user/schedule" replace
/>}, plus aliases like "/workspace" -> "/user/workspace", "/applied-stores" ->
"/user/applied-stores", "/job-lookup-map" -> "/user/job-lookup-map" and
"/social" -> "/manager/social" (use Navigate components similar to the existing
Route with path="/" element={<Navigate ... />}), placing them inside the Router
before the final fallback so legacy deep links resolve to the corresponding
pages such as UserHomePage, SchedulePage, WorkspacePage, AppliedStoresPage,
JobLookupMapPage and SocialPage.
src/features/home/manager/api/substitute.ts (1)

18-19: Replace truthy check with explicit undefined check for consistency and safety.

Line 18 uses params.status && (truthy check) while line 19 uses params.cursor !== undefined && (undefined check) on identical optional string fields. For consistency and to prevent accidentally dropping falsy-but-valid values, use params.status !== undefined && instead.

Current code
        ...(params.status && { status: params.status }),
        ...(params.cursor !== undefined && { cursor: params.cursor }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/api/substitute.ts` around lines 18 - 19, The spread
that builds the request object uses a truthy check for params.status but an
explicit undefined check for params.cursor; update the condition for
params.status to use params.status !== undefined (matching the params.cursor
check) so falsy-but-valid values (e.g., empty string or 0) are preserved—locate
the object spread where ...(params.status && { status: params.status }) is used
and replace the condition accordingly.
src/shared/ui/manager/SubstituteApprovalCard.tsx (1)

28-33: Replace the new hex literals with design tokens.

This card reintroduces hardcoded colors in statusConfig, the avatar fallback, and the role pill. Please map these to the registered Tailwind tokens so manager UI stays themeable and consistent.

Based on learnings, "In the alter-client repository, avoid hardcoding colors in TSX files. Use the Tailwind tokens already registered in tailwind.config.js: use text-sub and border-sub for the color '#3a9982' (sub.DEFAULT), and use bg-bg-dark for '#efefef'. Replace literals like text-[#3A9982] and bg-[#EFEFEF] with the appropriate tokens in src/pages/manager/home/index.tsx and src/shared/ui/manager/**/*.tsx."

Also applies to: 55-75

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/manager/SubstituteApprovalCard.tsx` around lines 28 - 33, The
statusConfig object in SubstituteApprovalCard (and related UI pieces: the avatar
fallback and the role pill) uses hardcoded hex colors—replace those hex literals
(e.g., text-[`#3A9982`], border-[`#3A9982`], bg-[`#EFEFEF`], text-[`#2CE283`],
bg-[`#EAFDF3`], text-[`#E28D2C`], bg-[`#FDF8EA`]) with the registered Tailwind tokens
so the UI remains themeable: use text-sub and border-sub for the green sub color
and bg-bg-dark for the gray background; update the statusConfig entries, the
avatar fallback rendering, and the role pill className in
SubstituteApprovalCard, and apply the same token replacements in
src/pages/manager/home/index.tsx and any files under
src/shared/ui/manager/**/*.tsx that still contain hardcoded hexes.
src/features/home/common/schedule/ui/MonthlyCalendar.tsx (1)

83-83: Replace hardcoded weekend color with a semantic Tailwind token.

Use a design token class instead of text-[#DC0000] to keep styling consistent and theme-safe.

Based on learnings: In the alter-client repository, avoid hardcoding colors in TSX files and use Tailwind tokens already registered in tailwind.config.js.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/common/schedule/ui/MonthlyCalendar.tsx` at line 83, The
component MonthlyCalendar.tsx currently hardcodes the weekend color using
'text-[`#DC0000`]' inside the className conditional; replace that literal color
with the semantic Tailwind token used in our design system (for example use the
registered token like text-status-error or the appropriate semantic class from
tailwind.config.js) so the conditional becomes a semantic class for weekends
(index === 5 || index === 6) and retains text-text-50 for others; update the
className conditional in the element where index is checked to reference the
semantic token instead of the hex color.
src/features/home/user/workspace/hooks/useWorkspacesViewModel.ts (1)

37-44: Consider normalizing hasNextPage to boolean for consistency.

Other similar hooks in this PR (e.g., useWorkspaceWorkersViewModel) normalize hasNextPage with !!hasNextPage. Returning the raw value here could yield undefined instead of false when there's no next page.

Suggested change
   return {
     workspaces,
     fetchNextPage,
-    hasNextPage,
+    hasNextPage: !!hasNextPage,
     isFetchingNextPage,
     isLoading,
     isError,
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/workspace/hooks/useWorkspacesViewModel.ts` around
lines 37 - 44, The returned hasNextPage in useWorkspacesViewModel may be
undefined; normalize it to a boolean like other hooks so callers always get
true/false. Update the return object in useWorkspacesViewModel to coerce
hasNextPage with !!hasNextPage (matching useWorkspaceWorkersViewModel) so the
exported property is consistently boolean.
src/pages/user/workspace-detail/index.tsx (2)

24-30: Unused mode variable.

mode is destructured from the view model but not used anywhere in the component. Consider removing it from the destructuring if it's not needed, or use it if mode switching is intended.

Remove unused variable
   const {
-    mode,
     baseDate,
     calendarData,
     isLoading: scheduleLoading,
     onDateChange,
   } = useWorkspaceScheduleViewModel(id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/user/workspace-detail/index.tsx` around lines 24 - 30, The
destructured variable "mode" from useWorkspaceScheduleViewModel(id) is unused;
remove "mode" from the destructuring to eliminate the unused variable warning,
or if mode switching is intended, wire the returned "mode" into the component's
state/JSX logic (e.g., pass it to rendering conditionals or handlers). Locate
the call useWorkspaceScheduleViewModel in the component and either delete "mode"
from the destructured list or use "mode" where appropriate (e.g., in rendering
or effect logic) to reflect the view model's mode.

91-92: Empty onOptions handlers are placeholders.

These no-op handlers work but suggest incomplete functionality. Consider adding a TODO comment or wiring up the actual behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/user/workspace-detail/index.tsx` around lines 91 - 92, The
onOptions prop is currently passed an empty no-op which signals incomplete
behavior; update the component instance that sets onOptions to either wire it to
a real handler (e.g., implement and pass a function like handleOptions in this
module that opens the options menu, navigates, or triggers the intended action)
or at minimum replace the empty arrow with a clear TODO comment (e.g., // TODO:
implement options handler) so the placeholder is explicit; locate the prop usage
named onOptions in this file and connect it to the appropriate handler function
or add the TODO inline.
src/shared/ui/manager/OngoingPostingCard.tsx (1)

68-74: Replace hardcoded hex colors with Tailwind tokens.

Lines 70-71 use hardcoded colors (#42E590, #EAFDF3) which should be replaced with registered Tailwind tokens. Consider adding these as semantic tokens (e.g., bg-urgent, bg-urgent-light, text-urgent) in tailwind.config.js if they don't already exist.

Example approach
           <span
             className={`inline-flex h-[22px] items-center rounded-[100px] px-2.5 typography-body03-semibold ${
-              isUrgent ? 'bg-[`#42E590`] text-white' : 'bg-[`#EAFDF3`] text-[`#42E590`]'
+              isUrgent ? 'bg-status-urgent text-white' : 'bg-status-urgent-light text-status-urgent'
             }`}
           >

Then register these colors in tailwind.config.js under theme.extend.colors.

Based on learnings: "In the alter-client repository, avoid hardcoding colors in TSX files. Use the Tailwind tokens already registered in tailwind.config.js."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/manager/OngoingPostingCard.tsx` around lines 68 - 74, In
OngoingPostingCard.tsx replace the hardcoded hex values in the span className
(the conditional branch that uses isUrgent and renders posting.dDay) with
Tailwind tokens (e.g., use bg-urgent text-urgent for the urgent branch and
bg-urgent-light text-urgent for the non-urgent branch), updating the template
literal around isUrgent accordingly; if those tokens don’t exist yet, add
semantic color tokens (bg-urgent, bg-urgent-light, text-urgent) under
theme.extend.colors in tailwind.config.js and then run the Tailwind build so the
new classes are available.
src/pages/manager/worker-schedule/index.tsx (1)

63-66: Consider extracting the placeholder avatar pattern.

The hardcoded #ececec in the gradient could be extracted to a reusable component or utility class if this pattern is used elsewhere. Low priority given it's a complex gradient pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manager/worker-schedule/index.tsx` around lines 63 - 66, Extract
the hardcoded gradient avatar placeholder into a reusable component or CSS
utility so the color (`#ececec`) can be centralized: create a PlaceholderAvatar
React component (or a .placeholder-avatar utility) that renders the div with the
repeating-conic-gradient and accepts a prop or CSS variable for the base color,
then replace the inline div in the worker-schedule page with <PlaceholderAvatar
/> (or apply the .placeholder-avatar class) so the pattern and color are
reusable across the app.
src/pages/user/applied-stores/index.tsx (1)

34-37: Race condition potential in cancel handler.

If cancelApplication is called multiple times rapidly (e.g., double-click), the selectedStore reference could become stale or multiple requests could fire. Consider using isCancelling to disable the button in the modal or debounce the action.

♻️ Suggested guard
   const handleCancel = () => {
-    if (!selectedStore) return
+    if (!selectedStore || isCancelling) return
     cancelApplication(selectedStore.id, { onSuccess: closeDetail })
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/user/applied-stores/index.tsx` around lines 34 - 37, The cancel
handler (handleCancel) can trigger multiple requests if invoked rapidly; add a
guard using the cancellation state (e.g., isCancelling or
cancelApplication.isLoading) to early-return when a cancellation is already in
progress, and ensure the modal/button is disabled while cancelling to prevent
double-invocation; update the modal's cancel button to use that disabled state
and keep closeDetail called only from the mutation onSuccess so duplicate calls
won't race with selectedStore becoming stale.
src/features/home/manager/types/substitute.ts (1)

63-68: Date range format may not display correctly for same-day ranges.

If startDateTime and endDateTime are on the same day, the output would be redundant (e.g., "4월 16일 ↔ 4월 16일"). Consider handling same-day ranges differently.

♻️ Suggested same-day handling
 function formatDateRange(startDateTime: string, endDateTime: string): string {
   const start = new Date(startDateTime)
   const end = new Date(endDateTime)
   const fmt = (d: Date) => `${d.getMonth() + 1}월 ${d.getDate()}일`
+  if (start.toDateString() === end.toDateString()) {
+    return fmt(start)
+  }
   return `${fmt(start)} ↔ ${fmt(end)}`
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/types/substitute.ts` around lines 63 - 68, The
formatDateRange function currently renders redundant output when startDateTime
and endDateTime fall on the same calendar day (e.g., "4월 16일 ↔ 4월 16일"); update
formatDateRange to detect same-day ranges (compare start and end via
getFullYear(), getMonth(), and getDate() or use toDateString()) and return a
single formatted date string (using the existing fmt(start)) for same-day cases,
otherwise keep the existing "start ↔ end" output; update the function signature
and maintain the fmt helper and return type string.
src/features/home/manager/hooks/useMonthlySchedulesViewModel.ts (1)

16-21: Query key uses fallback 0 when workspaceId is null.

While enabled: workspaceId !== null prevents execution, using workspaceId ?? 0 in the query key means a transient cache entry is created for workspace ID 0 before the actual workspace ID is available. This could lead to stale cache entries or unexpected refetch behavior.

Consider guarding the query key more explicitly or ensuring consistency:

♻️ Suggested improvement
   const { data: rawData, isPending } = useQuery({
-    queryKey: queryKeys.manager.schedules(workspaceId ?? 0, year, month),
+    queryKey: queryKeys.manager.schedules(workspaceId!, year, month),
     queryFn: () =>
       fetchMonthlySchedules({ workspaceId: workspaceId!, year, month }),
     enabled: workspaceId !== null,
   })

Since the query is only enabled when workspaceId !== null, the non-null assertion in the key is safe and ensures the cache key accurately reflects the workspace being queried.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/hooks/useMonthlySchedulesViewModel.ts` around lines
16 - 21, The query key currently uses workspaceId ?? 0 which creates a transient
cache entry for 0; update the queryKey to only use the real workspaceId
(matching the enabled guard) — e.g. replace
queryKeys.manager.schedules(workspaceId ?? 0, year, month) with
queryKeys.manager.schedules(workspaceId!, year, month) (or otherwise
short-circuit the whole useQuery when workspaceId is null) so the key and
enabled: workspaceId !== null are consistent; keep fetchMonthlySchedules and
enabled as-is.
src/features/home/manager/hooks/useManagerHomeViewModel.ts (1)

10-13: Hardcoded placeholder data for today's workers.

TODAY_WORKERS contains static dummy data. Per the PR description, the "금일 스케줄" API is planned for future implementation.

Would you like me to create an issue to track the integration of the today's workers API when it becomes available?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/hooks/useManagerHomeViewModel.ts` around lines 10 -
13, TODAY_WORKERS currently holds hardcoded placeholder data in
useManagerHomeViewModel.ts; remove the static entries and replace with an empty
array (or initialize from state) and add a clear TODO comment referencing the
planned "금일 스케줄" API, then wire the view model to load real data once the API
exists (e.g., replace TODAY_WORKERS with a state variable like todayWorkers and
a fetchTodayWorkers function to populate it later); ensure you keep the
TodayWorkerItem shape so the UI type contract remains valid and create a ticket
to track integrating the actual API.
src/features/home/user/workspace/api/workspaceMembers.ts (1)

12-42: Consider adding error handling for consistency.

Unlike getSelfSchedule in schedule.ts which wraps axios calls in try/catch with custom error handling, these API functions propagate errors directly. While TanStack Query handles errors at the hook level, consistent error handling across API modules improves debuggability and error messaging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/workspace/api/workspaceMembers.ts` around lines 12 -
42, Wrap the axios calls in getWorkspaceWorkers and getWorkspaceManagers in a
try/catch like getSelfSchedule does: catch Axios errors, normalize/format the
error (e.g., extract response.status/message or use the existing handleApiError
helper if one exists) and rethrow a consistent Error object or custom ApiError
so callers (and logs) get a uniform message; ensure you reference the functions
getWorkspaceWorkers and getWorkspaceManagers and maintain returning
response.data on success.
src/shared/lib/queryKeys.ts (1)

1-7: shared/lib is now coupled to a feature API module.

Importing SelfScheduleQueryParams from @/features/home/user/schedule/api/schedule inverts the dependency direction for the shared layer and makes this utility harder to keep cycle-free. Please move that type to a lower-level types module and import it from both places instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/lib/queryKeys.ts` around lines 1 - 7, The shared util queryKeys
currently imports SelfScheduleQueryParams from a feature API which inverts
dependencies; extract SelfScheduleQueryParams into a lower-level shared types
module (a small types-only file) and re-export it so both the feature API (where
the type originated) and this shared/lib/queryKeys import the type from that new
module instead of from the feature API; update the import in the queryKeys file
to reference the new types module and update the feature API to import the type
from the same new module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/home/manager/hooks/useManagedWorkspacesQuery.ts`:
- Around line 22-30: The useEffect in useManagedWorkspacesQuery only guards
against activeWorkspaceId being null but not against a stale persisted id
missing from workspaces; update the effect so it also checks whether
activeWorkspaceId exists in the current workspaces array (e.g.,
find/workspaces.some(w => w.id === activeWorkspaceId)) and if not, compute the
defaultWorkspace (using the existing reduce logic) and call
setActiveWorkspaceId(defaultWorkspace.id); keep the existing early return for
empty workspaces and ensure you reference activeWorkspaceId,
setActiveWorkspaceId, and workspaces in the updated condition.

In `@src/features/home/manager/hooks/useWorkspaceDetailQuery.ts`:
- Around line 8-11: The query key must not coerce null to 0; change the call to
queryKeys.managerWorkspace.detail(...) to pass workspaceId through as nullable
(or a distinct sentinel string) instead of using (workspaceId ?? 0) so the cache
differentiates "no workspace selected" from workspace id 0; update the useQuery
invocation (the queryFn fetchWorkspaceDetail(workspaceId!) can remain guarded by
enabled: workspaceId !== null) and, if necessary, adjust
queryKeys.managerWorkspace.detail to accept number | null (or a string sentinel)
so the key retains the null state.

In `@src/features/home/manager/types/posting.ts`:
- Around line 71-74: The function formatWage currently falls back to the '시급'
label for unknown paymentType values; update it so unknown/added backend enum
values are not misrepresented by removing that default and handling absent
labels explicitly: lookup PAYMENT_TYPE_LABEL[paymentType] (refer to
PAYMENT_TYPE_LABEL and formatWage), if a label exists return `${label}
${amount}원`, otherwise return just `${amount}원` (or a neutral placeholder) so
unknown types are not shown as hourly pay.

In `@src/features/home/manager/types/worker.ts`:
- Around line 1-2: The type StoreWorkerRole is defined in the UI component
StoreWorkerListItem and being imported into the adapter types (worker.ts),
reversing dependencies; extract StoreWorkerRole into a new shared types module
(e.g., create a new module named storeWorkerTypes or similar), export the
StoreWorkerRole type there, update the adapter type file that currently imports
StoreWorkerRole and the UI component StoreWorkerListItem to import
StoreWorkerRole from the new shared module, and remove the UI-layer import from
the adapter so the dependency direction is correct and the build passes.

In `@src/features/home/user/applied-stores/hooks/useAppliedStoresViewModel.ts`:
- Around line 18-23: FILTER_OPTIONS does not include the 'all' option even
though selectedFilter defaults to 'all', so users cannot return to the
unfiltered view; update the FILTER_OPTIONS array (and the identical array used
later around the other occurrence) to include an { key: 'all', label: '전체' }
entry (preferably as the first element) and ensure the FilterType union includes
'all' so the dropdown exposes and accepts that value.

In `@src/features/home/user/applied-stores/types/application.ts`:
- Around line 64-70: FILTER_TO_API_STATUS currently maps the same
ApplicationApiStatus 'ACCEPTED' into both the 'viewed' and 'completed'
FilterType causing duplication and mis-grouping; update the FILTER_TO_API_STATUS
Record so that 'ACCEPTED' only appears in the 'completed' array (remove it from
the 'viewed' array) and scan the other mapping blocks (the other similar mapping
region) to ensure no other duplicate status values exist; adjust any tests or
usages that relied on the old mapping if necessary.

In `@src/features/home/user/schedule/hooks/useScheduleListViewModel.ts`:
- Around line 13-24: The hook currently swallows request failures because it
only returns rawData and isPending; modify the useQuery call in
useScheduleListViewModel to also destructure and return isError and error (e.g.,
const { data: rawData, isPending, isError, error } = useQuery(...)) and add
those two values to the hook's returned object so the consumer can show an error
state instead of treating failures as an empty schedule list; make the same
change for the other useQuery block referenced (lines 48-56) so both schedule
queries expose isError/error.

In `@src/features/home/user/schedule/lib/date.ts`:
- Around line 81-90: getScheduleParamsByMode currently only returns year/month
based on baseDate so weekly views that span month boundaries miss schedules;
modify getScheduleParamsByMode to detect mode === 'weekly', compute the week's
start and end dates (e.g., startOfWeek and endOfWeek relative to baseDate), and
return a range instead of a single month—update SelfScheduleQueryParams to
accept a date range keys like { fromYear, fromMonth, fromDay, toYear, toMonth,
toDay } or { startDate, endDate } so the caller (useWeeklyCalendarViewModel) can
request both months and ensure events in the cross-month week are fetched.
Ensure other callers of getScheduleParamsByMode are updated to handle the new
shape.

In `@src/features/home/user/workspace/api/workspaceSchedule.ts`:
- Around line 83-101: The current loop over sorted shifts can pick a past shift
as the worker's nextShift; update deriveWorkerList so you only consider upcoming
shifts (compare shift.startDateTime or shift.endDateTime to new Date()) before
setting workerMap entries. In practice, inside the loop over sorted, skip shifts
that are entirely in the past and only call formatScheduleTimeRange and set
nextShiftDateTime/nextShiftTimeRange on workerMap for the first future shift per
worker (use the existing sorted, workerMap, formatScheduleTimeRange,
nextShiftDateTime, nextShiftTimeRange identifiers to locate and change the
logic).

In `@src/features/home/user/workspace/hooks/useWorkspaceManagersViewModel.ts`:
- Around line 20-25: The queryKey for the managers query (used in
useWorkspaceManagersViewModel) currently uses only
queryKeys.workspace.managers(workspaceId) which causes different pageSize
callers to share a cache entry; include pageSize in the query key (e.g. add
pageSize as part of the key tuple or pass it into queryKeys.workspace.managers)
so the key uniquely identifies both workspaceId and pageSize, keeping
getWorkspaceManagers pagination caches separate and preventing cross-cursor
collisions.

In `@src/features/home/user/workspace/hooks/useWorkspaceWorkersViewModel.ts`:
- Around line 19-29: The query key used in the useInfiniteQuery call is missing
the pageSize, causing cache collisions; update the queryKey invocation to
include pageSize by calling queryKeys.workspace.workers(workspaceId, pageSize)
so the cache tuple distinguishes different page sizes, leaving the rest of the
useInfiniteQuery config (queryFn, getNextPageParam, enabled) unchanged; locate
the useInfiniteQuery call in useWorkspaceWorkersViewModel and adjust the
queryKey parameter accordingly.

In `@src/pages/login/index.tsx`:
- Around line 22-25: The manager post-login redirect is pointing to the removed
route '/main'; update every place that navigates to '/main' so managers go to
'/manager/home' instead—specifically change the navigate('/main', { replace:
true }) call in the Login page component (scope === 'MANAGER' branch in
src/pages/login/index.tsx) and the corresponding redirect calls in the auth flow
in src/shared/api/auth.ts (the login completion, signup verification, and signup
completion logic that currently use navigate('/main') or return '/main') to use
'/manager/home' so all manager authentication flows land on the correct route.

In `@src/pages/manager/home/index.tsx`:
- Around line 109-131: The WorkspaceChangeList is using a hardcoded
categoryLabel and a noop edit handler and the add button has no click handler;
update WorkspaceChangeList to pass the correct category for each workspace
instead of categoryLabel="카페", replace onEditWorkspace={() => {}} with a real
handler that opens the workspace edit modal (hook or function used elsewhere,
e.g., openEditWorkspaceModal or editWorkspace), ensure selectedWorkspaceId uses
workspaceChangeModal.selectedWorkspaceId, and wire the add button (the element
rendering managerWorkspaceModalPlusIcon) to the workspace creation action (e.g.,
openCreateWorkspaceModal or createWorkspace) or hide the edit/add controls when
those actions are not available.

In `@src/pages/manager/worker-schedule/index.tsx`:
- Around line 160-167: The "저장" button currently has no onClick, so user edits
aren't persisted; add an onClick prop that calls a new handler (e.g.,
handleSave) and wire that handler to the existing save mutation or API call
(e.g., useSaveSchedule / saveSchedule / apiSaveWorkerSchedule) to perform the
persist operation; if the mutation doesn't exist yet, scaffold handleSave to
validate state, call the save API/mutation, handle success/error (show toast or
set error state) and disable the button while saving (or add a TODO comment and
set disabled={true}) so the UI is not misleading.

In `@src/shared/lib/queryKeys.ts`:
- Around line 10-13: The workers and managers query-key factories currently
include cursor and pageSize in their returned stable key which causes per-page
cache entries; update the workers and managers functions in queryKeys.ts to only
accept (workspaceId?: number) and return ['workspace','workers',workspaceId] as
const and ['workspace','managers',workspaceId] as const respectively (remove
cursor and pageSize from both the parameter list and the returned array); keep
the const tuple return and leave pagination cursor handling to pageParam in the
infinite query callers.

---

Minor comments:
In `@src/features/home/common/schedule/ui/MonthlyCalendar.tsx`:
- Around line 53-59: The button rendering the month label in MonthlyCalendar is
an interactive affordance with no handler; either attach an action (e.g., add an
onClick/onKeyDown that toggles the month picker or calls a provided prop like
onToggleMonthMenu) or make it non-interactive by replacing the <button> with a
non-button element (e.g., a <div> or <span>) styled the same and ensuring
DownIcon and monthLabel remain present; update the component to use the chosen
approach (add handler and accessibility attributes if interactive, or use a
semantic non-interactive element if purely decorative) and ensure references to
monthLabel and DownIcon stay intact.

In `@src/features/home/manager/hooks/useWorkerScheduleManageViewModel.ts`:
- Around line 32-35: The worker object inside useWorkerScheduleManageViewModel
currently returns hardcoded mock data (worker.name = '이름임') which must be
removed before release; replace the placeholder by wiring the hook to the real
identity source (props, context, or auth state) so that the worker object
(properties name and role) is populated from the actual user data provider
(e.g., currentUser, auth.getUser(), or incoming props) instead of the fixed
string, and ensure role remains typed as 'manager' only if that value is derived
from the real user record.

In `@src/features/home/manager/types/posting.ts`:
- Around line 84-100: formatWorkDays currently returns an empty string when
schedules contain only unknown weekday values; update the function
(formatWorkDays) to compute the mappedLabels from DAY_ORDER.filter(...).map(d =>
WORKING_DAY_KO[d] ?? d) and if mappedLabels is empty return '-' (same
placeholder used when schedules.length === 0), otherwise join and return the
labels, ensuring WORKING_DAY_KO lookup is used as before.

In `@src/features/home/user/schedule/hooks/useScheduleListViewModel.ts`:
- Around line 44-46: handleScheduleClick currently only logs the id making rows
appear interactive without behavior; replace the placeholder by wiring the real
navigation or callback and/or disabling interactivity until implemented. Update
the useScheduleListViewModel hook's handleScheduleClick to call the real
navigation or handler (e.g., invoke the router/navigation method or a passed-in
onSelectSchedule prop) and pass the schedule id, or change the row rendering to
not use onClick/role="button" when handleScheduleClick is a noop so items aren't
interactive; ensure you reference and update the handleScheduleClick function in
useScheduleListViewModel accordingly.

In `@src/features/home/user/workspace/hooks/useWorkspaceManagersViewModel.ts`:
- Line 39: The hook currently returns isLoading: isPending which can be true
when the query is disabled; update the returned isLoading to be guarded by the
query's enabled condition (e.g., use enabled && isPending or enabled ? isPending
: false) inside useWorkspaceManagersViewModel so that when workspaceId <= 0
(query disabled) isLoading is always false; locate the enabled flag and
isPending returned from the useQuery call in useWorkspaceManagersViewModel and
change the returned isLoading accordingly.

In `@src/pages/user/home/index.tsx`:
- Around line 24-35: The current mapping in appliedStores collapses 'submitted'
and 'accepted' into 'applied' (s.status === 'cancelled' ? 'rejected' :
'applied'), losing important UI state; update the mapping in the useMemo
(appliedStores) to map s.status explicitly (e.g., 'cancelled' -> 'rejected',
'accepted' -> 'accepted', 'submitted' -> 'submitted') and then update the
AppliedStoreItem type definition to include those display states (or, if you
intentionally only want two states, change the type to reflect only
'applied'|'rejected' and keep the explicit mapping). Ensure you update any
consumers of AppliedStoreItem (card components) to handle the newly preserved
'submitted' and 'accepted' values if you choose the first option.

In `@src/shared/stores/useWorkspaceStore.ts`:
- Around line 6-14: The setter type and implementation for activeWorkspaceId
currently only accept a number, preventing clearing to null; update the
setActiveWorkspaceId signature to accept number | null and change its
implementation inside useWorkspaceStore to call set({ activeWorkspaceId: id })
with id possibly null (so activeWorkspaceId can be explicitly reset), ensuring
the persisted store and initial activeWorkspaceId remain compatible with null
values.

In `@src/shared/ui/home/WorkerListItem.tsx`:
- Around line 81-88: The component WorkerListItem always renders the overflow
button even when the optional onOptions prop is undefined, creating a dead
control; update the rendering logic in WorkerListItem to conditionally render
(or return null for) the button containing EllipsisIcon only when onOptions is a
function, and ensure accessibility attributes are correct (remove aria-label or
mark aria-hidden when not rendered) so no clickable element appears if onOptions
is not provided.

---

Nitpick comments:
In `@src/app/App.tsx`:
- Around line 65-90: Add alias redirect Routes for legacy bookmarked paths by
adding Route entries that Navigate to the new /user or /manager paths; e.g., add
Route path="/home" element={<Navigate to="/user/home" replace />} and
path="/schedule" element={<Navigate to="/user/schedule" replace />}, plus
aliases like "/workspace" -> "/user/workspace", "/applied-stores" ->
"/user/applied-stores", "/job-lookup-map" -> "/user/job-lookup-map" and
"/social" -> "/manager/social" (use Navigate components similar to the existing
Route with path="/" element={<Navigate ... />}), placing them inside the Router
before the final fallback so legacy deep links resolve to the corresponding
pages such as UserHomePage, SchedulePage, WorkspacePage, AppliedStoresPage,
JobLookupMapPage and SocialPage.

In `@src/features/home/common/schedule/ui/MonthlyCalendar.tsx`:
- Line 83: The component MonthlyCalendar.tsx currently hardcodes the weekend
color using 'text-[`#DC0000`]' inside the className conditional; replace that
literal color with the semantic Tailwind token used in our design system (for
example use the registered token like text-status-error or the appropriate
semantic class from tailwind.config.js) so the conditional becomes a semantic
class for weekends (index === 5 || index === 6) and retains text-text-50 for
others; update the className conditional in the element where index is checked
to reference the semantic token instead of the hex color.

In `@src/features/home/manager/api/substitute.ts`:
- Around line 18-19: The spread that builds the request object uses a truthy
check for params.status but an explicit undefined check for params.cursor;
update the condition for params.status to use params.status !== undefined
(matching the params.cursor check) so falsy-but-valid values (e.g., empty string
or 0) are preserved—locate the object spread where ...(params.status && {
status: params.status }) is used and replace the condition accordingly.

In `@src/features/home/manager/hooks/useManagerHomeViewModel.ts`:
- Around line 10-13: TODAY_WORKERS currently holds hardcoded placeholder data in
useManagerHomeViewModel.ts; remove the static entries and replace with an empty
array (or initialize from state) and add a clear TODO comment referencing the
planned "금일 스케줄" API, then wire the view model to load real data once the API
exists (e.g., replace TODAY_WORKERS with a state variable like todayWorkers and
a fetchTodayWorkers function to populate it later); ensure you keep the
TodayWorkerItem shape so the UI type contract remains valid and create a ticket
to track integrating the actual API.

In `@src/features/home/manager/hooks/useMonthlySchedulesViewModel.ts`:
- Around line 16-21: The query key currently uses workspaceId ?? 0 which creates
a transient cache entry for 0; update the queryKey to only use the real
workspaceId (matching the enabled guard) — e.g. replace
queryKeys.manager.schedules(workspaceId ?? 0, year, month) with
queryKeys.manager.schedules(workspaceId!, year, month) (or otherwise
short-circuit the whole useQuery when workspaceId is null) so the key and
enabled: workspaceId !== null are consistent; keep fetchMonthlySchedules and
enabled as-is.

In `@src/features/home/manager/hooks/useWorkerScheduleManageViewModel.ts`:
- Around line 15-29: selectedDays and toggleDay accept any string; constrain
them to the WORKDAY_OPTIONS union to prevent invalid values. Update the state
declaration (selectedDays / setSelectedDays) to use the WORKDAY_OPTIONS type (or
a derived Workday type) instead of string[], change toggleDay(day: string) to
toggleDay(day: Workday) and update any callers to pass only valid
WORKDAY_OPTIONS entries, and add a runtime guard inside toggleDay (e.g., check
WORKDAY_OPTIONS.includes(day)) before calling setSelectedDays to be defensive;
reference DEFAULT_SELECTED_DAYS, WORKDAY_OPTIONS, toggleDay, selectedDays, and
setSelectedDays when making these edits.

In `@src/features/home/manager/types/substitute.ts`:
- Around line 63-68: The formatDateRange function currently renders redundant
output when startDateTime and endDateTime fall on the same calendar day (e.g.,
"4월 16일 ↔ 4월 16일"); update formatDateRange to detect same-day ranges (compare
start and end via getFullYear(), getMonth(), and getDate() or use
toDateString()) and return a single formatted date string (using the existing
fmt(start)) for same-day cases, otherwise keep the existing "start ↔ end"
output; update the function signature and maintain the fmt helper and return
type string.

In `@src/features/home/user/applied-stores/ui/AppliedStoreList.tsx`:
- Line 27: The section in AppliedStoreList.tsx uses a hard fixed width class
"w-[358px]" which can overflow on narrow screens; update the element (the
<section> in the AppliedStoreList component) to use a responsive guard by
replacing the fixed width with "w-full max-w-[358px]" (i.e., add w-full and
max-w-[358px] while keeping the existing rounded-2xl bg-white py-6 classes) so
it preserves the design width but remains responsive.

In `@src/features/home/user/workspace/api/workspaceMembers.ts`:
- Around line 12-42: Wrap the axios calls in getWorkspaceWorkers and
getWorkspaceManagers in a try/catch like getSelfSchedule does: catch Axios
errors, normalize/format the error (e.g., extract response.status/message or use
the existing handleApiError helper if one exists) and rethrow a consistent Error
object or custom ApiError so callers (and logs) get a uniform message; ensure
you reference the functions getWorkspaceWorkers and getWorkspaceManagers and
maintain returning response.data on success.

In `@src/features/home/user/workspace/api/workspaceSchedule.ts`:
- Around line 2-11: The file imports CalendarEvent, CalendarViewData and the
date helpers toDateKey, toTimeLabel, getDurationHours, formatScheduleTimeRange
from the legacy user/schedule module; update the import statements to pull these
types and functions from the new common schedule module instead (replace the
existing imports that reference CalendarEvent, CalendarViewData and the four
date helper functions), keeping the same symbol names so usages in
workspaceSchedule.ts remain unchanged and run the build to verify there are no
unresolved imports.

In `@src/features/home/user/workspace/hooks/useWorkspacesViewModel.ts`:
- Around line 37-44: The returned hasNextPage in useWorkspacesViewModel may be
undefined; normalize it to a boolean like other hooks so callers always get
true/false. Update the return object in useWorkspacesViewModel to coerce
hasNextPage with !!hasNextPage (matching useWorkspaceWorkersViewModel) so the
exported property is consistently boolean.

In `@src/features/home/user/workspace/ui/WorkingStoresList.tsx`:
- Line 28: The section in the WorkingStoresList component uses a hard fixed
width class "w-[358px]" which can cause overflow; update the section's class to
use responsive sizing (e.g., replace "w-[358px]" with "w-full max-w-[358px]") so
the container can shrink in tight layouts while keeping the intended maximum
width.

In `@src/pages/manager/worker-schedule/index.tsx`:
- Around line 63-66: Extract the hardcoded gradient avatar placeholder into a
reusable component or CSS utility so the color (`#ececec`) can be centralized:
create a PlaceholderAvatar React component (or a .placeholder-avatar utility)
that renders the div with the repeating-conic-gradient and accepts a prop or CSS
variable for the base color, then replace the inline div in the worker-schedule
page with <PlaceholderAvatar /> (or apply the .placeholder-avatar class) so the
pattern and color are reusable across the app.

In `@src/pages/user/applied-stores/index.tsx`:
- Around line 34-37: The cancel handler (handleCancel) can trigger multiple
requests if invoked rapidly; add a guard using the cancellation state (e.g.,
isCancelling or cancelApplication.isLoading) to early-return when a cancellation
is already in progress, and ensure the modal/button is disabled while cancelling
to prevent double-invocation; update the modal's cancel button to use that
disabled state and keep closeDetail called only from the mutation onSuccess so
duplicate calls won't race with selectedStore becoming stale.

In `@src/pages/user/workspace-detail/index.tsx`:
- Around line 24-30: The destructured variable "mode" from
useWorkspaceScheduleViewModel(id) is unused; remove "mode" from the
destructuring to eliminate the unused variable warning, or if mode switching is
intended, wire the returned "mode" into the component's state/JSX logic (e.g.,
pass it to rendering conditionals or handlers). Locate the call
useWorkspaceScheduleViewModel in the component and either delete "mode" from the
destructured list or use "mode" where appropriate (e.g., in rendering or effect
logic) to reflect the view model's mode.
- Around line 91-92: The onOptions prop is currently passed an empty no-op which
signals incomplete behavior; update the component instance that sets onOptions
to either wire it to a real handler (e.g., implement and pass a function like
handleOptions in this module that opens the options menu, navigates, or triggers
the intended action) or at minimum replace the empty arrow with a clear TODO
comment (e.g., // TODO: implement options handler) so the placeholder is
explicit; locate the prop usage named onOptions in this file and connect it to
the appropriate handler function or add the TODO inline.

In `@src/shared/lib/queryKeys.ts`:
- Around line 1-7: The shared util queryKeys currently imports
SelfScheduleQueryParams from a feature API which inverts dependencies; extract
SelfScheduleQueryParams into a lower-level shared types module (a small
types-only file) and re-export it so both the feature API (where the type
originated) and this shared/lib/queryKeys import the type from that new module
instead of from the feature API; update the import in the queryKeys file to
reference the new types module and update the feature API to import the type
from the same new module.

In `@src/shared/ui/manager/OngoingPostingCard.tsx`:
- Around line 68-74: In OngoingPostingCard.tsx replace the hardcoded hex values
in the span className (the conditional branch that uses isUrgent and renders
posting.dDay) with Tailwind tokens (e.g., use bg-urgent text-urgent for the
urgent branch and bg-urgent-light text-urgent for the non-urgent branch),
updating the template literal around isUrgent accordingly; if those tokens don’t
exist yet, add semantic color tokens (bg-urgent, bg-urgent-light, text-urgent)
under theme.extend.colors in tailwind.config.js and then run the Tailwind build
so the new classes are available.

In `@src/shared/ui/manager/SubstituteApprovalCard.tsx`:
- Around line 28-33: The statusConfig object in SubstituteApprovalCard (and
related UI pieces: the avatar fallback and the role pill) uses hardcoded hex
colors—replace those hex literals (e.g., text-[`#3A9982`], border-[`#3A9982`],
bg-[`#EFEFEF`], text-[`#2CE283`], bg-[`#EAFDF3`], text-[`#E28D2C`], bg-[`#FDF8EA`]) with
the registered Tailwind tokens so the UI remains themeable: use text-sub and
border-sub for the green sub color and bg-bg-dark for the gray background;
update the statusConfig entries, the avatar fallback rendering, and the role
pill className in SubstituteApprovalCard, and apply the same token replacements
in src/pages/manager/home/index.tsx and any files under
src/shared/ui/manager/**/*.tsx that still contain hardcoded hexes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

Comment on lines +22 to +30
useEffect(() => {
if (workspaces.length === 0) return
if (activeWorkspaceId !== null) return

const defaultWorkspace = workspaces.reduce((prev, curr) =>
curr.id < prev.id ? curr : prev
)
setActiveWorkspaceId(defaultWorkspace.id)
}, [workspaces, activeWorkspaceId, setActiveWorkspaceId])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle stale persisted activeWorkspaceId values.

Line 24 only checks for null. If persisted activeWorkspaceId is non-null but absent from workspaces, the hook never self-heals and dependent queries can target an invalid workspace.

🔧 Suggested fix
   useEffect(() => {
     if (workspaces.length === 0) return
-    if (activeWorkspaceId !== null) return
+    const hasValidActiveWorkspace =
+      activeWorkspaceId !== null &&
+      workspaces.some(workspace => workspace.id === activeWorkspaceId)
+    if (hasValidActiveWorkspace) return
 
     const defaultWorkspace = workspaces.reduce((prev, curr) =>
       curr.id < prev.id ? curr : prev
     )
     setActiveWorkspaceId(defaultWorkspace.id)
   }, [workspaces, activeWorkspaceId, setActiveWorkspaceId])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/hooks/useManagedWorkspacesQuery.ts` around lines 22
- 30, The useEffect in useManagedWorkspacesQuery only guards against
activeWorkspaceId being null but not against a stale persisted id missing from
workspaces; update the effect so it also checks whether activeWorkspaceId exists
in the current workspaces array (e.g., find/workspaces.some(w => w.id ===
activeWorkspaceId)) and if not, compute the defaultWorkspace (using the existing
reduce logic) and call setActiveWorkspaceId(defaultWorkspace.id); keep the
existing early return for empty workspaces and ensure you reference
activeWorkspaceId, setActiveWorkspaceId, and workspaces in the updated
condition.

Comment on lines +8 to +11
const { data, isPending, isError } = useQuery({
queryKey: queryKeys.managerWorkspace.detail(workspaceId ?? 0),
queryFn: () => fetchWorkspaceDetail(workspaceId!),
enabled: workspaceId !== null,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- queryKeys definition ---'
fd -a 'queryKeys.ts' | xargs -r sed -n '1,240p'

echo
echo '--- manager workspace detail key usages ---'
rg -n -C2 'managerWorkspace\.detail\(' --type ts --type tsx

echo
echo '--- activeWorkspaceId defaults and usages ---'
rg -n -C2 '\bactiveWorkspaceId\b' --type ts --type tsx

Repository: alter-app/alter-client

Length of output: 2019


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- useWorkspaceDetailQuery.ts full file ---'
fd 'useWorkspaceDetailQuery.ts' -x cat -n {}

echo
echo '--- managerWorkspace.detail usages (corrected search) ---'
rg -n -C3 'managerWorkspace\.detail\(' --type ts

echo
echo '--- workspace id type definitions ---'
rg -n 'workspaceId.*:' --type ts | head -30

echo
echo '--- usage of workspace id = 0 ---'
rg -n 'workspaceId.*0\|0.*workspaceId' --type ts | head -20

Repository: alter-app/alter-client

Length of output: 4660


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- useMonthlySchedulesViewModel.ts ---'
fd 'useMonthlySchedulesViewModel.ts' -x cat -n {}

echo
echo '--- useWorkspaceWorkersViewModel.ts ---'
fd 'useWorkspaceWorkersViewModel.ts' -x cat -n {}

echo
echo '--- queryKeys with optional parameters (pattern check) ---'
rg -B2 -A3 'workspaceId\?\s*[:=]' src/shared/lib/queryKeys.ts

Repository: alter-app/alter-client

Length of output: 6299


Don't alias null to workspace 0 in the query key.

detail(workspaceId ?? 0) makes the "no workspace selected" state share a cache key with workspace ID 0. Even though the query is disabled when workspaceId is null, the cache entry persists. If workspace 0 ever becomes a valid ID or the enabled state changes, you could retrieve stale/incorrect data from cache. Prefer a distinct nullable segment in the key (e.g., make the key builder accept number | null and pass it through, or use a string sentinel).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/hooks/useWorkspaceDetailQuery.ts` around lines 8 -
11, The query key must not coerce null to 0; change the call to
queryKeys.managerWorkspace.detail(...) to pass workspaceId through as nullable
(or a distinct sentinel string) instead of using (workspaceId ?? 0) so the cache
differentiates "no workspace selected" from workspace id 0; update the useQuery
invocation (the queryFn fetchWorkspaceDetail(workspaceId!) can remain guarded by
enabled: workspaceId !== null) and, if necessary, adjust
queryKeys.managerWorkspace.detail to accept number | null (or a string sentinel)
so the key retains the null state.

Comment on lines +71 to +74
function formatWage(payAmount: number, paymentType: string): string {
const label = PAYMENT_TYPE_LABEL[paymentType] ?? '시급'
const amount = payAmount.toLocaleString('ko-KR')
return `${label} ${amount}원`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not default unknown payment types to 시급.

Line 72 can misrepresent wage type when backend adds/changes enum values. Unknown values should not be displayed as hourly pay.

🔧 Proposed fix
 function formatWage(payAmount: number, paymentType: string): string {
-  const label = PAYMENT_TYPE_LABEL[paymentType] ?? '시급'
+  const label = PAYMENT_TYPE_LABEL[paymentType] ?? paymentType
   const amount = payAmount.toLocaleString('ko-KR')
   return `${label} ${amount}원`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function formatWage(payAmount: number, paymentType: string): string {
const label = PAYMENT_TYPE_LABEL[paymentType] ?? '시급'
const amount = payAmount.toLocaleString('ko-KR')
return `${label} ${amount}원`
function formatWage(payAmount: number, paymentType: string): string {
const label = PAYMENT_TYPE_LABEL[paymentType] ?? paymentType
const amount = payAmount.toLocaleString('ko-KR')
return `${label} ${amount}원`
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/types/posting.ts` around lines 71 - 74, The
function formatWage currently falls back to the '시급' label for unknown
paymentType values; update it so unknown/added backend enum values are not
misrepresented by removing that default and handling absent labels explicitly:
lookup PAYMENT_TYPE_LABEL[paymentType] (refer to PAYMENT_TYPE_LABEL and
formatWage), if a label exists return `${label} ${amount}원`, otherwise return
just `${amount}원` (or a neutral placeholder) so unknown types are not shown as
hourly pay.

Comment on lines +1 to +2
import type { CommonApiResponse } from '@/shared/types/common'
import type { StoreWorkerRole } from '@/features/home/manager/ui/StoreWorkerListItem'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move StoreWorkerRole out of the UI layer.

src/features/home/manager/types/worker.ts should not depend on @/features/home/manager/ui/StoreWorkerListItem. That reverses the dependency direction and makes this adapter layer fragile. Extract StoreWorkerRole into a shared types module and have both files import from there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/manager/types/worker.ts` around lines 1 - 2, The type
StoreWorkerRole is defined in the UI component StoreWorkerListItem and being
imported into the adapter types (worker.ts), reversing dependencies; extract
StoreWorkerRole into a new shared types module (e.g., create a new module named
storeWorkerTypes or similar), export the StoreWorkerRole type there, update the
adapter type file that currently imports StoreWorkerRole and the UI component
StoreWorkerListItem to import StoreWorkerRole from the new shared module, and
remove the UI-layer import from the adapter so the dependency direction is
correct and the build passes.

Comment on lines +18 to +23
const FILTER_OPTIONS: { key: FilterType; label: string }[] = [
{ key: 'completed', label: '지원 완료' },
{ key: 'viewed', label: '열람' },
{ key: 'not_viewed', label: '미열람' },
{ key: 'cancelled', label: '지원 취소' },
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add the 'all' filter to the dropdown options.

selectedFilter starts at 'all', but filterOptions never exposes that value. After choosing another filter, users can't get back to the full list without reloading the page.

Proposed fix
 const FILTER_OPTIONS: { key: FilterType; label: string }[] = [
+  { key: 'all', label: '전체' },
   { key: 'completed', label: '지원 완료' },
   { key: 'viewed', label: '열람' },
   { key: 'not_viewed', label: '미열람' },
   { key: 'cancelled', label: '지원 취소' },
 ]

Also applies to: 112-119

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/applied-stores/hooks/useAppliedStoresViewModel.ts`
around lines 18 - 23, FILTER_OPTIONS does not include the 'all' option even
though selectedFilter defaults to 'all', so users cannot return to the
unfiltered view; update the FILTER_OPTIONS array (and the identical array used
later around the other occurrence) to include an { key: 'all', label: '전체' }
entry (preferably as the first element) and ensure the FilterType union includes
'all' so the dropdown exposes and accepts that value.

Comment on lines +19 to +29
} = useInfiniteQuery({
queryKey: queryKeys.workspace.workers(workspaceId),
queryFn: ({ pageParam }) =>
getWorkspaceWorkers(workspaceId, {
pageSize,
cursor: pageParam as string | undefined,
}),
initialPageParam: undefined as string | undefined,
getNextPageParam: lastPage => lastPage.data.page.cursor ?? undefined,
enabled: workspaceId > 0,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how queryKeys.workspace.workers is defined and used
rg -n "workspace\.workers" --type=ts -C 2

Repository: alter-app/alter-client

Length of output: 602


🏁 Script executed:

#!/bin/bash
# Find where queryKeys is defined
fd -t f queryKeys -e ts | head -20

Repository: alter-app/alter-client

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Search for queryKeys.workspace definition
rg -n "queryKeys\s*[=:]" --type=ts | grep -E "(workspace|workers)" | head -20

Repository: alter-app/alter-client

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Find all usages of useWorkspaceWorkersViewModel hook
rg -n "useWorkspaceWorkersViewModel" --type=ts -C 3

Repository: alter-app/alter-client

Length of output: 6191


🏁 Script executed:

#!/bin/bash
# Look for the hook's definition to see pageSize parameter
cat -n src/features/home/user/workspace/hooks/useWorkspaceWorkersViewModel.ts | head -40

Repository: alter-app/alter-client

Length of output: 1389


🏁 Script executed:

#!/bin/bash
# Check queryKeys definition
cat -n src/shared/lib/queryKeys.ts | head -100

Repository: alter-app/alter-client

Length of output: 2264


🏁 Script executed:

#!/bin/bash
# Check what initialPageSize is in useWorkspaceMembers.ts
cat -n src/pages/user/workspace-members/hooks/useWorkspaceMembers.ts

Repository: alter-app/alter-client

Length of output: 1796


Include pageSize in the query key to prevent cache collisions.

The queryKey function queryKeys.workspace.workers already accepts pageSize as a parameter and includes it in the cache key tuple, but the hook calls it with only workspaceId, omitting pageSize. Since different call sites use different page sizes (5, 3, and 10), queries with the same workspace but different page sizes incorrectly share cached data.

Fix: Pass pageSize to queryKey
   } = useInfiniteQuery({
-    queryKey: queryKeys.workspace.workers(workspaceId),
+    queryKey: queryKeys.workspace.workers(workspaceId, undefined, pageSize),
     queryFn: ({ pageParam }) =>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/home/user/workspace/hooks/useWorkspaceWorkersViewModel.ts`
around lines 19 - 29, The query key used in the useInfiniteQuery call is missing
the pageSize, causing cache collisions; update the queryKey invocation to
include pageSize by calling queryKeys.workspace.workers(workspaceId, pageSize)
so the cache tuple distinguishes different page sizes, leaving the rest of the
useInfiniteQuery config (queryFn, getNextPageParam, enabled) unchanged; locate
the useInfiniteQuery call in useWorkspaceWorkersViewModel and adjust the
queryKey parameter accordingly.

Comment thread src/pages/login/index.tsx
Comment on lines 22 to +25
if (scope === 'MANAGER') {
navigate('/main', { replace: true })
} else {
navigate('/job-lookup-map', { replace: true })
navigate('/user/job-lookup-map', { replace: true })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Route declarations mentioning /main or /manager/home =="
rg -n --glob '**/*.{ts,tsx}' "path\s*[:=].*['\"]/(main|manager/home)|['\"]/(main|manager/home)['\"]"

echo
echo "== Manager login redirect call sites =="
rg -n --glob '**/*.{ts,tsx}' "scope\s*===\s*['\"]MANAGER['\"]|navigate\(['\"]/(main|manager/home)['\"]"

Repository: alter-app/alter-client

Length of output: 1122


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== All route path definitions in App.tsx =="
rg -n "path\s*=" src/app/App.tsx -A 1 | head -30

echo
echo "== Search for any /main route definition (exhaustive) =="
rg -n "'/main'|\"\/main\"" --glob '**/*.{ts,tsx}' | head -20

echo
echo "== Check routing files structure =="
fd -e tsx -e ts -path '*app*' -o -path '*route*' | head -20

Repository: alter-app/alter-client

Length of output: 1515


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for /main route definition =="
rg -n "/main" src/app/App.tsx

echo
echo "== Confirm all manager routes =="
rg -n "manager" src/app/App.tsx

echo
echo "== Examine the other 3 navigate('/main') calls in auth.ts =="
sed -n '100,115p' src/shared/api/auth.ts
sed -n '150,165p' src/shared/api/auth.ts
sed -n '320,335p' src/shared/api/auth.ts

Repository: alter-app/alter-client

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Context around navigate('/main') in auth.ts (all 3 occurrences) =="
sed -n '105,112p' src/shared/api/auth.ts
echo "---"
sed -n '156,163p' src/shared/api/auth.ts
echo "---"
sed -n '325,332p' src/shared/api/auth.ts

echo
echo "== Verify /manager/home route exists =="
grep -n "manager/home" src/app/App.tsx

Repository: alter-app/alter-client

Length of output: 836


Manager redirect path broken across authentication flows.

The route /main does not exist in the application—it was likely removed when migrating pages under /manager/*. This breaks manager post-login navigation in multiple places:

  • src/pages/login/index.tsx:23 (login)
  • src/shared/api/auth.ts:108 (login completion)
  • src/shared/api/auth.ts:159 (signup verification)
  • src/shared/api/auth.ts:328 (signup completion)

All four instances must redirect to /manager/home instead.

Proposed fix

For src/pages/login/index.tsx:23:

       if (scope === 'MANAGER') {
-        navigate('/main', { replace: true })
+        navigate('/manager/home', { replace: true })
       } else {
         navigate('/user/job-lookup-map', { replace: true })
       }

Apply the same change to src/shared/api/auth.ts at lines 108, 159, and 328.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (scope === 'MANAGER') {
navigate('/main', { replace: true })
} else {
navigate('/job-lookup-map', { replace: true })
navigate('/user/job-lookup-map', { replace: true })
if (scope === 'MANAGER') {
navigate('/manager/home', { replace: true })
} else {
navigate('/user/job-lookup-map', { replace: true })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/login/index.tsx` around lines 22 - 25, The manager post-login
redirect is pointing to the removed route '/main'; update every place that
navigates to '/main' so managers go to '/manager/home' instead—specifically
change the navigate('/main', { replace: true }) call in the Login page component
(scope === 'MANAGER' branch in src/pages/login/index.tsx) and the corresponding
redirect calls in the auth flow in src/shared/api/auth.ts (the login completion,
signup verification, and signup completion logic that currently use
navigate('/main') or return '/main') to use '/manager/home' so all manager
authentication flows land on the correct route.

Comment on lines +109 to +131
<WorkspaceChangeList
workspaces={workspaceChangeModal.items}
selectedWorkspaceId={
workspaceChangeModal.selectedWorkspaceId ?? undefined
}
categoryLabel="카페"
className="mt-[19px]"
onSelectWorkspace={selectWorkspace}
onEditWorkspace={() => {}}
/>

<button
type="button"
className="mx-auto mt-4 flex h-[26px] w-[26px] items-center justify-center"
aria-label="업장 추가"
>
<img
src={managerWorkspaceModalPlusIcon}
alt=""
aria-hidden="true"
className="h-full w-full"
/>
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Wire the workspace modal to real data/actions or hide the controls.

categoryLabel="카페" will mislabel non-cafe workspaces, onEditWorkspace={() => {}} exposes a dead edit action, and the “업장 추가” button has no click handler. This ships a modal with incorrect data and non-functional controls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manager/home/index.tsx` around lines 109 - 131, The
WorkspaceChangeList is using a hardcoded categoryLabel and a noop edit handler
and the add button has no click handler; update WorkspaceChangeList to pass the
correct category for each workspace instead of categoryLabel="카페", replace
onEditWorkspace={() => {}} with a real handler that opens the workspace edit
modal (hook or function used elsewhere, e.g., openEditWorkspaceModal or
editWorkspace), ensure selectedWorkspaceId uses
workspaceChangeModal.selectedWorkspaceId, and wire the add button (the element
rendering managerWorkspaceModalPlusIcon) to the workspace creation action (e.g.,
openCreateWorkspaceModal or createWorkspace) or hide the edit/add controls when
those actions are not available.

Comment on lines +160 to +167
<div className="px-4 pb-8">
<button
type="button"
className="h-12 w-full rounded-2xl bg-main typography-body01-semibold text-text-100"
>
저장
</button>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Save button has no onClick handler - changes cannot be persisted.

The "저장" button is rendered but clicking it does nothing. This leaves the feature incomplete. If the save API is not ready yet, consider adding a TODO comment or disabling the button.

Would you like me to help scaffold the save handler that wires up to a mutation?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manager/worker-schedule/index.tsx` around lines 160 - 167, The "저장"
button currently has no onClick, so user edits aren't persisted; add an onClick
prop that calls a new handler (e.g., handleSave) and wire that handler to the
existing save mutation or API call (e.g., useSaveSchedule / saveSchedule /
apiSaveWorkerSchedule) to perform the persist operation; if the mutation doesn't
exist yet, scaffold handleSave to validate state, call the save API/mutation,
handle success/error (show toast or set error state) and disable the button
while saving (or add a TODO comment and set disabled={true}) so the UI is not
misleading.

Comment on lines +10 to +13
workers: (workspaceId?: number, cursor?: string, pageSize?: number) =>
['workspace', 'workers', workspaceId, cursor, pageSize] as const,
managers: (workspaceId?: number, cursor?: string, pageSize?: number) =>
['workspace', 'managers', workspaceId, cursor, pageSize] as const,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C4 'useInfiniteQuery|queryKeys\.workspace\.(workers|managers)\(' src/features/home/user/workspace

Repository: alter-app/alter-client

Length of output: 4616


🏁 Script executed:

cat -n src/shared/lib/queryKeys.ts | sed -n '1,30p'

Repository: alter-app/alter-client

Length of output: 1471


Remove cursor and pageSize from the stable query key for paged member lists.

The workers and managers factories include these parameters in the returned key array, but they should only accept workspaceId as a stable parameter. Including page cursors in the key creates a separate cache entry per page, which breaks page accumulation in infinite queries. The cursor must stay in pageParam only.

  • Line 11: Remove cursor, pageSize from the key array for workers
  • Line 13: Remove cursor, pageSize from the key array for managers

Current callers already pass only workspaceId, but the factory signatures misleadingly suggest these parameters belong in the stable key.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/lib/queryKeys.ts` around lines 10 - 13, The workers and managers
query-key factories currently include cursor and pageSize in their returned
stable key which causes per-page cache entries; update the workers and managers
functions in queryKeys.ts to only accept (workspaceId?: number) and return
['workspace','workers',workspaceId] as const and
['workspace','managers',workspaceId] as const respectively (remove cursor and
pageSize from both the parameter list and the returned array); keep the const
tuple return and leave pagination cursor handling to pageParam in the infinite
query callers.

Copy link
Copy Markdown
Member

@dohy-eon dohy-eon left a comment

Choose a reason for hiding this comment

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

  1. 메인-상단네브-로고 폰트 깨짐이슈
Image
  1. 로그인 시 /main(빈 페이지)로 이동하는 라우팅 오류

  2. #DC0000 (오류 색상코드) / #2DE283 하드코딩 이슈

Image

수정 부탁드립니다

@limtjdghks
Copy link
Copy Markdown
Member Author

  1. 메인-상단네브-로고 폰트 깨짐이슈
Image 2. 로그인 시 /main(빈 페이지)로 이동하는 라우팅 오류 3. #DC0000 (오류 색상코드) / #2DE283 하드코딩 이슈 Image 수정 부탁드립니다
Image

어라 저는 완전 짱 잘 보이는데여..

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.

2 participants