Feat : admin 페이지 부분 가완성
This commit is contained in:
parent
cd4427fd74
commit
62cb367727
@ -1,12 +1,13 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useLocation } 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, FormMessage } from '../ui/form';
|
||||
import { Input } from '../ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
||||
import { ProjectMemberResponse } from '@/types';
|
||||
import useUpdateProjectMemberPrivilegeQuery from '@/queries/projects/useUpdateProjectMemberPrivilegeQuery';
|
||||
import useProjectMembersQuery from '@/queries/projects/useProjectMembersQuery';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
|
||||
type Role = 'ADMIN' | 'MANAGER' | 'EDITOR' | 'VIEWER';
|
||||
|
||||
@ -31,12 +32,14 @@ const formSchema = z.object({
|
||||
|
||||
export type ProjectMemberManageFormValues = z.infer<typeof formSchema>;
|
||||
|
||||
interface ProjectMemberManageFormProps {
|
||||
members: ProjectMemberResponse[];
|
||||
}
|
||||
export default function ProjectMemberManageForm() {
|
||||
const location = useLocation();
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const projectId = searchParams.get('projectId');
|
||||
const profile = useAuthStore((state) => state.profile);
|
||||
const memberId = profile?.id || 0;
|
||||
|
||||
export default function ProjectMemberManageForm({ members }: ProjectMemberManageFormProps) {
|
||||
const { projectId } = useParams<{ projectId: string }>();
|
||||
const { data: members = [] } = useProjectMembersQuery(Number(projectId), memberId);
|
||||
const { mutate: updatePrivilege } = useUpdateProjectMemberPrivilegeQuery();
|
||||
|
||||
const form = useForm<ProjectMemberManageFormValues>({
|
||||
|
@ -1,125 +1,30 @@
|
||||
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, FormMessage } from '../ui/form';
|
||||
import { Input } from '../ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
||||
import { WorkspaceMemberResponse } from '@/types';
|
||||
import useUpdateProjectMemberPrivilegeQuery from '@/queries/projects/useUpdateProjectMemberPrivilegeQuery';
|
||||
import useWorkspaceMembersQuery from '@/queries/workspaces/useWorkspaceMembersQuery';
|
||||
|
||||
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({
|
||||
memberId: z.number(),
|
||||
nickname: z.string().nonempty('닉네임을 입력하세요.'),
|
||||
role: z.enum(roles as [Role, ...Role[]], { errorMap: () => ({ message: '역할을 선택해주세요.' }) }),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type WorkspaceMemberManageFormValues = z.infer<typeof formSchema>;
|
||||
|
||||
interface WorkspaceMemberManageFormProps {
|
||||
members: WorkspaceMemberResponse[];
|
||||
}
|
||||
|
||||
export default function WorkspaceMemberManageForm({ members }: WorkspaceMemberManageFormProps) {
|
||||
export default function WorkspaceMemberManageForm() {
|
||||
const { workspaceId } = useParams<{ workspaceId: string }>();
|
||||
const { mutate: updatePrivilege } = useUpdateProjectMemberPrivilegeQuery();
|
||||
|
||||
const form = useForm<WorkspaceMemberManageFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
members: members.map((m) => ({
|
||||
memberId: m.memberId,
|
||||
nickname: m.nickname,
|
||||
role: m.privilegeType as Role,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
const handleRoleChange = (memberId: number, role: Role) => {
|
||||
updatePrivilege({
|
||||
workspaceId: Number(workspaceId),
|
||||
memberId,
|
||||
privilegeData: {
|
||||
memberId,
|
||||
privilegeType: role,
|
||||
},
|
||||
});
|
||||
};
|
||||
const { data: members = [] } = useWorkspaceMembersQuery(Number(workspaceId));
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
{members.map((member, index) => (
|
||||
{members.length === 0 ? (
|
||||
<div className="py-4 text-center">워크스페이스에 멤버가 없습니다.</div>
|
||||
) : (
|
||||
members.map((member) => (
|
||||
<div
|
||||
key={member.memberId}
|
||||
className="flex items-center gap-4"
|
||||
className="flex items-center gap-4 border-b pb-2"
|
||||
>
|
||||
<FormField
|
||||
name={`members.${index}.nickname`}
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="닉네임을 입력하세요."
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name={`members.${index}.role`}
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormControl>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
handleRoleChange(member.memberId, value as Role);
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="역할을 선택해주세요." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{roles.map((role) => (
|
||||
<SelectItem
|
||||
key={role}
|
||||
value={role}
|
||||
>
|
||||
{roleToStr[role]}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
<img
|
||||
src={member.profileImage}
|
||||
alt={member.nickname}
|
||||
className="h-8 w-8 rounded-full"
|
||||
/>
|
||||
<span>{member.nickname}</span>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
@ -1,35 +1,35 @@
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, useLocation } from 'react-router-dom';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import useAddWorkspaceMemberQuery from '@/queries/workspaces/useAddWorkspaceMemberQuery';
|
||||
import useAddProjectMemberQuery from '@/queries/projects/useAddProjectMemberQuery';
|
||||
import useWorkspaceMembersQuery from '@/queries/workspaces/useWorkspaceMembersQuery';
|
||||
import useProjectMembersQuery from '@/queries/projects/useProjectMembersQuery';
|
||||
import MemberAddModal from '../MemberAddModal';
|
||||
import { MemberAddFormValues } from '../MemberAddModal/MemberAddForm';
|
||||
import WorkspaceMemberManageForm from './WorkspaceMemberManageForm';
|
||||
import ProjectMemberManageForm from './ProjectMemberManageForm';
|
||||
|
||||
export default function AdminMemberManage() {
|
||||
const { workspaceId, projectId } = useParams<{ workspaceId?: string; projectId?: string }>();
|
||||
const { workspaceId } = useParams<{ workspaceId: string }>();
|
||||
const location = useLocation();
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const projectId = searchParams.get('projectId');
|
||||
|
||||
const profile = useAuthStore((state) => state.profile);
|
||||
const memberId = profile?.id || 0;
|
||||
|
||||
const addWorkspaceMember = useAddWorkspaceMemberQuery();
|
||||
const addProjectMember = useAddProjectMemberQuery();
|
||||
|
||||
const [, setInviteModalOpen] = useState(false);
|
||||
|
||||
const handleMemberInvite = (data: MemberAddFormValues) => {
|
||||
if (workspaceId) {
|
||||
const addWorkspaceMember = useAddWorkspaceMemberQuery();
|
||||
addWorkspaceMember.mutate({
|
||||
workspaceId: Number(workspaceId),
|
||||
memberId: memberId,
|
||||
newMember: {
|
||||
memberId: 0,
|
||||
privilegeType: data.role,
|
||||
},
|
||||
newMemberId: data.memberId,
|
||||
});
|
||||
} else if (projectId) {
|
||||
const addProjectMember = useAddProjectMemberQuery();
|
||||
} else if (projectId && Number(projectId) > 0) {
|
||||
addProjectMember.mutate({
|
||||
projectId: Number(projectId),
|
||||
memberId: memberId,
|
||||
@ -49,10 +49,8 @@ export default function AdminMemberManage() {
|
||||
<MemberAddModal onSubmit={handleMemberInvite} />
|
||||
</header>
|
||||
|
||||
{workspaceId && <WorkspaceMemberManageForm members={useWorkspaceMembersQuery(Number(workspaceId)).data || []} />}
|
||||
{projectId && (
|
||||
<ProjectMemberManageForm members={useProjectMembersQuery(Number(projectId), memberId).data || []} />
|
||||
)}
|
||||
{workspaceId && <WorkspaceMemberManageForm />}
|
||||
{projectId && <ProjectMemberManageForm />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -20,22 +20,14 @@ const privilegeTypeToStr: { [key in PrivilegeType]: string } = {
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.email({
|
||||
message: '올바른 이메일 형식을 입력해주세요.',
|
||||
})
|
||||
.max(40)
|
||||
.min(1, {
|
||||
message: '초대할 멤버의 이메일 주소를 입력해주세요.',
|
||||
}),
|
||||
memberId: z.number().nonnegative({ message: '멤버를 선택하세요.' }),
|
||||
role: z.enum(privilegeTypes),
|
||||
});
|
||||
|
||||
export type MemberAddFormValues = z.infer<typeof formSchema>;
|
||||
|
||||
const defaultValues: Partial<MemberAddFormValues> = {
|
||||
email: '',
|
||||
memberId: 0,
|
||||
role: undefined,
|
||||
};
|
||||
|
||||
@ -48,43 +40,40 @@ export default function MemberAddForm({ onSubmit }: { onSubmit: (data: MemberAdd
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const { data: members } = useSearchMembersByEmailQuery(keyword);
|
||||
|
||||
const handleMemberSelect = (memberId: number) => {
|
||||
form.setValue('memberId', memberId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="flex flex-col gap-5"
|
||||
>
|
||||
<FormField
|
||||
name="email"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="body-strong">이메일</FormLabel>
|
||||
<FormControl>
|
||||
<SearchInput
|
||||
placeholder="초대할 멤버의 이메일을 검색하세요."
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
setKeyword(e.target.value);
|
||||
}}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{members && (
|
||||
<ul className="mt-2">
|
||||
{members.map((member) => (
|
||||
<li
|
||||
key={member.id}
|
||||
className="py-1"
|
||||
className={`cursor-pointer py-1 ${form.getValues('memberId') === member.id ? 'bg-gray-200' : ''}`}
|
||||
onClick={() => handleMemberSelect(member.id)}
|
||||
>
|
||||
<img
|
||||
src={member.profileImage}
|
||||
alt={member.nickname}
|
||||
className="h-8 w-8 rounded-full"
|
||||
className="inline h-8 w-8 rounded-full"
|
||||
/>
|
||||
<span className="ml-2">
|
||||
{member.nickname} ({member.email})
|
||||
@ -124,10 +113,11 @@ export default function MemberAddForm({ onSubmit }: { onSubmit: (data: MemberAdd
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlinePrimary"
|
||||
disabled={!form.formState.isValid}
|
||||
disabled={!form.formState.isValid || !form.getValues('memberId')}
|
||||
>
|
||||
멤버 초대하기
|
||||
</Button>
|
||||
|
Loading…
Reference in New Issue
Block a user