Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
74 changes: 74 additions & 0 deletions src/pages/Result/LocationManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useState } from 'react';
import { LocationType } from '../../types/location';
import { v4 as uuid } from 'uuid';

interface LocationManagerProps {
recommendResults: any[];
onAddressAvailabilityChange: (isAvailable: boolean) => void;
onInformationAvailabilityChange: (isAvailable: boolean) => void;
}

export const useLocationManager = ({
recommendResults,
onAddressAvailabilityChange,
onInformationAvailabilityChange,
}: LocationManagerProps) => {
const [locations, setLocations] = useState<LocationType[]>(() => {
try {
if (!recommendResults || !Array.isArray(recommendResults)) {
console.warn('Invalid recommendResults:', recommendResults);
return [];
}

return recommendResults.map((item, index) => {
const lat = item.lat || 0;
const lng = item.lng || 0;
const address = item.address || '주소 정보 없음';

if (address === '주소 정보 없음') {
onAddressAvailabilityChange(false);
}

if (
address === '주소 정보 없음' &&
item.operationHour === '영업시간 정보 없음' &&
item.phoneNumber === '전화번호 정보 없음'
) {
onInformationAvailabilityChange(false);
}

return {
id: uuid(),
name: item.name || '이름 없음',
courseType: item.courseType || '미분류',
address,
operationHour: item.operationHour || '영업시간 정보 없음',
phoneNumber: item.phoneNumber || '전화번호 정보 없음',
lat,
lng,
isSelected: index === 0,
image: '/src/assets/image/placeholder.jpg',
};
});
} catch (error) {
console.error('Error transforming recommendations:', error);
return [];
}
});

const handleLocationSelect = (lat: number, lng: number, isMarker?: boolean) => {
if (isMarker) {
setLocations((prevLocations) =>
prevLocations.map((location) => ({
...location,
isSelected: location.lat === lat && location.lng === lng,
})),
);
}
};

return {
locations,
handleLocationSelect,
};
};
53 changes: 53 additions & 0 deletions src/pages/Result/WeatherForecast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useEffect, useState } from 'react';
import { getWeatherForecastApi } from '../../api/weatherForecast';
import { LocationType } from '../../types/location';

interface WeatherForecastProps {
selectedDate: Date | null;
selectedLocation: LocationType | null;
onWeatherUpdate: (weather: string) => void;
}

export const useWeatherForecast = ({
selectedDate,
selectedLocation,
onWeatherUpdate,
}: WeatherForecastProps) => {
const [weatherForecast, setWeatherForecast] = useState<string>('날씨 조회 중...');

useEffect(() => {
const fetchWeatherForecast = async () => {
if (!selectedDate || !selectedLocation?.lat || !selectedLocation?.lng) {
const defaultWeather = '화창할';
setWeatherForecast(defaultWeather);
onWeatherUpdate(defaultWeather);
return;
}

try {
const formattedDate = selectedDate
.toISOString()
.split('T')[0]
.replace(/-/g, '');
const response = await getWeatherForecastApi({
date: formattedDate,
latitude: selectedLocation.lat,
longitude: selectedLocation.lng,
});

const weather = response.result === '날씨 정보를 찾을 수 없습니다.' ? '화창할' : response.result;
setWeatherForecast(weather);
onWeatherUpdate(weather);
} catch (error) {
console.error('날씨 정보 조회 실패:', error);
const defaultWeather = '화창할';
setWeatherForecast(defaultWeather);
onWeatherUpdate(defaultWeather);
}
};

fetchWeatherForecast();
}, [selectedDate, selectedLocation, onWeatherUpdate]);

return weatherForecast;
};
187 changes: 52 additions & 135 deletions src/pages/Result/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { useState, useRef, useCallback, useEffect } from 'react';
import { useState, useRef, useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';

import { getWeatherForecastApi } from '../../api/weatherForecast';
import CoursePreview from '../../components/home/coursePreview/CoursePreview';
import { useMapCenter } from '../../hooks/useMapCenter';
import { useRandomCongestion } from '../../hooks/useRandomCongestion';
import { Topbar } from '../../layouts';
import { Topbar, Navigation } from '../../layouts';
import Map from '../../pages/Home/Map';
import { LocationType } from '../../types/location';
import { useWeatherForecast } from './WeatherForecast';
import { useLocationManager } from './LocationManager';

const Result = () => {
const navigate = useNavigate();
const location = useLocation();
const [isExpanded, setIsExpanded] = useState(false);
const mapRef = useRef<HTMLDivElement>(null);
const [weatherForecast, setWeatherForecast] =
useState<string>('날씨 조회 중...');
const [weatherForecast, setWeatherForecast] = useState<string>('날씨 조회 중...');
const [isAddress, setIsAddress] = useState<boolean>(true);
const [isInformation, setIsInformation] = useState<boolean>(true);

Expand All @@ -24,108 +23,20 @@ const Result = () => {
const { recommendResults, selectedDate, selectedLocation, radius } =
location.state || {};

const [locations, setLocations] = useState<LocationType[]>(() => {
try {
if (!recommendResults || !Array.isArray(recommendResults)) {
console.warn('Invalid recommendResults:', recommendResults);
return [];
}

// recommendResults가 직접 배열로 들어오므로 그대로 매핑
return recommendResults.map((item, index) => {
// 위도, 경도 변환
const lat = parseFloat(String(item.lat)) || 0;
const lng = parseFloat(String(item.lng)) || 0;

if (item.address === '주소 정보 없음') {
setIsAddress(false);
}

if (
item.address === '주소 정보 없음' &&
item.operationHour === '영업시간 정보 없음' &&
item.phoneNumber === '전화번호 정보 없음'
) {
setIsInformation(false);
}

// 주소 정보 처리
// const description = [
// item.ROAD_NM_ADDR || item.LOTNO_ADDR || '주소 정보 없음',
// `영업시간: ${item.OPERATION_HOUR || '정보 없음'}`,
// `전화번호: ${item.PHONE_NUMBER || '정보 없음'}`,
// ]
// .filter(Boolean)
// .join('\n');

return {
id: item.id || '',
name: item.name || '이름 없음',
courseType: item.courseType || '미분류',
address: item.address,
operationHour: item.operationHour,
phoneNumber: item.phoneNumber,
lat,
lng,
isSelected: index === 0,
image: '/src/assets/image/placeholder.jpg',
};
});
} catch (error) {
console.error('Error transforming recommendations:', error);
return [];
}
const { locations, handleLocationSelect } = useLocationManager({
recommendResults,
onAddressAvailabilityChange: setIsAddress,
onInformationAvailabilityChange: setIsInformation,
});

useEffect(() => {
const fetchWeatherForecast = async () => {
if (!selectedDate || !selectedLocation?.lat || !selectedLocation?.lng) {
setWeatherForecast('화창할');
return;
}

try {
const formattedDate = selectedDate
.toISOString()
.split('T')[0]
.replace(/-/g, '');
const response = await getWeatherForecastApi({
date: formattedDate,
latitude: selectedLocation.lat,
longitude: selectedLocation.lng,
});
console.log('날씨 response : ', response);

if (response.result === '날씨 정보를 찾을 수 없습니다.') {
setWeatherForecast('화창할');
} else {
setWeatherForecast(`${response.result}`);
}
} catch (error) {
console.error('날씨 정보 조회 실패:', error);
setWeatherForecast('화창할');
}
};

fetchWeatherForecast();
}, [selectedDate, selectedLocation]);
useWeatherForecast({
selectedDate,
selectedLocation,
onWeatherUpdate: setWeatherForecast,
});

const { mapCenter, mapBounds } = useMapCenter(locations);

const handleLocationSelect = useCallback(
(lat: number, lng: number, isMarker?: boolean) => {
if (isMarker) {
setLocations((prevLocations) =>
prevLocations.map((location) => ({
...location,
isSelected: location.lat === lat && location.lng === lng,
})),
);
}
},
[],
);

const handleExpand = (expanded: boolean) => {
setIsExpanded(expanded);
};
Expand All @@ -142,44 +53,50 @@ const Result = () => {
}

return (
<div className="h-full w-full overflow-x-hidden relative">
<div className="h-screen w-full flex flex-col relative">
<div className="sticky top-0 z-50 bg-white border-none">
<Topbar handleClick={() => navigate('/')} />
</div>

<div className="relative z-20 border-t-0">
<CoursePreview
date={
selectedDate ? selectedDate.toLocaleDateString() : '날짜 미지정'
}
place={selectedLocation?.text || locations[0]?.name || '위치 미지정'}
weather={weatherForecast}
companions={randomCongestion}
locations={locations}
isExpanded={isExpanded}
onExpand={handleExpand}
isAddress={isAddress}
isInformation={isInformation}
/>
<div className="flex-1 overflow-y-auto">
<div className="relative z-20 border-t-0">
<CoursePreview
date={
selectedDate ? selectedDate.toLocaleDateString() : '날짜 미지정'
}
place={selectedLocation?.text || locations[0]?.name || '위치 미지정'}
weather={weatherForecast}
companions={randomCongestion}
locations={locations}
isExpanded={isExpanded}
onExpand={handleExpand}
isAddress={isAddress}
isInformation={isInformation}
/>
</div>

<div
ref={mapRef}
className={`h-[453px] w-full relative ${isExpanded ? 'blur-sm' : ''}`}
>
<Map
lat={mapCenter.lat}
lng={mapCenter.lng}
locations={locations}
onLocationSelect={handleLocationSelect}
mapBounds={mapBounds}
/>
{isExpanded && (
<div
className="absolute inset-0 bg-black bg-opacity-50 z-10"
onClick={() => setIsExpanded(false)}
/>
)}
</div>
</div>

<div
ref={mapRef}
className={`h-[453px] w-full relative ${isExpanded ? 'blur-sm' : ''}`}
>
<Map
lat={mapCenter.lat}
lng={mapCenter.lng}
locations={locations}
onLocationSelect={handleLocationSelect}
mapBounds={mapBounds}
/>
{isExpanded && (
<div
className="absolute inset-0 bg-black bg-opacity-50 z-10"
onClick={() => setIsExpanded(false)}
/>
)}
<div className="sticky bottom-0 z-50">
<Navigation />
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/types/location.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface LocationType {
id: number;
id: string;
name: string;
courseType: string;
isSelected: boolean;
Expand Down