From a7ade439aa38f68bd3b9134e47e32af29a469205 Mon Sep 17 00:00:00 2001 From: minseo Date: Tue, 12 Nov 2024 10:18:43 +0900 Subject: [PATCH] =?UTF-8?q?style:=20Posting=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EA=B0=80=EB=8F=85=EC=84=B1=20=EB=86=92=EC=9D=B4=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=ED=95=A8=EC=88=98=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?-=20Payload=20=EC=83=9D=EC=84=B1,=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D,=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=20=EB=B6=84=EB=A6=AC,=20=EC=9D=BC=ED=95=98=EB=8A=94?= =?UTF-8?q?=20=EC=9A=94=EC=9D=BC=20convertDays=ED=95=A8=EC=88=98=EB=A5=BC?= =?UTF-8?q?=20/util/postin/=20=ED=95=98=EC=9C=84=EC=97=90=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?-=20phoneInput=EC=9D=98=20=EC=B2=B4=ED=81=AC=EB=B0=95=EC=8A=A4?= =?UTF-8?q?=20=EA=B0=92=EC=9D=B4=20=EC=9D=BD=EC=96=B4=EC=A7=80=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref : #18 --- src/api/postingAPI.js | 7 +- src/components/posting/PhoneInput.jsx | 23 ++- src/pages/recruitment/Posting.jsx | 283 ++++++++------------------ src/utils/posting/formatHelper.js | 18 ++ src/utils/posting/payloadHelper.js | 35 ++++ src/utils/posting/validationHelper.js | 28 +++ 6 files changed, 189 insertions(+), 205 deletions(-) create mode 100644 src/utils/posting/formatHelper.js create mode 100644 src/utils/posting/payloadHelper.js create mode 100644 src/utils/posting/validationHelper.js diff --git a/src/api/postingAPI.js b/src/api/postingAPI.js index 5a42f91..96bf21f 100644 --- a/src/api/postingAPI.js +++ b/src/api/postingAPI.js @@ -14,7 +14,7 @@ export const postJobPosting = async (accessToken, payload) => { if (result.status === 200) { response.isSuccess = true; response.message = result.data.message; - response.data = result.data.data; // 필요에 따라 데이터 할당 + response.data = result.data.data; } } catch (err) { response.isSuccess = false; @@ -22,7 +22,6 @@ export const postJobPosting = async (accessToken, payload) => { if (err.response?.status === 401) { alert("인증이 필요합니다. 다시 로그인해주세요."); - // 여기서 로그아웃 및 재로그인 프로세스를 트리거할 수 있음 } } @@ -40,8 +39,8 @@ export const updateJobPosting = async (accessToken, postId, postData) => { try { const body = { - postId, // 백엔드에서 postId가 필요하지 않다고 명시했으나, 요청 형식에 포함 - userId: 0, // 사용하지 않음 + postId, + userId, storeName: "", // 수정 불가 workPlaceAddress: "", // 수정 불가 postData, // 수정 가능한 postData만 포함 diff --git a/src/components/posting/PhoneInput.jsx b/src/components/posting/PhoneInput.jsx index 4cdd7f4..48291f6 100644 --- a/src/components/posting/PhoneInput.jsx +++ b/src/components/posting/PhoneInput.jsx @@ -19,15 +19,26 @@ const PhoneInput = ({ label, onChange, onValidityChange }) => { setPhone(value); const valid = /^\d{3}-\d{4}-\d{4}$/.test(value); // 올바른 형식인지 확인 - setIsValid(valid); - onValidityChange && onValidityChange(valid); // 유효성 상태 부모 전달 - onChange && onChange({ phone: value, noCalls }); - }; - + setIsValid(valid); + onValidityChange && onValidityChange(valid); + + if (onChange) { + setTimeout(() => { + onChange({ phone: value, noCalls }); + }, 0); + } + }; + const handleCheckboxChange = () => { setNoCalls((prev) => { const updatedNoCalls = !prev; - onChange && onChange({ phone, noCalls: updatedNoCalls }); + + if (onChange) { + setTimeout(() => { + onChange({ phone, noCalls: updatedNoCalls }); + }, 0); + } + return updatedNoCalls; }); }; diff --git a/src/pages/recruitment/Posting.jsx b/src/pages/recruitment/Posting.jsx index 874d43c..9649e28 100644 --- a/src/pages/recruitment/Posting.jsx +++ b/src/pages/recruitment/Posting.jsx @@ -1,72 +1,26 @@ import React, { useState } from "react"; -import { PageContainer, ContentContainer, FixedButtonContainer} from "../../styles/posting/PostingStyles"; -import {InputField,Tag,Toggle,WeekdayPicker,WorkTimePicker,PayPicker,AddressInput,PhotoUpload,DescriptionInput,PhoneInput,Button} from "../../components"; +import { PageContainer, ContentContainer, FixedButtonContainer } from "../../styles/posting/PostingStyles"; +import { InputField, Tag, Toggle, WeekdayPicker, WorkTimePicker, PayPicker, AddressInput, PhotoUpload, DescriptionInput, PhoneInput, Button } from "../../components"; import "../../styles/posting/Posting.css"; import { POSTING_UPMU_TAG } from "../../constants"; -import { postJobPosting, updateJobPosting } from "../../api"; // 수정 +import { postJobPosting, updateJobPosting } from "../../api"; import { useNavigate, useLocation } from "react-router-dom"; import { useSelector } from "react-redux"; import getAccessToken from "../../utils/getAccessToken"; // 일반 함수로 가져오기 +import { createPayload } from "../../utils/posting/payloadHelper"; // 분리된 payload 생성 함수 +import { validateForm } from "../../utils/posting/validationHelper"; // 분리된 유효성 검증 함수 +import { parseAddress, convertDays } from "../../utils/posting/formatHelper"; // 분리된 주소 및 요일 변환 함수 const Posting = () => { - const state = useSelector((state) => state); // 최상단에서 상태 가져오기 - const accessToken = getAccessToken(state); // 상태를 함수로 전달 + const state = useSelector((state) => state); + const accessToken = getAccessToken(state); const [isOptionSelected, setIsOptionSelected] = useState(false); - // 토글 클릭 시 하위 컴포넌트 활성화 시키도록 설정해둠 - const handleToggleClick = (value) => { - handleChange("selectedOption", value); - setIsOptionSelected(true); - }; - - // 값의 길이에 대한 유효성 검증하여 제출을 막아둠 const [validStates, setValidStates] = useState({ title: true, description: true, applyNumber: true, }); - const handleChange = (key, value) => { - setFormData((prev) => ({ - ...prev, - [key]: value, - })); - }; - - const handleValidityChange = (key, isValid) => { - setValidStates((prev) => ({ - ...prev, - [key]: isValid, - })); - }; - - const handlePhoneInputChange = ({ phone, noCalls }) => { - handleChange("applyNumber", phone); - handleChange("isNumberPublic", !noCalls); - }; - - const parseAddress = (address) => { - const [doName = "", siName = "", detailName = ""] = address.split(" "); - return { doName, siName, detailName }; - }; - - const convertDays = (days) => { - const dayMap = { - 월: "MONDAY", - 화: "TUESDAY", - 수: "WEDNESDAY", - 목: "THURSDAY", - 금: "FRIDAY", - 토: "SATURDAY", - 일: "SUNDAY", - }; - return days.map((day) => dayMap[day]).join(", "); - }; - const location = useLocation(); - const navigate = useNavigate(); - - const mode = location.state?.mode || "create"; // default "create" - const postId = location.state?.postId || null; - const userId = useSelector((state) => state.userInfo.userId); const [formData, setFormData] = useState({ title: "", workTags: [], @@ -79,115 +33,65 @@ const Posting = () => { applyNumber: "", isNumberPublic: true, description: "", - workPeriod: "1개월 이상" + workPeriod: "1개월 이상" }); - const createPayload = () => { - const { doName, siName, detailName } = parseAddress(formData.workLocation); - const workDays = convertDays(formData.workDays); - const [startHour, startMinute] = formData.workTime.start.split(":").map(Number); - const [endHour, endMinute] = formData.workTime.end.split(":").map(Number); - - return { - postId: postId || 0, // 수정 시 기존 postId 사용 - userId, - storeName: formData.storeName, - workPlaceAddress: formData.workLocation, - postData: { - doName, - siName, - detailName, - workType: formData.workTags[0] || "기타", - title: formData.title, - content: formData.description, - pay: parseInt(formData.pay, 10), - workStartHour: startHour, - workStartMinute: startMinute, - workEndHour: endHour, - workEndTimeMinute: endMinute, - isNegotiable: formData.isNegotiable || false, - applyNumber: formData.applyNumber, - workDays, - isShortTermJob: formData.workPeriod === "단기", - payType: formData.payType, - isNumberPublic: formData.isNumberPublic, - imageList: [""], - imageUrlList: [""], // 빈 값 처리 - }, - }; + const location = useLocation(); + const navigate = useNavigate(); + const mode = location.state?.mode || "create"; + const postId = location.state?.postId || null; + const userId = useSelector((state) => state.userInfo.userId); + + const handleChange = (key, value) => { + setFormData((prev) => ({ ...prev, [key]: value })); + }; + + const handleValidityChange = (key, isValid) => { + setValidStates((prev) => ({ ...prev, [key]: isValid })); }; - const validateForm = (payload, formData) => { - const requiredFields = { - "제목": payload.postData.title, - "하는 일": payload.postData.workType, - "일하는 기간": formData.workPeriod, - "일하는 요일": formData.workDays, - "일하는 시간": formData.workTime?.start && formData.workTime?.end, - "급여": formData.pay, - "일하는 장소": formData.workLocation, - "업체명": payload.storeName, - "연락처": payload.postData.applyNumber, - }; + const handlePhoneInputChange = ({ phone, noCalls }) => { + handleChange("applyNumber", phone); + handleChange("isNumberPublic", !noCalls); + }; - for (const [label, value] of Object.entries(requiredFields)) { - if ( - value === undefined || - value === null || - (typeof value === "string" && value.trim() === "") || - (Array.isArray(value) && value.length === 0) || - value === false - ) { - alert(`${label}을(를) 입력해주세요.`); - return false; // 유효성 검사 실패 - } - } - return true; // 유효성 검사 성공 + const handleToggleClick = (value) => { + handleChange("selectedOption", value); + setIsOptionSelected(true); }; const handleSubmit = async () => { - // 모든 입력 필드의 유효성 확인 const allValid = Object.values(validStates).every((isValid) => isValid); if (!allValid) { alert("모든 필드를 올바르게 입력해주세요."); - return; // 유효성 검사 실패 시 즉시 중단 + return; } - - // Payload 생성 - const payload = createPayload(); - console.log("Form Data (사용자 입력):", formData); - console.log("Payload (API로 전송되는 데이터):", payload); - // 유효성 검사 실패 시 이후 코드 중단 + + const payload = createPayload(formData, postId, userId, parseAddress, convertDays); + console.log("Payload:", payload); + if (!validateForm(payload, formData)) { - console.log("Validation failed. Aborting API call."); // 디버깅 로그 - return; // 이후 로직 실행 중단 + console.log("Validation failed. Aborting API call."); + return; } - + try { - // API 호출 로직 - if (mode === "create") { - const response = await postJobPosting(accessToken, payload); - if (response.isSuccess) { - alert("게시글이 성공적으로 등록되었습니다."); - navigate("/home"); - } else { - alert(`등록 실패: ${response.message}`); - } - } else if (mode === "modify") { - const response = await updateJobPosting(accessToken, postId, payload.postData); - if (response.isSuccess) { - alert("게시글이 성공적으로 수정되었습니다."); - navigate("/home"); - } else { - alert(`수정 실패: ${response.message}`); - } + const response = mode === "create" + ? await postJobPosting(accessToken, payload) + : await updateJobPosting(accessToken, postId, payload.postData); + + if (response.isSuccess) { + alert(mode === "create" ? "게시글이 성공적으로 등록되었습니다." : "게시글이 성공적으로 수정되었습니다."); + navigate("/home"); + } else { + alert(`${mode === "create" ? "등록" : "수정"} 실패: ${response.message}`); } } catch (error) { console.error("Error during submission:", error); alert("제출 과정에서 오류가 발생했습니다. 다시 시도해주세요."); } }; - + return ( @@ -196,7 +100,7 @@ const Posting = () => { @@ -204,26 +108,24 @@ const Posting = () => { {isOptionSelected && ( <>
- handleChange("title", value)} - onValidityChange={(isValid) => handleValidityChange("title", isValid)} - /> + handleChange("title", value)} + onValidityChange={(isValid) => handleValidityChange("title", isValid)} + />
-
- handleChange("workTags", tags)} - maxSelectable={1} - /> + handleChange("workTags", tags)} + maxSelectable={1} + />
-
- handleChange("workPeriod", value)} @@ -231,48 +133,40 @@ const Posting = () => { styleType="tag" />
-
- handleChange("workDays", days)} - /> + handleChange("workDays", days)} + />
-
- { - handleChange("workTime", { start: timeData.start, end: timeData.end }); - handleChange("isNegotiable", timeData.isNegotiable); - }} - /> + { + handleChange("workTime", { start: timeData.start, end: timeData.end }); + handleChange("isNegotiable", timeData.isNegotiable); + }} + />
-
- { - handleChange("pay", payData.pay); - handleChange("payType", payData.payType); - }} - /> + { + handleChange("pay", payData.pay); + handleChange("payType", payData.payType); + }} + />
-
-
- - handleChange("description", value)} - onValidityChange={(isValid) => handleValidityChange("description", isValid)} -/> - + handleChange("description", value)} + onValidityChange={(isValid) => handleValidityChange("description", isValid)} + />
-
업체 정보
{ value={formData.workLocation} onChange={(value) => handleChange("workLocation", value)} /> - handleValidityChange("applyNumber", isValid)} + handleValidityChange("applyNumber", isValid)} /> -
)} diff --git a/src/utils/posting/formatHelper.js b/src/utils/posting/formatHelper.js new file mode 100644 index 0000000..2db8ba3 --- /dev/null +++ b/src/utils/posting/formatHelper.js @@ -0,0 +1,18 @@ +// utils/formatHelper.js +export const parseAddress = (address) => { + const [doName = "", siName = "", detailName = ""] = address.split(" "); + return { doName, siName, detailName }; + }; + + export const convertDays = (days) => { + const dayMap = { + 월: "MONDAY", + 화: "TUESDAY", + 수: "WEDNESDAY", + 목: "THURSDAY", + 금: "FRIDAY", + 토: "SATURDAY", + 일: "SUNDAY", + }; + return days.map((day) => dayMap[day]).join(", "); + }; \ No newline at end of file diff --git a/src/utils/posting/payloadHelper.js b/src/utils/posting/payloadHelper.js new file mode 100644 index 0000000..50d6b33 --- /dev/null +++ b/src/utils/posting/payloadHelper.js @@ -0,0 +1,35 @@ +// utils/payloadHelper.js +export const createPayload = (formData, postId, userId, parseAddress, convertDays) => { + const { doName, siName, detailName } = parseAddress(formData.workLocation); + const workDays = convertDays(formData.workDays); + const [startHour, startMinute] = formData.workTime.start.split(":").map(Number); + const [endHour, endMinute] = formData.workTime.end.split(":").map(Number); + + return { + postId: postId || 0, + userId, + storeName: formData.storeName, + workPlaceAddress: formData.workLocation, + postData: { + doName, + siName, + detailName, + workType: formData.workTags[0] || "기타", + title: formData.title, + content: formData.description, + pay: parseInt(formData.pay, 10), + workStartHour: startHour, + workStartMinute: startMinute, + workEndHour: endHour, + workEndTimeMinute: endMinute, + isNegotiable: formData.isNegotiable || false, + applyNumber: formData.applyNumber, + workDays, + isShortTermJob: formData.workPeriod === "단기", + payType: formData.payType, + isNumberPublic: formData.isNumberPublic, + imageList: [""], + imageUrlList: [""], + }, + }; + }; \ No newline at end of file diff --git a/src/utils/posting/validationHelper.js b/src/utils/posting/validationHelper.js new file mode 100644 index 0000000..8005773 --- /dev/null +++ b/src/utils/posting/validationHelper.js @@ -0,0 +1,28 @@ +// utils/validationHelper.js +export const validateForm = (payload, formData) => { + const requiredFields = { + "제목": payload.postData.title, + "하는 일": payload.postData.workType, + "일하는 기간": formData.workPeriod, + "일하는 요일": formData.workDays, + "일하는 시간": formData.workTime?.start && formData.workTime?.end, + "급여": formData.pay, + "일하는 장소": formData.workLocation, + "업체명": payload.storeName, + "연락처": payload.postData.applyNumber, + }; + + for (const [label, value] of Object.entries(requiredFields)) { + if ( + value === undefined || + value === null || + (typeof value === "string" && value.trim() === "") || + (Array.isArray(value) && value.length === 0) || + value === false + ) { + alert(`${label}을(를) 입력해주세요.`); + return false; + } + } + return true; + }; \ No newline at end of file