Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/GU-99/grow-up-fe into fe…
Browse files Browse the repository at this point in the history
…ature/#120-team-leave-invite-management
  • Loading branch information
ice-bear98 committed Sep 10, 2024
2 parents d51ceaa + 8061a28 commit bd2d1c6
Show file tree
Hide file tree
Showing 13 changed files with 95 additions and 25 deletions.
24 changes: 20 additions & 4 deletions src/components/modal/task/CreateModalTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,37 @@ import ModalLayout from '@layouts/ModalLayout';
import ModalPortal from '@components/modal/ModalPortal';
import ModalTaskForm from '@components/modal/task/ModalTaskForm';
import ModalFormButton from '@components/modal/ModalFormButton';
import { useCreateStatusTask, useReadStatusTasks } from '@hooks/query/useTaskQuery';
import useToast from '@hooks/useToast';

import type { SubmitHandler } from 'react-hook-form';
import type { TaskForm } from '@/types/TaskType';
import type { Project } from '@/types/ProjectType';
import type { ProjectStatus } from '@/types/ProjectStatusType';

type CreateModalTaskProps = {
project: Project;
onClose: () => void;
};

export default function CreateModalTask({ project, onClose: handleClose }: CreateModalTaskProps) {
// ToDo: 상태 생성을 위한 네트워크 로직 추가
const handleSubmit: SubmitHandler<TaskForm> = async (data) => {
console.log('생성 폼 제출');
console.log(data);
const { toastError } = useToast();
const { mutate: createTaskMutate } = useCreateStatusTask(project.projectId);
const { statusTaskList } = useReadStatusTasks(project.projectId);

const getLastSortOrder = (statusId: ProjectStatus['statusId']) => {
const statusTask = statusTaskList.find((statusTask) => statusTask.statusId === Number(statusId));
if (!statusTask) {
toastError('선택하신 프로젝트 상태는 존재하지 않습니다. ');
throw Error('프로젝트 상태가 존재하지 않습니다.');
}
return statusTask.tasks.length + 1;
};

// ToDo: 파일 생성 위한 네트워크 로직 추가
const handleSubmit: SubmitHandler<TaskForm> = async (taskFormData) => {
const sortOrder = getLastSortOrder(taskFormData.statusId);
createTaskMutate({ ...taskFormData, sortOrder });
handleClose();
};
return (
Expand Down
7 changes: 6 additions & 1 deletion src/components/modal/task/ModalTaskForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,12 @@ export default function ModalTaskForm({ formId, project, taskId, onSubmit }: Mod
<input
type="date"
id="startDate"
{...register('startDate', TASK_VALIDATION_RULES.START_DATE(startDate, endDate))}
{...register('startDate', {
...TASK_VALIDATION_RULES.START_DATE(startDate, endDate),
onChange: (e) => {
if (!hasDeadline) setValue('endDate', e.target.value);
},
})}
/>
<div className={`my-5 h-10 grow text-xs text-error ${errors.startDate ? 'visible' : 'invisible'}`}>
{errors.startDate?.message}
Expand Down
2 changes: 1 addition & 1 deletion src/components/sidebar/ListSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function ListSidebar({ label, title, children, showButton, text,
};

return (
<aside className="mr-10 flex w-1/3 min-w-125 max-w-220 shrink-0 flex-col border border-list bg-contents-box">
<aside className="flex w-1/3 min-w-125 max-w-220 shrink-0 flex-col border border-list bg-contents-box">
<div className="flex min-h-30 items-center justify-between bg-sub px-10">
<div>
{label && <small className="mr-5 font-bold text-main">{label}</small>}
Expand Down
4 changes: 2 additions & 2 deletions src/components/task/kanban/TaskItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export default function TaskItem({ draggableId, name, colorCode, index }: TaskIt
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
>
<div style={{ borderColor: colorCode }} className="h-8 w-8 rounded-full border" />
<div className="select-none overflow-hidden text-ellipsis text-nowrap">{name}</div>
<div style={{ borderColor: colorCode }} className="size-8 shrink-0 rounded-full border" />
<div className="select-none truncate">{name}</div>
</div>
)}
</Draggable>
Expand Down
2 changes: 1 addition & 1 deletion src/components/task/kanban/TaskItemList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function TaskItemList({ statusId, colorCode, tasks }: TaskItemLis
{(taskDropProvided) => (
<article
style={{ borderColor: colorCode }}
className="h-full w-full grow border-l-[3px] bg-scroll"
className="h-full overflow-auto border-l-[3px] bg-scroll"
ref={taskDropProvided.innerRef}
{...taskDropProvided.droppableProps}
>
Expand Down
22 changes: 19 additions & 3 deletions src/hooks/query/useTaskQuery.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { findTaskList, updateTaskOrder } from '@services/taskService';
import { createTask, findTaskList, updateTaskOrder } from '@services/taskService';
import useToast from '@hooks/useToast';

import type { TaskListWithStatus, TaskOrder } from '@/types/TaskType';
import type { TaskForm, TaskListWithStatus, TaskOrder } from '@/types/TaskType';
import type { Project } from '@/types/ProjectType';

function getTaskNameList(taskList: TaskListWithStatus[]) {
Expand All @@ -14,7 +14,23 @@ function getTaskNameList(taskList: TaskListWithStatus[]) {
: [];
}

// Todo: Task Query CUD로직 작성하기
// Todo: Task Query UD로직 작성하기
export function useCreateStatusTask(projectId: Project['projectId']) {
const { toastSuccess } = useToast();
const queryClient = useQueryClient();
const queryKey = ['projects', projectId, 'tasks'];

const mutation = useMutation({
mutationFn: (formData: TaskForm) => createTask(projectId, formData),
onSuccess: () => {
toastSuccess('프로젝트 일정을 등록하였습니다.');
queryClient.invalidateQueries({ queryKey });
},
});

return mutation;
}

export function useReadStatusTasks(projectId: Project['projectId']) {
const {
data: statusTaskList = [],
Expand Down
12 changes: 7 additions & 5 deletions src/layouts/page/ProjectLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export default function ProjectLayout() {

return (
<>
<section className="flex h-full p-15">
<section className="flex h-full gap-10 p-15">
<ListSidebar label="team" title="팀 이름...">
<ListProject data={projectList} targetId={projectId} />
</ListSidebar>
<section className="flex grow flex-col border border-list bg-contents-box">
<section className="flex w-2/3 grow flex-col border border-list bg-contents-box">
<header className="flex h-30 items-center justify-between border-b p-10">
{/* ToDo: LabelTitle 공통 컴포넌트로 추출할 것 */}
<div>
Expand All @@ -42,8 +42,8 @@ export default function ProjectLayout() {
<RiSettings5Fill /> Project Setting
</div>
</header>
<div className="flex grow flex-col overflow-auto p-10 pt-0">
<div className="sticky top-0 z-10 mb-10 flex items-center justify-between border-b bg-contents-box pt-10">
<div className="flex grow flex-col overflow-auto">
<div className="sticky top-0 z-10 flex items-center justify-between border-b bg-contents-box p-10 pb-0">
<ul className="*:mr-15">
<li className="inline">
{/* ToDo: nav 옵션사항을 정리하여 map으로 정리할 것 */}
Expand All @@ -66,7 +66,9 @@ export default function ProjectLayout() {
</button>
</div>
</div>
<Outlet context={{ project } satisfies ProjectContext} />
<div className="flex grow overflow-auto p-10">
<Outlet context={{ project } satisfies ProjectContext} />
</div>
</div>
</section>
</section>
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/page/SettingLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function SettingLayout() {
};

return (
<section className="flex h-full p-15">
<section className="flex h-full gap-10 p-15">
<ListSidebar title={`${USER_INFO_DUMMY.nickname} 님의 정보`}>
<ListSetting navList={navList} />
</ListSidebar>
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/page/TeamLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function TeamLayout() {

return (
<>
<section className="flex h-full p-15">
<section className="flex h-full gap-10 p-15">
<ListSidebar title="팀 목록" showButton text="팀 생성" onClick={openTeamModal}>
<ListTeam data={TEAM_DUMMY} targetId={teamId} />
</ListSidebar>
Expand Down
22 changes: 20 additions & 2 deletions src/mocks/services/taskServiceHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { http, HttpResponse } from 'msw';
import { STATUS_DUMMY, TASK_DUMMY } from '@mocks/mockData';
import { getStatusHash, getTaskHash } from '@mocks/mockHash';
import type { TaskOrderForm } from '@/types/TaskType';
import type { TaskForm, TaskOrderForm } from '@/types/TaskType';

const BASE_URL = import.meta.env.VITE_BASE_URL;

Expand All @@ -13,7 +13,9 @@ const taskServiceHandler = [

if (!accessToken) return new HttpResponse(null, { status: 401 });

const statusList = STATUS_DUMMY.filter((status) => status.projectId === Number(projectId));
const statusList = STATUS_DUMMY.filter((status) => status.projectId === Number(projectId)).sort(
(a, b) => a.sortOrder - b.sortOrder,
);
const statusTaskList = statusList.map((status) => {
const tasks = TASK_DUMMY.filter((task) => task.statusId === status.statusId).sort(
(a, b) => a.sortOrder - b.sortOrder,
Expand All @@ -23,6 +25,22 @@ const taskServiceHandler = [

return HttpResponse.json(statusTaskList);
}),
// 일정 생성 API
http.post(`${BASE_URL}/project/:projectId/task`, async ({ request, params }) => {
const accessToken = request.headers.get('Authorization');
const formData = (await request.json()) as TaskForm;
const { projectId } = params;

if (!accessToken) return new HttpResponse(null, { status: 401 });

const statusList = STATUS_DUMMY.filter((status) => status.projectId === Number(projectId));
if (!statusList.find((status) => status.statusId === Number(formData.statusId))) {
return new HttpResponse(null, { status: 400 });
}

TASK_DUMMY.push({ ...formData, statusId: +formData.statusId, taskId: TASK_DUMMY.length + 1, files: [] });
return new HttpResponse(null, { status: 201 });
}),
// 일정 순서 변경 API
http.patch(`${BASE_URL}/project/:projectId/task/order`, async ({ request, params }) => {
const accessToken = request.headers.get('Authorization');
Expand Down
2 changes: 1 addition & 1 deletion src/pages/project/CalendarPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export default function CalendarPage() {
// ToDo: 캘린더 크기 전체적으로 조정
// ToDo: 코드 리팩토링
return (
<div className="flex h-full min-h-375 min-w-260 flex-col">
<div className="flex h-full min-h-375 w-full min-w-260 flex-col">
<CalendarToolbar date={date} startDate={startDate} onClick={handleNavigate} />
<Calendar
toolbar={false}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/project/KanbanPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function KanbanPage() {
<Droppable droppableId={DND_DROPPABLE_PREFIX.STATUS} type={DND_TYPE.STATUS} direction="horizontal">
{(statusDropProvided) => (
<section
className="flex grow gap-10 pt-10"
className="flex grow gap-10"
ref={statusDropProvided.innerRef}
{...statusDropProvided.droppableProps}
>
Expand Down
17 changes: 15 additions & 2 deletions src/services/taskService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,34 @@ import { authAxios } from '@services/axiosProvider';

import type { AxiosRequestConfig } from 'axios';
import type { Project } from '@/types/ProjectType';
import type { TaskListWithStatus, TaskOrderForm } from '@/types/TaskType';
import type { TaskForm, TaskListWithStatus, TaskOrderForm } from '@/types/TaskType';

/**
* 프로젝트에 속한 모든 일정 목록 조회 API
*
* @export
* @async
* @param {Project['projectId']} projectId - 대상 프로젝트 ID
* @param {Project['projectId']} projectId - 프로젝트 ID
* @param {AxiosRequestConfig} [axiosConfig={}] - axios 요청 옵션 설정 객체
* @returns {Promise<AxiosResponse<TaskListWithStatus[]>>}
*/
export async function findTaskList(projectId: Project['projectId'], axiosConfig: AxiosRequestConfig = {}) {
return authAxios.get<TaskListWithStatus[]>(`/project/${projectId}/task`, axiosConfig);
}

/**
* 일정 생성 API
*
* @export
* @param {Project['projectId']} projectId - 프로젝트 ID
* @param {TaskForm} formData - 새로운 일정 정보 객체
* @param {AxiosRequestConfig} [axiosConfig={}] - axios 요청 옵션 설정 객체
* @returns {Promise<AxiosResponse<void>>}
*/
export function createTask(projectId: Project['projectId'], formData: TaskForm, axiosConfig: AxiosRequestConfig = {}) {
return authAxios.post(`/project/${projectId}/task`, formData, axiosConfig);
}

/**
* 일정 순서 변경 API
*
Expand Down

0 comments on commit bd2d1c6

Please sign in to comment.