diff --git a/src/api/resumes.ts b/src/api/resumes.ts index 68fc3e9c..3488b67c 100644 --- a/src/api/resumes.ts +++ b/src/api/resumes.ts @@ -130,3 +130,16 @@ export const deleteEtcLanguageLevel = async (id: number) => { return response.data; }; +// 9.1 (유학생) 학교 검색하기 +export const getSearchSchools = async ({ + search, + page, + size +}:{ + search: string; + page: string; + size: string; +}) => { + const response = await api.get(`/api/v1/users/schools/brief?search=${search}&page=${page}&size=${size}`); + return response.data; +}; \ No newline at end of file diff --git a/src/assets/icons/ManageResume/GraySearchIcon.svg b/src/assets/icons/ManageResume/GraySearchIcon.svg new file mode 100644 index 00000000..fd7ce72c --- /dev/null +++ b/src/assets/icons/ManageResume/GraySearchIcon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/components/ManageResume/ResumeEditSection.tsx b/src/components/ManageResume/ResumeEditSection.tsx index 2cc7a402..27e09d16 100644 --- a/src/components/ManageResume/ResumeEditSection.tsx +++ b/src/components/ManageResume/ResumeEditSection.tsx @@ -41,10 +41,6 @@ const ResumeEditSection = ({ // 삭제 핸들러 (데이터 삭제 시 해당 API 호출) const handleDeleteIntroduction = () => { deleteIntroductionMutation.mutate(undefined, { - onSuccess: () => { - console.log('Introduction 삭제'); - // 필요 시 상태 업데이트나 추가 로직 실행 - }, onError: (error) => { console.error('Introduction 삭제 실패', error); }, @@ -53,9 +49,6 @@ const ResumeEditSection = ({ const handleDeleteEducation = (id: number) => { deleteEducationMutation.mutate(id, { - onSuccess: () => { - console.log(`Education 삭제: ${id}`); - }, onError: (error) => { console.error(`Education 삭제 실패: ${id}`, error); }, @@ -64,10 +57,6 @@ const ResumeEditSection = ({ const handleDeleteWorkExperience = (id: number) => { deleteWorkExperienceMutation.mutate(id, { - onSuccess: () => { - console.log(`Work experience 삭제: ${id}`); - // 필요 시 상태 업데이트나 추가 로직 실행 - }, onError: (error) => { console.error(`Work experience 삭제 실패: ${id}`, error); }, diff --git a/src/components/Profile/EditProfilePicture.tsx b/src/components/Profile/EditProfilePicture.tsx index 254f7102..989f37cc 100644 --- a/src/components/Profile/EditProfilePicture.tsx +++ b/src/components/Profile/EditProfilePicture.tsx @@ -24,7 +24,6 @@ const EditProfilePicture = ({ // 파일이 이미지 형식인지 확인 if (file && file.type.startsWith('image/')) { onImageUpdate(file); - // console.log('components : ' + file); const objectUrl = URL.createObjectURL(file); // 이미지 미리보기 URL 생성 setImagePreviewUrl(objectUrl); // 미리보기 URL 업데이트 diff --git a/src/components/SetEducation/EducationPatch.tsx b/src/components/SetEducation/EducationPatch.tsx new file mode 100644 index 00000000..2f8295f7 --- /dev/null +++ b/src/components/SetEducation/EducationPatch.tsx @@ -0,0 +1,157 @@ +import { InputType } from '@/types/common/input'; +import Input from '@/components/Common/Input'; +import Dropdown from '@/components/Common/Dropdown'; +import { PostEducationType } from '@/types/postResume/postEducation'; +import { EducationLevels } from '@/constants/manageResume'; +import GraySearchIcon from '@/assets/icons/ManageResume/GraySearchIcon.svg?react'; +import { useState } from 'react'; +import SearchSchools from '@/components/SetEducation/SearchSchools'; +import { School } from '@/types/api/document'; + +type EducationPatchProps = { + educationData: PostEducationType; + setEducationData: React.Dispatch>; + schoolData: School; +}; + +const EducationPatch = ({ + educationData, + setEducationData, + schoolData, +}: EducationPatchProps) => { + const [searchOpen, setSearchOpen] = useState(false); + const [school, setSchool] = useState(schoolData); + + const handleSchoolChange = (school: School) => { + setSchool(school); + }; + + const handleInputChange = ( + field: keyof PostEducationType, + value: string | number, + ) => { + setEducationData((prev) => ({ ...prev, [field]: value })); + }; + + const handleDateChange = ( + field: 'start_date' | 'end_date', + value: string, + ) => { + handleInputChange(field, value.replace(/\//g, '-')); + }; + + return ( + <> + {searchOpen && ( + + )} +
+
Modify Education
+ {/* 교육 기관 타입 선택 */} +
+

+ Education Levels* +

+
+ handleInputChange('education_level', value)} + /> +
+
{/* absolute 만큼의 공간 차지 */} +
+ {/* 학교명 선택 */} +
+

+ Name Of School* +

+
setSearchOpen(true)} + > + + {/* 선택되었다면, 선택한 학교명 */} +

+ {school ? school.name : 'Search Name of school'} +

+
+
+ {/* 전공 입력 */} +
+

+ Department (major)* +

+ handleInputChange('major', value)} + canDelete={false} + /> +
+ {/* 학년 입력 */} +
+

+ Grade* +

+ handleInputChange('grade', value)} + canDelete={false} + /> +
+ {/* 학점 입력 */} +
+

+ Credit* +

+ handleInputChange('gpa', value)} + canDelete={false} + /> +
+ {/* 입학 날짜 입력 */} +
+

+ Entrance Date * +

+ handleDateChange('start_date', value)} + /> +
+ {/* 졸업 날짜 입력 */} +
+

+ Graduation Date * +

+ handleDateChange('end_date', value)} + /> +
+
+ + ); +}; + +export default EducationPatch; diff --git a/src/components/SetEducation/EducationPost.tsx b/src/components/SetEducation/EducationPost.tsx new file mode 100644 index 00000000..77bfc3b5 --- /dev/null +++ b/src/components/SetEducation/EducationPost.tsx @@ -0,0 +1,155 @@ +import { InputType } from '@/types/common/input'; +import Input from '@/components/Common/Input'; +import Dropdown from '@/components/Common/Dropdown'; +import { InitailEducationType } from '@/types/postResume/postEducation'; +import { EducationLevels } from '@/constants/manageResume'; +import GraySearchIcon from '@/assets/icons/ManageResume/GraySearchIcon.svg?react'; +import { useState } from 'react'; +import SearchSchools from '@/components/SetEducation/SearchSchools'; +import { School } from '@/types/api/document'; + +type EducationPostProps = { + educationData: InitailEducationType; + setEducationData: React.Dispatch>; +}; + +const EducationPost = ({ + educationData, + setEducationData, +}: EducationPostProps) => { + const [searchOpen, setSearchOpen] = useState(false); + const [school, setSchool] = useState(); + + const handleSchoolChange = (school: School) => { + setSchool(school); + }; + + const handleInputChange = ( + field: keyof InitailEducationType, + value: string | number, + ) => { + setEducationData((prev) => ({ ...prev, [field]: value })); + }; + + const handleDateChange = ( + field: 'start_date' | 'end_date', + value: string, + ) => { + handleInputChange(field, value.replace(/\//g, '-')); + }; + + return ( + <> + {searchOpen && ( + + )} +
+
Add Education
+ {/* 교육 기관 타입 선택 */} +
+

+ Education Levels* +

+
+ handleInputChange('education_level', value)} + /> +
+
{/* absolute 만큼의 공간 차지 */} +
+ {/* 학교명 선택 */} +
+

+ Name Of School* +

+
setSearchOpen(true)} + > + + {/* 선택되었다면, 선택한 학교명 */} +

+ {school ? school.name : 'Search Name of school'} +

+
+
+ {/* 전공 입력 */} +
+

+ Department (major)* +

+ handleInputChange('major', value)} + canDelete={false} + /> +
+ {/* 학년 입력 */} +
+

+ Grade* +

+ handleInputChange('grade', value)} + canDelete={false} + /> +
+ {/* 학점 입력 */} +
+

+ Credit* +

+ handleInputChange('gpa', value)} + canDelete={false} + /> +
+ {/* 입학 날짜 입력 */} +
+

+ Entrance Date * +

+ handleDateChange('start_date', value)} + /> +
+ {/* 졸업 날짜 입력 */} +
+

+ Graduation Date * +

+ handleDateChange('end_date', value)} + /> +
+
+ + ); +}; + +export default EducationPost; diff --git a/src/components/SetEducation/SearchSchools.tsx b/src/components/SetEducation/SearchSchools.tsx new file mode 100644 index 00000000..bedb6338 --- /dev/null +++ b/src/components/SetEducation/SearchSchools.tsx @@ -0,0 +1,129 @@ +import { useState, useEffect } from 'react'; +import BaseHeader from '@/components/Common/Header/BaseHeader'; +import Input from '@/components/Common/Input'; +import { InputType } from '@/types/common/input'; +import Button from '@/components/Common/Button'; +import { buttonTypeKeys } from '@/constants/components'; +import { School } from '@/types/api/document'; +import { SearchSchollsList } from '@/constants/manageResume'; +import { InitailEducationType } from '@/types/postResume/postEducation'; +// import { useGetSearchSchools } from '@/hooks/api/useResume'; + +type SearchSchoolsProps = { + setSchool: (school: School) => void; + setSearchOpen: (value: boolean) => void; + handleInputChange: (field: keyof InitailEducationType, value: number) => void; +}; + +const SearchSchools = ({ + setSchool, + setSearchOpen, + handleInputChange, +}: SearchSchoolsProps) => { + // 학교명 검색어 상태 관리 + const [searchSchool, setSearchSchool] = useState(''); + // 선택한 학교 상태 관리 + const [selectedSchool, setSelectedSchool] = useState(null); + + // 학교 목록의 페이지(서버 요청) + // ===== API 연결 후 주석 해제(현재 학교 더미데이터로 대체) ===== + // const [page, setPage] = useState(1); + // const size = 10; + + const schoolList = SearchSchollsList; + // ===== API 연결 후 주석 해제(현재 학교 더미데이터로 대체) ===== + // const { data: schoolList = [], isLoading } = useGetSearchSchools( + // searchSchool, + // page, + // size, + // ); + + // 선택 버튼 + const handleSubmit = () => { + if (selectedSchool) { + setSchool(selectedSchool); + setSearchOpen(false); + handleInputChange('school_id', selectedSchool.id); + } + }; + + const handleSelectSchool = (school: School) => { + setSelectedSchool(school); + }; + + const handleSearchChange = (value: string) => { + setSearchSchool(value); + + // ===== API 연결 후 주석 해제(현재 학교 더미데이터로 대체) ===== + // setPage(1); // 새로운 검색어가 들어오면 페이지를 초기화 + }; + + // 모달이 열렸을 떄 스크롤 방지 + useEffect(() => { + document.body.style.overflow = 'hidden'; + return () => { + document.body.style.overflow = ''; + }; + }, []); + + return ( +
+ setSearchOpen(false)} + hasMenuButton={false} + title="Education" + /> +
+
+ Search for +
+ educational institutions +
+ setSearchSchool('')} + /> +
+ {/* API 연결 후 아래 주석 해제 */} + {/* {isLoading ? ( +
Loading...
+ ) : ( */} + {/* 검색된 학교 목록 */} + { + schoolList.map((school: School) => ( +
handleSelectSchool(school)} + > + {school.name} +
+ )) + // )} + } +
+
+
+
+
+ ); +}; + +export default SearchSchools; diff --git a/src/constants/manageResume.ts b/src/constants/manageResume.ts index 1540f62c..e271901a 100644 --- a/src/constants/manageResume.ts +++ b/src/constants/manageResume.ts @@ -1,4 +1,6 @@ +import { School } from "@/types/api/document"; import { ResumeDetailItemType } from "@/types/postApply/resumeDetailItem"; +import { GetEducationType } from "@/types/postResume/postEducation"; export const enum ManageResumeType { VISA = 'VISA', @@ -9,6 +11,8 @@ export const enum ManageResumeType { LANGUAGE = 'Language', } +export const EducationLevels = ['BACHELOR','ASSOCIATE', 'HIGHSCHOOL']; + // 더미데이터 - TODO : 연결 후 삭제 export const ResumeData: ResumeDetailItemType = { profile_img_url: @@ -83,4 +87,47 @@ export const ResumeData: ResumeDetailItemType = { }, ], }, +}; + +export const SearchSchollsList: School[] = [ + { id: 1, name: 'University of Oxford', phone_number: '000-0000' }, + { + id: 2, + name: 'National University of Lesotho International School', + phone_number: '000-0000', + }, + { id: 3, name: 'University of Chester CE Academy', phone_number: '000-0000' }, + { + id: 4, + name: 'University of Chester Academy Northwich', + phone_number: '000-0000', + }, + { id: 5, name: 'University of Birmingham School', phone_number: '000-0000' }, + { id: 6, name: 'University of Oxford', phone_number: '000-0000' }, + { + id: 7, + name: 'National University of Lesotho International School', + phone_number: '000-0000', + }, + { id: 8, name: 'University of Chester CE Academy', phone_number: '000-0000' }, + { + id: 9, + name: 'University of Chester Academy Northwich', + phone_number: '000-0000', + }, + { id: 10, name: 'University of Birmingham School', phone_number: '000-0000' }, +]; + +export const GetEducationData: GetEducationType = { + education_level: 'BACHELOR', // Enum(BACHELOR, ASSOCIATE, HIGHSCHOOL), + school:{ + id: 1, + name: 'University of Chester Academy Northwich', + phone_number: '000-0000' + }, + major: 'Department of Computer Engineering', + gpa: 3.5, + start_date: '2021-03-01', // yyyy-MM-dd + end_date: '2026-03-01', // yyyy-MM-dd + grade: 4, }; \ No newline at end of file diff --git a/src/hooks/api/useResume.ts b/src/hooks/api/useResume.ts index 5dd0ba4f..59de581e 100644 --- a/src/hooks/api/useResume.ts +++ b/src/hooks/api/useResume.ts @@ -1,4 +1,4 @@ -import { deleteEducation, deleteEtcLanguageLevel, deleteIntroduction, deleteWorkExperience, getEducation, getLanguagesSummaries, getResume, getWorkExperience, patchIntroduction, patchWorkExperience, postEducation, postEtcLanguageLevel, postWorkExperience } from "@/api/resumes"; +import { deleteEducation, deleteEtcLanguageLevel, deleteIntroduction, deleteWorkExperience, getEducation, getLanguagesSummaries, getResume, getSearchSchools, getWorkExperience, patchIntroduction, patchWorkExperience, postEducation, postEtcLanguageLevel, postWorkExperience } from "@/api/resumes"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; @@ -30,9 +30,6 @@ export const useDeleteIntroduction = () => { export const useDeleteWorkExperience = () => { return useMutation({ mutationFn: deleteWorkExperience, - onSuccess: () => { - console.log('경력 삭제 성공'); - }, onError: (error) => { console.error('경력 삭제 실패', error); }, @@ -43,9 +40,6 @@ export const useDeleteWorkExperience = () => { export const useDeleteEducation = () => { return useMutation({ mutationFn: deleteEducation, - onSuccess: () => { - console.log('학력 삭제 성공'); - }, onError: (error) => { console.error('학력 삭제 실패', error); }, @@ -166,13 +160,19 @@ export const usePostEtcLanguageLevel = () => { export const useDeleteEtcLanguageLevel = () => { return useMutation({ mutationFn: deleteEtcLanguageLevel, - onSuccess: () => { - console.log('ETC 삭제 성공'); - }, onError: (error) => { console.error('ETC 삭제 실패', error); }, }); }; -// TODO: ETC 수정하기 추가 \ No newline at end of file +// TODO: ETC 수정하기 추가 + +// 9.1 (유학생) 학교 검색하기 +export const useGetSearchSchools = (search: string, page: number, size: number) => { + return useQuery({ + queryKey: ['searchSchools', search, page, size], + queryFn: () => getSearchSchools({ search, page: page.toString(), size: size.toString() }), + enabled: !!search, // 검색어가 있을 때만 쿼리 활성화 + }); +}; \ No newline at end of file diff --git a/src/pages/Introduction/IntroductionPage.tsx b/src/pages/Introduction/IntroductionPage.tsx index a4e0b70b..e3ae896b 100644 --- a/src/pages/Introduction/IntroductionPage.tsx +++ b/src/pages/Introduction/IntroductionPage.tsx @@ -33,7 +33,6 @@ const IntroductionPage = () => { const handleSubmit = () => { // TODO: API - 7.8 (유학생) 자기소개 수정하기 - // console.log('introduction : ' + data); navigate('/profile/manage-resume'); }; diff --git a/src/pages/Profile/EditProfilePage.tsx b/src/pages/Profile/EditProfilePage.tsx index 4d1ad275..db7c276d 100644 --- a/src/pages/Profile/EditProfilePage.tsx +++ b/src/pages/Profile/EditProfilePage.tsx @@ -54,7 +54,6 @@ const EditProfilePage = () => { profileImage, Boolean(profileImage), // 이미지 변경 여부 확인 ); - // console.log('transformedData: ', transformedData); try { const formData = new FormData(); @@ -70,14 +69,6 @@ const EditProfilePage = () => { type: 'application/json', }), ); - /* - const response = await axios.patch('/api/v1/users', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - console.log('API 성공:', response.data); - */ navigate('/profile'); } catch (error) { console.error('API 호출 중 에러 발생:', error); @@ -117,7 +108,7 @@ const EditProfilePage = () => { hasMenuButton={false} title="Edit Profile" /> -
+
{
-
+
+ + ) : ( +
로딩 중
+ )} + + ); }; export default PatchEducationPage; diff --git a/src/pages/SetEducation/PostEducationPage.tsx b/src/pages/SetEducation/PostEducationPage.tsx index 4f044347..854a2f40 100644 --- a/src/pages/SetEducation/PostEducationPage.tsx +++ b/src/pages/SetEducation/PostEducationPage.tsx @@ -1,5 +1,66 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import BaseHeader from '@/components/Common/Header/BaseHeader'; +import Button from '@/components/Common/Button'; +import EducationPost from '@/components/SetEducation/EducationPost'; +import { buttonTypeKeys } from '@/constants/components'; +import useNavigateBack from '@/hooks/useNavigateBack'; +import { InitailEducationType } from '@/types/postResume/postEducation'; +import { isPostEducationType } from '@/utils/introduction'; + +// input 기본값 설정 +const InitailEducation = (): InitailEducationType => ({ + education_level: '', + school_id: 0, + major: '', + gpa: 0.0, + start_date: '', + end_date: '', + grade: 0, +}); + const PostEducationPage = () => { - return
PostEducationPage
; + const handleBackButtonClick = useNavigateBack(); + const navigate = useNavigate(); + const [educationData, setEducationData] = + useState(InitailEducation()); + const [isValid, setIsValid] = useState(false); + + const handleSubmit = () => { + // TODO: API - 7.6 학력 생성하기 + navigate('/profile/manage-resume'); + }; + + useEffect(() => { + setIsValid(isPostEducationType(educationData)); + }, [educationData]); + + return ( + <> +
+ + +
+
+
+ + ); }; export default PostEducationPage; diff --git a/src/pages/SetWorkExperience/PatchWorkExperiencePage.tsx b/src/pages/SetWorkExperience/PatchWorkExperiencePage.tsx index 2a88196a..92d67a28 100644 --- a/src/pages/SetWorkExperience/PatchWorkExperiencePage.tsx +++ b/src/pages/SetWorkExperience/PatchWorkExperiencePage.tsx @@ -70,18 +70,20 @@ const PatchWorkExperiencePage = () => { return (
- - {/* input 영역 */} - -
+
+ + {/* input 영역 */} + +
+
{/* 리셋 버튼 */}