Skip to content

Commit

Permalink
Merge pull request #46 from eunji-0623/feature-황은지
Browse files Browse the repository at this point in the history
Feat: 수정 페이지 제작
  • Loading branch information
eunji-0623 authored Jul 19, 2024
2 parents c434cef + dd95075 commit 1e26efd
Show file tree
Hide file tree
Showing 14 changed files with 472 additions and 209 deletions.
1 change: 1 addition & 0 deletions components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export function PaginationArrowButton({
export function CircleCloseButton({ onClick }: SimpleButtonProps) {
return (
<button
type="button"
className="flex items-center justify-center rounded-full bg-nomad-black bg-opacity-80 w-[40px] h-[40px] t:w-[32px] t:h-[32px] m:w-[24px] m:h-[24px]"
onClick={onClick}
>
Expand Down
5 changes: 4 additions & 1 deletion components/MyActivity/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ function Popover({ activityId, closePopover }: PopoverProps) {
const { deleteMyActivityMutation } = useDeleteActivity();

const handleClickEdit = () => {
router.push('/myActivity/edit');
router.push({
pathname: '/myactivity/edit',
query: { activityId: activityId },
});
};
const handleClickDelete = () => {
openPopup({
Expand Down
4 changes: 3 additions & 1 deletion components/MyActivity/Register/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ function Date({ index }: TimeSlotGroupProps) {
onClick={handleInputClick}
readOnly
className="w-full h-[56px] py-[8px] px-[16px] rounded-md border border-var-gray6 bg-white"
placeholder="YY/MM/DD"
placeholder={
selectedDate[index] !== '' ? selectedDate[index] : 'YY/MM/DD'
}
/>
</div>
{showCalendar && (
Expand Down
240 changes: 240 additions & 0 deletions components/MyActivity/Register/RegisterForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import { FieldValues, useForm } from 'react-hook-form';
import { PrimaryButton } from '@/components/Button/Button';
import InputBox from '@/components/InputBox/InputBox';
import KategorieDropdown from '@/components/KategorieDropdown/KategorieDropdown';
import TextArea from '@/components/InputBox/TextArea';
import TimeSlot from '@/components/MyActivity/Register/TimeSlot';
import {
UploadBannerImage,
UploadDetailImage,
} from '@/components/MyActivity/Register/UploadImage';
import { validation } from '@/components/MyActivity/Register/validation';
import { useRecoilState } from 'recoil';
import { KategoriedDropState } from '@/states/KategorieDropState';
import {
addressState,
bannerImageState,
detailImageState,
endTimeState,
selectedDateState,
startTimeState,
timeSlotCountState,
timeSlotState,
} from '@/states/registerState';
import useRegisterActivity from '@/hooks/myActivity/useRegisterActivity';
import { formatDate } from '@/utils/formatDate';
import SidenNavigation from '@/components/SideNavigation/SideNavigation';
import AddressInput from '@/components/MyActivity/Register/AddressInput';
import { useEffect } from 'react';
import { RegisterFormProps } from './RegisterForm.types';
import useEditMyActivity from '@/hooks/myActivity/useEditMyActivity';
import useResetRegisterState from '@/hooks/myActivity/useResetRegisterState';

function RegisterForm({ activityData, isEdit = false }: RegisterFormProps) {
const [selectedKateogorie, setSelectedKategorie] =
useRecoilState(KategoriedDropState);
const [bannerImage, setBannerImage] = useRecoilState(bannerImageState);
const [detailImage, setDetailImage] = useRecoilState(detailImageState);
const [selectedDate, setSelectedDate] = useRecoilState(selectedDateState);
const [timeSlots, setTimeSlots] = useRecoilState(timeSlotState);
const [timeSlotCount, setTimeSlotCount] = useRecoilState(timeSlotCountState);
const [startTime, setStartTime] = useRecoilState(startTimeState);
const [endTime, setEndTime] = useRecoilState(endTimeState);
const [address, setAddress] = useRecoilState(addressState);

const {
register,
handleSubmit,
setValue,
watch,
formState: { errors },
} = useForm<FieldValues>({
mode: 'onChange',
});

// 수정 시 기존 데이터 불러오기
useEffect(() => {
if (activityData) {
setValue('title', activityData.title);
setValue('description', activityData.description);
setValue('price', activityData.price);
setSelectedKategorie({ name: activityData.category });
setBannerImage([activityData.bannerImageUrl]);
setDetailImage(activityData.subImages);
setAddress(activityData.address);
setTimeSlotCount(activityData.schedules.length);
const tempDate = [...selectedDate];
const tempStartTime = [...startTime];
const tempEndTime = [...endTime];
const tempTimeSlots: { id: number }[] = [];

activityData.schedules.forEach((schedule, index) => {
tempDate[index] = schedule.date;
tempStartTime[index] = schedule.startTime;
tempEndTime[index] = schedule.endTime;
tempTimeSlots.push({ id: schedule.id });
});
tempTimeSlots.pop();

setSelectedDate(tempDate);
setStartTime(tempStartTime);
setEndTime(tempEndTime);
setTimeSlots(tempTimeSlots);
}
}, [activityData]);

// api 호출 관련 hooks
const { postActivityMutation } = useRegisterActivity();
const { patchActivityMutation } = useEditMyActivity();

const formatSchedules = () =>
Array.from({ length: timeSlotCount }, (_, i) => ({
endTime: endTime[i],
startTime: startTime[i],
date: formatDate(selectedDate[i]),
}));

// 수정 버튼 클릭 시
const onSubmitEdit = async (data: FieldValues) => {
if (!activityData) {
return;
}
const { title, description, price } = data;
const schedules = formatSchedules();
const subImageUrls = detailImage.map((image) => image.imageUrl);
const subImageIdsToRemove = [];
const scheduleIdsToRemove = [];
for (let i = 0; i < activityData.subImages.length; i++) {
subImageIdsToRemove.push(activityData.subImages[i].id);
}
for (let i = 0; i < activityData.schedules.length; i++) {
scheduleIdsToRemove.push(activityData.schedules[i].id);
}

patchActivityMutation.mutate({
activityId: activityData.id,
params: {
title,
category: selectedKateogorie.name,
description,
address,
price: Number(price),
bannerImageUrl: bannerImage[0],
subImageIdsToRemove,
subImageUrlsToAdd: subImageUrls,
scheduleIdsToRemove,
schedulesToAdd: schedules,
},
});
};

// 등록 버튼 클릭 시
const onSubmitRegister = async (data: FieldValues) => {
const { title, description, price } = data;
const schedules = formatSchedules();
const subImageUrls = detailImage.map((image) => image.imageUrl);

postActivityMutation.mutate({
title,
category: selectedKateogorie.name,
description,
address,
price: Number(price),
schedules,
bannerImageUrl: bannerImage[0],
subImageUrls,
});
};

// form 제출이 가능한지 체크 - 시간, 날짜 관련
const isTimeFieldValid = () =>
Array.from({ length: timeSlotCount }).every(
(_, i) =>
endTime[i] !== '00:00' &&
startTime[i] <= endTime[i] &&
selectedDate[i] !== ''
);

// form 제출이 가능한지 체크
const isAllFieldsValid = () => {
const { title, description, price } = watch();
return (
!!title &&
!!description &&
!!price &&
!errors.title &&
!errors.description &&
!errors.price &&
selectedKateogorie.name !== '' &&
address !== '' &&
bannerImage.length !== 0 &&
isTimeFieldValid()
);
};

// 페이지 나갈 때 state reset
const resetRegisterState = useResetRegisterState();
useEffect(() => {
return () => {
resetRegisterState();
};
}, []);

return (
<div className="flex gap-[20px] py-[72px] m:gap-0 w-full">
<div className="m:hidden">
<SidenNavigation />
</div>
<form
onSubmit={isEdit ? onSubmitEdit : onSubmitRegister}
className="w-full m:p-[16px]"
>
<div className="flex flex-col w-full gap-[20px]">
<div className="flex justify-between items-center">
<h1 className="text-[32px] font-[700]">내 체험 등록</h1>
<PrimaryButton
type="submit"
size="small"
style={isAllFieldsValid() ? 'dark' : 'disabled'}
onClick={handleSubmit(isEdit ? onSubmitEdit : onSubmitRegister)}
disabled={!isAllFieldsValid()}
>
{isEdit ? '수정하기' : '등록하기'}
</PrimaryButton>
</div>
<div className="space-y-[24px]">
<InputBox
placeholder="제목"
name="title"
validation={validation.title}
register={register}
errors={errors}
/>
<KategorieDropdown />
<TextArea
placeholder="설명"
name="description"
validation={validation.description}
register={register}
errors={errors}
/>
<InputBox
label="가격"
placeholder="가격"
name="price"
validation={validation.price}
register={register}
errors={errors}
/>
<AddressInput />
<TimeSlot />
<UploadBannerImage />
<UploadDetailImage />
</div>
</div>
</form>
</div>
);
}

export default RegisterForm;
31 changes: 31 additions & 0 deletions components/MyActivity/Register/RegisterForm.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export interface RegisterFormProps {
activityData?: {
id: number;
userId: number;
title: string;
description: string;
category: string;
price: number;
address: string;
bannerImageUrl: string;
subImages: [
{
id: number;
imageUrl: string;
},
];
schedules: [
{
endTime: string;
startTime: string;
date: string;
id: number;
},
];
rating: number;
reviewCount: number;
createdAt: string;
updatedAt: string;
};
isEdit?: boolean;
}
11 changes: 9 additions & 2 deletions components/MyActivity/Register/TimeSlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { MinusButton, PlusButton } from '@/components/Button/Button';
import DateInput from './DateInput';
import TimeDropdown from './TimeDropdown';
import { TimeSlotGroupProps } from './TimeSlot.types';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
import {
startTimeState,
endTimeState,
timeSlotCountState,
timeSlotState,
} from '@/states/registerState';

function autoSelectedTime(time: string) {
Expand Down Expand Up @@ -49,6 +50,11 @@ function TimeSlotGroup({
}
};

useEffect(() => {
setSelectedStartTime(startTime[index]);
setSelectedEndTime(endTime[index]);
}, [startTime[index], endTime[index]]);

return (
<div className="flex items-center t:justify-between m:justify-between gap-[20px] t:gap-[4px] m:gap-[4px]">
<div className="flex items-center gap-[20px] t:gap-[4px] m:gap-[4px]">
Expand Down Expand Up @@ -85,7 +91,7 @@ function TimeSlotGroup({
}

function TimeSlot() {
const [timeSlots, setTimeSlots] = useState<{ id: number }[]>([]);
const [timeSlots, setTimeSlots] = useRecoilState(timeSlotState);
const [timeSlotCount, setTimeSlotCount] = useRecoilState(timeSlotCountState);

const handleClickPlus = () => {
Expand All @@ -95,6 +101,7 @@ function TimeSlot() {
setTimeSlots((prevTimeSlots) => [...prevTimeSlots, newTimeSlot]);
setTimeSlotCount(timeSlotCount + 1);
};

const handleClickMinus = (id: number) => {
setTimeSlots((prevTimeSlots) =>
prevTimeSlots.filter((timeSlot) => timeSlot.id !== id)
Expand Down
Loading

0 comments on commit 1e26efd

Please sign in to comment.