Skip to content

Commit

Permalink
Merge pull request #120 from Team-inglo/fix/105-고용주유학생서류작성
Browse files Browse the repository at this point in the history
🐛 fix: 공고 수정 기존 내용 자동 입력, 데이터 매핑 #105
  • Loading branch information
MrMirror21 authored Nov 5, 2024
2 parents e8ec2dc + 2fcada0 commit a53b3cf
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 48 deletions.
1 change: 0 additions & 1 deletion src/components/Employer/PostCreate/Step1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ const Step1 = ({
<div
key={index}
className="flex-shrink-0"
style={{ width: '124px' }}
>
<div className="w-full h-6 flex items-center justify-center px-3 py-1 bg-[#FEF387] button-2 rounded-[1.125rem] whitespace-nowrap">
{workDayTimeToString(workdaytime)}
Expand Down
23 changes: 5 additions & 18 deletions src/components/Employer/PostCreate/Step2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => {
Expand All @@ -77,6 +60,10 @@ const Step2 = ({
[searchAddress],
);

useEffect(() => {
if(addressInput !== '') handleAddressSearch(addressInput);
}, []);

// 검색 결과 중 원하는 주소를 선택할 시 state에 입력
const handleAddressSelect = (selectedAddressName: string) => {
// 사용자가 선택한 주소와 일치하는 결과를 검색 결과를 저장하는 array에서 탐색
Expand Down
54 changes: 48 additions & 6 deletions src/components/Employer/PostCreate/Step4.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<JobPostingForm>(postInfo);
const [newPostInfo, setNewPostInfo] = useState<JobPostingForm>({
images: [],
body: { ...postInfo.body },
});
const [storedImageUrls, setStoredImageUrls] = useState<Image[]>(
isEdit ? (postInfo.images as Image[]) : [],
);
// 세 부분으로 나누어 입력받는 방식을 위해 전화번호만 별도의 state로 분리, 추후 유효성 검사 단에서 통합
const [phoneNum, setPhoneNum] = useState({
start: newPostInfo.body.recruiter_phone_number
Expand All @@ -51,15 +59,15 @@ 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]);

// 이미지 선택
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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 });
}
Expand Down Expand Up @@ -145,20 +153,54 @@ const Step4 = ({
/>
</label>
)}
{storedImageUrls[0] &&
storedImageUrls.map((image, idx) => (
<div className="w-[7.5rem] h-[7.5rem] relative rounded-lg flex flex-row items-center justify-center bg-no-repeat bg-top text-left text-gray-400">
<div className="w-[7.5rem] h-[7.5rem] flex items-center justify-center rounded-lg">
<img
src={String(image.img_url)}
className="w-[7.5rem] h-[7.5rem] rounded-lg object-cover"
/>
<div
className="absolute top-[0.625rem] right-[0.625rem]"
onClick={() => {
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,
},
});
}}
>
<CircleDeleteIcon />
</div>
</div>
</div>
))}
{newPostInfo.images[0] &&
newPostInfo.images.map((image, idx) => (
<div className="w-[7.5rem] h-[7.5rem] relative rounded-lg flex flex-row items-center justify-center bg-no-repeat bg-top text-left text-gray-400">
<div className="w-[7.5rem] h-[7.5rem] flex items-center justify-center rounded-lg">
<img
src={URL.createObjectURL(image)}
src={URL.createObjectURL(image as File)}
className="w-[7.5rem] h-[7.5rem] rounded-lg object-cover"
/>
<div
className="absolute top-[0.625rem] right-[0.625rem]"
onClick={() => {
setNewPostInfo((prev) => ({
...prev,
images: prev.images.filter((_, i) => i !== idx),
images: prev.images.filter(
(_, i) => i !== idx,
) as File[],
}));
}}
>
Expand Down
35 changes: 28 additions & 7 deletions src/components/Employer/PostCreate/Step5.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/api/usePost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
83 changes: 71 additions & 12 deletions src/pages/Employer/Post/EmployerEditPostPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<JobPostingForm>(
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으로 이동한다.
Expand All @@ -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 (
<div>
<BaseHeader hasBackButton onClickBackButton={() => navigate(-1)} hasMenuButton={false} title="공고등록" />
<BaseHeader
hasBackButton
onClickBackButton={() => navigate(-1)}
hasMenuButton={false}
title="공고등록"
/>
{devIsModal ? (
<CompleteModal
title="공고 등록이 완료되었습니다."
title="공고 수정이 완료되었습니다."
onNext={() => {}} // 생성한 공고에 대한 공고 상세 페이지로 이동 요망
/>
) : (
<>
<div className="w-full flex flex-row p-6 items-center justify-between">
<div className="relative w-full flex items-center justify-start title-1 text-[#1e1926] text-left">
{isEdit ? "공고수정" : "공고등록"}
{isEdit ? '공고수정' : '공고등록'}
</div>
<StepIndicator
length={5}
Expand All @@ -60,32 +110,41 @@ const EmployerEditPostPage = () => {
/>
</div>
<div className="w-full flex justify-center px-6">
{currentStep === 1 && (
<Step1 postInfo={postInfo} onNext={handleNext} />
{!isPending && isDataMapped && currentStep === 1 && (
<Step1
key={data?.data.id} // 또는 다른 유니크한 값
postInfo={postInfo}
onNext={handleNext}
/>
)}
{currentStep === 2 && (
{!isPending && isDataMapped && currentStep === 2 && (
<Step2
key={`${data?.data.id}2`} // 또는 다른 유니크한 값
postInfo={postInfo}
onNext={handleNext}
onPrev={() => setCurrentStep((prev) => prev - 1)}
/>
)}
{currentStep === 3 && (
{!isPending && isDataMapped && currentStep === 3 && (
<Step3
key={`${data?.data.id}3`} // 또는 다른 유니크한 값
postInfo={postInfo}
onNext={handleNext}
onPrev={() => setCurrentStep((prev) => prev - 1)}
/>
)}
{currentStep === 4 && (
{!isPending && isDataMapped && currentStep === 4 && (
<Step4
key={`${data?.data.id}4`} // 또는 다른 유니크한 값
postInfo={postInfo}
onNext={handleNext}
onPrev={() => setCurrentStep((prev) => prev - 1)}
isEdit={isEdit}
/>
)}
{currentStep === 5 && (
{!isPending && isDataMapped && currentStep === 5 && (
<Step5
key={`${data?.data.id}5`} // 또는 다른 유니크한 값
postInfo={postInfo}
onNext={handleNext}
onSubmit={(newPost) => handleSubmit(newPost)}
Expand Down
Loading

0 comments on commit a53b3cf

Please sign in to comment.