diff --git a/src/components/org/OrgAdmin/AboutSection/Curriculum/index.tsx b/src/components/org/OrgAdmin/AboutSection/Curriculum/index.tsx index a96a34c..18dc249 100644 --- a/src/components/org/OrgAdmin/AboutSection/Curriculum/index.tsx +++ b/src/components/org/OrgAdmin/AboutSection/Curriculum/index.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useFormContext } from 'react-hook-form'; -import { PART_LIST, VALIDATION_CHECK } from '@/utils/org'; +import { PART_KO, PART_LIST, VALIDATION_CHECK } from '@/utils/org'; import PartCategory from '../../PartCategory'; import { StInput, StTitle, StWrapper } from '../style'; @@ -21,9 +21,9 @@ const Curriculum = () => { formState: { errors }, } = useFormContext(); - const [selectedPart, setSelectedPart] = useState('기획'); + const [selectedPart, setSelectedPart] = useState('기획'); - const handleSetSelectedPart = (value: string) => { + const handleSetSelectedPart = (value: PART_KO) => { setSelectedPart(value); }; diff --git a/src/components/org/OrgAdmin/PartCategory/index.tsx b/src/components/org/OrgAdmin/PartCategory/index.tsx index 637ba1e..118386f 100644 --- a/src/components/org/OrgAdmin/PartCategory/index.tsx +++ b/src/components/org/OrgAdmin/PartCategory/index.tsx @@ -1,12 +1,12 @@ import { Chip } from '@sopt-makers/ui'; -import { PART_LIST } from '@/utils/org'; +import { PART_KO, PART_LIST } from '@/utils/org'; import { StPartCategoryWrapper } from './style'; interface PartCategoryProps { - selectedPart: string; - onSetSelectedPart: (part: string) => void; + selectedPart: PART_KO; + onSetSelectedPart: (part: PART_KO) => void; } const PartCategory = ({ selectedPart, diff --git a/src/components/org/OrgAdmin/RecruitSection/Fna.tsx b/src/components/org/OrgAdmin/RecruitSection/Fna.tsx index ddc7539..3052652 100644 --- a/src/components/org/OrgAdmin/RecruitSection/Fna.tsx +++ b/src/components/org/OrgAdmin/RecruitSection/Fna.tsx @@ -1,22 +1,39 @@ -import { useState } from 'react'; import { useFormContext } from 'react-hook-form'; -import { VALIDATION_CHECK } from '@/utils/org'; +import { PART_KO, VALIDATION_CHECK } from '@/utils/org'; import PartCategory from '../PartCategory'; import { StTextArea, StTitle, StTitleWrapper, StWrapper } from '../style'; import { StFnaWrapper, StTextAreaWrapper } from './style'; -const Fna = () => { +const QUESTION_NUMBERS = [0, 1, 2]; + +interface FnaProps { + fnaPart: PART_KO; + onChangeFnaPart: (part: PART_KO) => void; +} + +const Fna = ({ fnaPart, onChangeFnaPart }: FnaProps) => { const { register, + clearErrors, + setError, formState: { errors }, } = useFormContext(); - const [selectedPart, setSelectedPart] = useState('기획'); + const handleSetSelectedPart = (value: PART_KO) => { + onChangeFnaPart(value); + }; - const handleSetSelectedPart = (value: string) => { - setSelectedPart(value); + const handleValidation = (field: string, value: string) => { + if (value) { + clearErrors(field); + } else { + setError(field, { + type: 'required', + message: VALIDATION_CHECK.required.errorText, + }); + } }; return ( @@ -25,135 +42,68 @@ const Fna = () => { 자주 묻는 질문 - - - - - - - - - - - - - + + {QUESTION_NUMBERS.map((index) => ( + + + handleValidation( + `recruitQuestion_${fnaPart}_questions_${index}_question`, + e.currentTarget.value, + ) + } + isError={ + !!errors[ + `recruitQuestion_${fnaPart}_questions_${index}_question` + ] + } + errorMessage={ + errors[`recruitQuestion_${fnaPart}_questions_${index}_question`] + ?.message as string + } + /> + + handleValidation( + `recruitQuestion_${fnaPart}_questions_${index}_answer`, + e.currentTarget.value, + ) + } + isError={ + !!errors[`recruitQuestion_${fnaPart}_questions_${index}_answer`] + } + errorMessage={ + errors[`recruitQuestion_${fnaPart}_questions_${index}_answer`] + ?.message as string + } + /> + + ))} ); diff --git a/src/components/org/OrgAdmin/RecruitSection/PartCurriculum.tsx b/src/components/org/OrgAdmin/RecruitSection/PartCurriculum.tsx index 362a171..dc5ca3e 100644 --- a/src/components/org/OrgAdmin/RecruitSection/PartCurriculum.tsx +++ b/src/components/org/OrgAdmin/RecruitSection/PartCurriculum.tsx @@ -1,22 +1,29 @@ -import { useState } from 'react'; import { useFormContext } from 'react-hook-form'; -import { VALIDATION_CHECK } from '@/utils/org'; +import { PART_KO, VALIDATION_CHECK } from '@/utils/org'; import PartCategory from '../PartCategory'; import { StTextArea, StTitle, StTitleWrapper, StWrapper } from '../style'; import { StTextAreaWrapper } from './style'; -const PartCurriculum = () => { +interface RecruitSectionProps { + curriculumPart: PART_KO; + onChangeCurriculumPart: (part: PART_KO) => void; +} + +const PartCurriculum = ({ + curriculumPart, + onChangeCurriculumPart, +}: RecruitSectionProps) => { const { register, + clearErrors, + setError, formState: { errors }, } = useFormContext(); - const [selectedPart, setSelectedPart] = useState('기획'); - - const handleSetSelectedPart = (value: string) => { - setSelectedPart(value); + const handleSetSelectedPart = (value: PART_KO) => { + onChangeCurriculumPart(value); }; return ( @@ -25,32 +32,42 @@ const PartCurriculum = () => { 파트별 소개 - + { + if (e.currentTarget.value) { + clearErrors(`recruitPartCurriculum_${curriculumPart}_content`); + } else { + setError(`recruitPartCurriculum_${curriculumPart}_content`, { + type: 'required', + message: VALIDATION_CHECK.required.errorText, + }); + } + }} isError={ - errors[`recruitPartCurriculum_${selectedPart}_content`]?.message != - undefined + errors[`recruitPartCurriculum_${curriculumPart}_content`] + ?.message != undefined } errorMessage={ - errors[`recruitPartCurriculum_${selectedPart}_content`] + errors[`recruitPartCurriculum_${curriculumPart}_content`] ?.message as string } /> { ex. - 어려움과 고민을 편하게 나누고 공감할 수 있는 유대감과 열린 마음을 가진 분 - 타 파트와 협업하며 존중과 신뢰를 바탕으로 원활한 팀워크를 만들어갈 수 있는 분`} + onChange={(e) => { + if (e.currentTarget.value) { + clearErrors(`recruitPartCurriculum_${curriculumPart}_preference`); + } else { + setError(`recruitPartCurriculum_${curriculumPart}_preference`, { + type: 'required', + message: VALIDATION_CHECK.required.errorText, + }); + } + }} isError={ - errors[`recruitPartCurriculum_${selectedPart}_preference`] + errors[`recruitPartCurriculum_${curriculumPart}_preference`] ?.message != undefined } errorMessage={ - errors[`recruitPartCurriculum_${selectedPart}_preference`] + errors[`recruitPartCurriculum_${curriculumPart}_preference`] ?.message as string } /> diff --git a/src/components/org/OrgAdmin/RecruitSection/index.tsx b/src/components/org/OrgAdmin/RecruitSection/index.tsx index 9e51a7d..66cd506 100644 --- a/src/components/org/OrgAdmin/RecruitSection/index.tsx +++ b/src/components/org/OrgAdmin/RecruitSection/index.tsx @@ -1,14 +1,31 @@ +import { PART_KO } from '@/utils/org'; + import { StContainer } from '../style'; import Fna from './Fna'; import Header from './Header'; import PartCurriculum from './PartCurriculum'; -const RecruitSection = () => { +interface RecruitSectionProps { + curriculumPart: PART_KO; + onChangeCurriculumPart: (part: PART_KO) => void; + fnaPart: PART_KO; + onChangeFnaPart: (part: PART_KO) => void; +} + +const RecruitSection = ({ + curriculumPart, + onChangeCurriculumPart, + fnaPart, + onChangeFnaPart, +}: RecruitSectionProps) => { return (
- - + + ); }; diff --git a/src/components/org/OrgAdmin/index.tsx b/src/components/org/OrgAdmin/index.tsx index ce0095a..09a0aa9 100644 --- a/src/components/org/OrgAdmin/index.tsx +++ b/src/components/org/OrgAdmin/index.tsx @@ -3,7 +3,12 @@ import { FormProvider, useForm } from 'react-hook-form'; import { StListHeader } from '@/components/attendanceAdmin/session/SessionList/style'; import FilterButton from '@/components/common/FilterButton'; -import { ORG_ADMIN_LIST, SCHEDULE_FIELDS } from '@/utils/org'; +import { + ORG_ADMIN_LIST, + PART_KO, + PART_LIST, + SCHEDULE_FIELDS, +} from '@/utils/org'; import AboutSection from './AboutSection'; import SubmitIcon from './assets/SubmitIcon'; @@ -16,6 +21,9 @@ import { Group } from './types'; function OrgAdmin() { const [selectedPart, setSelectedPart] = useState('공통'); const [group, setGroup] = useState('OB'); + const [curriculumPart, setCurriculumPart] = useState('기획'); + const [fnaPart, setFnaPart] = useState('기획'); + const methods = useForm({ mode: 'onBlur' }); const { handleSubmit, getValues } = methods; @@ -49,9 +57,47 @@ function OrgAdmin() { return true; }; + const validateCurriculum = () => { + for (const part of PART_LIST) { + const content = getValues(`recruitPartCurriculum_${part}_content`); + const preference = getValues(`recruitPartCurriculum_${part}_preference`); + if (!content || !preference) { + setCurriculumPart(part); + setSelectedPart('지원하기'); + return false; + } + } + return true; + }; + + const validateFna = () => { + for (const part of PART_LIST) { + const questionsAndAnswers = Array.from({ length: 3 }, (_, index) => ({ + question: getValues( + `recruitQuestion_${part}_questions_${index}_question`, + ), + answer: getValues(`recruitQuestion_${part}_questions_${index}_answer`), + })); + + const isPartValid = questionsAndAnswers.every( + ({ question, answer }) => !!question && !!answer, + ); + + if (!isPartValid) { + setFnaPart(part); + setSelectedPart('지원하기'); + return false; + } + } + return true; + }; + const onSubmit = (data: any) => { - const isValid = validateSchedule(); - if (isValid) { + const isScheduleValid = validateSchedule(); + const isCurriculumValid = validateCurriculum(); + const isFnaValid = validateFna(); + + if (isScheduleValid && isCurriculumValid && isFnaValid) { console.log(data); } }; @@ -67,7 +113,7 @@ function OrgAdmin() { /> -
+ {selectedPart === '공통' ? ( ) : ( - + + setCurriculumPart(part) + } + fnaPart={fnaPart} + onChangeFnaPart={(part: PART_KO) => setFnaPart(part)} + /> )} diff --git a/src/utils/org.ts b/src/utils/org.ts index b909b68..d8f2e3e 100644 --- a/src/utils/org.ts +++ b/src/utils/org.ts @@ -32,7 +32,9 @@ export const 임원진_LIST = [ ] as const; // utils/session/partList와 순서 달라서 분리 -export const PART_LIST = [ +export type PART_KO = '기획' | '디자인' | '안드로이드' | 'iOS' | '웹' | '서버'; + +export const PART_LIST: PART_KO[] = [ '기획', '디자인', '안드로이드',