Skip to content

Commit

Permalink
Feat: #200 프로젝트 생성 로직 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
ice-bear98 committed Oct 23, 2024
1 parent 1a94ffc commit 26e8d95
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 75 deletions.
9 changes: 7 additions & 2 deletions src/components/modal/project/CreateModalProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import ModalButton from '@components/modal/ModalButton';
import ModalProjectForm from '@components/modal/project/ModalProjectForm';

import type { SubmitHandler } from 'react-hook-form';
import { useParams } from 'react-router-dom';
import { useCreateProject } from '@hooks/query/useProjectQuery';
import type { ProjectForm } from '@/types/ProjectType';

type CreateModalProjectProps = {
Expand All @@ -12,10 +14,13 @@ type CreateModalProjectProps = {

export default function CreateModalProject({ onClose: handleClose }: CreateModalProjectProps) {
const createProjectFormId = 'createProjectForm';
const { teamId } = useParams();

const numberTeamId = Number(teamId);
const { mutate: createProjectMutate } = useCreateProject(numberTeamId);

const handleSubmit: SubmitHandler<ProjectForm> = async (data) => {
console.log('프로젝트 생성 폼 제출');
console.log(data);
createProjectMutate(data);
handleClose();
};
return (
Expand Down
165 changes: 102 additions & 63 deletions src/components/modal/project/ModalProjectForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FormProvider, useForm } from 'react-hook-form';

import type { SubmitHandler } from 'react-hook-form';
import { FormProvider, useForm, useWatch } from 'react-hook-form';
import { DateTime } from 'luxon';
import { useMemo, useState } from 'react';
import RoleTooltip from '@components/common/RoleTooltip';
import { PROJECT_ROLE_INFO, PROJECT_ROLES } from '@constants/role';
Expand All @@ -11,28 +10,36 @@ import PeriodDateInput from '@components/common/PeriodDateInput';
import SearchUserInput from '@components/common/SearchUserInput';
import UserRoleSelectBox from '@components/common/UserRoleSelectBox';
import useAxios from '@hooks/useAxios';
import { findUser } from '@services/userService';
import { AllSearchCallback } from '@/types/SearchCallbackType';
import useToast from '@hooks/useToast';
import { findUserByTeam } from '@services/teamService';
import { useParams } from 'react-router-dom';
import type { SubmitHandler } from 'react-hook-form';
import type { TeamSearchCallback } from '@/types/SearchCallbackType';
import type { ProjectRoleName } from '@/types/RoleType';
import type { Project, ProjectCoworkerInfo, ProjectForm } from '@/types/ProjectType';
import type { SearchUser } from '@/types/UserType';
import type { Project, ProjectCoworker, ProjectForm } from '@/types/ProjectType';
import type { SearchUser, User } from '@/types/UserType';

type ModalProjectFormProps = {
formId: string;
projectId?: Project['projectId'];
onSubmit: SubmitHandler<ProjectForm>;
};

export default function ModalProjectForm({ formId, projectId, onSubmit }: ModalProjectFormProps) {
export default function ModalProjectForm({ formId, onSubmit }: ModalProjectFormProps) {
const [showTooltip, setShowTooltip] = useState(false);
const [keyword, setKeyword] = useState('');
const [coworkerInfos, setCoworkerInfos] = useState<ProjectCoworkerInfo[]>([]);
// TODO: 프로젝트 생성 팀 사용자 찾기로 바꾸기
const { loading, data: userList = [], fetchData } = useAxios(findUser);
const [coworkerInfos, setCoworkerInfos] = useState<ProjectCoworker[]>([]);
const { toastInfo } = useToast();
const { teamId: teamIdString } = useParams();
const teamId = Number(teamIdString);
const { loading, data: userList = [], clearData, fetchData } = useAxios(findUserByTeam);

// TODO: 프로젝트 생성 팀 사용자 찾기로 바꾸기
const searchCallbackInfo: AllSearchCallback = useMemo(
() => ({ type: 'ALL', searchCallback: fetchData }),
const searchCallbackInfo: TeamSearchCallback = useMemo(
() => ({
type: 'TEAM',
searchCallback: (teamId: number, nickname: User['nickname']) => {
return fetchData(teamId, nickname);
},
}),
[fetchData],
);

Expand All @@ -41,47 +48,82 @@ export default function ModalProjectForm({ formId, projectId, onSubmit }: ModalP
defaultValues: {
projectName: '',
content: '',
startDate: new Date(),
endDate: null,
startDate: DateTime.fromJSDate(new Date()).toFormat('yyyy-LL-dd'),
endDate: DateTime.fromJSDate(new Date()).toFormat('yyyy-LL-dd'),
coworkers: [],
},
});

const {
watch,
handleSubmit,
setValue,
formState: { errors },
register,
} = methods;

const startDate = watch('startDate') || new Date();
const endDate = watch('endDate') || null;
const handleRoleChange = (userId: User['userId'], roleName: ProjectRoleName) => {
const updatedCoworkerInfos = coworkerInfos.map((coworkerInfo) =>
coworkerInfo.userId === userId ? { ...coworkerInfo, roleName } : coworkerInfo,
);

const handleCoworkersClick = (user: SearchUser) => {
console.log(user);
const updatedCoworkers = updatedCoworkerInfos.map(({ userId, roleName, nickname }) => ({
userId,
roleName,
nickname,
}));

setValue('coworkers', updatedCoworkers);
setCoworkerInfos(updatedCoworkerInfos);
};

const handleRemoveUser = (userId: User['userId']) => {
const filteredCoworkerInfos = coworkerInfos.filter((coworkerInfo) => coworkerInfo.userId !== userId);
const filteredCoworkers = filteredCoworkerInfos.map(({ userId, roleName, nickname }) => ({
userId,
roleName,
nickname,
}));

setValue('coworkers', filteredCoworkers);
setCoworkerInfos(filteredCoworkerInfos);
};

const handleKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setKeyword(e.target.value.trim());
};

const handleRoleChange = (userId: number, roleName: ProjectRoleName) => {
console.log(userId, roleName);
};
const handleCoworkersClick = (user: SearchUser) => {
const isIncludedUser = coworkerInfos.find((coworkerInfo) => coworkerInfo.userId === user.userId);
if (isIncludedUser) return toastInfo('이미 포함된 팀원입니다');

const handleRemoveUser = (userId: number) => {
console.log(userId);
const updatedCoworkerInfos: ProjectCoworker[] = [
...coworkerInfos,
{ userId: user.userId, nickname: user.nickname, roleName: 'ASSIGNEE' },
];
const updatedCoworkers = updatedCoworkerInfos.map(({ userId, roleName, nickname }) => ({
userId,
roleName,
nickname,
}));
setCoworkerInfos(updatedCoworkerInfos);
setValue('coworkers', updatedCoworkers);
setKeyword('');
clearData();
};

const handleSubmitForm: SubmitHandler<ProjectForm> = (formData: ProjectForm) => onSubmit(formData);

return (
<FormProvider {...methods}>
<form id={formId} className="mb-10 flex grow flex-col justify-center" onSubmit={handleSubmit(onSubmit)}>
<form id={formId} className="mb-10 flex grow flex-col justify-center" onSubmit={handleSubmit(handleSubmitForm)}>
<div className="relative" onMouseEnter={() => setShowTooltip(true)} onMouseLeave={() => setShowTooltip(false)}>
<p className="text-sky-700">
<strong>프로젝트 권한 정보</strong>
</p>
<RoleTooltip showTooltip={showTooltip} rolesInfo={PROJECT_ROLE_INFO} />
</div>

<DuplicationCheckInput
id="projectName"
label="프로젝트 명"
Expand All @@ -90,51 +132,48 @@ export default function ModalProjectForm({ formId, projectId, onSubmit }: ModalP
errors={errors.projectName?.message}
register={register('projectName', PROJECT_VALIDATION_RULES.PROJECT_NAME)}
/>
<div className="mb-30">
<DescriptionTextarea
id="content"
label="프로젝트 설명"
fieldName="content"
placeholder="프로젝트 내용을 입력해주세요."
validationRole={PROJECT_VALIDATION_RULES.PROJECT_DESCRIPTION}
errors={errors.content?.message}
/>
</div>

<DescriptionTextarea
id="content"
label="프로젝트 설명"
fieldName="content"
placeholder="프로젝트 내용을 입력해주세요."
validationRole={PROJECT_VALIDATION_RULES.PROJECT_DESCRIPTION}
errors={errors.content?.message}
/>

<PeriodDateInput
startDateLabel="시작일"
endDateLabel="종료일"
startDateId="startDate"
endDateId="endDate"
startDateFieldName="startDate"
endDateFieldName="endDate"
limitStartDate={startDate}
limitEndDate={endDate}
/>

<div className="mb-16">
<SearchUserInput
id="search"
label="팀원"
keyword={keyword}
loading={loading}
userList={userList}
searchCallbackInfo={searchCallbackInfo}
onKeywordChange={handleKeywordChange}
onUserClick={handleCoworkersClick}
/>
<div className="flex flex-wrap">
{coworkerInfos.map(({ userId, nickname }) => (
<UserRoleSelectBox
key={userId}
userId={userId}
nickname={nickname}
roles={PROJECT_ROLES}
defaultValue="MATE"
onRoleChange={handleRoleChange}
onRemoveUser={handleRemoveUser}
/>
))}
</div>
<SearchUserInput
id="search"
label="팀원"
keyword={keyword}
loading={loading}
userList={userList}
searchId={teamId}
searchCallbackInfo={searchCallbackInfo}
onKeywordChange={handleKeywordChange}
onUserClick={handleCoworkersClick}
/>
<div className="flex flex-wrap">
{coworkerInfos.map(({ userId, nickname }) => (
<UserRoleSelectBox
key={userId}
userId={userId}
nickname={nickname}
roles={PROJECT_ROLES}
defaultValue="MATE"
onRoleChange={handleRoleChange}
onRemoveUser={handleRemoveUser}
/>
))}
</div>
</form>
</FormProvider>
Expand Down
25 changes: 23 additions & 2 deletions src/hooks/query/useProjectQuery.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { generateProjectsQueryKey, generateProjectUsersQueryKey } from '@utils/queryKeyGenerator';
import { deleteProject, getProjectList, getProjectUserRoleList } from '@services/projectService';
import { createProject, deleteProject, getProjectList, getProjectUserRoleList } from '@services/projectService';

import useToast from '@hooks/useToast';
import type { Team } from '@/types/TeamType';
import type { Project } from '@/types/ProjectType';
import type { Project, ProjectForm } from '@/types/ProjectType';

// Todo: Project Query CUD로직 작성하기
// 팀에 속한 프로젝트 목록 조회
Expand Down Expand Up @@ -63,3 +63,24 @@ export function useDeleteProject(teamId: Team['teamId']) {

return mutation;
}

// 프로젝트 생성
export function useCreateProject(teamId: Team['teamId']) {
const queryClient = useQueryClient();
const { toastSuccess, toastError } = useToast();
const projectsQueryKey = generateProjectsQueryKey(teamId);

const mutation = useMutation({
mutationFn: (projectData: ProjectForm) => createProject(teamId, projectData),
onError: (error) => {
console.log(error);
toastError('프로젝트 생성을 실패했습니다. 다시 시도해 주세요.');
},
onSuccess: () => {
toastSuccess('프로젝트를 생성하였습니다.');
queryClient.invalidateQueries({ queryKey: projectsQueryKey });
},
});

return mutation;
}
24 changes: 23 additions & 1 deletion src/mocks/mockAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { Team } from '@/types/TeamType';
import type { Project } from '@/types/ProjectType';
import type { ProjectStatus, ProjectStatusForm } from '@/types/ProjectStatusType';
import type { Task, TaskUpdateForm } from '@/types/TaskType';
import type { TaskFileForMemory, TaskUser, UploadTaskFile } from '@/types/MockType';
import type { ProjectUser, TaskFileForMemory, TaskUser, UploadTaskFile } from '@/types/MockType';

/* ===================== 역할(Role) 관련 처리 ===================== */

Expand All @@ -26,6 +26,11 @@ export function findRole(roleId: Role['roleId']) {
return ROLE_DUMMY.find((role) => role.roleId === roleId);
}

// ToDo: 유저 ID로 조회해야하나, 현재 이름을 넘겨주고 있어서 임시로 만든 조회 방법 수정 필요
export function findRoleByRoleName(roleName: Role['roleName']) {
return ROLE_DUMMY.find((role) => role.roleName === roleName);
}

/* ===================== 유저(User) 관련 처리 ===================== */

// 유저 조회
Expand All @@ -40,8 +45,20 @@ export function findTeamUser(teamId: Team['teamId'], userId: User['userId']) {
return TEAM_USER_DUMMY.find((teamUser) => teamUser.teamId === teamId && teamUser.userId === userId);
}

// 팀에 속한 모든 유저 조회
export function findAllTeamUsers(teamId: Team['teamId']) {
return TEAM_USER_DUMMY.filter((teamUser) => teamUser.teamId === teamId);
}

/* ====================== 팀(Team) 관련 처리 ====================== */

/* ========= 프로젝트에 연결된 유저(Project User) 관련 처리 ========= */

// 프로젝트와 유저 연결 생성
export function createProjectUser(newProjectUser: ProjectUser) {
PROJECT_USER_DUMMY.push(newProjectUser);
}

// 프로젝트와 연결된 유저 조회
export function findProjectUser(projectId: Project['projectId'], userId: User['userId']) {
return PROJECT_USER_DUMMY.find((projectUser) => projectUser.projectId === projectId && projectUser.userId === userId);
Expand All @@ -63,6 +80,11 @@ export function deleteAllProjectUser(projectId: Project['projectId']) {

/* ================= 프로젝트(Project) 관련 처리 ================= */

// 프로젝트 생성
export function createProject(newProject: Project) {
PROJECT_DUMMY.push(newProject);
}

// 프로젝트 조회
export function findProject(projectId: Project['projectId']) {
return PROJECT_DUMMY.find((project) => project.projectId === projectId);
Expand Down
Loading

0 comments on commit 26e8d95

Please sign in to comment.