diff --git a/src/components/common/ToggleButton.tsx b/src/components/common/ToggleButton.tsx index 4a540da4..7c99bc3a 100644 --- a/src/components/common/ToggleButton.tsx +++ b/src/components/common/ToggleButton.tsx @@ -16,11 +16,11 @@ export default function ToggleButton({ id, checked, onChange: handleChange }: To /> {/* prettier-ignore */} + absolute bottom-0 left-0 right-0 top-0 cursor-pointer rounded-full bg-disable transition duration-300 + before:content-[''] before:absolute before:left-2 before:top-1/2 before:-translate-y-1/2 before:size-7 + before:rounded-full before:bg-white before:transition before:duration-300 + peer-checked:bg-main peer-checked:before:translate-x-9 + "/> ); } diff --git a/src/components/modal/project-status/CreateModalProjectStatus.tsx b/src/components/modal/project-status/CreateModalProjectStatus.tsx index 853e41ec..669ecf1e 100644 --- a/src/components/modal/project-status/CreateModalProjectStatus.tsx +++ b/src/components/modal/project-status/CreateModalProjectStatus.tsx @@ -1,15 +1,18 @@ -import { SubmitHandler } from 'react-hook-form'; import ModalLayout from '@layouts/ModalLayout'; import ModalPortal from '@components/modal/ModalPortal'; import ModalProjectStatusForm from '@components/modal/project-status/ModalProjectStatusForm'; import ModalFormButton from '@components/modal/ModalFormButton'; -import { ProjectStatusForm } from '@/types/ProjectStatusType'; + +import type { SubmitHandler } from 'react-hook-form'; +import type { Project } from '@/types/ProjectType'; +import type { ProjectStatusForm } from '@/types/ProjectStatusType'; type CreateModalProjectStatusProps = { + project: Project; onClose: () => void; }; -export default function CreateModalProjectStatus({ onClose: handleClose }: CreateModalProjectStatusProps) { +export default function CreateModalProjectStatus({ project, onClose: handleClose }: CreateModalProjectStatusProps) { // ToDo: 상태 생성을 위한 네트워크 로직 추가 const handleSubmit: SubmitHandler = async (data) => { console.log('생성 폼 제출'); @@ -19,7 +22,7 @@ export default function CreateModalProjectStatus({ onClose: handleClose }: Creat return ( - + diff --git a/src/components/modal/project-status/ModalProjectStatusForm.tsx b/src/components/modal/project-status/ModalProjectStatusForm.tsx index c9a78088..2e1afed8 100644 --- a/src/components/modal/project-status/ModalProjectStatusForm.tsx +++ b/src/components/modal/project-status/ModalProjectStatusForm.tsx @@ -1,18 +1,22 @@ -import { SubmitHandler, useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; +import { RiProhibited2Fill } from 'react-icons/ri'; import { STATUS_VALIDATION_RULES } from '@constants/formValidationRules'; -import useProjectStatusQuery from '@hooks/query/useProjectStatusQuery'; import DuplicationCheckInput from '@components/common/DuplicationCheckInput'; -import { RiProhibited2Fill } from 'react-icons/ri'; +import useStatusQuery from '@hooks/query/useStatusQuery'; + +import type { SubmitHandler } from 'react-hook-form'; import type { ProjectStatus, ProjectStatusForm } from '@/types/ProjectStatusType'; +import type { Project } from '@/types/ProjectType'; type ModalProjectStatusFormProps = { formId: string; + project: Project; statusId?: ProjectStatus['statusId']; onSubmit: SubmitHandler; }; -export default function ModalProjectStatusForm({ formId, statusId, onSubmit }: ModalProjectStatusFormProps) { - const { initialValue, nameList, colorList, usableColorList } = useProjectStatusQuery(statusId); +export default function ModalProjectStatusForm({ formId, project, statusId, onSubmit }: ModalProjectStatusFormProps) { + const { initialValue, nameList, colorList, usableColorList } = useStatusQuery(project.projectId, statusId); const { register, watch, @@ -20,17 +24,15 @@ export default function ModalProjectStatusForm({ formId, statusId, onSubmit }: M formState: { errors }, } = useForm({ mode: 'onChange', - defaultValues: initialValue || { name: '', color: '' }, + defaultValues: initialValue, }); - const statusName = watch('name'); - const selectedColor = watch('color'); return ( void; }; -export default function UpdateModalProjectStatus({ statusId, onClose: handleClose }: UpdateModalProjectStatusProps) { +export default function UpdateModalProjectStatus({ + project, + statusId, + onClose: handleClose, +}: UpdateModalProjectStatusProps) { // ToDo: 상태 수정을 위한 네트워크 로직 추가 const handleSubmit: SubmitHandler = async (data) => { console.log(statusId, '수정 폼 제출'); @@ -21,7 +28,12 @@ export default function UpdateModalProjectStatus({ statusId, onClose: handleClos return ( - + diff --git a/src/components/modal/task/CreateModalTask.tsx b/src/components/modal/task/CreateModalTask.tsx index 7707e734..c5342da2 100644 --- a/src/components/modal/task/CreateModalTask.tsx +++ b/src/components/modal/task/CreateModalTask.tsx @@ -1,10 +1,11 @@ -import { SubmitHandler } from 'react-hook-form'; 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 { TaskForm } from '@/types/TaskType'; -import { Project } from '@/types/ProjectType'; + +import type { SubmitHandler } from 'react-hook-form'; +import type { TaskForm } from '@/types/TaskType'; +import type { Project } from '@/types/ProjectType'; type CreateModalTaskProps = { project: Project; diff --git a/src/components/modal/task/ModalTaskForm.tsx b/src/components/modal/task/ModalTaskForm.tsx index b6c940a2..4ffe4ff6 100644 --- a/src/components/modal/task/ModalTaskForm.tsx +++ b/src/components/modal/task/ModalTaskForm.tsx @@ -1,13 +1,16 @@ import { useState } from 'react'; import { DateTime } from 'luxon'; import { IoSearch } from 'react-icons/io5'; -import { SubmitHandler, useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { TASK_VALIDATION_RULES } from '@constants/formValidationRules'; import ToggleButton from '@components/common/ToggleButton'; import DuplicationCheckInput from '@components/common/DuplicationCheckInput'; import useTaskQuery from '@hooks/query/useTaskQuery'; -import { Project } from '@/types/ProjectType'; -import { Task, TaskForm } from '@/types/TaskType'; +import useStatusQuery from '@hooks/query/useStatusQuery'; + +import type { SubmitHandler } from 'react-hook-form'; +import type { Project } from '@/types/ProjectType'; +import type { Task, TaskForm } from '@/types/TaskType'; type ModalTaskFormProps = { formId: string; @@ -18,7 +21,9 @@ type ModalTaskFormProps = { export default function ModalTaskForm({ formId, project, taskId, onSubmit }: ModalTaskFormProps) { const [hasDeadline, setHasDeadline] = useState(false); + const { statusList } = useStatusQuery(project.projectId, taskId); const { taskNameList } = useTaskQuery(project.projectId); + // ToDo: 상태 수정 모달 작성시 기본값 설정 방식 변경할 것 const { register, watch, @@ -34,6 +39,7 @@ export default function ModalTaskForm({ formId, project, taskId, onSubmit }: Mod content: '', startDate: DateTime.fromJSDate(new Date()).toFormat('yyyy-LL-dd'), endDate: DateTime.fromJSDate(new Date()).toFormat('yyyy-LL-dd'), + statusId: statusList[0].statusId, }, }); @@ -49,6 +55,35 @@ export default function ModalTaskForm({ formId, project, taskId, onSubmit }: Mod className="mb-20 flex w-4/5 max-w-375 grow flex-col justify-center" onSubmit={handleSubmit(onSubmit)} > + {/* ToDo: 상태 선택 리팩토링 할 것 */} + + {statusList.map((status) => { + const { statusId, name, color } = status; + const isChecked = +watch('statusId') === statusId; + return ( + + + + {name} + + ); + })} + + + {errors.statusId?.message} + + generatePrefixId(statusId, DND_DRAGGABLE_PREFIX.STATUS), [statusId]); @@ -40,7 +42,7 @@ export default function TaskStatusContainer({ statusTask }: TaskStatusContainerP )} - {showModal && } + {showModal && } > ); } diff --git a/src/constants/formValidationRules.ts b/src/constants/formValidationRules.ts index ea6dc8c7..2708355a 100644 --- a/src/constants/formValidationRules.ts +++ b/src/constants/formValidationRules.ts @@ -1,8 +1,9 @@ import Validator from '@utils/Validator'; import { deepFreeze } from '@utils/deepFreeze'; import { EMAIL_REGEX, ID_REGEX, NICKNAME_REGEX, PASSWORD_REGEX, PHONE_REGEX } from '@constants/regex'; -import { Project } from '@/types/ProjectType'; -import { Task } from '@/types/TaskType'; + +import type { Project } from '@/types/ProjectType'; +import type { Task } from '@/types/TaskType'; type ValidateOption = { [key: string]: (value: string) => string | boolean }; @@ -121,6 +122,9 @@ export const STATUS_VALIDATION_RULES = deepFreeze({ }); export const TASK_VALIDATION_RULES = deepFreeze({ + STATUS: { + required: '상태를 선택해주세요.', + }, TASK_NAME: (nameList: string[]) => ({ required: '일정명을 입력해주세요.', maxLength: { diff --git a/src/hooks/query/useProjectStatusQuery.ts b/src/hooks/query/useProjectStatusQuery.ts deleted file mode 100644 index 57efede6..00000000 --- a/src/hooks/query/useProjectStatusQuery.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { PROJECT_STATUS_COLORS } from '@constants/projectStatus'; -import { STATUS_DUMMY } from '@mocks/mockData'; -import { ProjectStatus, UsableColor } from '@/types/ProjectStatusType'; - -function getStatusNameList(projectStatusList: ProjectStatus[], excludedName?: ProjectStatus['name']) { - const statusNameList = projectStatusList.map((projectStatus) => projectStatus.name); - - const statusNameSet = new Set(statusNameList); - if (excludedName && statusNameSet.has(excludedName)) { - statusNameSet.delete(excludedName); - } - - return [...statusNameSet.values()]; -} - -function getStatusColorList(projectStatusList: ProjectStatus[], excludedColor?: ProjectStatus['color']) { - const statusColorList = projectStatusList.map((projectStatus) => projectStatus.color); - - const statusColorSet = new Set(statusColorList); - if (excludedColor && statusColorSet.has(excludedColor)) { - statusColorSet.delete(excludedColor); - } - - return [...statusColorSet.values()]; -} - -function getUsableStatusColorList( - projectStatusList: ProjectStatus[], - excludedColor?: ProjectStatus['color'], -): UsableColor[] { - const statusColorMap = new Map(); - Object.values(PROJECT_STATUS_COLORS).forEach((color) => { - statusColorMap.set(color, { color, isUsable: true }); - }); - - projectStatusList.forEach(({ color }) => { - if (!statusColorMap.has(color)) throw Error('[Error] 등록되지 않은 색상입니다.'); - - if (excludedColor === color) return; - statusColorMap.set(color, { ...statusColorMap.get(color), isUsable: false }); - }); - - return [...statusColorMap.values()]; -} - -export default function useProjectStatusQuery(statusId?: ProjectStatus['statusId']) { - // ToDo: ProjectStatus 관련 Query 로직 작성하기 - const projectStatusList = STATUS_DUMMY; - const projectStatus = projectStatusList.find((status) => status.statusId === statusId); - - const initialValue = { name: projectStatus?.name || '', color: projectStatus?.color || '' }; - const nameList = getStatusNameList(projectStatusList, projectStatus?.name); - const colorList = getStatusColorList(projectStatusList, projectStatus?.color); - const usableColorList = getUsableStatusColorList(projectStatusList, projectStatus?.color); - - return { projectStatusList, initialValue, nameList, colorList, usableColorList }; -} diff --git a/src/hooks/query/useStatusQuery.ts b/src/hooks/query/useStatusQuery.ts new file mode 100644 index 00000000..50d07195 --- /dev/null +++ b/src/hooks/query/useStatusQuery.ts @@ -0,0 +1,56 @@ +import { PROJECT_STATUS_COLORS } from '@constants/projectStatus'; +import { STATUS_DUMMY } from '@mocks/mockData'; +import type { Project } from '@/types/ProjectType'; +import type { ProjectStatus, UsableColor } from '@/types/ProjectStatusType'; + +function getStatusNameList(statusList: ProjectStatus[], excludedName?: ProjectStatus['name']) { + const statusNameList = statusList.map((projectStatus) => projectStatus.name); + + const statusNameSet = new Set(statusNameList); + if (excludedName && statusNameSet.has(excludedName)) { + statusNameSet.delete(excludedName); + } + + return [...statusNameSet.values()]; +} + +function getStatusColorList(statusList: ProjectStatus[], excludedColor?: ProjectStatus['color']) { + const statusColorList = statusList.map((projectStatus) => projectStatus.color); + + const statusColorSet = new Set(statusColorList); + if (excludedColor && statusColorSet.has(excludedColor)) { + statusColorSet.delete(excludedColor); + } + + return [...statusColorSet.values()]; +} + +function getUsableStatusColorList(statusList: ProjectStatus[], excludedColor?: ProjectStatus['color']): UsableColor[] { + const statusColorMap = new Map(); + Object.values(PROJECT_STATUS_COLORS).forEach((color) => { + statusColorMap.set(color, { color, isUsable: true }); + }); + + statusList.forEach(({ color }) => { + if (!statusColorMap.has(color)) throw Error('[Error] 등록되지 않은 색상입니다.'); + + if (excludedColor === color) return; + statusColorMap.set(color, { ...statusColorMap.get(color), isUsable: false }); + }); + + return [...statusColorMap.values()]; +} + +// ToDo: ProjectStatus 관련 Query 로직 작성하기 +// Query Key: project, projectId, status +export default function useStatusQuery(projectId: Project['projectId'], statusId?: ProjectStatus['statusId']) { + const statusList = STATUS_DUMMY; + + const status = statusList.find((status) => status.statusId === statusId); + const initialValue = { name: status?.name || '', color: status?.color || '' }; + const nameList = getStatusNameList(statusList, status?.name); + const colorList = getStatusColorList(statusList, status?.color); + const usableColorList = getUsableStatusColorList(statusList, status?.color); + + return { statusList, initialValue, nameList, colorList, usableColorList }; +} diff --git a/src/layouts/page/ProjectLayout.tsx b/src/layouts/page/ProjectLayout.tsx index 767f2d07..bceb7bf4 100644 --- a/src/layouts/page/ProjectLayout.tsx +++ b/src/layouts/page/ProjectLayout.tsx @@ -66,7 +66,7 @@ export default function ProjectLayout() { {showTaskModal && } - {showStatusModal && } + {showStatusModal && } > ); } diff --git a/src/types/TaskType.tsx b/src/types/TaskType.tsx index 6a313661..4865a924 100644 --- a/src/types/TaskType.tsx +++ b/src/types/TaskType.tsx @@ -26,6 +26,7 @@ export type TaskForm = { content: string; startDate: string; endDate: string; + statusId: number; }; export type TaskWithStatus = RenameKeys & Task;