diff --git a/app/baby/page.tsx b/app/baby/page.tsx index f78b72c..044a085 100644 --- a/app/baby/page.tsx +++ b/app/baby/page.tsx @@ -1,7 +1,7 @@ 'use client' -import { useState, useEffect } from "react" -import { useRouter } from "next/navigation" +import { useState, useEffect, Suspense } from "react" +import { useRouter, useSearchParams } from "next/navigation" import Container from "../components/Container" import { useChild } from "../contexts/ChildContext" import { @@ -34,11 +34,14 @@ import ErrorMessage from "../components/ErrorMessage" import EmptyState from "../components/EmptyState" import TimeSlotSelector from "../components/TimeSlotSelector" import DiaryCard from "../components/DiaryCard" +import EmotionForecastPopup from "../components/EmotionForecastPopup" +import EmotionResultPopup from "../components/EmotionResultPopup" // 공통 타입 사용 -export default function Present() { +function BabyPageContent() { const router = useRouter() + const searchParams = useSearchParams() const { selectedChild, isLoading } = useChild() const [selectedDate, setSelectedDate] = useState(null) const [selectedTimeSlot, setSelectedTimeSlot] = useState('morning') @@ -46,8 +49,35 @@ export default function Present() { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [forecastData, setForecastData] = useState>({}) + const [showForecastPopup, setShowForecastPopup] = useState(false) + const [showRecordPopup, setShowRecordPopup] = useState(false) + const [popupEmotions, setPopupEmotions] = useState([]) - + // URL 파라미터 확인하여 팝업 표시 + useEffect(() => { + const showForecast = searchParams.get('showForecastPopup') + const showRecord = searchParams.get('showRecordPopup') + + if (showForecast === 'true') { + const savedEmotions = localStorage.getItem('forecastEmotions') + if (savedEmotions) { + const emotions = JSON.parse(savedEmotions) + setPopupEmotions(emotions) + setShowForecastPopup(true) + // URL에서 파라미터 제거 + router.replace('/baby') + } + } else if (showRecord === 'true') { + const savedEmotions = localStorage.getItem('forecastRecordEmotions') + if (savedEmotions) { + const emotions = JSON.parse(savedEmotions) + setPopupEmotions(emotions) + setShowRecordPopup(true) + // URL에서 파라미터 제거 + router.replace('/baby') + } + } + }, [searchParams, router]) const isTomorrow = (date: Date) => { const tomorrow = new Date(); @@ -524,6 +554,47 @@ export default function Present() { {/* 하단 여백 */}
+ {/* 예보 결과 팝업 */} + { + setShowForecastPopup(false); + localStorage.removeItem('forecastEmotions'); // 저장된 감정 데이터 정리 + }} + forecasts={popupEmotions.map((emotion: any) => ({ + timeSlot: emotion.step, + emotion: emotion.emotion.name, + temperature: emotion.emotion.temp, + image: emotion.emotion.image, + category: emotion.category + }))} + /> + + {/* 예보 기록 결과 팝업 */} + { + setShowRecordPopup(false); + localStorage.removeItem('forecastRecordEmotions'); // 저장된 감정 데이터 정리 + }} + emotions={popupEmotions} + /> + ) +} + +export default function Present() { + return ( + +
+
+

로딩 중...

+
+ + }> + +
+ ); } \ No newline at end of file diff --git a/app/components/EmotionForecastPopup.tsx b/app/components/EmotionForecastPopup.tsx index 64b6155..9e750f3 100644 --- a/app/components/EmotionForecastPopup.tsx +++ b/app/components/EmotionForecastPopup.tsx @@ -57,6 +57,7 @@ export default function EmotionForecastPopup({ isOpen, onClose, forecasts }: Emo const router = useRouter(); const [dragDirection, setDragDirection] = useState<'left' | 'right' | null>(null); const [hasSwiped, setHasSwiped] = useState(false); + const [slideDirection, setSlideDirection] = useState<'left' | 'right' | null>(null); const handleClose = () => { onClose(); @@ -112,10 +113,12 @@ export default function EmotionForecastPopup({ isOpen, onClose, forecasts }: Emo if (info.offset.x > threshold) { // 오른쪽으로 드래그 - 이전 슬라이드 + setSlideDirection('right'); setCurrentIndex((prev) => (prev - 1 + forecasts.length) % forecasts.length); setHasSwiped(true); } else if (info.offset.x < -threshold) { // 왼쪽으로 드래그 - 다음 슬라이드 + setSlideDirection('left'); setCurrentIndex((prev) => (prev + 1) % forecasts.length); setHasSwiped(true); } @@ -133,8 +136,7 @@ export default function EmotionForecastPopup({ isOpen, onClose, forecasts }: Emo initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 bg-black bg-opacity-40 backdrop-blur-md flex items-center justify-center z-50 p-4" - onClick={handleClose} + className="fixed inset-0 bg-black bg-opacity-20 backdrop-blur-sm flex items-center justify-center z-50 p-4" > + + {/* 종료 버튼 */} +
+ +
); diff --git a/app/components/EmotionResultPopup.tsx b/app/components/EmotionResultPopup.tsx index 44cbedc..9ce1a73 100644 --- a/app/components/EmotionResultPopup.tsx +++ b/app/components/EmotionResultPopup.tsx @@ -1,7 +1,9 @@ 'use client' -import React, { useState } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; +import React, { useState, useEffect } from 'react'; +import { motion, AnimatePresence, PanInfo } from 'framer-motion'; +import { useRouter } from 'next/navigation'; +import { CATEGORY_COLORS } from '../types/common'; interface EmotionData { step: string; @@ -22,30 +24,86 @@ interface EmotionResultPopupProps { emotions: EmotionData[]; } -const TIME_PERIODS = { - morning: { label: '오전', text: '오전의 감정은' }, - afternoon: { label: '오후', text: '오후의 감정은' }, - evening: { label: '저녁', text: '저녁의 감정은' } +const getEmotionImage = (imageUrl: string) => { + // DB에서 받은 이미지 URL을 SVG big 버전으로 변환 + console.log('원본 이미지 URL:', imageUrl); + + if (!imageUrl) { + console.error('이미지 URL이 없습니다'); + return '/icon/기쁜-big.svg'; // 기본 이미지 + } + + let processedUrl = imageUrl; + + // PNG를 SVG로 변경하고 big 버전 사용 + + // small을 big으로 변경 + + console.log('최종 SVG URL:', processedUrl); + return processedUrl; }; -const getEmotionColor = (type: string) => { - switch (type) { - case 'positive': - return 'bg-red-500'; - case 'negative': - return 'bg-blue-500'; - default: - return 'bg-gray-500'; - } +const getTimeSlotLabel = (timeSlot: string) => { + const timeLabels: { [key: string]: string } = { + 'morning': '아침', + 'afternoon': '점심', + 'evening': '저녁' + }; + + return timeLabels[timeSlot] || timeSlot; }; -const getTemperatureColor = (temp: number) => { - if (temp >= 0) return 'text-red-600'; - return 'text-blue-600'; +const getCategoryColor = (category: string) => { + return CATEGORY_COLORS[category as keyof typeof CATEGORY_COLORS] || '#666666'; }; export default function EmotionResultPopup({ isVisible, onClose, emotions }: EmotionResultPopupProps) { const [currentIndex, setCurrentIndex] = useState(0); + const router = useRouter(); + const [dragDirection, setDragDirection] = useState<'left' | 'right' | null>(null); + const [hasSwiped, setHasSwiped] = useState(false); + const [slideDirection, setSlideDirection] = useState<'left' | 'right' | null>(null); + + const handleClose = () => { + onClose(); + router.push('/baby'); + }; + + useEffect(() => { + if (isVisible) { + setCurrentIndex(0); + setHasSwiped(false); + console.log('팝업 열림 - 감정 데이터:', emotions); + } + }, [isVisible, emotions]); + + // 키보드 이벤트 처리 + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (!isVisible || emotions.length <= 1) return; + + switch (event.key) { + case 'ArrowLeft': + event.preventDefault(); + setCurrentIndex((prev) => (prev + 1) % emotions.length); + setHasSwiped(true); + break; + case 'ArrowRight': + event.preventDefault(); + setCurrentIndex((prev) => (prev - 1 + emotions.length) % emotions.length); + setHasSwiped(true); + break; + } + }; + + if (isVisible) { + document.addEventListener('keydown', handleKeyDown); + } + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [isVisible, emotions.length]); const nextSlide = () => { setCurrentIndex((prev) => (prev + 1) % emotions.length); @@ -55,154 +113,141 @@ export default function EmotionResultPopup({ isVisible, onClose, emotions }: Emo setCurrentIndex((prev) => (prev - 1 + emotions.length) % emotions.length); }; - const currentEmotion = emotions[currentIndex]; - const step = currentEmotion?.step as keyof typeof TIME_PERIODS; + const handleDragEnd = (event: any, info: PanInfo) => { + const threshold = 50; // 슬라이드 임계값 + + if (info.offset.x > threshold) { + // 오른쪽으로 드래그 - 이전 슬라이드 + setSlideDirection('right'); + setCurrentIndex((prev) => (prev - 1 + emotions.length) % emotions.length); + setHasSwiped(true); + } else if (info.offset.x < -threshold) { + // 왼쪽으로 드래그 - 다음 슬라이드 + setSlideDirection('left'); + setCurrentIndex((prev) => (prev + 1) % emotions.length); + setHasSwiped(true); + } + + setDragDirection(null); + }; - if (!isVisible || !currentEmotion) return null; + if (!isVisible || emotions.length === 0) return null; + + const currentEmotion = emotions[currentIndex]; return ( - + e.stopPropagation()} > - {/* 닫기 버튼 */} - - - {/* 슬라이드 컨테이너 */} -
- - {/* 시간대 표시 */} -
- - {TIME_PERIODS[step]?.label} + {/* Result Card */} +
+ {/* Top Row - Temperature */} +
+ {/* Temperature - 왼쪽 상단 */} +
+ + {currentEmotion.emotion.temp}°
+
- {/* 감정 카드 */} -
-
- {/* 온도 */} -
- - {currentEmotion.emotion.temp > 0 ? '+' : ''}{currentEmotion.emotion.temp}° - -
- - {/* 감정 아이콘 */} -
-
- {currentEmotion.emotion.name} -
-
-
- - {/* 감정 설명 */} -
-

- {TIME_PERIODS[step]?.text} {currentEmotion.emotion.name}으로 {currentEmotion.emotion.temp > 0 ? '+' : ''}{currentEmotion.emotion.temp}° 예정이에요. -

-
-
- - {/* 메모 (있는 경우) */} - {currentEmotion.memo && ( -
-

- "{currentEmotion.memo}" -

-
- )} - - {/* 감정 카테고리 */} -
- - {currentEmotion.category} - + {/* Content Area */} +
+ {/* Emotion Icon - 가운데 */} +
+ {currentEmotion.emotion.name} { + console.error('이미지 로딩 실패:', currentEmotion.emotion.image); + console.error('감정 데이터:', currentEmotion); + // 이미지 로딩 실패 시 기본 이모지 표시 + e.currentTarget.style.display = 'none'; + const fallbackDiv = document.createElement('div'); + fallbackDiv.className = 'w-full h-full flex items-center justify-center text-4xl'; + fallbackDiv.textContent = '😐'; + e.currentTarget.parentNode?.appendChild(fallbackDiv); + }} + onLoad={() => { + console.log('이미지 로딩 성공:', currentEmotion.emotion.image); + }} + />
- -
+
- {/* 네비게이션 */} - {emotions.length > 1 && ( -
- - - {/* 인디케이터 */} -
- {emotions.map((_, index) => ( -
+
+
- + {/* Swipe Hint */} + {emotions.length > 1 && !hasSwiped && ( +
+
← 슬라이드 →
+
또는 화살표 키 사용
)} - - {/* 완료 버튼 */} -
- -
+ + + {/* 종료 버튼 */} +
+ +
); diff --git a/app/insert-after/page.tsx b/app/insert-after/page.tsx index f0b3d1c..2a4cd05 100644 --- a/app/insert-after/page.tsx +++ b/app/insert-after/page.tsx @@ -132,14 +132,14 @@ function InsertAfterPageContent() { initial="hidden" animate="visible" variants={fadeInOutVariants} - className="flex flex-col items-center w-full max-w-sm mx-auto mt-auto mb-9" + className="flex flex-col items-center w-full max-w-sm mx-auto mt-auto mb-20" >
diff --git a/app/insert-after/reason/page.tsx b/app/insert-after/reason/page.tsx index 4bccc35..55c29d3 100644 --- a/app/insert-after/reason/page.tsx +++ b/app/insert-after/reason/page.tsx @@ -5,7 +5,7 @@ import { motion } from "framer-motion"; import { useRouter, useSearchParams } from "next/navigation"; import Button from "../../components/Button"; import { useChild } from "../../contexts/ChildContext"; -import EmotionForecastPopup from "../../components/EmotionForecastPopup"; + import { getCurrentDate } from "../../utils/dateUtils"; const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; @@ -35,8 +35,7 @@ function ReasonPageContent() { const [reason, setReason] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const [showResultPopup, setShowResultPopup] = useState(false); - const [allEmotions, setAllEmotions] = useState([]); + const router = useRouter(); const searchParams = useSearchParams(); const forecastId = searchParams.get('forecastId'); @@ -174,6 +173,28 @@ function ReasonPageContent() { console.log(`✅ ${currentStep} 예보 기록 생성 완료 - ${new Date().toLocaleString('ko-KR')}`); + // 현재 단계의 감정 데이터에 메모 추가 + const currentEmotionData = { + step: currentStep, + emotion: currentEmotion.emotion, + category: currentEmotion.category, + memo: reason.trim() + }; + + // localStorage에서 기존 데이터 가져오기 + let allEmotionData = []; + if (typeof window !== 'undefined') { + const existingData = localStorage.getItem('forecastRecordEmotions'); + if (existingData) { + allEmotionData = JSON.parse(existingData); + } + + // 현재 단계 데이터 업데이트 + allEmotionData = allEmotionData.filter((data: any) => data.step !== currentStep); + allEmotionData.push(currentEmotionData); + localStorage.setItem('forecastRecordEmotions', JSON.stringify(allEmotionData)); + } + // 다음 단계로 이동 const steps = ['morning', 'afternoon', 'evening']; const currentIndex = steps.indexOf(currentStep); @@ -182,15 +203,9 @@ function ReasonPageContent() { if (nextStep) { router.push(`/insert-after?step=${nextStep}&forecastId=${forecastId}&date=${forecastData.date}&timeZone=${TIME_PERIODS[nextStep as keyof typeof TIME_PERIODS].label}`); } else { - // 모든 단계 완료 - 결과 팝업 표시 - const savedEmotions = localStorage.getItem('forecastRecordEmotions'); - if (savedEmotions) { - const emotions = JSON.parse(savedEmotions); - setAllEmotions(emotions); - setShowResultPopup(true); - } else { - router.push('/home'); - } + // 모든 단계 완료 - baby 페이지로 이동 후 팝업 표시 + // URL 파라미터로 팝업 표시 여부 전달 + router.push('/baby?showRecordPopup=true'); } } catch (error) { console.error('예보 기록 생성 실패:', error); @@ -245,22 +260,6 @@ function ReasonPageContent() {
- {/* 결과 팝업 */} - { - setShowResultPopup(false); - localStorage.removeItem('forecastRecordEmotions'); // 저장된 감정 데이터 정리 - }} - forecasts={allEmotions.map((emotion: any) => ({ - timeSlot: emotion.step, - emotion: emotion.emotion.name, - temperature: emotion.emotion.temp, - image: emotion.emotion.image, - category: emotion.category - }))} - /> -
{getCurrentDate()} {TIME_PERIODS[currentStep].label}
{TIME_PERIODS[currentStep].text}{`\n`}느꼈나요? @@ -270,7 +269,7 @@ function ReasonPageContent() {