diff --git a/frontend/src/components/AdminMemberManage/AdminMemberManageForm.tsx b/frontend/src/components/AdminMemberManage/AdminMemberManageForm.tsx index b8abdff..1bf1ee8 100644 --- a/frontend/src/components/AdminMemberManage/AdminMemberManageForm.tsx +++ b/frontend/src/components/AdminMemberManage/AdminMemberManageForm.tsx @@ -1,25 +1,29 @@ +import { useParams } from 'react-router-dom'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form'; +import { Form, FormControl, FormField, FormItem, FormMessage } from '../ui/form'; import { Input } from '../ui/input'; -import { Button } from '../ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; +import { ProjectMemberResponse } from '@/types'; +import { useUpdateProjectMemberPrivilege } from '@/hooks/useProjectHooks'; -type Role = 'admin' | 'editor' | 'viewer'; +type Role = 'ADMIN' | 'MANAGER' | 'EDITOR' | 'VIEWER'; -const roles: Role[] = ['admin', 'editor', 'viewer']; +const roles: Role[] = ['ADMIN', 'MANAGER', 'EDITOR', 'VIEWER']; const roleToStr: { [key in Role]: string } = { - admin: '관리자', - editor: '에디터', - viewer: '뷰어', + ADMIN: '관리자', + MANAGER: '매니저', + EDITOR: '에디터', + VIEWER: '뷰어', }; const formSchema = z.object({ members: z.array( z.object({ - email: z.string().email({ message: '올바른 이메일 형식을 입력해주세요.' }), + memberId: z.number(), + nickname: z.string().nonempty('닉네임을 입력하세요.'), role: z.enum(roles as [Role, ...Role[]], { errorMap: () => ({ message: '역할을 선택해주세요.' }) }), }) ), @@ -27,113 +31,95 @@ const formSchema = z.object({ export type MemberManageFormValues = z.infer; -interface Member { - email: string; - role: Role; -} - interface AdminMemberManageFormProps { - members: Member[]; - onSubmit: (data: MemberManageFormValues) => void; + members: ProjectMemberResponse[]; } -export default function AdminMemberManageForm({ members, onSubmit }: AdminMemberManageFormProps) { +export default function AdminMemberManageForm({ members }: AdminMemberManageFormProps) { + const { projectId } = useParams<{ projectId: string }>(); + const { mutate: updatePrivilege } = useUpdateProjectMemberPrivilege(); + const form = useForm({ resolver: zodResolver(formSchema), - defaultValues: { members }, + defaultValues: { + members: members.map((m) => ({ + memberId: m.memberId, + nickname: m.nickname, + role: m.privilegeType as Role, + })), + }, }); - const groupedMembers = members.reduce<{ [key: string]: { email: string; role: Role }[] }>((acc, member) => { - if (!acc[member.role]) acc[member.role] = []; - acc[member.role].push(member); - return acc; - }, {}); - - const roleOrder: Role[] = ['admin', 'editor', 'viewer']; - - const sortedGroupedMembers = Object.entries(groupedMembers).sort( - ([roleA], [roleB]) => roleOrder.indexOf(roleA as Role) - roleOrder.indexOf(roleB as Role) - ); + const handleRoleChange = (memberId: number, role: Role) => { + updatePrivilege({ + projectId: Number(projectId), + memberId, + privilegeData: { + memberId, + privilegeType: role, + }, + }); + }; return (
- -
- {sortedGroupedMembers.map(([role, groupMembers]) => { - if (!groupMembers || groupMembers.length === 0) return null; +
+ {members.map((member, index) => ( +
+ ( + + + + + + + )} + /> - return ( -
- {roleToStr[role as Role]} - {groupMembers.map((member, index) => ( -
- m.email === member.email)}.email`} - control={form.control} - render={({ field }) => ( - - - - - - - )} - /> - m.email === member.email)}.role`} - control={form.control} - render={({ field }) => ( - - - - - - - )} - /> -
- ))} -
- ); - })} -
- - + ( + + + + + + + )} + /> +
+ ))} +
); } diff --git a/frontend/src/components/AdminMemberManage/index.tsx b/frontend/src/components/AdminMemberManage/index.tsx index e870a8b..e428b00 100644 --- a/frontend/src/components/AdminMemberManage/index.tsx +++ b/frontend/src/components/AdminMemberManage/index.tsx @@ -1,70 +1,45 @@ -import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select'; -import AdminMemberManageForm, { MemberManageFormValues } from './AdminMemberManageForm'; -import { Button } from '@/components/ui/button'; +import { useState } from 'react'; +import AdminMemberManageForm from './AdminMemberManageForm'; +import { useParams } from 'react-router-dom'; +import useProjectMembersQuery from '@/queries/useProjectMembersQuery'; +import useAuthStore from '@/stores/useAuthStore'; +import { useAddProjectMember } from '@/hooks/useProjectHooks'; +import MemberAddModal from '../MemberAddModal'; +import { MemberAddFormValues } from '../MemberAddModal/MemberAddForm'; -type Role = 'admin' | 'editor' | 'viewer'; +export default function AdminMemberManage() { + const { projectId } = useParams<{ workspaceId: string; projectId: string }>(); + const profile = useAuthStore((state) => state.profile); + const memberId = profile?.id || 0; -interface Member { - email: string; - role: Role; -} + const { data: members = [] } = useProjectMembersQuery(Number(projectId), memberId); + const addProjectMember = useAddProjectMember(); -interface Project { - id: string; - name: string; -} + const [, setInviteModalOpen] = useState(false); + + const handleMemberInvite = (data: MemberAddFormValues) => { + addProjectMember.mutate({ + projectId: Number(projectId), + memberId: memberId, + newMember: { + // Todo : 멤버 id로 수정하는 로직 수정해야한다. + // memberId: data.email, + memberId: 0, + privilegeType: data.role, + }, + }); + console.log('Invited:', data); + setInviteModalOpen(false); + }; -export default function AdminMemberManage({ - title = '멤버 관리', - projects = [ - { id: 'project-1', name: '프로젝트 A' }, - { id: 'project-2', name: '프로젝트 B' }, - ], - onProjectChange = (projectId: string) => console.log('Selected Project:', projectId), - onSubmit = (data: MemberManageFormValues) => console.log('Submitted:', data), - members = [ - { email: 'admin1@example.com', role: 'admin' }, - { email: 'viewer2@example.com', role: 'viewer' }, - ], - onMemberInvite = () => console.log('Invite member'), -}: { - title?: string; - projects?: Project[]; - onProjectChange?: (projectId: string) => void; - onSubmit?: (data: MemberManageFormValues) => void; - members?: Member[]; - onMemberInvite?: () => void; -}) { return (
-

{title}

- - +

멤버 관리

+
- + +
); } diff --git a/frontend/src/components/MemberAddModal/MemberAddForm.tsx b/frontend/src/components/MemberAddModal/MemberAddForm.tsx index 441ab58..b23745b 100644 --- a/frontend/src/components/MemberAddModal/MemberAddForm.tsx +++ b/frontend/src/components/MemberAddModal/MemberAddForm.tsx @@ -6,14 +6,15 @@ import { Input } from '../ui/input'; import { Button } from '../ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; -type Role = 'admin' | 'editor' | 'viewer'; +type PrivilegeType = 'ADMIN' | 'MANAGER' | 'EDITOR' | 'VIEWER'; -const roles: Role[] = ['admin', 'editor', 'viewer']; +const privilegeTypes: readonly ['ADMIN', 'MANAGER', 'EDITOR', 'VIEWER'] = ['ADMIN', 'MANAGER', 'EDITOR', 'VIEWER']; -const roleToStr: { [key in Role]: string } = { - admin: '관리자', - editor: '에디터', - viewer: '뷰어', +const privilegeTypeToStr: { [key in PrivilegeType]: string } = { + ADMIN: '관리자', + MANAGER: '매니저', + EDITOR: '에디터', + VIEWER: '뷰어', }; const formSchema = z.object({ @@ -26,7 +27,7 @@ const formSchema = z.object({ .min(1, { message: '초대할 멤버의 이메일 주소를 입력해주세요.', }), - role: z.enum(['admin', 'editor', 'viewer']), + role: z.enum(privilegeTypes), }); export type MemberAddFormValues = z.infer; @@ -80,12 +81,12 @@ export default function MemberAddForm({ onSubmit }: { onSubmit: (data: MemberAdd - {roles.map((role) => ( + {privilegeTypes.map((role) => ( - {roleToStr[role]} + {privilegeTypeToStr[role]} ))} diff --git a/frontend/src/components/MemberAddModal/index.tsx b/frontend/src/components/MemberAddModal/index.tsx index 71264c0..9d568cb 100644 --- a/frontend/src/components/MemberAddModal/index.tsx +++ b/frontend/src/components/MemberAddModal/index.tsx @@ -1,27 +1,44 @@ +import React from 'react'; import MemberAddForm, { MemberAddFormValues } from './MemberAddForm'; -import XIcon from '@/assets/icons/x.svg?react'; +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom'; +import { Button } from '@/components/ui/button'; +import { Plus } from 'lucide-react'; -export default function MemberAddModal({ - title = '새 멤버 초대', - onClose, - onSubmit, -}: { - title?: string; - onClose: () => void; +interface MemberAddModalProps { onSubmit: (data: MemberAddFormValues) => void; -}) { + buttonClass?: string; +} + +export default function MemberAddModal({ onSubmit, buttonClass = '' }: MemberAddModalProps) { + const [isOpen, setIsOpen] = React.useState(false); + + const handleOpen = () => setIsOpen(true); + const handleClose = () => setIsOpen(false); + return ( -
-
-

{title}

- -
- -
+ + 멤버 초대하기 + + + + + { + onSubmit(data); + handleClose(); + }} + /> + + ); } diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index fc6bfdb..bb914ae 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -71,7 +71,11 @@ const router = createBrowserRouter([ }, { path: `${webPath.admin()}/:workspaceId/project/:projectId?`, - element: , + element: ( + }> + + + ), children: [ { index: true,