Skip to content

[Feature/#91] 멤버 관리 페이지 UI 구현 - #97

Merged
jjjsun merged 11 commits into
developfrom
feature/#91
Mar 15, 2026
Merged

[Feature/#91] 멤버 관리 페이지 UI 구현#97
jjjsun merged 11 commits into
developfrom
feature/#91

Conversation

@jjjsun

@jjjsun jjjsun commented Mar 13, 2026

Copy link
Copy Markdown
Collaborator

🚨 관련 이슈

Closed #91

✨ 변경사항

  • 🐞 BugFix Something isn't working
  • 💻 CrossBrowsing Browser compatibility
  • 🌏 Deploy Deploy
  • 🎨 Design Markup & styling
  • 📃 Docs Documentation writing and editing (README.md, etc.)
  • ✨ Feature Feature
  • 🔨 Refactor Code refactoring
  • ⚙️ Setting Development environment setup
  • ✅ Test Test related (storybook, jest, etc.)

✏️ 작업 내용

  • 워크스페이스 멤버관리 페이지 레이아웃구현했습니다
  • 현재 워크스페이스의 팀 구성원 파트는 MemberList UI로 구현하였고, 권한설정에 대한 파트는 PermissionTable UI로 구현했습니다.
  • 데이터는 현재는 데이터 타입은 API명세서에 맞게 구현하였고, 현재는 API연동전이라 mock데이터를 사용하였고, API연동시작하면 삭제할 예정입니다.
  • 권한 설정에서 관리자 권한은 고정 CheckIcon으로 표시하고, 멤버권한은 토글 형태로 변경할수 있도록 구현하였습니다.
  • 소유권 이전은 ControlBox 컴포넌트를 재사용하였습니다.
멤버관리 1차구현

😅 미완성 작업

  • 모달 작업
  • API 연동

📢 논의 사항 및 참고 사항

  • 작업단위가 커져서 모달 작업은 따로 이슈올려서 작업진행하겠습니다.

  • toggle은 재사용가능성이 높아서 따로 컴포넌트만들어놓았으니, 앞으로의 다른작업에 사용하실때 디자인 적용해서 사용하시면될것같습니다.

  • 팀 구성원 파트에서 관리자와 멤버 역할 변경하는 부분 현재는 모달이 열릴시에 카드높이가 변경되어서 부자연스러워보이는데, 리팩토링 작업 진행예정입니다.

  • 관리자 변경 ControlBox는 워크스페이스 수정 부분과 동일한 패턴으로 배치되도록 수정했습니다

  • 권한 관련해서는 현재 mock데이터 만들어서 7개의 권한을 넣어두었는데, 이부분에 대해서 어떤 권한을 변경가능하도록 할지는 1차 MVP 완료후 2차 MVP에 대해서 논의할때 얘기해보면 좋을것같습니다.

  • 그리고 권한 파트에서 지금 멤버 권한을 토글로 변경가능하도록 설정되었는데, 토글은 손쉽게 상태변경이 가능하다는 장점이 있고 직관적으로 보여서 좋다는 장점이 있지만, 권한은 중요도가 높은 작업이기 때문에 사용자가 한번더 체크하는 장치가 있으면 좋을것같습니다. 매번 토글 상태 변경할때마다 맞습니까?라는 문구가 나오기보다는 현재처럼 유지하고, 권한설정과 소유권변경 사이에 변경사항 저장하기 버튼을 추가로 만들어서, 권한설정을 변경하시겠습니까?라고 안내문구를 한번에 내고 상태 저장도 한번에 되도록 진행하는것이 좋을것같다고 생각했는데, 어떠실까요?

💬 리뷰어 가이드 (P-Rules)
P1: 필수 반영 (Critical) - 버그 가능성, 컨벤션 위반. 해결 전 머지 불가.
P2: 적극 권장 (Recommended) - 더 나은 대안 제시. 가급적 반영 권장.
P3: 제안 (Suggestion) - 아이디어 공유. 반영 여부는 드라이버 자율.
P4: 단순 확인/칭찬 (Nit) - 사소한 오타, 칭찬 등 피드백.

Summary by CodeRabbit

Summary by CodeRabbit

  • 새로운 기능

    • 워크스페이스 멤버 관리 페이지 및 라우트 추가
    • 팀 멤버 목록(아바타, 이름, 이메일), 초대 버튼, 개별 멤버 항목(역할 변경, 삭제) UI 추가
    • 멤버 역할 선택 드롭다운(관리자/멤버) 추가
    • 멤버별 권한을 설정하는 권한 테이블 추가
    • 접근성 고려한 토글 스위치 UI 컴포넌트 추가
  • 타입/데이터

    • 멤버 및 권한 관련 공개 타입 정의 추가
  • 스타일/레이아웃

    • 레이아웃 및 오버플로우, 컴포넌트 너비 처리 관련 스타일 개선 (응답형 레이아웃 개선, 메인 영역 스크롤 처리 변경)

@jjjsun
jjjsun requested review from Seojegyeong and YermIm March 13, 2026 11:03
@jjjsun jjjsun self-assigned this Mar 13, 2026
@jjjsun jjjsun added the ✨ Feature 기능 개발 label Mar 13, 2026
@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

워크스페이스 멤버 관리 UI를 추가합니다. 토글 컴포넌트, 멤버 리스트/아이템/역할 선택, 권한 테이블, 멤버 관리 페이지 및 라우트(/workspace/:workspaceId/members)와 관련 타입들이 새로 추가되었습니다.

Changes

Cohort / File(s) Summary
공용 컴포넌트
src/components/common/toggle/Toggle.tsx
새로운 Switch 역할의 토글 컴포넌트 추가: checked, onToggle, ariaLabel, disabled, className, thumbClassName 등 props 제공. twMerge로 동적 클래스 적용 및 접근성 role/aria 속성 포함.
멤버 리스트 / 아이템
src/components/workspace/MemberList.tsx, src/components/workspace/MemberItem.tsx
멤버 목록과 항목 컴포넌트 추가. 모의 데이터로 렌더링하고 역할 변경 핸들러와 삭제 버튼(현재 placeholder) 포함. 초대 버튼 UI 포함.
역할 선택 드롭다운
src/components/workspace/MemberRoleSelect.tsx
ADMIN/MEMBER 선택 드롭다운 추가: 열림/닫힘 상태 관리, 외부 클릭 감지로 닫기 처리, 옵션 선택 시 onChange 호출, 트랜지션 및 스타일 맵 적용.
권한 테이블
src/components/workspace/PermissionTable.tsx
권한 행 정의 및 멤버 권한 토글 상태 관리 추가. 관리자 열은 읽기 전용 표시, 각 행에 토글(새 Toggle 컴포넌트) 배치.
페이지 및 라우팅
src/pages/workspace/MemberManagement.tsx, src/pages/workspace/Workspace.tsx, src/routes/MainRoutes.tsx
멤버 관리 페이지 컴포넌트 추가 및 라우트 등록(/workspace/:workspaceId/members). Workspace 메뉴의 멤버 관리 항목 클릭 시 해당 경로로 네비게이션으로 변경.
타입 정의
src/types/workspace/workspace.ts
멤버/역할/권한 관련 타입들 추가: TMemberRole, TWorkspaceMember, TUpdateMemberRoleRequest/Response, TGetWorkspaceMembersData, TWorkspaceMemberCount, TDeleteWorkspaceMemberResponse, TPermissionValue, TPermissionRow.
레이아웃/스타일 조정
src/layout/main/MainLayout.tsx, src/components/common/controlbox/ControlBox.tsx, src/index.css
레이아웃 클래스에 min-w/min-h/overflow 관련 조정 적용 및 전역에서 세로 스크롤 관련 overflow 변경(세로 스크롤 비활성화). 레이아웃 관련 동작과 반응형 영향 확인 필요.

Sequence Diagram(s)

(생성 조건에 부합하지 않아 생략)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • YermIm
  • Seojegyeong
🚥 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 제목이 멤버 관리 페이지 UI 구현이라는 핵심 변경사항을 명확하게 전달하고 있으며, 이슈 번호 #91도 포함되어 있습니다.
Description check ✅ Passed PR 설명이 템플릿을 따르고 있으며, 관련 이슈, 변경사항 체크박스, 상세한 작업 내용, 미완성 작업, 논의 사항을 모두 포함하고 있습니다.
Linked Issues check ✅ Passed 멤버 관리 페이지 레이아웃, 팀 구성원 카드, 멤버 리스트, 역할 드롭다운, 초대 버튼, 권한 설정, 소유권 이전 UI 등 이슈 #91의 모든 주요 요구사항이 구현되었습니다.
Out of Scope Changes check ✅ Passed 레이아웃 문제 해결을 위해 MainLayout, ControlBox, index.css에 스크롤 및 오버플로우 관련 스타일 수정이 추가되었으나, 이는 멤버 관리 페이지 UI 구현 범위 내의 필요한 조정입니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#91
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@github-actions

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-ikbhqfepna.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=73

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/types/workspace/workspace.ts (1)

41-44: ⚠️ Potential issue | 🔴 Critical

TUploadImageResponse 타입 누락으로 인한 컴파일 오류 발생

src/types/workspace/workspace.ts에서 TUploadImageResponse 타입이 제거되었으나, src/api/workspace/org.ts에서 여전히 이 타입을 import하여 사용 중입니다. 이로 인해 TypeScript 컴파일 오류가 발생합니다.

uploadImage 함수에서 반환 값이 data.data.url이므로, 다음과 같이 타입을 복원해야 합니다:

export type TUploadImageResponse = {
  url: string;
};

이 타입 정의를 TApiResult 타입 정의 다음에 추가하여 org.ts의 import 오류를 해결하세요.

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

In `@src/types/workspace/workspace.ts` around lines 41 - 44, Restore the missing
TUploadImageResponse type in the workspace types file: after the existing
TApiResult declaration add a new exported type named TUploadImageResponse
matching the shape used by uploadImage (an object with a url string) so imports
in src/api/workspace/org.ts resolve; ensure the exported name exactly matches
TUploadImageResponse and that uploadImage's return type aligns with
data.data.url.
🧹 Nitpick comments (2)
src/pages/workspace/MemberManagement.tsx (1)

7-18: 라우트 파라미터 workspaceId를 페이지에서 소비하지 않고 있습니다.

경로가 workspace/:workspaceId/members인데 현재 페이지/하위 컴포넌트가 해당 값을 사용하지 않아, API 연동 시 조직 스코프 불일치가 생길 수 있습니다. 최소한 페이지에서 workspaceId를 읽고 하위 컴포넌트로 전달하는 구조로 잡아두는 편이 안전합니다.
As per coding guidelines, src/**: 1. 상태 관리: 서버 상태(React Query)와 전역 상태(Zustand)의 분리 여부 확인.2. 구조와 책임 분리: 페이지에 비즈니스 로직이 과도하지 않은지 확인.

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

In `@src/pages/workspace/MemberManagement.tsx` around lines 7 - 18, The page
component MemberManagement currently ignores the route param workspaceId; update
MemberManagement to read workspaceId (e.g., via useParams) and pass it down as a
prop to child components (MemberList and PermissionTable) so they can scope API
calls to the correct workspace; ensure you only read the param in the page (no
heavy business logic here) and forward workspaceId (e.g.,
workspaceId={workspaceId}) to both MemberList and PermissionTable so downstream
components use that ID for their React Query requests or Zustand actions.
src/types/workspace/workspace.ts (1)

73-73: TPermissionValue에 한글 문자열 리터럴 사용 검토

TPermissionValue"가능" | "불가능" 한글 문자열을 사용하고 있습니다. 일반적으로 API 응답은 boolean 또는 영문 문자열("ALLOWED" | "DENIED")을 사용하는 경우가 많습니다.

현재 API 명세와 일치하는지 확인해 주세요. 만약 UI 표시용 데이터라면 괜찮지만, API 응답 타입이라면 실제 응답 형식에 맞춰 수정이 필요할 수 있습니다. 다국어 지원을 고려한다면 표시 문자열은 별도로 관리하는 것이 좋습니다.

♻️ boolean 사용 제안 (API 응답이 boolean인 경우)
-export type TPermissionValue = "가능" | "불가능";
+export type TPermissionValue = boolean;

 export type TPermissionRow = {
   key:
     | "campaignView"
     | "billingManage"
     | "workspaceView"
     | "memberInvite"
     | "memberRoleEdit"
     | "workspaceEdit"
     | "projectDelete";
   label: string;
   description: string;
   admin: TPermissionValue;
   member: TPermissionValue;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/workspace/workspace.ts` at line 73, TPermissionValue currently uses
Korean string literals ("가능" | "불가능"); verify whether this type models API
responses or UI display values and align it accordingly: if the API returns
booleans, change TPermissionValue to boolean; if the API returns English enums,
change it to a string union like "ALLOWED" | "DENIED"; if these are only UI
labels, keep the current type but move mapping logic into the UI layer (e.g.,
create a function that maps API boolean/enum to the Korean display strings) and
update all usages of TPermissionValue to use the appropriate representation
(refer to the TPermissionValue type declaration to locate and update usages).
🤖 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/components/common/toggle/Toggle.tsx`:
- Around line 23-25: The switch element with role="switch" is missing
aria-checked, so assistive tech cannot determine on/off state; update the Toggle
component to add aria-checked that reflects the component's internal state/prop
(e.g., aria-checked={!!checked} or aria-checked={isOn}) alongside role="switch",
ensuring the value is a boolean and kept in sync with the same state used for
visual rendering (look for the Toggle component and the JSX element that
currently has role="switch", aria-label, disabled and add aria-checked
referencing the existing checked/isOn state).

In `@src/components/workspace/MemberItem.tsx`:
- Around line 37-41: The onChange in MemberItem currently only logs the new role
and doesn't update state; update MemberList to hold members via useState and
pass a role-change handler down to MemberItem: add an onChange prop to
MemberItem's Props type (onChange: (newRole: string) => void), have MemberItem
call that prop from its MemberRoleSelect onChange, and implement the handler in
MemberList to update the specific member's role in the useState-managed members
array (by id/index) so the UI reflects the change.

In `@src/components/workspace/MemberList.tsx`:
- Line 51: The invite button in the MemberList component currently uses
aria-label="업로드", which mismatches its function; update the invite button
element in MemberList (the button or IconButton used for inviting members) to
use an accurate ARIA label like aria-label="팀원 초대" or the component's i18n key
(e.g., aria-label={t('invite_member')}) so screen readers convey the correct
action, and ensure any tooltip/title text is consistent with the new ARIA label.
- Around line 44-45: The text "현재 4명의 구성원이 활동 중입니다" is hardcoded; update the
MemberList component to compute and render the member count from the data source
instead — replace the literal "4명" with the length of the members array
(mockMembers.length or the prop/state variable used in MemberList) so the
displayed count reflects actual data; ensure you reference the same variable the
component uses to render member items (mockMembers) and keep the surrounding
string formatting intact.

In `@src/components/workspace/MemberRoleSelect.tsx`:
- Around line 60-71: The trigger button in MemberRoleSelect lacks ARIA
attributes for dropdown state; update the button element in the MemberRoleSelect
component (the button using setIsOpen and rendering ChevronIcon) to include
aria-haspopup="listbox" and aria-expanded={isOpen} (ensure it resolves to
"false" when disabled), so assistive tech can announce the popup role and
current open/closed state; keep existing disabled handling and class logic but
add these attributes bound to the component's isOpen state.
- Around line 74-85: The dropdown keeps option buttons in the DOM when closed,
allowing keyboard focus; update MemberRoleSelect so option elements are only
interactive when visible by either conditionally rendering the restOptions map
(render it only when isOpen && !disabled) or by setting each option button to
tabIndex={isOpen && !disabled ? 0 : -1} and adding aria-hidden={!(isOpen &&
!disabled)}; target the restOptions map rendering and the button elements (keyed
by option, using handleSelect and roleLabelMap) and ensure isOpen/disabled
gating is applied consistently.

In `@src/components/workspace/PermissionTable.tsx`:
- Around line 126-129: 해당 관리자인지 표시하는 셀(내부에 AdminCheckBadge 렌더링)은 아이콘만으로는 스크린리더에
의미가 없으므로 AdminCheckBadge가 렌더되는 td 내부(현재 <td className="px-6 py-5 text-center">
안)의 적절한 위치에 시각적으로 숨겨진 텍스트(span 등)에 sr-only 클래스를 추가하여 접근성 텍스트(예: "가능")를 함께 렌더하도록
수정하세요; 즉 AdminCheckBadge 컴포넌트 호출 옆이나 내부에 <span className="sr-only">가능</span> 형태의
보이지 않는 텍스트를 삽입해 스크린리더가 아이콘의 의미를 읽을 수 있게 만드세요.

In `@src/pages/workspace/MemberManagement.tsx`:
- Line 23: The ownership-transfer button currently calls a plain alert via the
onButtonClick prop; replace that with a real handler or disable UI: wire
onButtonClick to a modal-opening function (e.g., openOwnershipTransferModal)
that triggers your ownership confirmation modal component (create
OwnershipTransferModal skeleton and pass necessary props/callbacks like
onConfirm/onCancel), and ensure API errors are handled with user feedback in the
onConfirm flow; if the feature is not ready, instead set the button to disabled
and show a concise helper message or tooltip explaining it's coming soon (update
the same component where onButtonClick is defined, referencing the onButtonClick
prop and the ownership transfer button element).

---

Outside diff comments:
In `@src/types/workspace/workspace.ts`:
- Around line 41-44: Restore the missing TUploadImageResponse type in the
workspace types file: after the existing TApiResult declaration add a new
exported type named TUploadImageResponse matching the shape used by uploadImage
(an object with a url string) so imports in src/api/workspace/org.ts resolve;
ensure the exported name exactly matches TUploadImageResponse and that
uploadImage's return type aligns with data.data.url.

---

Nitpick comments:
In `@src/pages/workspace/MemberManagement.tsx`:
- Around line 7-18: The page component MemberManagement currently ignores the
route param workspaceId; update MemberManagement to read workspaceId (e.g., via
useParams) and pass it down as a prop to child components (MemberList and
PermissionTable) so they can scope API calls to the correct workspace; ensure
you only read the param in the page (no heavy business logic here) and forward
workspaceId (e.g., workspaceId={workspaceId}) to both MemberList and
PermissionTable so downstream components use that ID for their React Query
requests or Zustand actions.

In `@src/types/workspace/workspace.ts`:
- Line 73: TPermissionValue currently uses Korean string literals ("가능" |
"불가능"); verify whether this type models API responses or UI display values and
align it accordingly: if the API returns booleans, change TPermissionValue to
boolean; if the API returns English enums, change it to a string union like
"ALLOWED" | "DENIED"; if these are only UI labels, keep the current type but
move mapping logic into the UI layer (e.g., create a function that maps API
boolean/enum to the Korean display strings) and update all usages of
TPermissionValue to use the appropriate representation (refer to the
TPermissionValue type declaration to locate and update usages).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 895ff846-89f3-41b4-b9b4-bae8cf2625c2

📥 Commits

Reviewing files that changed from the base of the PR and between 885e566 and a2df0d0.

⛔ Files ignored due to path filters (4)
  • src/assets/icon/workspace/check.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/workspace/mail.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/workspace/trash.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/workspace/user.svg is excluded by !**/*.svg and included by src/**
📒 Files selected for processing (9)
  • src/components/common/toggle/Toggle.tsx
  • src/components/workspace/MemberItem.tsx
  • src/components/workspace/MemberList.tsx
  • src/components/workspace/MemberRoleSelect.tsx
  • src/components/workspace/PermissionTable.tsx
  • src/pages/workspace/MemberManagement.tsx
  • src/pages/workspace/Workspace.tsx
  • src/routes/MainRoutes.tsx
  • src/types/workspace/workspace.ts

Comment thread src/components/common/toggle/Toggle.tsx
Comment thread src/components/workspace/MemberItem.tsx Outdated
Comment thread src/components/workspace/MemberList.tsx Outdated
Comment thread src/components/workspace/MemberList.tsx Outdated
Comment thread src/components/workspace/MemberRoleSelect.tsx
Comment thread src/components/workspace/MemberRoleSelect.tsx Outdated
Comment thread src/components/workspace/PermissionTable.tsx
Comment thread src/pages/workspace/MemberManagement.tsx
@github-actions

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-kdzivbhlfs.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=74

@github-actions

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-isdorhcdip.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=75

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
src/pages/workspace/MemberManagement.tsx (1)

23-23: ⚠️ Potential issue | 🟡 Minor

소유권 이전 버튼은 placeholder alert 대신 실제 핸들러(또는 비활성화)로 처리해 주세요.

현재 클릭 시 사용자 흐름이 끊깁니다. 모달 오픈 핸들러로 연결하거나, 준비 전이라면 buttonDisabled와 안내 문구를 같이 두는 편이 안전합니다.

As per coding guidelines, src/**: 6. 에러 처리: API 실패 대응 및 사용자 피드백 적절성 검토. 에러 바운더리 사용 확인.

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

In `@src/pages/workspace/MemberManagement.tsx` at line 23, Replace the placeholder
alert in the MemberManagement component's onButtonClick with a real handler:
wire it to an ownership-transfer flow (e.g. call openOwnershipTransferModal or
dispatch an action that opens OwnershipTransferModal and performs the API call),
and implement proper error handling/feedback on failure; if the feature isn’t
ready, set buttonDisabled on the ownership transfer button and show a contextual
message or tooltip explaining why (use unique identifiers onButtonClick,
MemberManagement, buttonDisabled,
openOwnershipTransferModal/OwnershipTransferModal to locate and update the
code).
src/components/workspace/MemberItem.tsx (1)

37-41: ⚠️ Potential issue | 🟠 Major

역할 변경 콜백이 실제 상태를 갱신하지 않아 기능이 동작하지 않습니다.

현재는 console.log만 실행되어 사용자가 바꾼 역할이 어디에도 반영되지 않습니다. MemberItem은 변경 이벤트만 전달하고, 실제 멤버 상태 갱신은 MemberList(부모)에서 처리하도록 연결해 주세요.

As per coding guidelines, src/**: 1. 상태 관리: 서버 상태(React Query)와 전역 상태(Zustand)의 분리 여부 확인. useMutation, useQuery의 올바른 사용 확인.

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

In `@src/components/workspace/MemberItem.tsx` around lines 37 - 41, Replace the
no-op console.log in MemberItem's MemberRoleSelect onChange with a call that
forwards the change to the parent so the parent can perform the actual
state/server update: add/ensure MemberItem accepts a prop like onRoleChange (or
similar) and call onRoleChange(member.id, newRole) inside the onChange handler
for MemberRoleSelect; in MemberList implement that handler to run the mutation
(useMutation) to update the member role on the server and update React Query
cache/global state accordingly.
src/components/common/toggle/Toggle.tsx (1)

23-25: ⚠️ Potential issue | 🟠 Major

switch 상태값(aria-checked)을 꼭 같이 전달해 주세요.

role="switch"만 있고 상태 속성이 없어 보조기기에서 on/off를 알 수 없습니다. 이전 리뷰에서 지적된 내용과 동일 이슈입니다.

수정 예시
     <button
       type="button"
       role="switch"
+      aria-checked={checked}
       aria-label={ariaLabel}
       disabled={disabled}
       onClick={onToggle}

As per coding guidelines, src/**: 7. 접근성: 시맨틱 HTML, ARIA 속성 사용 확인.

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

In `@src/components/common/toggle/Toggle.tsx` around lines 23 - 25, The toggle
with role="switch" in the Toggle component is missing the required ARIA state
attribute aria-checked so assistive tech can't read on/off; update the element
rendered by Toggle (the element that currently sets role="switch",
aria-label={ariaLabel}, disabled={disabled}) to also include aria-checked bound
to the component's checked state/prop (e.g., the internal state or prop name
used to represent on/off) and ensure that checked is a boolean; also keep
aria-label and disabled as-is so screen readers get label, state, and disabled
info.
🧹 Nitpick comments (1)
src/types/workspace/workspace.ts (1)

52-57: 멤버 타입에 불변 식별자 필드를 추가하는 게 안전합니다.

현재 TWorkspaceMemberemail 외에 안정적인 키가 없어, 이후 역할 변경/삭제 API 연동 시 대상 식별이 취약해질 수 있습니다. memberId(또는 orgMemberId)를 타입에 포함하고, 리스트 key/업데이트 payload도 해당 필드 기준으로 맞추는 쪽을 권장합니다.

As per coding guidelines, src/**: 4. 타입 안정성: TypeScript 타입의 명확성 확인. any 사용 지양, 제네릭 활용 검토.

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

In `@src/types/workspace/workspace.ts` around lines 52 - 57, TWorkspaceMember
lacks a stable identifier which makes member updates/removals brittle; add a
non-nullable unique id field (e.g. memberId or orgMemberId) to the
TWorkspaceMember type, update any list rendering keys and API payload shapes to
use that id instead of email, and ensure functions/types that consume
TWorkspaceMember (e.g. update/delete handlers and DTOs) accept and propagate the
new id field for reliable identification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/components/common/toggle/Toggle.tsx`:
- Around line 23-25: The toggle with role="switch" in the Toggle component is
missing the required ARIA state attribute aria-checked so assistive tech can't
read on/off; update the element rendered by Toggle (the element that currently
sets role="switch", aria-label={ariaLabel}, disabled={disabled}) to also include
aria-checked bound to the component's checked state/prop (e.g., the internal
state or prop name used to represent on/off) and ensure that checked is a
boolean; also keep aria-label and disabled as-is so screen readers get label,
state, and disabled info.

In `@src/components/workspace/MemberItem.tsx`:
- Around line 37-41: Replace the no-op console.log in MemberItem's
MemberRoleSelect onChange with a call that forwards the change to the parent so
the parent can perform the actual state/server update: add/ensure MemberItem
accepts a prop like onRoleChange (or similar) and call onRoleChange(member.id,
newRole) inside the onChange handler for MemberRoleSelect; in MemberList
implement that handler to run the mutation (useMutation) to update the member
role on the server and update React Query cache/global state accordingly.

In `@src/pages/workspace/MemberManagement.tsx`:
- Line 23: Replace the placeholder alert in the MemberManagement component's
onButtonClick with a real handler: wire it to an ownership-transfer flow (e.g.
call openOwnershipTransferModal or dispatch an action that opens
OwnershipTransferModal and performs the API call), and implement proper error
handling/feedback on failure; if the feature isn’t ready, set buttonDisabled on
the ownership transfer button and show a contextual message or tooltip
explaining why (use unique identifiers onButtonClick, MemberManagement,
buttonDisabled, openOwnershipTransferModal/OwnershipTransferModal to locate and
update the code).

---

Nitpick comments:
In `@src/types/workspace/workspace.ts`:
- Around line 52-57: TWorkspaceMember lacks a stable identifier which makes
member updates/removals brittle; add a non-nullable unique id field (e.g.
memberId or orgMemberId) to the TWorkspaceMember type, update any list rendering
keys and API payload shapes to use that id instead of email, and ensure
functions/types that consume TWorkspaceMember (e.g. update/delete handlers and
DTOs) accept and propagate the new id field for reliable identification.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4000b33d-9a06-4d23-8d7a-5b910007b6b4

📥 Commits

Reviewing files that changed from the base of the PR and between a2df0d0 and e0122b9.

⛔ Files ignored due to path filters (4)
  • src/assets/icon/workspace/check.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/workspace/mail.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/workspace/trash.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/workspace/user.svg is excluded by !**/*.svg and included by src/**
📒 Files selected for processing (9)
  • src/components/common/toggle/Toggle.tsx
  • src/components/workspace/MemberItem.tsx
  • src/components/workspace/MemberList.tsx
  • src/components/workspace/MemberRoleSelect.tsx
  • src/components/workspace/PermissionTable.tsx
  • src/pages/workspace/MemberManagement.tsx
  • src/pages/workspace/Workspace.tsx
  • src/routes/MainRoutes.tsx
  • src/types/workspace/workspace.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/workspace/MemberList.tsx
  • src/pages/workspace/Workspace.tsx
  • src/components/workspace/MemberRoleSelect.tsx
  • src/components/workspace/PermissionTable.tsx

@github-actions

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-hahosarmph.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=76

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/components/workspace/MemberList.tsx (1)

64-64: ⚠️ Potential issue | 🟡 Minor

구성원 수 표시는 memberList.length 기준으로 맞춰주세요.

현재는 mockMembers.length를 사용해서, 실제 렌더 데이터(memberList)와 쉽게 어긋납니다.

간단 수정 diff
-            현재 {mockMembers.length}명의 구성원이 활동 중입니다
+            현재 {memberList.length}명의 구성원이 활동 중입니다
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/workspace/MemberList.tsx` at line 64, The displayed member
count is using mockMembers.length instead of the actual rendered array
memberList; update the string in MemberList (component MemberList.tsx) to use
memberList.length (replace mockMembers.length with memberList.length) so the UI
reflects the real data source, and verify any nearby usages of mockMembers are
similarly replaced or scoped only to test fixtures.
🧹 Nitpick comments (1)
src/components/workspace/MemberList.tsx (1)

67-74: 초대 버튼의 alert TODO는 제거하고, 상위에서 핸들러를 주입받는 형태가 좋습니다.

지금 방식은 사용자 흐름을 끊고 테스트도 어려워집니다. onInviteClick prop으로 분리하거나, 미구현 단계면 버튼을 비활성화해 의도를 명확히 해주세요. 원하면 이 컴포넌트에 맞춘 최소 패치(Props 정의 + 호출부 연결)까지 바로 정리해드릴게요.

As per coding guidelines, src/**: 2. 구조와 책임 분리: 페이지에 비즈니스 로직이 과도하지 않은지 확인. 커스텀 훅으로의 분리 여부 검토.

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

In `@src/components/workspace/MemberList.tsx` around lines 67 - 74, Replace the
inline alert handler on the invite Button with a prop-based handler: add an
optional onInviteClick prop to the MemberList component's props, call that prop
in the Button's onClick (e.g., onClick={() => onInviteClick?.()}), and if the
prop is absent disable the button (or set aria-disabled) to indicate
unimplemented behavior; update the Button usage and the MemberList props/type
definition accordingly so parent components can inject the invite modal logic
instead of using alert.
🤖 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/components/workspace/MemberRoleSelect.tsx`:
- Around line 28-31: When the MemberRoleSelect component's disabled prop toggles
to true the dropdown state (isOpen) should be closed to avoid showing options
when re-enabled; add an effect in MemberRoleSelect that watches the disabled
prop and calls setIsOpen(false) whenever disabled becomes true (use the existing
isOpen and setIsOpen state variables), and ensure similar logic is applied at
the other location referenced (around line 75) so both places synchronize isOpen
with disabled.
- Around line 60-89: The trigger button in MemberRoleSelect (the one that
toggles isOpen via setIsOpen) lacks an ARIA relationship to the popup and the
options lack semantic roles; add aria-controls on that trigger pointing to a
unique id for the popup, give the popup div role="menu" with that id and
aria-hidden based on disabled/isOpen, and mark each option button rendered from
restOptions with role="menuitem" (and manage aria-selected or tabIndex as
appropriate) so assistive tech can recognize the menu structure; update
references where handleSelect is called to ensure selection accessibility
attributes are updated accordingly.

---

Duplicate comments:
In `@src/components/workspace/MemberList.tsx`:
- Line 64: The displayed member count is using mockMembers.length instead of the
actual rendered array memberList; update the string in MemberList (component
MemberList.tsx) to use memberList.length (replace mockMembers.length with
memberList.length) so the UI reflects the real data source, and verify any
nearby usages of mockMembers are similarly replaced or scoped only to test
fixtures.

---

Nitpick comments:
In `@src/components/workspace/MemberList.tsx`:
- Around line 67-74: Replace the inline alert handler on the invite Button with
a prop-based handler: add an optional onInviteClick prop to the MemberList
component's props, call that prop in the Button's onClick (e.g., onClick={() =>
onInviteClick?.()}), and if the prop is absent disable the button (or set
aria-disabled) to indicate unimplemented behavior; update the Button usage and
the MemberList props/type definition accordingly so parent components can inject
the invite modal logic instead of using alert.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 259fd3c6-0b80-4a96-b19d-648ccb757917

📥 Commits

Reviewing files that changed from the base of the PR and between e0122b9 and d8ec3dc.

📒 Files selected for processing (5)
  • src/components/common/toggle/Toggle.tsx
  • src/components/workspace/MemberItem.tsx
  • src/components/workspace/MemberList.tsx
  • src/components/workspace/MemberRoleSelect.tsx
  • src/components/workspace/PermissionTable.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/workspace/MemberItem.tsx
  • src/components/workspace/PermissionTable.tsx
  • src/components/common/toggle/Toggle.tsx

Comment thread src/components/workspace/MemberRoleSelect.tsx
Comment thread src/components/workspace/MemberRoleSelect.tsx
@github-actions

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-ifhzepdtky.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=77

@jjjsun jjjsun added the 🎨 Html&css 마크업 & 스타일링 label Mar 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/workspace/MemberRoleSelect.tsx (1)

83-100: 조건부 렌더링과 ARIA 역할이 잘 적용되었습니다.

이전 리뷰에서 지적된 문제들이 해결되었습니다:

  • isOpen && !disabled 조건부 렌더링으로 닫힌 상태에서 포커스 문제 해결
  • role="menu", role="menuitem" 적용으로 보조기기 지원 개선

참고: Line 87의 클래스 조건 ${isOpen && !disabled ? "max-h-10 opacity-100" : "max-h-0 opacity-0"}는 이미 조건부 렌더링 블록 내에 있어 항상 첫 번째 분기가 실행됩니다. 코드 가독성을 위해 정적 클래스로 단순화할 수 있습니다.

♻️ 단순화 예시
          <div
            id={menuId}
            role="menu"
-            className={`overflow-hidden bg-gray-100 transition-all duration-200 ease-out ${isOpen && !disabled ? "max-h-10 opacity-100" : "max-h-0 opacity-0"}`}
+            className="overflow-hidden bg-gray-100 transition-all duration-200 ease-out max-h-10 opacity-100"
          >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/workspace/MemberRoleSelect.tsx` around lines 83 - 100, The
className conditional on the div with id={menuId} (currently using `${isOpen &&
!disabled ? "max-h-10 opacity-100" : "max-h-0 opacity-0"}`) is redundant because
the entire block is already guarded by `isOpen && !disabled`; update the
`className` on that element to use the static classes for the open state (e.g.,
include "max-h-10 opacity-100" along with the existing classes) and remove the
ternary expression so the string is constant; the rest of the component
(including `handleSelect`, `restOptions`, and `role="menu"/"menuitem"`) can
remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/workspace/MemberRoleSelect.tsx`:
- Around line 83-100: The className conditional on the div with id={menuId}
(currently using `${isOpen && !disabled ? "max-h-10 opacity-100" : "max-h-0
opacity-0"}`) is redundant because the entire block is already guarded by
`isOpen && !disabled`; update the `className` on that element to use the static
classes for the open state (e.g., include "max-h-10 opacity-100" along with the
existing classes) and remove the ternary expression so the string is constant;
the rest of the component (including `handleSelect`, `restOptions`, and
`role="menu"/"menuitem"`) can remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 837ba1ce-2a71-4e3c-a7e7-0ac3cafb7ebd

📥 Commits

Reviewing files that changed from the base of the PR and between d8ec3dc and b1aecd7.

📒 Files selected for processing (1)
  • src/components/workspace/MemberRoleSelect.tsx

@jjjsun jjjsun changed the title [Feature/#91]: 멤버 관리 페이지 UI 구현 [Feature/#91] 멤버 관리 페이지 UI 구현 Mar 13, 2026
@YermIm

YermIm commented Mar 13, 2026

Copy link
Copy Markdown
Collaborator

P4: 저는 변경사항 저장하기 버튼 괜찮은 것 같습니다. 버튼이 아래에 위치할 경우 사용자가 인지하지 못할 수도 있으니, 설명에 저장하기 버튼을 클릭해야 저장된다는 문구 넣으면 좋을 것 같아요!

@YermIm

YermIm commented Mar 13, 2026

Copy link
Copy Markdown
Collaborator

P2: 우측에 이중 스크롤바가 생기면서 사이드바까지 같이 스크롤되는 현상이 있습니다. 사이드바는 고정하고 컨텐츠만 스크롤 되도록 확인 부탁드립니다!
그리고 권한 설정 박스와 관리자 변경 컨트롤 박스의 가로 너비가 달라서, 반응형 너비를 맞춰주시면 좋을 것 같아요!

스크린샷 2026-03-14 오전 4 21 02

@github-actions

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-bmcikrxpky.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=80

@jjjsun

jjjsun commented Mar 15, 2026

Copy link
Copy Markdown
Collaborator Author

P2: 우측에 이중 스크롤바가 생기면서 사이드바까지 같이 스크롤되는 현상이 있습니다. 사이드바는 고정하고 컨텐츠만 스크롤 되도록 확인 부탁드립니다! 그리고 권한 설정 박스와 관리자 변경 컨트롤 박스의 가로 너비가 달라서, 반응형 너비를 맞춰주시면 좋을 것 같아요!

스크린샷 2026-03-14 오전 4 21 02
image image

이중스크롤제거 및 ControlBox와 권한설정 너비 너비 맞췄습니다!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/common/controlbox/ControlBox.tsx (1)

45-49: ⚠️ Potential issue | 🟡 Minor

WorkspaceSetting.tsx에서 min-width 설정 추가 필요

min-w-180 제거로 최소 너비 보장이 없어졌습니다. WorkspaceSetting.tsx 299줄의 ControlBox는 containerClassNamemin-w-* 클래스 없이 사용되고 있어, 좁은 뷰포트에서 콘텐츠가 의도치 않게 축소될 수 있습니다. containerClassName에 적절한 min-w-* 값을 추가하거나, className prop에 min-w-*를 명시해주세요.

다른 사용처(AdsListPage, AdDetailContent, CampaignDetail)는 이미 containerClassNamemin-w-*를 전달하고 있어 영향이 없습니다.

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

In `@src/components/common/controlbox/ControlBox.tsx` around lines 45 - 49, The
ControlBox usage in WorkspaceSetting (the ControlBox component instance around
the WorkspaceSetting rendering) lacks a min-width class because
containerClassName has no min-w-*; update the WorkspaceSetting.tsx where
ControlBox is rendered (the instance near line ~299) to include an appropriate
Tailwind min-w class (e.g., add "min-w-180" or another required size) by
appending it to containerClassName or to the className prop passed into
ControlBox so the ControlBox component (which composes containerClassName and
className) always enforces the minimum width and prevents content from
collapsing on narrow viewports.
🤖 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/index.css`:
- Around line 10-11: 전역 CSS에서 html, body에 설정된 overflow: hidden은 인증 페이지의 스크롤을
막으므로 index.css에서 이 전역 속성을 제거하고 중복된 overflow-x: hidden도 삭제하거나 축소하세요; 대신 인증 화면 전용
스크롤 처리는 AuthLayout.tsx(또는 인증 폼 래퍼)에서 overflow-y: auto/scroll과 max-height 또는 적절한
컨테이너 스타일을 적용해 처리하고, MainLayout이 페이지 전체 레이아웃용 오버플로 제어를 담당하도록 역할을 분리하세요.

---

Outside diff comments:
In `@src/components/common/controlbox/ControlBox.tsx`:
- Around line 45-49: The ControlBox usage in WorkspaceSetting (the ControlBox
component instance around the WorkspaceSetting rendering) lacks a min-width
class because containerClassName has no min-w-*; update the WorkspaceSetting.tsx
where ControlBox is rendered (the instance near line ~299) to include an
appropriate Tailwind min-w class (e.g., add "min-w-180" or another required
size) by appending it to containerClassName or to the className prop passed into
ControlBox so the ControlBox component (which composes containerClassName and
className) always enforces the minimum width and prevents content from
collapsing on narrow viewports.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 770d270b-acc3-4105-837e-0f1a3301a896

📥 Commits

Reviewing files that changed from the base of the PR and between b1aecd7 and 2ef05ec.

📒 Files selected for processing (4)
  • src/components/common/controlbox/ControlBox.tsx
  • src/index.css
  • src/layout/main/MainLayout.tsx
  • src/pages/workspace/MemberManagement.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/workspace/MemberManagement.tsx

Comment thread src/index.css Outdated
@github-actions

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-ggsgadcunk.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=81

@Seojegyeong Seojegyeong left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P4: 확인했습니다! 수고하셨습니다.

@YermIm

YermIm commented Mar 15, 2026

Copy link
Copy Markdown
Collaborator

P4: 확인했습니다 수고하셨습니다!

@jjjsun
jjjsun merged commit f844773 into develop Mar 15, 2026
3 checks passed
@jjjsun
jjjsun deleted the feature/#91 branch March 15, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발 🎨 Html&css 마크업 & 스타일링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ [Feature] 멤버 관리 페이지 UI 구현

3 participants