diff --git a/public/images/icons/CalendarIcon.svg b/public/images/icons/CalendarIcon.svg index 9595725..cc11b89 100644 --- a/public/images/icons/CalendarIcon.svg +++ b/public/images/icons/CalendarIcon.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/app/(global)/(mypage)/myCreateExperiences/page.tsx b/src/app/(global)/(mypage)/myCreateExperiences/page.tsx index d595220..1b33d77 100644 --- a/src/app/(global)/(mypage)/myCreateExperiences/page.tsx +++ b/src/app/(global)/(mypage)/myCreateExperiences/page.tsx @@ -17,6 +17,7 @@ import { useTimeSlotStore } from '@/src/store/TimeSlotStore'; import { useActivityStore } from '@/src/store/useActivityStore'; import { openDaumPostcode } from '@/src/utils/daumPostcode'; import { format } from 'date-fns'; +import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { useForm, SubmitHandler, Controller } from 'react-hook-form'; @@ -54,7 +55,6 @@ export default function MyCreateExperiencesPage() { } = useForm({ mode: 'onBlur' }); const [isAlertOpen, setIsAlertOpen] = useState(false); const [isConfirmOpen, setIsConfirmOpen] = useState(false); - const router = useRouter(); const handleAddressClick = async () => { @@ -66,18 +66,27 @@ export default function MyCreateExperiencesPage() { } }; + const handleBackClick = () => { + setIsConfirmOpen(true); + }; + const handleCloseModal = () => { router.back(); }; const handleConfirm = () => { setIsConfirmOpen(false); + router.back(); }; const handleCancel = () => { setIsConfirmOpen(false); }; + const formatNumber = (value?: number) => { + if (value === undefined || value === null) return ''; + return value.toLocaleString(); + }; const { timeSlots } = useTimeSlotStore(); const selectedDate = useReservationStore( (state) => state.dateSelector.selectedDate @@ -85,13 +94,12 @@ export default function MyCreateExperiencesPage() { const { bannerImages, subImages } = useActivityStore(); const onSubmit: SubmitHandler = async (data) => { - console.log('소개 이미지 상태:', subImages); try { // 1️⃣ schedules 변환 const schedules: Schedule[] = timeSlots .filter((slot) => slot.startTime && slot.endTime) .map((slot) => ({ - date: selectedDate ? format(selectedDate, 'yyyy-MM-dd') : '', + date: selectedDate ? format(selectedDate, 'yyyy-MM-dd') : 'yy/mm/dd', startTime: slot.startTime!, endTime: slot.endTime!, })); @@ -105,8 +113,6 @@ export default function MyCreateExperiencesPage() { ? await uploadActivityImages(subImages) : []; - console.log('업로드 후 소개 이미지 URL:', subImageUrls); - // 4️⃣ payload 생성 const payload = { ...data, @@ -128,9 +134,19 @@ export default function MyCreateExperiencesPage() {
-

내 체험 등록

+
+

내 체험 등록

+ +
- ( + { + const raw = e.target.value.replace(/,/g, ''); + const parsed = raw === '' ? undefined : Number(raw); + field.onChange(parsed); + }} + errorMessage={errors.price?.message} + /> + )} /> (); const router = useRouter(); const [isAlertOpen, setIsAlertOpen] = useState(false); + const [isConfirmOpen, setIsConfirmOpen] = useState(false); const [existingSubImages, setExistingSubImages] = useState< { id: number; url: string }[] >([]); @@ -80,10 +86,28 @@ export default function MyUpdateExperiencesPage() { } }; + const handleBackClick = () => { + setIsConfirmOpen(true); + }; + + const handleConfirm = () => { + setIsConfirmOpen(false); + router.back(); + }; + + const handleCancel = () => { + setIsConfirmOpen(false); + }; + const handleCloseModal = () => { router.back(); }; + const formatNumber = (value?: number) => { + if (value === undefined || value === null) return ''; + return value.toLocaleString(); + }; + const { timeSlots, setTimeSlots } = useTimeSlotStore(); const { bannerImages, subImages, setBannerImages, setSubImages } = useActivityStore(); @@ -196,10 +220,19 @@ export default function MyUpdateExperiencesPage() {
-

체험 수정하기

- +
+

체험 수정하기

+ +
- )} /> - - - ( + { + const raw = e.target.value.replace(/,/g, ''); + const parsed = raw === '' ? undefined : Number(raw); + field.onChange(parsed); + }} + errorMessage={errors.price?.message} + /> + )} /> - - - - +
); } diff --git a/src/components/pages/myCreateExperiences/AvailableTimeSlots.tsx b/src/components/pages/myCreateExperiences/AvailableTimeSlots.tsx index 37e27a7..9258f40 100644 --- a/src/components/pages/myCreateExperiences/AvailableTimeSlots.tsx +++ b/src/components/pages/myCreateExperiences/AvailableTimeSlots.tsx @@ -1,21 +1,38 @@ -import TimepickerDropdown from './TimePickerDropdown'; +'use client'; + +import { useBreakPoint } from '@/src/hooks/useBreakPoint'; import CalendarInput from '../../primitives/input/CalendarInput'; +import TimepickerDropdown from './TimePickerDropdown'; import { useTimeSlotStore } from '@/src/store/TimeSlotStore'; import Image from 'next/image'; export default function AvailableTimeSlots() { const { timeSlots, setTimeSlots } = useTimeSlotStore(); + const { isMd } = useBreakPoint(); + const availableTimes: string[] = Array.from({ length: 24 }, (_, i) => { const hour = i.toString().padStart(2, '0'); return `${hour}:00`; }); + // 가이드 슬롯 (UI용, 첫 번째) + const guideSlot = { startTime: '', endTime: '' }; + const handleAddSlot = () => { - const lastSlot = timeSlots[timeSlots.length - 1]; - if (!lastSlot.startTime || !lastSlot.endTime) return; - setTimeSlots([...timeSlots, { startTime: '', endTime: '' }]); - }; + // 실제 슬롯 배열 (첫 번째는 UI 가이드용이므로 제외) + const actualSlots = timeSlots; + // 마지막 실제 슬롯이 완전히 입력되었거나 슬롯이 없는 경우에만 추가 + if ( + actualSlots.length === 0 || + (actualSlots[actualSlots.length - 1].startTime && + actualSlots[actualSlots.length - 1].endTime) + ) { + setTimeSlots([...actualSlots, { startTime: '', endTime: '' }]); + } else { + alert('예약 날짜를 모두 입력해야 새로운 슬롯을 추가할 수 있습니다.'); + } + }; const handleRemoveSlot = (index: number) => { const updatedSlots = timeSlots.filter((_, i) => i !== index); setTimeSlots(updatedSlots); @@ -28,6 +45,8 @@ export default function AvailableTimeSlots() { ) => { const updatedSlots = [...timeSlots]; updatedSlots[index][field] = value; + + // 중복 체크 const duplicate = updatedSlots.some( (slot, i) => i !== index && @@ -48,55 +67,97 @@ export default function AvailableTimeSlots() {

예약 가능한 시간대

날짜

- -
-
- handleChange(0, 'startTime', time)} - placeholder='00:00' - /> -
- MinusIcon -
- handleChange(0, 'endTime', time)} - placeholder='00:00' - /> +
+ {/* 첫 번째 가이드 슬롯 */} +
+
+ +
-
+
+
+
+ +
+
PlusIcon +
+
+ +
+
+
+ PlusIcon +
+
+ {timeSlots.map((slot, index) => ( +
+ {/* CalendarInput */} +
+ +
- {timeSlots.slice(1).map((slot, index) => ( -
- -
+ {/* Timepicker + 버튼 그룹 (남은 공간 차지) */} +
- handleChange(index + 1, 'startTime', time) - } + onSelect={(time) => handleChange(index, 'startTime', time)} placeholder='00:00' />
@@ -110,16 +171,17 @@ export default function AvailableTimeSlots() { handleChange(index + 1, 'endTime', time)} + onSelect={(time) => handleChange(index, 'endTime', time)} placeholder='00:00' />
-
+
handleRemoveSlot(index)} + className='flex cursor-pointer items-center justify-center bg-gray-50 rounded-[30px] w-7 h-7' + > handleRemoveSlot(index + 1)} src='/images/icons/MinusIcon.svg' alt='MinusIcon' - className='cursor-pointer' width={16} height={16} /> diff --git a/src/components/pages/myCreateExperiences/TimePickerDropdown.tsx b/src/components/pages/myCreateExperiences/TimePickerDropdown.tsx index 482b28b..0d86023 100644 --- a/src/components/pages/myCreateExperiences/TimePickerDropdown.tsx +++ b/src/components/pages/myCreateExperiences/TimePickerDropdown.tsx @@ -8,6 +8,7 @@ interface TimepickerDropdownProps { selectedTime?: string; onSelect: (time: string) => void; placeholder?: string; + disabled?: boolean; } export default function TimepickerDropdown({ @@ -15,15 +16,21 @@ export default function TimepickerDropdown({ selectedTime, onSelect, placeholder = '시간 선택', + disabled = false, }: TimepickerDropdownProps) { const [open, setOpen] = useState(false); - + const handleButtonClick = () => { + if (disabled) return; // ✅ 비활성화 시 클릭 무시 + setOpen((prev) => !prev); + }; return (