Skip to content

[feat] 근무자 퇴사 API 연동#48

Merged
limtjdghks merged 16 commits into
devfrom
feat/ALT-243
Jun 8, 2026
Merged

[feat] 근무자 퇴사 API 연동#48
limtjdghks merged 16 commits into
devfrom
feat/ALT-243

Conversation

@limtjdghks
Copy link
Copy Markdown
Member

@limtjdghks limtjdghks commented Jun 6, 2026

ID

  • ALT-243

변경 내용

  • 매니저 페이지에 근무자 퇴사처리 기능 추가
  • 알바생 페이지에 퇴사하기 기능 추가
  • 퇴사한 근무자가 목록에 노출되지 않도록 필터링 처리

구현 사항

공통

  • ActionMenu 공통 컴포넌트 구현 (더보기 버튼 클릭 시 나타나는 드롭다운 메뉴)
  • ConfirmModal 공통 컴포넌트 구현 (파괴적 액션 확인용 모달)
  • 퇴사 아이콘(resign.svg) 추가

매니저 — 근무자 퇴사처리

  • StoreWorkerListItem에 액션 메뉴 연결, 퇴사하기 메뉴 항목 추가
  • 퇴사하기 클릭 시 확인 모달 노출 → 확정 시 퇴사 처리 API 호출
  • useResignWorkerMutation 구현 (PATCH /manager/workspaces/{id}/workers/{workerId}/resign)
  • 퇴사 처리 후 근무자 목록 쿼리 자동 무효화(invalidate)
  • 액션 메뉴와 더보기 버튼 간격 피그마 스펙(8px)에 맞게 수정
  • useManagerHomeViewModel에서 status: 'ACTIVATED' 필터 적용 → 퇴사자 목록 미노출

알바생 — 퇴사하기

  • 근무중인 가게 상세 페이지 하단에 퇴사하기 버튼 추가 (연한 빨간 배경, 퇴사 아이콘 포함)
  • 클릭 시 확인 모달 노출 → 확정 시 퇴사 API 호출 후 지도 페이지로 이동
  • useResignWorkspaceMutation 구현 (PATCH /user/workspaces/{workspaceId}/resign)

구현 시연 (필요 시)

사장님

2026-06-06.9.23.07.mov

알바생

2026-06-06.9.44.37.mov

Summary by CodeRabbit

  • New Features

    • 매니저가 직원 퇴사 처리 기능 추가 (확인 모달 포함)
    • 사용자가 작업공간에서 퇴사할 수 있는 기능 추가
    • ActionMenu 컴포넌트 추가 (메뉴 항목 동적 구성)
    • ConfirmModal 컴포넌트 기능 확장 (Escape 키 지원, 스크롤 잠금)
  • Refactor

    • StoreWorkerListItem 컴포넌트 메뉴 처리 방식 개선
    • 활성화된 직원만 표시하도록 필터링 개선
  • Documentation

    • ActionMenu Storybook 스토리 추가

@limtjdghks limtjdghks requested review from dohy-eon and kim3360 June 6, 2026 00:47
@limtjdghks limtjdghks self-assigned this Jun 6, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 6, 2026

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

Project Deployment Actions Updated (UTC)
alter-client Ready Ready Preview, Comment Jun 8, 2026 2:02am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

두 개의 resignation 기능(관리자의 근무자 퇴사 처리, 사용자의 작업공간 퇴사)을 추가합니다. 공용 UI 컴포넌트(ActionMenu, ConfirmModal)를 먼저 구현하고, 각 기능별로 API, 훅, 페이지 통합을 순차적으로 추가합니다.

Changes

Worker and Workspace Resignation Flows

Layer / File(s) Summary
Shared UI components
src/shared/ui/common/ActionMenu.tsx, src/shared/ui/common/ConfirmModal.tsx, storybook/stories/ActionMenu.stories.tsx
ActionMenu는 메뉴 항목 배열을 받아 토글 가능한 메뉴를 렌더링하며, 외부 클릭/터치로 자동 닫힘. ConfirmModal은 선택적 취소 버튼, Escape 키 처리, 본문 스크롤 잠금, isPending 상태별 버튼 표시를 지원합니다.
Manager worker resignation API & types
src/features/manager/worker-list/types/resign.ts, src/features/manager/api/worker.ts
ResignWorkerParams와 ResignWorkerResponse 타입 정의; resignWorker 함수는 PATCH /manager/workspaces/{workspaceId}/workers/{workerId}/resign 호출합니다.
Manager worker resignation hook & UI adaptation
src/features/manager/worker-list/hooks/mutation/useResignWorkerMutation.ts, src/features/manager/home/ui/StoreWorkerListItem.tsx, src/features/manager/home/hooks/useManagerHomeViewModel.ts
useResignWorkerMutation은 API 호출 후 워커 목록 쿼리 무효화. StoreWorkerListItem은 onOptions 콜백에서 menuItems 배열 기반으로 전환하고, 내부 ActionMenu 상태 관리. useManagerHomeViewModel에 { status: 'ACTIVATED' } 필터 추가합니다.
Manager home page integration
src/pages/manager/home/index.tsx
근무자 목록의 각 항목에 "퇴사하기" 메뉴 추가; 선택 시 대상자 ID 저장하고 ConfirmModal로 확인. 성공 시 목록 새로고침, 실패 시 재시도 모달 표시합니다.
User workspace resignation API & types
src/features/user/home/workspace/types/resign.ts, src/features/user/home/workspace/api/workspace.ts
ResignWorkspaceResponse 타입 정의; resignWorkspace 함수는 PATCH /app/users/me/workspaces/{workspaceId}/resign 호출합니다.
User workspace detail page integration
src/features/user/home/workspace/hooks/mutation/useResignWorkspaceMutation.ts, src/pages/user/workspace-detail/index.tsx
useResignWorkspaceMutation은 API 호출 후 워크스페이스 목록 쿼리 무효화 및 JOB_LOOKUP_MAP 라우팅. WorkspaceDetailPage는 화면 하단 "퇴사하기" 버튼과 이중 ConfirmModal(확인/실패) 흐름을 추가합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Possibly related PRs

  • alter-app/alter-client#7: ManagerHomePage 구조와 근무자 목록 렌더링 기초를 제공하는 PR로, 본 PR이 동일 페이지에 퇴사 메뉴 및 모달 로직을 추가합니다.

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항인 근무자 퇴사 API 연동을 명확하게 요약하고 있으며, 개발자 관점에서 핵심 변화를 잘 전달하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 ID, 변경 내용, 구현 사항을 충분히 포함하고 있으며, 시연 영상도 제공되어 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

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

❤️ Share

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

@dohy-eon
Copy link
Copy Markdown
Member

dohy-eon commented Jun 6, 2026

@coderabbitai 코드리뷰해줘

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR diff and file list
gh pr view 48 --repo alter-app/alter-client --json files,additions,deletions,changedFiles
echo "---FILES---"
gh pr diff 48 --repo alter-app/alter-client

Length of output: 46282



@dohy-eon 시니어 FE 관점에서 핵심 이슈만 정리했습니다.


🔴 버그 / 회귀

1. resignWorkspace API 메서드·엔드포인트 불일치

PR 설명에는 POST /user/workspaces/{workspaceId}/resign이라고 되어 있지만, 실제 구현은 PATCH /app/users/me/workspaces/${workspaceId}/resign을 사용합니다. 서버 스펙과 맞지 않을 경우 405 Method Not Allowed 또는 잘못된 동작이 발생합니다.

// src/features/user/home/workspace/api/workspace.ts
- const response = await axiosInstance.patch<ResignWorkspaceResponse>(
-   `/app/users/me/workspaces/${workspaceId}/resign`
- )
+ const response = await axiosInstance.post<ResignWorkspaceResponse>(
+   `/user/workspaces/${workspaceId}/resign`
+ )

백엔드 스펙을 다시 확인하고 메서드와 경로를 맞춰주세요.


2. activeWorkspaceId ?? 0 — 잘못된 워크스페이스 ID로 mutation 초기화

// src/pages/manager/home/index.tsx
const { mutate: resignWorker } = useResignWorkerMutation(activeWorkspaceId ?? 0)

activeWorkspaceIdnull이면 workspaceId = 0으로 훅이 초기화되어, 퇴사 API가 /manager/workspaces/0/workers/.../resign으로 호출됩니다. activeWorkspaceId가 없을 때는 버튼 자체를 비활성화하거나, mutation 호출 시점에 가드를 추가하세요.

onConfirm={() => {
  if (resignTargetWorkerId === null || activeWorkspaceId === null) return
  resignWorker(resignTargetWorkerId, {
    onSuccess: () => setResignTargetWorkerId(null),
  })
}}

🔴 에러 처리 누락 (두 Mutation 공통)

onError 핸들러가 없어서 API 실패 시 사용자에게 아무런 피드백이 없습니다. 퇴사는 파괴적인 액션이므로 실패 시 반드시 알려야 합니다.

// useResignWorkerMutation.ts / useResignWorkspaceMutation.ts 공통
return useMutation({
  mutationFn: ...,
  onSuccess: () => { ... },
  onError: () => {
    // toast 또는 alert로 실패 알림
    alert('퇴사 처리에 실패했습니다. 다시 시도해주세요.')
  },
})

🟡 모바일 터치 이벤트 누락 (ActionMenu)

ActionMenu의 외부 클릭 감지가 mousedown만 등록하고 touchstart는 빠져 있습니다. 모바일 앱이므로 터치 환경에서 메뉴가 닫히지 않습니다.

// src/shared/ui/common/ActionMenu.tsx
document.addEventListener('mousedown', handleMouseDown)
document.addEventListener('touchstart', handleMouseDown)  // 추가
return () => {
  document.removeEventListener('mousedown', handleMouseDown)
  document.removeEventListener('touchstart', handleMouseDown)  // 추가
}

🟡 접근성 — ConfirmModal aria-labelledby 누락

role="dialog" + aria-modal="true" 조합에는 반드시 aria-labelledby로 제목을 연결해야 스크린 리더가 다이얼로그를 올바르게 안내할 수 있습니다.

// src/shared/ui/common/ConfirmModal.tsx
<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="confirm-modal-title"   // 추가
  ...
>
  <h2 id="confirm-modal-title" ...>{title}</h2>

요약하면, API 스펙 불일치에러 핸들링 누락이 가장 우선 수정이 필요한 항목입니다.

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.

수고하셧습니다~!

Comment thread src/shared/ui/common/ActionMenu.tsx
@dohy-eon
Copy link
Copy Markdown
Member

dohy-eon commented Jun 6, 2026

래빗이랑 코멘트 확인해주시고 반영사항은 수정 부탁드리겠습니다!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/features/user/home/workspace/hooks/mutation/useResignWorkspaceMutation.ts (1)

11-19: ⚡ Quick win

기본 에러 처리 추가를 권장합니다.

현재 mutation에 onError 핸들러가 없어, 호출하는 쪽에서 매번 에러 처리를 구현해야 합니다. workspace-detail 페이지는 per-call onError로 처리하고 있지만, 다른 곳에서 이 훅을 사용할 때 에러 처리를 빠뜨리면 사용자에게 아무 피드백 없이 실패하게 됩니다.

mutation 옵션에 기본 onError 핸들러를 추가해 최소한의 에러 피드백(토스트 등)을 보장하는 것을 권장합니다.

제안: 기본 에러 처리 추가
 return useMutation({
   mutationFn: (workspaceId: number) => resignWorkspace(workspaceId),
   onSuccess: () => {
     queryClient.invalidateQueries({
       queryKey: queryKeys.workspace.list(),
     })
     navigate(ROUTES.USER.JOB_LOOKUP_MAP)
   },
+  onError: (error) => {
+    console.error('퇴사 처리 실패:', error)
+    // toast.error('퇴사 처리에 실패했습니다.')
+  },
 })

참고: 페이지에서 제공한 per-call onError는 이 기본 핸들러 이후에도 실행됩니다.

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

In
`@src/features/user/home/workspace/hooks/mutation/useResignWorkspaceMutation.ts`
around lines 11 - 19, Add a default onError handler to the
useResignWorkspaceMutation hook so failures from resignWorkspace produce minimal
user feedback and logging; inside the useMutation call for
useResignWorkspaceMutation, add an onError: (error) => { show a toast/error
notification and log the error } while keeping the existing onSuccess logic
(queryClient.invalidateQueries and navigate(ROUTES.USER.JOB_LOOKUP_MAP)) so
callers still can supply per-call onError if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/manager/home/index.tsx`:
- Around line 56-57: The resign mutation is being initialized with a fallback
workspaceId (activeWorkspaceId ?? 0) which leads to calls to /workspaces/0/...
when activeWorkspaceId is null; remove the ?? 0 fallback and instead disable the
resign menu when activeWorkspaceId is null (recommended): update the UI logic
that renders the resign menu/button to be disabled/hidden when activeWorkspaceId
is null, and change the useResignWorkerMutation call to only be invoked with a
real id (useResignWorkerMutation(activeWorkspaceId) without ?? 0) so
resignWorker/isResigning are not wired up for a missing workspace; also audit
the onConfirm handler (onConfirm) to ensure it does nothing if activeWorkspaceId
is null as an extra guard.

In `@src/pages/user/workspace-detail/index.tsx`:
- Line 29: The code converts workspaceId with const id = Number(workspaceId) but
doesn't validate it, allowing NaN to reach the resign API; fix by validating
workspaceId before use: ensure Number(workspaceId) is a finite integer (e.g.,
Number.isFinite(id) && id > 0) and either disable the resign button when invalid
or return early in the resign handler (the function that triggers the resign
mutation/API call) to avoid calling /workspaces/NaN/resign; update the UI state
(button disabled) and add a guard in the mutation-invoking code path that
references workspaceId/id to prevent invalid requests.

In `@src/shared/ui/common/ActionMenu.tsx`:
- Around line 51-54: The list key for items.map in ActionMenu.tsx currently uses
key={item.label}, which can cause React reuse bugs when labels duplicate; change
the key to a stable unique identifier from the item (e.g., item.id or item.key)
or ensure each item is assigned a persistent unique id before rendering (only
use array index as a last resort when items are guaranteed never to reorder),
updating the mapping where items.map is rendered (the button/key usage).

---

Nitpick comments:
In
`@src/features/user/home/workspace/hooks/mutation/useResignWorkspaceMutation.ts`:
- Around line 11-19: Add a default onError handler to the
useResignWorkspaceMutation hook so failures from resignWorkspace produce minimal
user feedback and logging; inside the useMutation call for
useResignWorkspaceMutation, add an onError: (error) => { show a toast/error
notification and log the error } while keeping the existing onSuccess logic
(queryClient.invalidateQueries and navigate(ROUTES.USER.JOB_LOOKUP_MAP)) so
callers still can supply per-call onError if needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 48dd596e-265a-43da-82ae-7693a03dc616

📥 Commits

Reviewing files that changed from the base of the PR and between 5204ddb and 4888919.

⛔ Files ignored due to path filters (1)
  • src/assets/icons/home/resign.svg is excluded by !**/*.svg
📒 Files selected for processing (13)
  • src/features/manager/api/worker.ts
  • src/features/manager/home/hooks/useManagerHomeViewModel.ts
  • src/features/manager/home/ui/StoreWorkerListItem.tsx
  • src/features/manager/worker-list/hooks/mutation/useResignWorkerMutation.ts
  • src/features/manager/worker-list/types/resign.ts
  • src/features/user/home/workspace/api/workspace.ts
  • src/features/user/home/workspace/hooks/mutation/useResignWorkspaceMutation.ts
  • src/features/user/home/workspace/types/resign.ts
  • src/pages/manager/home/index.tsx
  • src/pages/user/workspace-detail/index.tsx
  • src/shared/ui/common/ActionMenu.tsx
  • src/shared/ui/common/ConfirmModal.tsx
  • storybook/stories/ActionMenu.stories.tsx

Comment thread src/pages/manager/home/index.tsx
@@ -26,6 +29,10 @@ export function WorkspaceDetailPage() {
const id = Number(workspaceId)
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 | ⚡ Quick win

workspaceId 파라미터 유효성 검증이 필요합니다.

Number(workspaceId)NaN이 될 수 있으며, 이 경우 Line 231에서 API 호출 시 /app/users/me/workspaces/NaN/resign과 같은 잘못된 URL을 생성합니다.

퇴사 버튼을 비활성화하거나 최소한 mutation 호출 전에 유효한 숫자인지 검증해야 합니다.

제안: 유효성 검증 추가

방법 1: 버튼 비활성화

+const isValidWorkspaceId = Number.isFinite(id) && id > 0
+
 <button
   type="button"
   className="w-full mt-12 mb-6 py-[14px] rounded-xl bg-error/10 typography-body01-semibold text-error"
   onClick={() => setIsResignModalOpen(true)}
+  disabled={!isValidWorkspaceId}
 >
   퇴사하기
 </button>

방법 2: mutation 호출 전 검증

 onConfirm={() => {
+  if (!Number.isFinite(id) || id <= 0) return
   resignWorkspace(id, {
     onError: () => {
       setIsResignModalOpen(false)
       setIsResignErrorOpen(true)
     },
   })
 }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/user/workspace-detail/index.tsx` at line 29, The code converts
workspaceId with const id = Number(workspaceId) but doesn't validate it,
allowing NaN to reach the resign API; fix by validating workspaceId before use:
ensure Number(workspaceId) is a finite integer (e.g., Number.isFinite(id) && id
> 0) and either disable the resign button when invalid or return early in the
resign handler (the function that triggers the resign mutation/API call) to
avoid calling /workspaces/NaN/resign; update the UI state (button disabled) and
add a guard in the mutation-invoking code path that references workspaceId/id to
prevent invalid requests.

Comment on lines +51 to +54
{items.map((item, index) => (
<button
key={item.label}
type="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 | 🟡 Minor | ⚡ Quick win

리스트 key를 label 대신 안정적인 고유값으로 바꿔주세요.

Line 53의 key={item.label}은 라벨 중복 시 React가 항목을 잘못 재사용해 메뉴 항목 렌더/이벤트 매핑이 어긋날 수 있습니다.

수정 예시
 export interface ActionMenuItem {
+  id: string
   icon: React.ReactNode
   label: string
   iconColor?: string
   onClick: () => void
 }
@@
-      {items.map((item, index) => (
+      {items.map((item, index) => (
         <button
-          key={item.label}
+          key={item.id}
           type="button"

As per coding guidelines, **/*.tsx에서는 실질 이슈인 “리스트 key 오용”을 우선 지적해야 합니다.

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

In `@src/shared/ui/common/ActionMenu.tsx` around lines 51 - 54, The list key for
items.map in ActionMenu.tsx currently uses key={item.label}, which can cause
React reuse bugs when labels duplicate; change the key to a stable unique
identifier from the item (e.g., item.id or item.key) or ensure each item is
assigned a persistent unique id before rendering (only use array index as a last
resort when items are guaranteed never to reorder), updating the mapping where
items.map is rendered (the button/key usage).

Source: Coding guidelines

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.

👍

@limtjdghks limtjdghks merged commit f0949cc into dev Jun 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants