Skip to content

Commit

Permalink
Merge pull request #17 from ktb1-eight/feature/long_trip_ai
Browse files Browse the repository at this point in the history
[Feat] 장기 여행지 추천 AI 연결 성공
  • Loading branch information
alswp006 authored Sep 12, 2024
2 parents 4a27dbf + cb82e36 commit 6782858
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 104 deletions.
9 changes: 6 additions & 3 deletions src/components/ConfirmModal.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import React from "react";
import "../styles/confirmModal.css"

const ConfirmModal = ({ isOpen, onClose, cityName }) => {
const ConfirmModal = ({ isOpen, onClose, cityName, gender, ageGroup }) => {
if(!isOpen || !cityName) {
return null;
}

const queryParams = new URLSearchParams({
gender: gender,
ageGroup: ageGroup,
}).toString();
return (
<div className="modal-overlay">
<div id="confirm-modal-content" onClick={(e) => e.stopPropagation()}>
<div id="confirm-modal-container">
<p id="modal-headline1">{cityName}을(를) 선택하셨군요!</p>
<p id="modal-headline2">머무시는 동안 짧게 다녀올 근처 여행도 계획해볼까요?</p>
<div id="modal-links">
<a href="/short-term" id="go-to-short-trip" onClick={onClose}>단기 여행 계획하러 가기</a>
<a href={`/short-term?longterm=true&userInfo=${queryParams}`} id="go-to-short-trip" onClick={onClose}>단기 여행 계획하러 가기</a>
<a href="/myTravel" id="later" onClick={onClose}>나중에 계획하기</a>
</div>
</div>
Expand Down
35 changes: 26 additions & 9 deletions src/components/GenderAgeSelector.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { useState, useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
import '../styles/genderAgeSelector.css';
import Header from './Header';

const GenderAgeSelector = () => {
const [searchParams] = useSearchParams();

// longterm 파라미터를 가져옴
const isLongTerm = searchParams.get('longterm') === 'true';


const [selectedGender, setSelectedGender] = useState('');
const [selectedAgeGroup, setSelectedAgeGroup] = useState('');
const [isApplyEnabled, setIsApplyEnabled] = useState(false);
Expand Down Expand Up @@ -33,14 +39,25 @@ const GenderAgeSelector = () => {
console.log(location.state?.latitude)
console.log(location.state?.longitude)
// 위치 정보도 함께 전달
navigate('/travel-planner', {
state: {
gender: selectedGender,
ageGroup: selectedAgeGroup,
latitude: location.state?.latitude,
longitude: location.state?.longitude
}
});

if(isLongTerm) {
navigate('/longTrip', {
state: {
gender: selectedGender,
ageGroup: selectedAgeGroup
}
});
}else {
navigate('/travel-planner', {
state: {
gender: selectedGender,
ageGroup: selectedAgeGroup,
latitude: location.state?.latitude,
longitude: location.state?.longitude
}
});
}

};

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const Header = () => {
<nav>
<ul>
<li><a href="#how-to-use">이용 방법</a></li>
<li><a href="/longTrip">장기 여행지 추천</a></li>
<li><a href="/travel-selectInfo?longterm=true">장기 여행지 추천</a></li>
<li><a href="/short-term">단기 일정 추천</a></li>
<li><a href="/myTravel">내 여행</a></li>
<li><a href="#support">고객 지원</a></li>
Expand Down
34 changes: 29 additions & 5 deletions src/components/ShortTermMap.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import '../styles/shortTermMap.css';
import Header from './Header';

const { kakao } = window;

const ShortTermMap = () => {
const location = useLocation();

// URLSearchParams를 사용해 쿼리 파라미터를 파싱
const searchParams = new URLSearchParams(location.search);

// 개별 파라미터 값 가져오기
const isLongTerm = searchParams.get('longterm') === 'true';
const gender = searchParams.get('gender');
const ageGroup = searchParams.get('ageGroup');

const [map, setMap] = useState(null);
const [infowindow, setInfowindow] = useState(null);
const [markers, setMarkers] = useState([]);
Expand Down Expand Up @@ -34,10 +44,24 @@ const ShortTermMap = () => {
setMap(map);
setInfowindow(infowindow);

kakao.maps.event.addListener(marker, 'click', () => {
console.log(latitude, longitude);
navigate('/travel-selectInfo', { state: { latitude, longitude } });
});

if(isLongTerm) {
kakao.maps.event.addListener(marker, 'click', () => {
navigate('/travel-planner', {
state: {
gender: gender,
ageGroup: ageGroup,
latitude: latitude,
longitude: longitude
}
});
});
} else {
kakao.maps.event.addListener(marker, 'click', () => {
console.log(latitude, longitude);
navigate('/travel-selectInfo', { state: { latitude, longitude } });
});
}

kakao.maps.event.addListener(marker, 'mouseover', () => {
infowindow.setContent(`<div style="padding:5px;font-size:12px;"> 서울 시청 </div>`);
Expand Down
3 changes: 3 additions & 0 deletions src/data/long_trip_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const transports = [
'차가 있어요',
'대중교통',
];
export const transportsValue = [
'자가용', '대중교통 등'
];

export const activities = [
'체험',
Expand Down
42 changes: 41 additions & 1 deletion src/styles/long_trip.css
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,44 @@ button:hover {
width: 200px;
font-size: 36px;
font-family: "Regular";
}
}

/* long_trip.css 파일에 추가 */
.city-dropdown {
margin-bottom: 20px;
display: flex;
flex-direction: column;
align-items: center;
}

.city-dropdown label {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
color: #333;
}

.city-dropdown select {
width: 100%;
max-width: 400px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
outline: none;
transition: border-color 0.3s ease-in-out;
}

.city-dropdown select:focus {
border-color: #007bff;
}

.city-dropdown select option {
padding: 10px;
}

.city-dropdown select:hover {
cursor: pointer;
}
89 changes: 52 additions & 37 deletions src/views/longRecommendationResult.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';

import Header from '../components/Header';
import axios from 'axios';
import { convert as romanize } from 'hangul-romanization'; // 'convert' 함수를 'romanize'로 사용
import { convert as romanize } from 'hangul-romanization';
import '../styles/longRecommendationResult.css';
import CityModal from '../components/CityModal';
import ConfirmModal from '../components/ConfirmModal';
Expand All @@ -12,18 +11,18 @@ const fetchImages = async (query) => {
try {
const response = await axios.get(process.env.REACT_APP_PROXY + `/api/longstay/city-image/${query}`);
const data = response.data;
return data && data.items ? data.items.map(item => item.link) : []; // 모든 이미지 URL을 배열로 반환
return data && data.items ? data.items.map(item => item.link) : [];
} catch (error) {
console.error('Error fetching images:', error);
return []; // 에러 발생 시 빈 배열 반환
return [];
}
};

const fetchDescription = async (cityName) => {
try {
const response = await axios.get(process.env.REACT_APP_PROXY + `/api/longstay/city-description/${cityName}`);
return response.data;
} catch(e) {
} catch (e) {
console.error("Error fetching city description:", e);
return "설명을 불러오는 중 오류가 발생했습니다.";
}
Expand All @@ -39,46 +38,65 @@ export const removeCitySuffix = (cityName) => {
.replace('특별시', '')
.replace('광역시', '')
.replace('시', '')
.replace('군', '');
.replace('군', '')
.replace('구', '');
};

const LongRecommendationResult = () => {
const [cities, setCities] = useState([]);
const [modalOepn, setModalOpen] = useState(false);
const [confirmModalOepn, setConfirmModalOpen] = useState(false);
const [selectedCity, setSelectedCity] = useState(null);
// const [aiResponse, setAiResponse] = useState([]);

const location = useLocation();

const queryParams = new URLSearchParams(location.search);
const userId = queryParams.get('id') ?? '';
const startDate = queryParams.get('startDate');
const endDate = queryParams.get('endDate');
// location.state를 통해 데이터 받아오기
const { id, startDate, endDate, gender, ageGroup, cityCode, response } = location.state || {};

// useEffect(() => {
// // response가 있는 경우 처리
// if (response) {
// setAiResponse(response.split(',')); // ','로 나눠서 배열로 변환
// }
// }, [response]);

const getCityNames = () => {
return [
{ name: '서울특별시' },
{ name: '제주특별자치도' },
{ name: '수원' },
{ name: '대구광역시' },
{ name: '대관령' },
{ name: '부산광역시' },
{ name: '보성군' },
{ name: '속초시' },
];
// aiResponse가 null이거나 배열이 비어있으면 기본값 반환
if (!response || response.length === 0) {
return [
{ name: '서울특별시' },
{ name: '제주특별자치도' },
{ name: '수원' },
{ name: '대구광역시' },
{ name: '대관령' },
{ name: '부산광역시' },
{ name: '보성군' },
{ name: '속초시' },
];
} else {
const cityNames = [];
for (let i = 0; i < 8; i++) {
const city = response[i];
const cityName = city.split(' ')[1];
console.log("cityName: ",cityName);

cityNames.push({ name: cityName });
}
return cityNames;
}
};

useEffect(() => {
const cityNames = getCityNames();

const fetchCity = async () => {
const updatedCities = await Promise.all(cityNames.map(async (city) => {
try {
const imageUrls = await fetchImages(city.name + " 풍경"); // 이미지 URL 배열 가져오기
const displayName = removeCitySuffix(city.name); // 화면에 표시할 때의 이름 변환
const englishName = capitalizeFirstLetter(romanize(displayName)); // 한글 이름을 로마자로 변환
const imageUrls = await fetchImages(city.name + " 풍경");
const displayName = removeCitySuffix(city.name);
const englishName = capitalizeFirstLetter(romanize(displayName));
const description = await fetchDescription(city.name);
return { ...city, displayName: displayName, englishName, description, imageUrls, currentImageIndex: 0 }; // 변환된 이름 사용
return { ...city, displayName, englishName, description, imageUrls, currentImageIndex: 0 };
} catch (error) {
console.error(`Error fetching images for ${city.name}`, error);
return { ...city, displayName: removeCitySuffix(city.name), englishName: capitalizeFirstLetter(romanize(removeCitySuffix(city.name))), description: "", imageUrls: [], currentImageIndex: 0 };
Expand All @@ -88,14 +106,14 @@ const LongRecommendationResult = () => {
};

fetchCity();
}, []);
}, [response]);

const handleImageError = (cityIndex) => {
setCities(prevCities => {
const newCities = [...prevCities];
const city = newCities[cityIndex];
if (city.currentImageIndex < city.imageUrls.length - 1) {
city.currentImageIndex += 1; // 다음 이미지로 인덱스 증가
city.currentImageIndex += 1;
}
return newCities;
});
Expand All @@ -111,13 +129,10 @@ const LongRecommendationResult = () => {
};

const selectCity = async (city) => {
// DB 저장
if(userId !== '' && userId !== null) {
if (id) {
try {
const response = await axios.post("/api/longstay/save-schedule", {
'user': {
'id': userId
},
'user': { 'id': id },
'location': city.name,
'startDate': startDate,
'endDate': endDate,
Expand Down Expand Up @@ -149,7 +164,7 @@ const LongRecommendationResult = () => {
<img
src={city.imageUrls[city.currentImageIndex]}
alt={city.name}
onError={() => handleImageError(index)} // 이미지 로드 실패 시 다음 이미지로
onError={() => handleImageError(index)}
/>
) : (
<p>이미지를 불러올 수 없습니다.</p>
Expand All @@ -160,10 +175,10 @@ const LongRecommendationResult = () => {
))}
</ul>

<CityModal isOpen={modalOepn} onClose={closeModal} selectCity={selectCity} city={selectedCity}/>
{confirmModalOepn && <ConfirmModal isOpen={confirmModalOepn} onClose={closeConfirmModal} cityName={selectedCity.displayName}/>}
<CityModal isOpen={modalOepn} onClose={closeModal} selectCity={selectCity} city={selectedCity} />
{confirmModalOepn && <ConfirmModal isOpen={confirmModalOepn} onClose={closeConfirmModal} cityName={selectedCity.displayName} ageGroup={ageGroup} gender={gender} />}
</div>
);
}
};

export default LongRecommendationResult;
export default LongRecommendationResult;
Loading

0 comments on commit 6782858

Please sign in to comment.