Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default function Home() {
한 곳에서 관리하는 알바 구인 플랫폼
</p>
{user ? (
<Link href="/albaList">
<Link href="/albalist">
<p className="font-nexon-regular text-black inline-block rounded-lg bg-green-500 px-4 py-2 text-sm sm:px-6 sm:py-3 sm:text-base md:px-8 md:py-4 md:text-lg lg:px-10 lg:py-5 lg:text-xl">
알바 둘러보기
</p>
Expand Down
122 changes: 100 additions & 22 deletions src/app/(pages)/(albaform)/addform/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import RecruitConditionSection from "./section/RecruitConditionSection";
import WorkConditionSection from "./section/WorkConditionSection";
import useEditing from "@/hooks/useEditing";
import { SubmitFormDataType } from "@/types/addform";
import CustomFormModal from "@/app/components/modal/modals/confirm/CustomFormModal";

export default function AddFormPage() {
const router = useRouter();
const formId = useParams().formId;
// 리액트 훅폼에서 관리할 데이터 타입 지정 및 메서드 호출 (상위 컴포넌트 = useForm 사용)
// 리액트 훅폼에서 관리할 데이터 타입 지정 및 메서드 호출 (상위 컴���트 = useForm 사용)
const methods = useForm<SubmitFormDataType>({
mode: "onChange",
defaultValues: {
Expand Down Expand Up @@ -60,23 +61,47 @@ export default function AddFormPage() {
// 폼 제출 리액트쿼리
const mutation = useMutation({
mutationFn: async () => {
// 이미지 필수 체크
if (!imageFiles || imageFiles.length === 0) {
toast.error("이미지를 첨부해주세요.");
throw new Error("이미지는 필수입니다.");
}

// 이미지 업로드 처리
let uploadedUrls: string[] = [];
try {
uploadedUrls = await uploadImages(Array.from(imageFiles));
if (!uploadedUrls.length) {
toast.error("이미지 업로드에 실패했습니다.");
throw new Error("이미지 업로드 실패");
}
setValue("imageUrls", uploadedUrls);
} catch (error) {
console.error("이미지 업로드 중 오류 발생:", error);
toast.error("이미지 업로드 중 오류가 발생했습니다.");
throw error;
}

const excludedKeys = ["displayDate", "workDateRange", "recruitDateRange", "imageFiles"];

// 원하는 필드만 포함된 새로운 객체 만들기
const filteredData = Object.entries(currentValues)
.filter(([key]) => !excludedKeys.includes(key)) // 제외할 키를 필터링
.filter(([key]) => !excludedKeys.includes(key))
.reduce((acc: Partial<SubmitFormDataType>, [key, value]) => {
if (key === "numberOfPositions") {
// numberOfPositions는 숫자형으로 변환
acc[key] = Number(value);
} else if (key === "hourlyWage") {
// hourlyWage는 쉼표를 제거하고 숫자형으로 변환
if (value.includes(",")) acc[key] = Number(value.replaceAll(/,/g, "")); // 쉼표 제거 후 숫자형 변환
// 문자열이면 콤마 제거 후 숫자로 변환
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로직을 수정해주셨네요 👍

acc[key] = typeof value === "string" ? Number(value.replace(/,/g, "")) : Number(value);
} else if (key === "imageUrls") {
// 업로드된 이미지 URL 사용
acc[key] = uploadedUrls;
} else {
acc[key as keyof SubmitFormDataType] = value; // 나머지 값은 그대로 추가
acc[key as keyof SubmitFormDataType] = value;
}
return acc;
}, {});

await axios.post("/api/forms", filteredData);
},
onSuccess: () => {
Expand Down Expand Up @@ -175,21 +200,6 @@ export default function AddFormPage() {

// 폼데이터 임시 저장 함수
const onTempSave = async () => {
// 이미지 처리 로직
if (imageFiles && imageFiles.length > 0) {
try {
const uploadedUrls = await uploadImages(Array.from(imageFiles));
if (uploadedUrls && uploadedUrls.length > 0) {
setValue("imageUrls", [...uploadedUrls]);
} else {
setValue("imageUrls", [...currentValues.imageUrls]);
}
} catch (error) {
console.error("임시저장 - 이미지 업로드 중 오류 발생:", error);
toast.error("이미지 업로드 중 오류가 발생했습니다.");
setValue("imageUrls", []);
}
}
// 임시저장
if (typeof window !== "undefined") {
window.localStorage.setItem("tempAddFormData", JSON.stringify(currentValues));
Expand All @@ -201,6 +211,64 @@ export default function AddFormPage() {
// 각각의 탭 작성중 여부
const { isEditingRecruitContent, isEditingRecruitCondition, isEditingWorkCondition } = useEditing(currentValues);

const [showTempDataModal, setShowTempDataModal] = useState(false);

// 임시저장 데이터 로드 함수
const loadTempData = () => {
const tempData = localStorage.getItem("tempAddFormData");
if (tempData) {
const parsedData: SubmitFormDataType = JSON.parse(tempData);

// 기본 필드들 설정
Object.entries(parsedData).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
setValue(key as keyof SubmitFormDataType, value);
}
});

// 날짜 관련 필드들은 Date 객체로 변환
if (parsedData.recruitmentStartDate && parsedData.recruitmentEndDate) {
setValue("recruitmentStartDate", parsedData.recruitmentStartDate);
setValue("recruitmentEndDate", parsedData.recruitmentEndDate);
}

if (parsedData.workStartDate && parsedData.workEndDate) {
setValue("workStartDate", parsedData.workStartDate);
setValue("workEndDate", parsedData.workEndDate);
}

// 이미지 URL 설정
if (parsedData.imageUrls?.length > 0) {
setValue("imageUrls", parsedData.imageUrls);
}
}
};

// 임시저장 데이터 초기화
const clearTempData = () => {
localStorage.removeItem("tempAddFormData");
};

// 임시저장 데이터 확인 및 모달 표시
useEffect(() => {
const tempData = localStorage.getItem("tempAddFormData");
if (tempData) {
setShowTempDataModal(true);
}
}, []);

// 모달 확인 버튼 핸들러
const handleConfirmTemp = () => {
loadTempData();
setShowTempDataModal(false);
};

// 모달 취소 버튼 핸들러
const handleCancelTemp = () => {
clearTempData();
setShowTempDataModal(false);
};

return (
<FormProvider {...methods}>
<div className="relative pb-10 lg:pb-0">
Expand All @@ -225,7 +293,6 @@ export default function AddFormPage() {
color="orange"
className="h-[58px] border bg-background-100 lg:h-[72px] lg:text-xl lg:leading-8"
onClick={() => onTempSave()}
disabled={!isDirty}
>
임시 저장
</Button>
Expand All @@ -243,6 +310,17 @@ export default function AddFormPage() {
</div>
</aside>
{renderChildren()}
{/* 임시저장 데이터 확인 모달 */}
<CustomFormModal
isOpen={showTempDataModal}
title="임시저장 데이터 확인"
content="임시저장된 데이터가 있습니다. 이어서 작성하시겠습니까?"
confirmText="이어서 작성하기"
cancelText="새로 작성하기"
onConfirm={handleConfirmTemp}
onCancel={handleCancelTemp}
closeOnOverlayClick={false}
/>
</div>
</FormProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ImageInput from "@/app/components/input/file/ImageInput/ImageInput";
import DatePickerInput from "@/app/components/input/dateTimeDaypicker/DatePickerInput";
import { cn } from "@/lib/tailwindUtil";
import { useFormContext } from "react-hook-form";
import { useEffect, useState } from "react";
import { useState } from "react";
import Label from "../../component/Label";

// 알바폼 만들기 - 사장님 - 1-모집내용
Expand All @@ -19,34 +19,9 @@ export default function RecruitContentSection() {
register,
setValue,
getValues,
formState: { errors, isDirty },
formState: { errors },
} = useFormContext();

const currentValue = getValues();

// 이미지 파일 change핸들러
const handleChangeImages = (files: File[]) => {
// 훅폼 데이터에 추가-> 상위 페이지에서 "imageFiles" data를 관리할 수 있음
setValue("imageFiles", files);

// 기존 이미지 리스트와 새로운 이미지를 합침
setInitialImageList((prevList) => [
...prevList,
...files.map((file: File) => ({
file,
url: URL.createObjectURL(file),
id: crypto.randomUUID(),
})),
]);
};

// 컴포넌트가 마운트될 때 이미지 초기값 설정 (초기로딩 제외)
useEffect(() => {
if (currentValue.imageFiles?.length > 0) {
handleChangeImages(currentValue.imageFiles);
}
}, []);

// 날짜 선택
const [recruitmentDateRange, setRecruitmentDateRange] = useState<[Date | null, Date | null]>([null, null]);
const handleRecruitmentDateChange = (dates: [Date | null, Date | null]) => {
Expand All @@ -55,6 +30,20 @@ export default function RecruitContentSection() {
if (start) setValue("recruitmentStartDate", start.toISOString());
if (end) setValue("recruitmentEndDate", end.toISOString());
};

// 이미지 파일 change핸들러
const handleChangeImages = (files: File[]) => {
setValue("imageFiles", files);

const newImages = files.map((file: File) => ({
file,
url: URL.createObjectURL(file),
id: crypto.randomUUID(),
}));

setInitialImageList(newImages);
};

const errorTextStyle =
"absolute -bottom-[26px] right-1 text-[13px] text-sm font-medium leading-[22px] text-state-error lg:text-base lg:leading-[26px]";

Expand Down Expand Up @@ -90,22 +79,24 @@ export default function RecruitContentSection() {
endDate={recruitmentDateRange[1] || undefined}
onChange={handleRecruitmentDateChange}
required={true}
errormessage={isDirty && (!recruitmentDateRange[0] || !recruitmentDateRange[1])}
errormessage={!recruitmentDateRange[0] || !recruitmentDateRange[1]}
displayValue="recruitDateRange"
/>
{!recruitmentDateRange[0] ||
(!recruitmentDateRange[1] && <p className={cn(errorTextStyle, "")}> 모집 기간은 필수입니다.</p>)}
</div>

<Label>이미지 첨부</Label>
<ImageInput
{...register("imageUrls", { required: "이미지는 필수입니다." })}
onChange={(files: File[]) => {
handleChangeImages(files);
}}
initialImageList={initialImageList || []}
/>
{errors.imageUrls && <p className={cn(errorTextStyle, "")}>{errors.imageUrls.message as string}</p>}
<div>
<ImageInput
{...register("imageUrls", { required: "이미지는 필수입니다." })}
onChange={(files: File[]) => {
handleChangeImages(files);
}}
initialImageList={initialImageList}
/>
{errors.imageUrls && <p className={cn(errorTextStyle, "")}>{errors.imageUrls.message as string}</p>}
</div>
</form>
</div>
);
Expand Down
Loading
Loading