Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/pages/Home/components/CompanionSection.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="mt-[23px]">
<p className="text-body4 text-bk-70 mb-2">누구와 함께 가시나요?</p>
<div className="w-[280px]">
<Dropdown
dropdownItems={withOptions}
size="lg"
type="sub"
onSelect={onCompanionSelect}
isHome={true}
/>
</div>
</div>
);
};

export default CompanionSection;
16 changes: 16 additions & 0 deletions src/pages/Home/components/DateSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SelectBox from '../../../components/home/selectBox/SelectBox';

interface DateSectionProps {
onDateChange: (date: Date | null) => void;
}

const DateSection = ({ onDateChange }: DateSectionProps) => {
return (
<div>
<p className="text-body4 text-bk-70 mb-2">언제 방문할 예정인가요?</p>
<SelectBox onChange={onDateChange} />
</div>
);
};

export default DateSection;
50 changes: 50 additions & 0 deletions src/pages/Home/components/LocationSection.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='mt-[31px]'>
<p className="text-body4 text-bk-70">어디를 방문할 예정인가요?</p>
<div className="my-[7px] w-[280px]">
<div className="w-full">
<Dropdown
dropdownItems={locationOptions}
size="lg"
type="sub"
onSelect={onLocationSelect}
isHome={true}
/>
</div>
</div>
<p className="text-body6 text-sub-300">
* 여기를 누르면 지도에서 지역을 선택할 수 있어요
</p>
{showLocationDialog && selectedLocation.lat && selectedLocation.lng && (
<LocationDialog
lat={selectedLocation.lat}
lng={selectedLocation.lng}
onClose={onCloseLocationDialog}
onConfirm={onLocationConfirm}
/>
)}
</div>
);
};

export default LocationSection;
45 changes: 45 additions & 0 deletions src/pages/Home/components/PlacesSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import Chip from '../../../components/common/chip/Chip';
import { chipOptions } from '../../../constants/homeOption';

interface PlacesSectionProps {
selectedPlaces: Record<string, boolean>;
onPlaceSelect: (place: string) => void;
}

const PlacesSection: React.FC<PlacesSectionProps> = ({
selectedPlaces,
onPlaceSelect,
}) => {
return (
<div className="mt-[31px]">
<p className="text-body3 text-bk-70 mb-[13px]">어떤 곳을 방문하고 싶나요?</p>
<div className="grid grid-cols-4 gap-x-[7px] gap-y-[11px] mb-[11px] ml-[-15px]">
{chipOptions.slice(0, 4).map((option) => (
<div key={option} onClick={() => onPlaceSelect(option)}>
<Chip
size="md"
state={selectedPlaces[option] ? 'active' : 'inactive'}
>
{option}
</Chip>
</div>
))}
</div>
<div className="grid grid-cols-3 gap-x-[7px] gap-y-[11px] ml-[25px]">
{chipOptions.slice(4).map((option) => (
<div key={option} onClick={() => onPlaceSelect(option)}>
<Chip
size="md"
state={selectedPlaces[option] ? 'active' : 'inactive'}
>
{option}
</Chip>
</div>
))}
</div>
</div>
);
};

export default PlacesSection;
85 changes: 85 additions & 0 deletions src/pages/Home/hooks.ts
Original file line number Diff line number Diff line change
@@ -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 { createRecommendationRequest, calculateRadiusInMeters } from './utils';

interface MutationContext {
selectedDate: Date | null;
selectedLocation: LocationItemType;
selectedCompanion: DropdownItemType;
}

export const useHomeState = () => {
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const [selectedLocation, setSelectedLocation] = useState<LocationItemType>(locationOptions[0]);
const [selectedCompanion, setSelectedCompanion] = useState<DropdownItemType>(withOptions[0]);
const [selectedPlaces, setSelectedPlaces] = useState<Record<string, boolean>>(
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<any, unknown, any, MutationContext>(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('추천 과정에서 오류가 발생했습니다. 다시 시도해주세요.');
},
});
};
Loading