diff --git a/src/components/modals/create-evaluation-modal.tsx b/src/components/modals/create-evaluation-modal.tsx index 57d39bc3..e6697bb7 100644 --- a/src/components/modals/create-evaluation-modal.tsx +++ b/src/components/modals/create-evaluation-modal.tsx @@ -23,7 +23,7 @@ const CreateEvaluationFormSchema = z.object({ 'C_MINUS', 'F', ]), - comment: z.string().min(1, '정성 코멘트를 입력해주세요.'), + comment: z.string().min(1, '정성 코멘트를 입력해주세요.').max(1000), }); type CreateEvaluationFormValues = z.infer; @@ -147,7 +147,7 @@ function CreateEvaluationForm({ id="comment" placeholder="정성 코멘트를 입력해 주세요." className="min-h-[230px]" - maxLength={5000} + maxLength={1000} /> diff --git a/src/components/modals/create-mission-modal.tsx b/src/components/modals/create-mission-modal.tsx index 4cd2c7f5..ad1bc783 100644 --- a/src/components/modals/create-mission-modal.tsx +++ b/src/components/modals/create-mission-modal.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import dayjs from 'dayjs'; import { Plus, XIcon } from 'lucide-react'; import { useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; @@ -9,8 +10,11 @@ import FormField from '@/components/ui/form/form-field'; import { BaseInput, TextAreaInput } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal'; import { useGroupStudyDetailQuery } from '@/features/study/group/model/use-study-query'; -import { useCreateMission } from '@/hooks/queries/mission-api'; -import { createDateDisabledMatcher } from '@/utils/time'; +import { useCreateMission, useGetMissions } from '@/hooks/queries/mission-api'; +import { + createDisabledDateMatcherForMission, + MissionPeriod, +} from '@/utils/time'; // Form Schema const CreateMissionFormSchema = z.object({ @@ -45,7 +49,12 @@ export default function CreateMissionModal({ }: CreateMissionModalProps) { const [open, setOpen] = useState(false); const { data: studyData } = useGroupStudyDetailQuery(groupStudyId); + const { data: existingMissions } = useGetMissions({ + groupStudyId, + pageSize: 100, + }); + const studyStartDate = studyData?.basicInfo?.startDate; const studyEndDate = studyData?.basicInfo?.endDate; return ( @@ -76,7 +85,9 @@ export default function CreateMissionModal({ setOpen(false)} /> @@ -87,13 +98,17 @@ export default function CreateMissionModal({ interface CreateMissionFormProps { groupStudyId: number; + studyStartDate?: string; studyEndDate?: string; + existingMissions?: MissionPeriod[]; onClose: () => void; } function CreateMissionForm({ groupStudyId, + studyStartDate, studyEndDate, + existingMissions, onClose, }: CreateMissionFormProps) { const methods = useForm({ @@ -113,8 +128,8 @@ function CreateMissionForm({ const { mutate: createMission } = useCreateMission(); const onValidSubmit = (values: CreateMissionFormValues) => { - const startDate = values.dateRange.from.toISOString(); - const endDate = values.dateRange.to.toISOString(); + const startDate = dayjs(values.dateRange.from).format('YYYY-MM-DD'); + const endDate = dayjs(values.dateRange.to).format('YYYY-MM-DD'); createMission( { @@ -217,7 +232,11 @@ function CreateMissionForm({ mode="range" selected={field.value} onSelect={(date) => field.onChange(date)} - disabled={createDateDisabledMatcher(studyEndDate)} + disabled={createDisabledDateMatcherForMission({ + studyStartDate, + studyEndDate, + existingMissions, + })} /> )} /> diff --git a/src/components/modals/edit-evaluation-modal.tsx b/src/components/modals/edit-evaluation-modal.tsx index 243568b4..65c18b58 100644 --- a/src/components/modals/edit-evaluation-modal.tsx +++ b/src/components/modals/edit-evaluation-modal.tsx @@ -157,7 +157,7 @@ function EditEvaluationForm({ id="comment" placeholder="정성 코멘트를 입력해 주세요." className="min-h-[230px]" - maxLength={5000} + maxLength={1000} /> diff --git a/src/components/modals/edit-homework-modal.tsx b/src/components/modals/edit-homework-modal.tsx index 9ad0a937..13bd9149 100644 --- a/src/components/modals/edit-homework-modal.tsx +++ b/src/components/modals/edit-homework-modal.tsx @@ -11,7 +11,7 @@ import { useEditHomework } from '@/hooks/queries/group-study-homework-api'; import { BaseInput, TextAreaInput } from '../ui/input'; const EditHomeworkFormSchema = z.object({ - textContent: z.string().min(1, '과제 상세 내용을 입력해주세요.'), + textContent: z.string().min(1, '과제 상세 내용을 입력해주세요.').max(1000), attachmentLink: z.string().optional(), }); @@ -135,7 +135,7 @@ function EditHomeworkForm({ id="textContent" className="min-h-[230px]" placeholder="학습한 내용을 자세히 작성해 주세요." - maxLength={5000} + maxLength={1000} /> diff --git a/src/components/modals/edit-mission-modal.tsx b/src/components/modals/edit-mission-modal.tsx index 4ea8341d..3b2e668a 100644 --- a/src/components/modals/edit-mission-modal.tsx +++ b/src/components/modals/edit-mission-modal.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import dayjs from 'dayjs'; import { XIcon } from 'lucide-react'; import { useEffect, useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; @@ -9,8 +10,15 @@ import FormField from '@/components/ui/form/form-field'; import { BaseInput, TextAreaInput } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal'; import { useGroupStudyDetailQuery } from '@/features/study/group/model/use-study-query'; -import { useGetMission, useUpdateMission } from '@/hooks/queries/mission-api'; -import { createDateDisabledMatcher } from '@/utils/time'; +import { + useGetMission, + useGetMissions, + useUpdateMission, +} from '@/hooks/queries/mission-api'; +import { + createDisabledDateMatcherForMission, + MissionPeriod, +} from '@/utils/time'; // Form Schema const EditMissionFormSchema = z.object({ @@ -42,7 +50,12 @@ export default function EditMissionModal({ const [open, setOpen] = useState(false); const { data: missionData, isLoading } = useGetMission(missionId); const { data: studyData } = useGroupStudyDetailQuery(groupStudyId); + const { data: existingMissions } = useGetMissions({ + groupStudyId, + pageSize: 100, + }); + const studyStartDate = studyData?.basicInfo?.startDate; const studyEndDate = studyData?.basicInfo?.endDate; return ( @@ -77,7 +90,9 @@ export default function EditMissionModal({ setOpen(false)} /> ) : null} @@ -97,14 +112,18 @@ interface EditMissionFormProps { missionEndDate?: string; }; missionId: number; + studyStartDate?: string; studyEndDate?: string; + existingMissions?: MissionPeriod[]; onClose: () => void; } function EditMissionForm({ missionData, missionId, + studyStartDate, studyEndDate, + existingMissions, onClose, }: EditMissionFormProps) { const methods = useForm({ @@ -126,8 +145,10 @@ function EditMissionForm({ }, }); + const { handleSubmit, formState, control, reset } = methods; + useEffect(() => { - methods.reset({ + reset({ title: missionData.missionTitle || '', description: missionData.missionDescription || '', weekNum: missionData.weekNum?.toString() || '', @@ -141,15 +162,13 @@ function EditMissionForm({ : new Date(), }, }); - }, [missionData, methods]); - - const { handleSubmit, formState, control } = methods; + }, [missionData, reset]); const { mutate: updateMission } = useUpdateMission(); const onValidSubmit = (values: EditMissionFormValues) => { - const startDate = values.dateRange.from.toISOString(); - const endDate = values.dateRange.to.toISOString(); + const startDate = dayjs(values.dateRange.from).format('YYYY-MM-DD'); + const endDate = dayjs(values.dateRange.to).format('YYYY-MM-DD'); updateMission( { @@ -252,7 +271,12 @@ function EditMissionForm({ mode="range" selected={field.value} onSelect={(date) => field.onChange(date)} - disabled={createDateDisabledMatcher(studyEndDate)} + disabled={createDisabledDateMatcherForMission({ + studyStartDate, + studyEndDate, + existingMissions, + editingMissionId: missionId, + })} /> )} /> diff --git a/src/features/study/group/api/creat-group-study.ts b/src/features/study/group/api/create-group-study.ts similarity index 100% rename from src/features/study/group/api/creat-group-study.ts rename to src/features/study/group/api/create-group-study.ts diff --git a/src/features/study/group/const/use-group-study-mutation.ts b/src/features/study/group/const/use-group-study-mutation.ts index b7aab825..848ffd30 100644 --- a/src/features/study/group/const/use-group-study-mutation.ts +++ b/src/features/study/group/const/use-group-study-mutation.ts @@ -1,5 +1,5 @@ import { useMutation } from '@tanstack/react-query'; -import { createGroupStudy } from '../api/creat-group-study'; +import { createGroupStudy } from '../api/create-group-study'; import { GroupStudyFormRequest } from '../api/group-study-types'; import { updateGroupStudy } from '../api/update-group-study'; diff --git a/src/utils/time.ts b/src/utils/time.ts index 29f042a6..4c87577a 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -91,23 +91,74 @@ export const getKoreaDisplayMonday = (base?: Date) => { return dow === 0 || dow === 6 ? addDays(monday, 7) : monday; }; -export const createDateDisabledMatcher = (endDateString?: string) => { +export interface MissionPeriod { + missionId?: number; + startDate?: string; + endDate?: string; +} + +export interface CreateDisabledDateMatcherForMissionOptions { + studyStartDate?: string; + studyEndDate?: string; + existingMissions?: MissionPeriod[]; + editingMissionId?: number; +} + +export const createDisabledDateMatcherForMission = ( + options: CreateDisabledDateMatcherForMissionOptions, +) => { + const { studyStartDate, studyEndDate, existingMissions, editingMissionId } = + options; + return (date: Date) => { + const normalizedDate = new Date(date); + normalizedDate.setHours(0, 0, 0, 0); + const today = new Date(); today.setHours(0, 0, 0, 0); - if (date < today) { + if (normalizedDate < today) { return true; } - if (endDateString) { - const endDate = new Date(endDateString); - endDate.setHours(23, 59, 59, 999); - if (date > endDate) { + if (studyStartDate) { + const startDate = new Date(studyStartDate); + startDate.setHours(0, 0, 0, 0); + if (normalizedDate < startDate) { + return true; + } + } + + if (studyEndDate) { + const endDate = new Date(studyEndDate); + endDate.setHours(0, 0, 0, 0); + if (normalizedDate > endDate) { return true; } } + if (existingMissions) { + const targetTime = normalizedDate.getTime(); + for (const mission of existingMissions) { + if (editingMissionId && mission.missionId === editingMissionId) + continue; + + if (mission.startDate && mission.endDate) { + const missionStart = new Date(mission.startDate); + missionStart.setHours(0, 0, 0, 0); + const missionEnd = new Date(mission.endDate); + missionEnd.setHours(0, 0, 0, 0); + + if ( + targetTime >= missionStart.getTime() && + targetTime <= missionEnd.getTime() + ) { + return true; + } + } + } + } + return false; }; };