diff --git a/src/pages/Home/components/CompanionSection.tsx b/src/pages/Home/components/CompanionSection.tsx new file mode 100644 index 0000000..d1b3ceb --- /dev/null +++ b/src/pages/Home/components/CompanionSection.tsx @@ -0,0 +1,27 @@ +import { DropdownItemType } from '../../../components/common/dropdown'; +import Dropdown from '../../../components/common/dropdown/Dropdown'; +import { withOptions } from '../../../constants/homeOption'; + +interface CompanionSectionProps { + onCompanionSelect: (item: DropdownItemType) => void; +} + +const CompanionSection = ({ onCompanionSelect, +}: CompanionSectionProps) => { + return ( +
+

누구와 함께 가시나요?

+
+ +
+
+ ); +}; + +export default CompanionSection; diff --git a/src/pages/Home/components/DateSection.tsx b/src/pages/Home/components/DateSection.tsx new file mode 100644 index 0000000..9cb9ce0 --- /dev/null +++ b/src/pages/Home/components/DateSection.tsx @@ -0,0 +1,16 @@ +import SelectBox from '../../../components/home/selectBox/SelectBox'; + +interface DateSectionProps { + onDateChange: (date: Date | null) => void; +} + +const DateSection = ({ onDateChange }: DateSectionProps) => { + return ( +
+

언제 방문할 예정인가요?

+ +
+ ); +}; + +export default DateSection; diff --git a/src/pages/Home/components/LocationSection.tsx b/src/pages/Home/components/LocationSection.tsx new file mode 100644 index 0000000..fcb8fa4 --- /dev/null +++ b/src/pages/Home/components/LocationSection.tsx @@ -0,0 +1,50 @@ +import { DropdownItemType, LocationItemType } from '../../../components/common/dropdown'; +import Dropdown from '../../../components/common/dropdown/Dropdown'; +import LocationDialog from '../../../components/home/locationDialog/LocationDialog'; +import { locationOptions } from '../../../constants/homeOption'; + +interface LocationSectionProps { + selectedLocation: LocationItemType; + showLocationDialog: boolean; + onLocationSelect: (item: DropdownItemType) => void; + onLocationConfirm: (lat: number, lng: number, radius: number) => void; + onCloseLocationDialog: () => void; +} + +const LocationSection = ({ + selectedLocation, + showLocationDialog, + onLocationSelect, + onLocationConfirm, + onCloseLocationDialog, +}: LocationSectionProps) => { + return ( +
+

어디를 방문할 예정인가요?

+
+
+ +
+
+

+ * 여기를 누르면 지도에서 지역을 선택할 수 있어요 +

+ {showLocationDialog && selectedLocation.lat && selectedLocation.lng && ( + + )} +
+ ); +}; + +export default LocationSection; diff --git a/src/pages/Home/components/PlacesSection.tsx b/src/pages/Home/components/PlacesSection.tsx new file mode 100644 index 0000000..87ed4a5 --- /dev/null +++ b/src/pages/Home/components/PlacesSection.tsx @@ -0,0 +1,44 @@ +import Chip from '../../../components/common/chip/Chip'; +import { chipOptions } from '../../../constants/homeOption'; + +interface PlacesSectionProps { + selectedPlaces: Record; + onPlaceSelect: (place: string) => void; +} + +const PlacesSection = ({ + selectedPlaces, + onPlaceSelect, +}: PlacesSectionProps) => { + return ( +
+

어떤 곳을 방문하고 싶나요?

+
+ {chipOptions.slice(0, 4).map((option) => ( +
onPlaceSelect(option)}> + + {option} + +
+ ))} +
+
+ {chipOptions.slice(4).map((option) => ( +
onPlaceSelect(option)}> + + {option} + +
+ ))} +
+
+ ); +}; + +export default PlacesSection; diff --git a/src/pages/Home/hooks.ts b/src/pages/Home/hooks.ts new file mode 100644 index 0000000..01925eb --- /dev/null +++ b/src/pages/Home/hooks.ts @@ -0,0 +1,85 @@ +import { useState, useEffect } from 'react'; +import { useMutation } from 'react-query'; +import { useNavigate } from 'react-router-dom'; +import { fetchRecommendations } from '../../api/recommendation'; +import { LocationItemType, DropdownItemType } from '../../components/common/dropdown'; +import { locationOptions, withOptions, chipOptions } from '../../constants/homeOption'; +import { calculateRadiusInMeters } from './utils'; + +interface MutationContext { + selectedDate: Date | null; + selectedLocation: LocationItemType; + selectedCompanion: DropdownItemType; +} + +export const useHomeState = () => { + const [selectedDate, setSelectedDate] = useState(null); + const [selectedLocation, setSelectedLocation] = useState(locationOptions[0]); + const [selectedCompanion, setSelectedCompanion] = useState(withOptions[0]); + const [selectedPlaces, setSelectedPlaces] = useState>( + chipOptions.reduce((acc, option) => ({ ...acc, [option]: false }), {}) + ); + const [showLocationDialog, setShowLocationDialog] = useState(false); + const [isAllSelected, setIsAllSelected] = useState(false); + + useEffect(() => { + const dateSelected = selectedDate !== null; + const locationSelected = selectedLocation.id !== 0; + const companionSelected = selectedCompanion.id !== 0; + const placesSelected = Object.values(selectedPlaces).some(Boolean); + + setIsAllSelected(dateSelected && locationSelected && companionSelected && placesSelected); + }, [selectedDate, selectedLocation, selectedCompanion, selectedPlaces]); + + return { + selectedDate, + setSelectedDate, + selectedLocation, + setSelectedLocation, + selectedCompanion, + setSelectedCompanion, + selectedPlaces, + setSelectedPlaces, + showLocationDialog, + setShowLocationDialog, + isAllSelected + }; +}; + +export const useRecommendation = () => { + const navigate = useNavigate(); + + return useMutation(fetchRecommendations, { + onMutate: async (variables) => { + return { + selectedDate: variables.selectedDate, + selectedLocation: variables.selectedLocation, + selectedCompanion: variables.selectedCompanion, + }; + }, + onSuccess: (data, variables, context) => { + if (Array.isArray(data) && data.length === 0) { + alert('선택하신 조건에 맞는 추천 결과를 찾지 못했습니다.\n다른 조건으로 다시 시도해보세요.'); + return; + } + + if (context) { + const { selectedDate, selectedLocation, selectedCompanion } = context; + + navigate('/result', { + state: { + recommendResults: data, + selectedDate, + selectedLocation, + selectedCompanion, + radius: calculateRadiusInMeters(selectedLocation), + }, + }); + } + }, + onError: (error) => { + console.error('Recommendation Error:', error); + alert('추천 과정에서 오류가 발생했습니다. 다시 시도해주세요.'); + }, + }); +}; diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 4cc2344..c2ec61c 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,135 +1,29 @@ -import { useEffect, useState } from 'react'; -import { useMutation } from 'react-query'; -import { useNavigate } from 'react-router-dom'; - -import { fetchRecommendations } from '../../api/recommendation'; import Button from '../../components/common/button/Button'; -import Chip from '../../components/common/chip/Chip'; -import { - DropdownItemType, - LocationItemType, -} from '../../components/common/dropdown'; -import Dropdown from '../../components/common/dropdown/Dropdown'; -import LocationDialog from '../../components/home/locationDialog/LocationDialog'; -import SelectBox from '../../components/home/selectBox/SelectBox'; -import { - locationOptions, - withOptions, - chipOptions, -} from '../../constants/homeOption'; -import { - transformAgeToGroup, - transformGender, - transformTravelStyle, -} from '../../utils/onboardingTransformer'; +import { DropdownItemType } from '../../components/common/dropdown'; +import { createRecommendationRequest } from './utils'; +import { useHomeState, useRecommendation } from './hooks'; +import DateSection from './components/DateSection'; +import LocationSection from './components/LocationSection'; +import CompanionSection from './components/CompanionSection'; +import PlacesSection from './components/PlacesSection'; const Home = () => { - const navigate = useNavigate(); - const [selectedDate, setSelectedDate] = useState(null); - const [selectedLocation, setSelectedLocation] = useState( - locationOptions[0], - ); - const [selectedCompanion, setSelectedCompanion] = useState( - withOptions[0], - ); - const [selectedPlaces, setSelectedPlaces] = useState>( - chipOptions.reduce((acc, option) => ({ ...acc, [option]: false }), {}), - ); - const [showLocationDialog, setShowLocationDialog] = useState(false); + const { + selectedDate, + setSelectedDate, + selectedLocation, + setSelectedLocation, + selectedCompanion, + setSelectedCompanion, + selectedPlaces, + setSelectedPlaces, + showLocationDialog, + setShowLocationDialog, + isAllSelected + } = useHomeState(); + + const recommendMutation = useRecommendation(); - const calculateRadiusInMeters = (location: LocationItemType) => { - return (location.radius || 0.5) * 1000; - }; - - const recommendMutation = useMutation(fetchRecommendations, { - onSuccess: (data) => { - navigate('/result', { - state: { - recommendResults: data, - selectedDate, - selectedLocation, - selectedCompanion, - radius: calculateRadiusInMeters(selectedLocation), - }, - }); - }, - onError: (error) => { - console.error('Failed to get recommendations:', error); - }, - }); - - const handleRecommendation = () => { - if (isAllSelected) { - const selectedPlaceTypes = Object.entries(selectedPlaces) - .filter(([_, isSelected]) => isSelected) - .map(([placeType]) => placeType); - - const radiusInMeters = calculateRadiusInMeters(selectedLocation); - - const gender = transformGender(localStorage.getItem('gender') || 'MALE'); - const ageGroup = transformAgeToGroup( - localStorage.getItem('generation') || 'YOUNG_ADULT', - ); - - const travelStyleStr = localStorage.getItem('travelStyle'); - const travelStyle = travelStyleStr ? JSON.parse(travelStyleStr) : {}; - - const travelStyleValues = transformTravelStyle( - travelStyle.areaType || 'NATURE', - travelStyle.familiarity || 'NEW', - travelStyle.activityType || 'REST', - travelStyle.popularity || 'NOT_WIDELY_KNOWN', - travelStyle.planningType || 'PLANNED', - travelStyle.photoPriority || 'NOT_IMPORTANT', - ); - - const requestData = { - GUNGU: selectedLocation.text, - LONGITUDE: Number(selectedLocation.lng) || 0, - LATITUDE: Number(selectedLocation.lat) || 0, - RADIUS: radiusInMeters, - AGE_GRP: ageGroup, - GENDER: gender, - TRAVEL_STYL_1: travelStyleValues.TRAVEL_STYL_1, - TRAVEL_STYL_2: travelStyleValues.TRAVEL_STYL_2, - TRAVEL_STYL_3: travelStyleValues.TRAVEL_STYL_3, - TRAVEL_STYL_4: travelStyleValues.TRAVEL_STYL_4, - TRAVEL_STYL_5: travelStyleValues.TRAVEL_STYL_5, - TRAVEL_STYL_6: travelStyleValues.TRAVEL_STYL_6, - TRAVEL_STATUS_ACCOMPANY: selectedCompanion.text, - VISIT_AREA_TYPE_CD: selectedPlaceTypes[0], - }; - - console.log('Sending Request Data:', requestData); - - recommendMutation.mutate(requestData, { - onSuccess: (data) => { - console.log('Recommendation Success Data:', data); - - if (Array.isArray(data) && data.length === 0) { - alert( - '선택하신 조건에 맞는 추천 결과를 찾지 못했습니다.\n다른 조건으로 다시 시도해보세요.', - ); - return; - } - - navigate('/result', { - state: { - recommendResults: data, - selectedDate, - selectedLocation, - selectedCompanion, - state: calculateRadiusInMeters(selectedLocation), - }, - }); - }, - onError: (error) => { - console.error('Recommendation Error:', error); - alert('추천 과정에서 오류가 발생했습니다. 다시 시도해주세요.'); - }, - }); - } - }; const handleDateChange = (date: Date | null) => { setSelectedDate(date); console.log('날짜 변경 :', date); @@ -164,20 +58,30 @@ const Home = () => { setShowLocationDialog(false); }; - // 전부 선택했는지 - const [isAllSelected, setIsAllSelected] = useState(false); - - useEffect(() => { - const dateSelected = selectedDate !== null; - const locationSelected = selectedLocation.id !== 0; - const companionSelected = selectedCompanion.id !== 0; - const placesSelected = Object.values(selectedPlaces).some(Boolean); - - const allSelected = - dateSelected && locationSelected && companionSelected && placesSelected; + const handleRecommendation = () => { + if (isAllSelected) { + const requestData = createRecommendationRequest( + selectedLocation, + selectedCompanion, + selectedPlaces + ); - setIsAllSelected(allSelected); - }, [selectedDate, selectedLocation, selectedCompanion, selectedPlaces]); + console.log('Sending Request Data:', requestData); + recommendMutation.mutate({ + ...requestData, + selectedDate, + selectedLocation, + selectedCompanion, + }, { + onSuccess: (data) => { + if (Array.isArray(data) && data.length === 0) { + alert('선택하신 조건에 맞는 추천 결과를 찾지 못했습니다.\n다른 조건으로 다시 시도해보세요.'); + return; + } + } + }); + } + }; return (
@@ -190,69 +94,24 @@ const Home = () => {

추천받고 싶은 코스에 대한 정보를 입력해주세요

-

언제 방문할 예정인가요?

- + -

- 어디를 방문할 예정인가요? -

-
-
- -
-
-

- * 여기를 누르면 지도에서 지역을 선택할 수 있어요 -

+ -

누구와 방문할 예정인가요?

+ -
-
- -
-
+ -

- 어떤 곳을 방문하고 싶나요? -

-
- {chipOptions.slice(0, 4).map((option) => ( -
handlePlaceSelect(option)}> - - {option} - -
- ))} -
-
- {chipOptions.slice(4).map((option) => ( -
handlePlaceSelect(option)}> - - {option} - -
- ))} -
- {showLocationDialog && selectedLocation.lat && selectedLocation.lng && ( - - )} ); }; diff --git a/src/pages/Home/types.ts b/src/pages/Home/types.ts new file mode 100644 index 0000000..18e28b6 --- /dev/null +++ b/src/pages/Home/types.ts @@ -0,0 +1,25 @@ +import { DropdownItemType, LocationItemType } from '../../components/common/dropdown'; + +export interface RecommendationRequest { + GUNGU: string; + LONGITUDE: number; + LATITUDE: number; + RADIUS: number; + AGE_GRP: string; + GENDER: string; + TRAVEL_STYL_1: string; + TRAVEL_STYL_2: string; + TRAVEL_STYL_3: string; + TRAVEL_STYL_4: string; + TRAVEL_STYL_5: string; + TRAVEL_STYL_6: string; + TRAVEL_STATUS_ACCOMPANY: string; + VISIT_AREA_TYPE_CD: string; +} + +export interface HomeState { + selectedDate: Date | null; + selectedLocation: LocationItemType; + selectedCompanion: DropdownItemType; + selectedPlaces: Record; +} diff --git a/src/pages/Home/utils.ts b/src/pages/Home/utils.ts new file mode 100644 index 0000000..2f80604 --- /dev/null +++ b/src/pages/Home/utils.ts @@ -0,0 +1,56 @@ +import { LocationItemType } from '../../components/common/dropdown'; +import { RecommendationRequest } from './types'; +import { + transformAgeToGroup, + transformGender, + transformTravelStyle, +} from '../../utils/onboardingTransformer'; + +export const calculateRadiusInMeters = (location: LocationItemType): number => { + return (location.radius || 0.5) * 1000; +}; + +export const createRecommendationRequest = ( + selectedLocation: LocationItemType, + selectedCompanion: { text: string }, + selectedPlaces: Record, +): RecommendationRequest => { + const selectedPlaceTypes = Object.entries(selectedPlaces) + .filter(([_, isSelected]) => isSelected) + .map(([placeType]) => placeType); + + const radiusInMeters = calculateRadiusInMeters(selectedLocation); + const gender = transformGender(localStorage.getItem('gender') || 'MALE'); + const ageGroup = transformAgeToGroup( + localStorage.getItem('generation') || 'YOUNG_ADULT', + ); + + const travelStyleStr = localStorage.getItem('travelStyle'); + const travelStyle = travelStyleStr ? JSON.parse(travelStyleStr) : {}; + + const travelStyleValues = transformTravelStyle( + travelStyle.areaType || 'NATURE', + travelStyle.familiarity || 'NEW', + travelStyle.activityType || 'REST', + travelStyle.popularity || 'NOT_WIDELY_KNOWN', + travelStyle.planningType || 'PLANNED', + travelStyle.photoPriority || 'NOT_IMPORTANT', + ); + + return { + GUNGU: selectedLocation.text, + LONGITUDE: Number(selectedLocation.lng) || 0, + LATITUDE: Number(selectedLocation.lat) || 0, + RADIUS: radiusInMeters, + AGE_GRP: ageGroup, + GENDER: gender, + TRAVEL_STYL_1: travelStyleValues.TRAVEL_STYL_1, + TRAVEL_STYL_2: travelStyleValues.TRAVEL_STYL_2, + TRAVEL_STYL_3: travelStyleValues.TRAVEL_STYL_3, + TRAVEL_STYL_4: travelStyleValues.TRAVEL_STYL_4, + TRAVEL_STYL_5: travelStyleValues.TRAVEL_STYL_5, + TRAVEL_STYL_6: travelStyleValues.TRAVEL_STYL_6, + TRAVEL_STATUS_ACCOMPANY: selectedCompanion.text, + VISIT_AREA_TYPE_CD: selectedPlaceTypes[0], + }; +};