diff --git a/frontend/src/components/MemberAddModal/MemberAddForm.tsx b/frontend/src/components/MemberAddModal/MemberAddForm.tsx index a9d2a5a..9c4b83b 100644 --- a/frontend/src/components/MemberAddModal/MemberAddForm.tsx +++ b/frontend/src/components/MemberAddModal/MemberAddForm.tsx @@ -6,13 +6,14 @@ 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 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: '사용자', + manager: '매니저', + editor: '에디터', viewer: '뷰어', }; diff --git a/frontend/src/components/MemberManageModal/MemberManageForm.tsx b/frontend/src/components/MemberManageModal/MemberManageForm.tsx new file mode 100644 index 0000000..223cdc2 --- /dev/null +++ b/frontend/src/components/MemberManageModal/MemberManageForm.tsx @@ -0,0 +1,141 @@ +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 { Input } from '../ui/input'; +import { Button } from '../ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; + +type Role = 'admin' | 'manager' | 'editor' | 'viewer'; + +const roles: Role[] = ['admin', 'manager', 'editor', 'viewer']; + +const roleToStr: { [key in Role]: string } = { + admin: '관리자', + manager: '매니저', + editor: '에디터', + viewer: '뷰어', +}; + +const formSchema = z.object({ + members: z.array( + z.object({ + email: z.string().email({ message: '올바른 이메일 형식을 입력해주세요.' }), + role: z.enum(roles as [Role, ...Role[]], { errorMap: () => ({ message: '역할을 선택해주세요.' }) }), + }) + ), +}); + +export type MemberManageFormValues = z.infer; + +interface Member { + email: string; + role: Role; +} + +interface MemberManageFormProps { + members: Member[]; + onSubmit: (data: MemberManageFormValues) => void; +} + +export default function MemberManageForm({ members, onSubmit }: MemberManageFormProps) { + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { members }, + }); + + 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', 'manager', 'editor', 'viewer']; + + const sortedGroupedMembers = Object.entries(groupedMembers).sort( + ([roleA], [roleB]) => roleOrder.indexOf(roleA as Role) - roleOrder.indexOf(roleB as Role) + ); + + return ( +
+ +
+ {sortedGroupedMembers.map(([role, groupMembers]) => { + if (!groupMembers || groupMembers.length === 0) return null; + + 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/MemberManageModal/index.stories.tsx b/frontend/src/components/MemberManageModal/index.stories.tsx new file mode 100644 index 0000000..97c6172 --- /dev/null +++ b/frontend/src/components/MemberManageModal/index.stories.tsx @@ -0,0 +1,34 @@ +import { Meta, StoryObj } from '@storybook/react'; +import MemberManageModal from '.'; + +const meta: Meta = { + title: 'Modal/MemberManageModal', + component: MemberManageModal, + argTypes: { + title: { control: 'text' }, + members: { control: 'object' }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + title: '프로젝트 멤버 관리하기', + members: [ + { email: 'admin1@example.com', role: 'admin' }, + { email: 'admin2@example.com', role: 'admin' }, + { email: 'manager1@example.com', role: 'manager' }, + { email: 'manager2@example.com', role: 'manager' }, + { email: 'viewer3@example.com', role: 'viewer' }, + { email: 'editor1@example.com', role: 'editor' }, + { email: 'editor2@example.com', role: 'editor' }, + { email: 'editor3@example.com', role: 'editor' }, + { email: 'editor4@example.com', role: 'editor' }, + ], + onClose: () => console.log('Modal Closed'), + onSubmit: (data) => console.log('Submitted Data:', data), + }, +}; diff --git a/frontend/src/components/MemberManageModal/index.tsx b/frontend/src/components/MemberManageModal/index.tsx new file mode 100644 index 0000000..41d316e --- /dev/null +++ b/frontend/src/components/MemberManageModal/index.tsx @@ -0,0 +1,39 @@ +import MemberManageForm, { MemberManageFormValues } from './MemberManageForm'; +import XIcon from '@/assets/icons/x.svg?react'; + +type Role = 'admin' | 'manager' | 'editor' | 'viewer'; + +interface Member { + email: string; + role: Role; +} + +export default function MemberManageModal({ + title = '멤버 관리', + onClose, + onSubmit, + members, +}: { + title?: string; + onClose: () => void; + onSubmit: (data: MemberManageFormValues) => void; + members: Member[]; +}) { + return ( +
+
+

{title}

+ +
+ +
+ ); +}