From bd73ee33221d5f3439156fd4aa8cd8cd210539bb Mon Sep 17 00:00:00 2001 From: Savien/Woo Jun Han <49388937+MrMirror21@users.noreply.github.com> Date: Wed, 6 Nov 2024 06:10:15 +0900 Subject: [PATCH] =?UTF-8?q?:bug:=20fix:=20=EA=B3=B5=EA=B3=A0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EC=A1=B4=20=EB=82=B4=EC=9A=A9=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EC=9E=85=EB=A0=A5,=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=EB=A7=A4=ED=95=91=20#105?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Employer/PostCreate/Step1.tsx | 1 - src/components/Employer/PostCreate/Step2.tsx | 23 ++--- src/components/Employer/PostCreate/Step4.tsx | 54 ++++++++++-- src/components/Employer/PostCreate/Step5.tsx | 35 ++++++-- src/hooks/api/usePost.ts | 6 +- .../Employer/Post/EmployerEditPostPage.tsx | 83 ++++++++++++++++--- src/types/postCreate/postCreate.ts | 8 +- 7 files changed, 162 insertions(+), 48 deletions(-) diff --git a/src/components/Employer/PostCreate/Step1.tsx b/src/components/Employer/PostCreate/Step1.tsx index ac676571..d71f214c 100644 --- a/src/components/Employer/PostCreate/Step1.tsx +++ b/src/components/Employer/PostCreate/Step1.tsx @@ -107,7 +107,6 @@ const Step1 = ({
{workDayTimeToString(workdaytime)} diff --git a/src/components/Employer/PostCreate/Step2.tsx b/src/components/Employer/PostCreate/Step2.tsx index 322483e5..2c554d7b 100644 --- a/src/components/Employer/PostCreate/Step2.tsx +++ b/src/components/Employer/PostCreate/Step2.tsx @@ -3,7 +3,7 @@ import Button from '@/components/Common/Button'; import Dropdown, { DropdownModal } from '@/components/Common/Dropdown'; import Input from '@/components/Common/Input'; import InputLayout from '@/components/WorkExperience/InputLayout'; -import { useGetGeoInfo, useSearchAddress } from '@/hooks/api/useKaKaoMap'; +import { useSearchAddress } from '@/hooks/api/useKaKaoMap'; import { AddressType, Document } from '@/types/api/map'; import { InputType } from '@/types/common/input'; import { JobPostingForm } from '@/types/postCreate/postCreate'; @@ -43,27 +43,10 @@ const Step2 = ({ lon: 0, }); - // 현재 좌표 기준 주소 획득 - const { data, isSuccess } = useGetGeoInfo(setCurrentGeoInfo); // 키워드로 주소 검색 const { searchAddress } = useSearchAddress({ onSuccess: (data) => setAddressSearchResult(data), }); - - // 첫 로딩 시 현재 사용자의 위치 파악 해 지도에 표기 - useEffect(() => { - setNewPostInfo({ - ...newPostInfo, - body: { - ...newPostInfo.body, - address: { - ...newPostInfo.body.address, - address_name: String(data?.address.address_name), - }, - }, - }); - }, [isSuccess]); - // 검색할 주소 입력 시 실시간 검색 const handleAddressSearch = useCallback( (address: string) => { @@ -77,6 +60,10 @@ const Step2 = ({ [searchAddress], ); + useEffect(() => { + if(addressInput !== '') handleAddressSearch(addressInput); + }, []); + // 검색 결과 중 원하는 주소를 선택할 시 state에 입력 const handleAddressSelect = (selectedAddressName: string) => { // 사용자가 선택한 주소와 일치하는 결과를 검색 결과를 저장하는 array에서 탐색 diff --git a/src/components/Employer/PostCreate/Step4.tsx b/src/components/Employer/PostCreate/Step4.tsx index a580fe7c..09f6819b 100644 --- a/src/components/Employer/PostCreate/Step4.tsx +++ b/src/components/Employer/PostCreate/Step4.tsx @@ -6,7 +6,7 @@ import InputLayout from '@/components/WorkExperience/InputLayout'; import { buttonTypeKeys } from '@/constants/components'; import { phone } from '@/constants/information'; import { InputType } from '@/types/common/input'; -import { JobPostingForm } from '@/types/postCreate/postCreate'; +import { Image, JobPostingForm } from '@/types/postCreate/postCreate'; import { useEffect, useState } from 'react'; import AddFileIcon from '@/assets/icons/FileAddIcon.svg?react'; import { @@ -20,13 +20,21 @@ const Step4 = ({ postInfo, onNext, onPrev, + isEdit, }: { postInfo: JobPostingForm; onNext: (postInfo: JobPostingForm) => void; onPrev: () => void; + isEdit?: boolean; }) => { // 현재 step내에서 입력받는 정보를 따로 관리할 state, 추후 다음 step으로 넘어갈 때 funnel 관리 페이지의 state로 통합된다. - const [newPostInfo, setNewPostInfo] = useState(postInfo); + const [newPostInfo, setNewPostInfo] = useState({ + images: [], + body: { ...postInfo.body }, + }); + const [storedImageUrls, setStoredImageUrls] = useState( + isEdit ? (postInfo.images as Image[]) : [], + ); // 세 부분으로 나누어 입력받는 방식을 위해 전화번호만 별도의 state로 분리, 추후 유효성 검사 단에서 통합 const [phoneNum, setPhoneNum] = useState({ start: newPostInfo.body.recruiter_phone_number @@ -51,7 +59,7 @@ const Step4 = ({ recruiter_name !== '' && basicEmailRegex.test(recruiter_email) && isValidPhoneNumber(phoneNum) && - newPostInfo.images.length > 0; + newPostInfo.images.length > 0 || storedImageUrls.length > 0; setIsInvalid(!isFormValid); }, [newPostInfo, phoneNum]); @@ -59,7 +67,7 @@ const Step4 = ({ const handleImageChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { - const newFiles = newPostInfo.images; + const newFiles = newPostInfo.images as File[]; newFiles.push(file); setNewPostInfo({ ...newPostInfo, images: newFiles }); } @@ -145,12 +153,44 @@ const Step4 = ({ /> )} + {storedImageUrls[0] && + storedImageUrls.map((image, idx) => ( +
+
+ +
{ + const deletedImgs = newPostInfo.body.deleted_img_ids + ? newPostInfo.body.deleted_img_ids + : []; + deletedImgs.push(Number(image.id)); + setStoredImageUrls((prev) => + prev.filter((_, i) => i !== idx), + ); + setNewPostInfo({ + ...newPostInfo, + body: { + ...newPostInfo.body, + deleted_img_ids: deletedImgs, + }, + }); + }} + > + +
+
+
+ ))} {newPostInfo.images[0] && newPostInfo.images.map((image, idx) => (
{ setNewPostInfo((prev) => ({ ...prev, - images: prev.images.filter((_, i) => i !== idx), + images: prev.images.filter( + (_, i) => i !== idx, + ) as File[], })); }} > diff --git a/src/components/Employer/PostCreate/Step5.tsx b/src/components/Employer/PostCreate/Step5.tsx index 48229c90..1078c8b5 100644 --- a/src/components/Employer/PostCreate/Step5.tsx +++ b/src/components/Employer/PostCreate/Step5.tsx @@ -45,17 +45,38 @@ const Step5 = ({ const handleSubmit = () => { if (isInvalid) return; + const updatedWorkDayTimes = newPostInfo.body.work_day_times.map( + (workday) => ({ + ...workday, + work_start_time: + workday.work_start_time === '협의가능' + ? null + : workday.work_start_time, + work_end_time: + workday.work_end_time === '협의가능' ? null : workday.work_end_time, + }), + ); + const formData = new FormData(); - newPostInfo.images.forEach((image) => { - formData.append('image', image); - }); + newPostInfo.images + .filter((image): image is File => image instanceof File) + .forEach((image) => { + formData.append('image', image as File); + }); formData.append( 'body', - new Blob([JSON.stringify(newPostInfo.body)], { - type: 'application/json', - }), + new Blob( + [ + JSON.stringify({ + ...newPostInfo.body, + work_day_times: updatedWorkDayTimes, + }), + ], + { + type: 'application/json', + }, + ), ); - onNext({ ...postInfo, body: { diff --git a/src/hooks/api/usePost.ts b/src/hooks/api/usePost.ts index 20b4243e..b961598d 100644 --- a/src/hooks/api/usePost.ts +++ b/src/hooks/api/usePost.ts @@ -191,16 +191,16 @@ export const useCreatePost = (updateCurrentPostId: (id: number) => void) => { }; // 4.10 (고용주) 공고 수정하기 훅 -export const useEditPost = () => { +export const useEditPost = (id: number) => { const navigate = useNavigate(); return useMutation({ mutationFn: editPost, - onSuccess: (response) => { + onSuccess: () => { // response는 API 응답 데이터 // { success: true, data: { id: Long }, error: null } setTimeout(() => { - navigate(`/employer/post/${response.data.id}`); + navigate(`/employer/post/${id}`); }, 1000); }, onError: (error) => { diff --git a/src/pages/Employer/Post/EmployerEditPostPage.tsx b/src/pages/Employer/Post/EmployerEditPostPage.tsx index ee55c0f6..e9605a81 100644 --- a/src/pages/Employer/Post/EmployerEditPostPage.tsx +++ b/src/pages/Employer/Post/EmployerEditPostPage.tsx @@ -6,25 +6,30 @@ import Step3 from '@/components/Employer/PostCreate/Step3'; import Step4 from '@/components/Employer/PostCreate/Step4'; import Step5 from '@/components/Employer/PostCreate/Step5'; import StepIndicator from '@/components/Information/StepIndicator'; -import { useEditPost } from '@/hooks/api/usePost'; +import { useEditPost, useGetPostDetail } from '@/hooks/api/usePost'; +import { useCurrentPostIdStore } from '@/store/url'; import { initialJobPostingState, JobPostingForm, } from '@/types/postCreate/postCreate'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useLocation, useParams, useNavigate } from 'react-router-dom'; const EmployerEditPostPage = () => { const location = useLocation(); const { isEdit } = location.state || {}; const { id } = useParams(); + const { currentPostId } = useCurrentPostIdStore(); + console.log(currentPostId) const [currentStep, setCurrentStep] = useState(1); const [postInfo, setPostInfo] = useState( initialJobPostingState, ); - const { mutate: editPost } = useEditPost(); // 공고 수정 시 호출하는 훅 + const { data, isPending } = useGetPostDetail(Number(id), true); + const { mutate: editPost } = useEditPost(Number(currentPostId)); // 공고 수정 시 호출하는 훅 const [devIsModal, setDevIsModal] = useState(false); + const [isDataMapped, setIsDataMapped] = useState(false); const navigate = useNavigate(); // 다음 step으로 넘어갈 때 호출되며, 각 step에서 입력한 정보를 userInfo에 저장, 다음 step으로 이동한다. @@ -39,19 +44,64 @@ const EmployerEditPostPage = () => { setDevIsModal(true); } }; + + const mapServerDataToFormData = ( + serverData: typeof data.data, + ): JobPostingForm => ({ + images: serverData.company_img_url_list, + body: { + title: serverData.title, + job_category: serverData.tags.job_category, + work_day_times: serverData.working_conditions.work_day_times, + work_period: serverData.working_conditions.work_period, + hourly_rate: serverData.working_conditions.hourly_rate, + employment_type: serverData.working_conditions.employment_type, + address: {...postInfo.body.address, address_name: serverData.company_information.company_address}, + recruitment_dead_line: serverData.recruitment_conditions.recruitment_deadline, + recruitment_number: serverData.recruitment_conditions.number_of_recruits, + gender: serverData.recruitment_conditions.gender, + age_restriction: initialJobPostingState.body.age_restriction, + education_level: serverData.recruitment_conditions.education, + visa: serverData.recruitment_conditions.visa, + recruiter_name: serverData.company_information.recruiter, + recruiter_email: serverData.company_information.email, + recruiter_phone_number: serverData.company_information.contact, + description: serverData.detailed_overview, + preferred_conditions: + serverData.recruitment_conditions.preferred_conditions, + }, + }); + useEffect(() => { + if (data?.data) { + try { + const mappedData = mapServerDataToFormData(data.data); + setPostInfo(mappedData); + setIsDataMapped(true); + console.log('데이터 매핑 완료:', mappedData); + } catch (error) { + console.error('데이터 매핑 중 오류 발생:', error); + setIsDataMapped(false); + } + } + }, [data?.data]); return (
- navigate(-1)} hasMenuButton={false} title="공고등록" /> + navigate(-1)} + hasMenuButton={false} + title="공고등록" + /> {devIsModal ? ( {}} // 생성한 공고에 대한 공고 상세 페이지로 이동 요망 /> ) : ( <>
- {isEdit ? "공고수정" : "공고등록"} + {isEdit ? '공고수정' : '공고등록'}
{ />
- {currentStep === 1 && ( - + {!isPending && isDataMapped && currentStep === 1 && ( + )} - {currentStep === 2 && ( + {!isPending && isDataMapped && currentStep === 2 && ( setCurrentStep((prev) => prev - 1)} /> )} - {currentStep === 3 && ( + {!isPending && isDataMapped && currentStep === 3 && ( setCurrentStep((prev) => prev - 1)} /> )} - {currentStep === 4 && ( + {!isPending && isDataMapped && currentStep === 4 && ( setCurrentStep((prev) => prev - 1)} + isEdit={isEdit} /> )} - {currentStep === 5 && ( + {!isPending && isDataMapped && currentStep === 5 && ( handleSubmit(newPost)} diff --git a/src/types/postCreate/postCreate.ts b/src/types/postCreate/postCreate.ts index 585678c1..692a1bd4 100644 --- a/src/types/postCreate/postCreate.ts +++ b/src/types/postCreate/postCreate.ts @@ -67,8 +67,13 @@ export type Address = { latitude: number; }; +export type Image = { + id: number, + img_url: string; +} + export type JobPostingForm = { - images: File[]; + images: File[] | Image[]; body: { title: string; job_category: JobCategory | string; @@ -88,6 +93,7 @@ export type JobPostingForm = { recruiter_phone_number: string; description: string; preferred_conditions: string; + deleted_img_ids?: number[]; }; };