From da7c73a820075af8643b6d8074f4b78020bcf961 Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Sun, 20 Jul 2025 11:47:47 +0900 Subject: [PATCH 01/10] =?UTF-8?q?fix=20::=20=EC=98=88=EB=B3=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=EC=97=90=EC=84=9C=EB=8F=84=20=EC=98=88=EB=B3=B4=20?= =?UTF-8?q?=ED=8C=9D=EC=97=85=20=EB=9C=A8=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/EmotionResultPopup.tsx | 309 ++++++++++++++------------ app/insert-after/reason/page.tsx | 46 ++-- 2 files changed, 196 insertions(+), 159 deletions(-) diff --git a/app/components/EmotionResultPopup.tsx b/app/components/EmotionResultPopup.tsx index 44cbedc..594d5cd 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,85 @@ 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 handleClose = () => { + onClose(); + router.push('/home'); + }; + + 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,10 +112,25 @@ 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) { + // 오른쪽으로 드래그 - 이전 슬라이드 + setCurrentIndex((prev) => (prev - 1 + emotions.length) % emotions.length); + setHasSwiped(true); + } else if (info.offset.x < -threshold) { + // 왼쪽으로 드래그 - 다음 슬라이드 + 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 ( @@ -66,142 +138,97 @@ export default function EmotionResultPopup({ isVisible, onClose, emotions }: Emo initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4" - onClick={onClose} + className="fixed inset-0 bg-black bg-opacity-40 backdrop-blur-md flex items-center justify-center z-50 p-4" + onClick={handleClose} > 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/reason/page.tsx b/app/insert-after/reason/page.tsx index 4bccc35..de1a218 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 EmotionResultPopup from "../../components/EmotionResultPopup"; import { getCurrentDate } from "../../utils/dateUtils"; const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; @@ -174,6 +174,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); @@ -183,14 +205,8 @@ function ReasonPageContent() { 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'); - } + setAllEmotions(allEmotionData); + setShowResultPopup(true); } } catch (error) { console.error('예보 기록 생성 실패:', error); @@ -246,19 +262,13 @@ 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 - }))} + emotions={allEmotions} />
{getCurrentDate()} {TIME_PERIODS[currentStep].label}
From e3c4ef87bb8c0fccdd7455c2ae9c9757133f7123 Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Sun, 20 Jul 2025 11:50:26 +0900 Subject: [PATCH 02/10] =?UTF-8?q?fix=20::=20=EB=8B=A4=EC=9D=8C/=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C=EC=9C=BC=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/insert-after/page.tsx | 2 +- app/insert/page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/insert-after/page.tsx b/app/insert-after/page.tsx index f0b3d1c..9db1e9e 100644 --- a/app/insert-after/page.tsx +++ b/app/insert-after/page.tsx @@ -139,7 +139,7 @@ function InsertAfterPageContent() { disabled={!isEmotionSelected || isLoading} className={`w-full transition-opacity ${!isEmotionSelected ? 'opacity-50' : ''}`} > - {isLoading ? '처리 중...' : '다음'} + {isLoading ? '처리 중...' : '다음으로'}
diff --git a/app/insert/page.tsx b/app/insert/page.tsx index c82de6b..b161d9b 100644 --- a/app/insert/page.tsx +++ b/app/insert/page.tsx @@ -126,7 +126,7 @@ function InsertPageContent() { disabled={!isEmotionSelected || isLoading} className={`w-full transition-opacity ${!isEmotionSelected ? 'opacity-50' : ''}`} > - {isLoading ? '처리 중...' : '다음'} + {isLoading ? '처리 중...' : '다음으로'}
From c4eb0bad24dc84349c830a442b761773617cace4 Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Sun, 20 Jul 2025 11:56:17 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat=20::=20=EB=8F=8C=EC=95=84=EA=B0=80?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/EmotionForecastPopup.tsx | 13 +++++++++++-- app/components/EmotionResultPopup.tsx | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/components/EmotionForecastPopup.tsx b/app/components/EmotionForecastPopup.tsx index 64b6155..4da0f2c 100644 --- a/app/components/EmotionForecastPopup.tsx +++ b/app/components/EmotionForecastPopup.tsx @@ -133,8 +133,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-40 backdrop-blur-md flex items-center justify-center z-50 p-4" > + + {/* 종료 버튼 */} +
+ +
); diff --git a/app/components/EmotionResultPopup.tsx b/app/components/EmotionResultPopup.tsx index 594d5cd..6f16073 100644 --- a/app/components/EmotionResultPopup.tsx +++ b/app/components/EmotionResultPopup.tsx @@ -65,7 +65,7 @@ export default function EmotionResultPopup({ isVisible, onClose, emotions }: Emo const handleClose = () => { onClose(); - router.push('/home'); + router.push('/baby'); }; useEffect(() => { @@ -139,7 +139,6 @@ export default function EmotionResultPopup({ isVisible, onClose, emotions }: Emo 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} > + + {/* 종료 버튼 */} +
+ +
); From 80de8ac9761def3145a70add1062d27f0e4a5cad Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Sun, 20 Jul 2025 12:11:58 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor=20::=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=98=AC=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/insert-after/reason/page.tsx | 4 ++-- app/insert/reason/page.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/insert-after/reason/page.tsx b/app/insert-after/reason/page.tsx index de1a218..51ab7f1 100644 --- a/app/insert-after/reason/page.tsx +++ b/app/insert-after/reason/page.tsx @@ -280,7 +280,7 @@ function ReasonPageContent() {