diff --git a/src/apis/tasks/getTask/GetTasksResponse.ts b/src/apis/tasks/getTask/GetTasksResponse.ts new file mode 100644 index 00000000..f7e22cbf --- /dev/null +++ b/src/apis/tasks/getTask/GetTasksResponse.ts @@ -0,0 +1,9 @@ +import { TaskType } from '@/types/tasks/taskType'; + +export interface GetTasksResponse { + code: string; + data: { + tasks: TaskType[]; + }; + message: string | null; +} diff --git a/src/apis/tasks/getTask/query.ts b/src/apis/tasks/getTask/query.ts index 2d91fcab..607c86fd 100644 --- a/src/apis/tasks/getTask/query.ts +++ b/src/apis/tasks/getTask/query.ts @@ -3,37 +3,18 @@ import { useQuery } from '@tanstack/react-query'; import getTasks from './axios'; import { GetTasksType } from './GetTasksType'; -const placeholderData = { - code: 'success', - data: { - tasks: [ - { - id: 1, - name: 'task name', - deadLine: { - date: '2024-06-30', - time: '12:30', - }, - }, - { - id: 2, - name: 'task name', - deadLine: { - date: '2024-06-30', - time: '12:30', - }, - }, - ], - }, - message: null, -}; - /** Task 리스트 조회 */ const useGetTasks = ({ isTotal, sortOrder, targetDate }: GetTasksType) => useQuery({ queryKey: ['today', isTotal, sortOrder, targetDate], queryFn: () => getTasks({ isTotal, sortOrder, targetDate }), - placeholderData, + placeholderData: { + code: 'success', + data: { + tasks: [], + }, + message: null, + }, }); export default useGetTasks; diff --git a/src/apis/tasks/updateTaskStatus/UpdateTaskStatusType.ts b/src/apis/tasks/updateTaskStatus/UpdateTaskStatusType.ts new file mode 100644 index 00000000..cb391571 --- /dev/null +++ b/src/apis/tasks/updateTaskStatus/UpdateTaskStatusType.ts @@ -0,0 +1,5 @@ +export interface UpdateTaskStatusType { + taskId: number; + targetDate?: string | null; + status?: string | null; +} diff --git a/src/apis/tasks/updateTaskStatus/axios.ts b/src/apis/tasks/updateTaskStatus/axios.ts new file mode 100644 index 00000000..1f77bc49 --- /dev/null +++ b/src/apis/tasks/updateTaskStatus/axios.ts @@ -0,0 +1,12 @@ +import { UpdateTaskStatusType } from './UpdateTaskStatusType'; + +import { privateInstance } from '@/apis/instance'; + +const updateTaskStatus = async ({ taskId, targetDate, status }: UpdateTaskStatusType) => { + await privateInstance.patch(`/api/tasks/${taskId}/status`, { + targetDate, + status, + }); +}; + +export default updateTaskStatus; diff --git a/src/apis/tasks/updateTaskStatus/query.ts b/src/apis/tasks/updateTaskStatus/query.ts new file mode 100644 index 00000000..1a12efd6 --- /dev/null +++ b/src/apis/tasks/updateTaskStatus/query.ts @@ -0,0 +1,17 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import updateTaskStatus from './axios'; +import { UpdateTaskStatusType } from './UpdateTaskStatusType'; + +const useUpdateTaskStatus = () => { + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (updateData: UpdateTaskStatusType) => updateTaskStatus(updateData), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['today'] }), + }); + + return { mutate: mutation.mutate, queryClient }; +}; + +export default useUpdateTaskStatus; diff --git a/src/components/common/StagingArea/StagingArea.tsx b/src/components/common/StagingArea/StagingArea.tsx index 5d444750..6b09f186 100644 --- a/src/components/common/StagingArea/StagingArea.tsx +++ b/src/components/common/StagingArea/StagingArea.tsx @@ -3,21 +3,31 @@ import { Droppable } from 'react-beautiful-dnd'; import StagingAreaTaskContainer from './StagingAreaTaskContainer'; +import StagingAreaSetting from '@/components/common/StagingArea/StagingAreaSetting'; import TextInputStaging from '@/components/common/textbox/TextInputStaging'; import { TaskType } from '@/types/tasks/taskType'; +import { StagingAreaSettingProps } from '@/types/today/stagingAreaSettingProps'; -interface StagingAreaProps { +interface StagingAreaProps extends StagingAreaSettingProps { handleSelectedTarget: (task: TaskType | null) => void; selectedTarget: TaskType | null; tasks: TaskType[]; } + function StagingArea(props: StagingAreaProps) { - const { handleSelectedTarget, selectedTarget, tasks } = props; + const { handleSelectedTarget, selectedTarget, tasks, activeButton, sortOrder, handleTextBtnClick, handleSortOrder } = + props; return ( 쏟아내기 + {(provided) => (
diff --git a/src/components/common/StagingArea/StagingAreaSetting.tsx b/src/components/common/StagingArea/StagingAreaSetting.tsx index 27eb1fa1..bc7c6e1d 100644 --- a/src/components/common/StagingArea/StagingAreaSetting.tsx +++ b/src/components/common/StagingArea/StagingAreaSetting.tsx @@ -6,14 +6,8 @@ import TextBtn from '../button/textBtn/TextBtn'; import ModalArrange from '../modal/ModalArrange/ModalArrange'; import ModalBackdrop from '../modal/ModalBackdrop'; -import { SortOrderType } from '@/types/sortOrderType'; +import { StagingAreaSettingProps } from '@/types/today/stagingAreaSettingProps'; -interface StagingAreaSettingProps { - handleTextBtnClick: (button: '전체' | '지연') => void; - activeButton: '전체' | '지연'; - sortOrder: SortOrderType; - handleSortOrder: (order: SortOrderType) => void; -} function StagingAreaSetting({ handleTextBtnClick, activeButton, sortOrder, handleSortOrder }: StagingAreaSettingProps) { const [isModalOpen, setIsModalOpen] = useState(false); diff --git a/src/components/common/StagingArea/StagingAreaTaskContainer.tsx b/src/components/common/StagingArea/StagingAreaTaskContainer.tsx index 616101b6..04dceeb5 100644 --- a/src/components/common/StagingArea/StagingAreaTaskContainer.tsx +++ b/src/components/common/StagingArea/StagingAreaTaskContainer.tsx @@ -1,15 +1,10 @@ import styled from '@emotion/styled'; -import { useState } from 'react'; import { Draggable } from 'react-beautiful-dnd'; import BtnTaskContainer from '../BtnTaskContainer'; import EmptyContainer from '../EmptyContainer'; -import ScrollGradient from '../ScrollGradient'; -import useGetTasks from '@/apis/tasks/getTask/query'; import BtnTask from '@/components/common/BtnTask/BtnTask'; -import StagingAreaSetting from '@/components/common/StagingArea/StagingAreaSetting'; -import { SortOrderType } from '@/types/sortOrderType'; import { TaskType } from '@/types/tasks/taskType'; interface StagingAreaTaskContainerProps { @@ -18,38 +13,15 @@ interface StagingAreaTaskContainerProps { tasks: TaskType[]; } -function StagingAreaTaskContainer({ handleSelectedTarget, selectedTarget }: StagingAreaTaskContainerProps) { - const [activeButton, setActiveButton] = useState<'전체' | '지연'>('전체'); - const [sortOrder, setSortOrder] = useState('recent'); - const isTotal = activeButton === '전체'; - - // Task 목록 Get - const { data } = useGetTasks({ isTotal, sortOrder }); - - /** isTotal 핸들링 함수 */ - const handleTextBtnClick = (button: '전체' | '지연') => { - setActiveButton(button); - }; - - const handleSortOrder = (order: SortOrderType) => { - setSortOrder(order); - }; - console.log(data); - +function StagingAreaTaskContainer({ handleSelectedTarget, selectedTarget, tasks }: StagingAreaTaskContainerProps) { return ( - - {data.data.tasks.length === 0 ? ( + {tasks.length === 0 ? ( ) : ( <> - {data.data.tasks.map((task: TaskType, index: number) => ( + {tasks.map((task: TaskType, index: number) => ( {(provided, snapshot) => (
@@ -76,8 +48,6 @@ function StagingAreaTaskContainer({ handleSelectedTarget, selectedTarget }: Stag ))} )} - -
); @@ -88,7 +58,7 @@ export default StagingAreaTaskContainer; const StagingAreaTaskContainerLayout = styled.div` display: flex; flex-direction: column; - gap: 1.3rem; align-items: flex-start; align-self: stretch; + margin-top: 1.3rem; `; diff --git a/src/components/targetArea/TargetArea.tsx b/src/components/targetArea/TargetArea.tsx index 33f845a9..a76524b0 100644 --- a/src/components/targetArea/TargetArea.tsx +++ b/src/components/targetArea/TargetArea.tsx @@ -1,5 +1,4 @@ import styled from '@emotion/styled'; -import { useState } from 'react'; import { Droppable } from 'react-beautiful-dnd'; import TargetAreaDate from './TargetAreaDate'; @@ -7,36 +6,24 @@ import TargetControlSection from './TargetControlSection'; import TargetTaskSection from './TargetTaskSection'; import { TaskType } from '@/types/tasks/taskType'; +import { TargetControlSectionProps } from '@/types/today/TargetControlSectionProps'; -interface TargetAreaProps { +interface TargetAreaProps extends TargetControlSectionProps { handleSelectedTarget: (task: TaskType | null) => void; selectedTarget: TaskType | null; tasks: TaskType[]; } -function TargetArea({ handleSelectedTarget, selectedTarget }: TargetAreaProps) { - const [targetDate, setTargetDate] = useState(new Date()); - - const handlePrevBtn = () => { - const newDate = new Date(targetDate); - newDate.setDate(newDate.getDate() - 1); - setTargetDate(newDate); - }; - - const handleNextBtn = () => { - const newDate = new Date(targetDate); - newDate.setDate(newDate.getDate() + 1); - setTargetDate(newDate); - }; - - const handleTodayBtn = () => { - setTargetDate(new Date()); - }; - - const handleChangeDate = (target: Date) => { - setTargetDate(target); - }; - +function TargetArea({ + handleSelectedTarget, + selectedTarget, + tasks, + onClickPrevDate, + onClickNextDate, + onClickTodayDate, + onClickDatePicker, + targetDate, +}: TargetAreaProps) { return ( {/* 날짜 */} @@ -46,10 +33,10 @@ function TargetArea({ handleSelectedTarget, selectedTarget }: TargetAreaProps) { {/* 버튼 */} {/* 태스크 목록 */} @@ -59,7 +46,7 @@ function TargetArea({ handleSelectedTarget, selectedTarget }: TargetAreaProps) { {provided.placeholder}
diff --git a/src/components/targetArea/TargetAreaDate.tsx b/src/components/targetArea/TargetAreaDate.tsx index 16cc3255..7db730e3 100644 --- a/src/components/targetArea/TargetAreaDate.tsx +++ b/src/components/targetArea/TargetAreaDate.tsx @@ -6,12 +6,15 @@ import formatDatetoStrinKor from '@/utils/formatDatetoStringKor'; interface TargetAreaDateProps { targetDate: Date; } + function TargetAreaDate({ targetDate }: TargetAreaDateProps) { const formatDate = formatDatetoStrinKor(targetDate); return {formatDate}; } + const DateText = styled.h2` ${({ theme }) => theme.fontTheme.HEADLINE_02}; padding: 0.7rem 0.2rem 0.7rem 1rem; `; + export default TargetAreaDate; diff --git a/src/components/targetArea/TargetControlSection.tsx b/src/components/targetArea/TargetControlSection.tsx index 2bd49b15..9c295756 100644 --- a/src/components/targetArea/TargetControlSection.tsx +++ b/src/components/targetArea/TargetControlSection.tsx @@ -6,16 +6,9 @@ import TextBtn from '@/components/common/button/textBtn/TextBtn'; import DateCorrectionModal from '@/components/common/datePicker/DateCorrectionModal'; import ModalBackdrop from '@/components/common/modal/ModalBackdrop'; import MODAL from '@/constants/modalLocation'; +import { TargetControlSectionProps } from '@/types/today/TargetControlSectionProps'; import formatDatetoString from '@/utils/formatDatetoString'; -interface TargetControlSectionProps { - onClickPrevDate: () => void; - onClickNextDate: () => void; - onClickTodayDate: () => void; - onClickDatePicker: (target: Date) => void; - targetDate: Date; -} - function TargetControlSection({ onClickPrevDate, onClickNextDate, diff --git a/src/components/targetArea/TargetTaskSection.tsx b/src/components/targetArea/TargetTaskSection.tsx index 51491472..61bfaccd 100644 --- a/src/components/targetArea/TargetTaskSection.tsx +++ b/src/components/targetArea/TargetTaskSection.tsx @@ -5,28 +5,25 @@ import BtnTask from '../common/BtnTask/BtnTask'; import BtnTaskContainer from '../common/BtnTaskContainer'; import EmptyContainer from '../common/EmptyContainer'; -import useGetTasks from '@/apis/tasks/getTask/query'; import { TaskType } from '@/types/tasks/taskType'; -import formatDatetoLocalDate from '@/utils/formatDatetoLocalDate'; interface TargetTaskSectionProps { handleSelectedTarget: (task: TaskType | null) => void; selectedTarget: TaskType | null; - selectedDate: Date | null; + tasks: TaskType[]; } function TargetTaskSection(props: TargetTaskSectionProps) { - const { handleSelectedTarget, selectedTarget, selectedDate } = props; - const targetDate = formatDatetoLocalDate(selectedDate); - const { data } = useGetTasks({ targetDate }); + const { handleSelectedTarget, selectedTarget, tasks } = props; + return ( - {data.data.tasks.length === 0 ? ( + {tasks.length === 0 ? ( ) : ( <> - {data.data.tasks.map((task: TaskType, index: number) => ( + {tasks.map((task: TaskType, index: number) => ( {(provided, snapshot) => (
(dummyTasks); const [selectedTarget, setSelectedTarget] = useState(null); + const [activeButton, setActiveButton] = useState<'전체' | '지연'>('전체'); + const [sortOrder, setSortOrder] = useState('recent'); + const [selectedDate, setTargetDate] = useState(new Date()); + const isTotal = activeButton === '전체'; + const targetDate = formatDatetoLocalDate(selectedDate); + + // Task 목록 Get + const { data: stagingData } = useGetTasks({ isTotal, sortOrder }); + const { data: targetData } = useGetTasks({ targetDate }); + const { mutate, queryClient } = useUpdateTaskStatus(); + + /** isTotal 핸들링 함수 */ + const handleTextBtnClick = (button: '전체' | '지연') => { + setActiveButton(button); + }; + + const handleSortOrder = (order: SortOrderType) => { + setSortOrder(order); + }; + const handleSelectedTarget = (task: TaskType | null) => { setSelectedTarget(task); }; + const handlePrevBtn = () => { + const newDate = new Date(selectedDate); + newDate.setDate(newDate.getDate() - 1); + setTargetDate(newDate); + }; + + const handleNextBtn = () => { + const newDate = new Date(selectedDate); + newDate.setDate(newDate.getDate() + 1); + setTargetDate(newDate); + }; + + const handleTodayBtn = () => { + setTargetDate(new Date()); + }; + + const handleChangeDate = (target: Date) => { + setTargetDate(target); + }; + const handleDragEnd = (result: DropResult) => { const { source, destination } = result; // 드래그가 끝난 위치가 없으면 리턴 if (!destination) return; - // TODO: api 연결 시에는 밑에 부분 지우고 api 호출 예정 - - // 같은 위치로 드래그 - if (source.droppableId === destination.droppableId && source.index === destination.index) { - return; + // sourceTasks와 destinationTasks를 배열로 변환 + const sourceTasks = source.droppableId === 'target' ? [...targetData.data.tasks] : [...stagingData.data.tasks]; + const destinationTasks = + destination.droppableId === 'target' ? [...targetData.data.tasks] : [...stagingData.data.tasks]; + + // 드래그된 항목을 sourceTasks에서 제거하고 destinationTasks에 추가 + const [movedTask] = sourceTasks.splice(source.index, 1); + destinationTasks.splice(destination.index, 0, movedTask); + + // 상태 업데이트 + if (source.droppableId === 'target') { + queryClient.setQueryData(['tasks'], { + target: { ...targetData, data: { ...targetData.data, tasks: sourceTasks } }, + staging: { ...stagingData, data: { ...stagingData.data, tasks: destinationTasks } }, + }); + } else { + queryClient.setQueryData(['tasks'], { + target: { ...targetData, data: { ...targetData.data, tasks: destinationTasks } }, + staging: { ...stagingData, data: { ...stagingData.data, tasks: sourceTasks } }, + }); } - // 다른 위치로 드래그 - const sourceClone = Array.from(tasks[source.droppableId as keyof typeof tasks]); - const destClone = - source.droppableId === destination.droppableId - ? sourceClone - : Array.from(tasks[destination.droppableId as keyof typeof tasks]); - - const [removed] = sourceClone.splice(source.index, 1); - - destClone.splice(destination.index, 0, removed); - - const newTasks: TaskState = { - ...tasks, - [source.droppableId]: sourceClone, - }; - - if (source.droppableId !== destination.droppableId) { - newTasks[destination.droppableId] = destClone; + // API 호출 + if (destination.droppableId === 'target') { + mutate({ + taskId: movedTask.id, + targetDate, + status: '미완료', + }); + } else if (destination.droppableId === 'staging') { + mutate({ + taskId: movedTask.id, + targetDate: null, + status: null, + }); } - - setTasks(newTasks); }; return ( @@ -96,12 +111,22 @@ function Today() { + diff --git a/src/types/today/TargetControlSectionProps.ts b/src/types/today/TargetControlSectionProps.ts new file mode 100644 index 00000000..4d8e780b --- /dev/null +++ b/src/types/today/TargetControlSectionProps.ts @@ -0,0 +1,7 @@ +export interface TargetControlSectionProps { + onClickPrevDate: () => void; + onClickNextDate: () => void; + onClickTodayDate: () => void; + onClickDatePicker: (target: Date) => void; + targetDate: Date; +} diff --git a/src/types/today/stagingAreaSettingProps.ts b/src/types/today/stagingAreaSettingProps.ts new file mode 100644 index 00000000..e75ea7c9 --- /dev/null +++ b/src/types/today/stagingAreaSettingProps.ts @@ -0,0 +1,8 @@ +import { SortOrderType } from '../sortOrderType'; + +export interface StagingAreaSettingProps { + handleTextBtnClick: (button: '전체' | '지연') => void; + activeButton: '전체' | '지연'; + sortOrder: SortOrderType; + handleSortOrder: (order: SortOrderType) => void; +}