From 91f9e06f4ca718b9185c178e7f1c67a1c2fa2ddf Mon Sep 17 00:00:00 2001 From: KangBoSeok Date: Wed, 11 Dec 2024 14:54:00 +0900 Subject: [PATCH] =?UTF-8?q?feat(#75)=20:=20=EB=B0=B0=ED=8F=AC=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=20=EC=8B=9C=EC=84=A4=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/place/placedetail/page.jsx | 45 +++++-- src/app/place/placemap/page.jsx | 135 +++++++++++++++++-- src/app/place/placesearch/page.jsx | 153 +++++++++++++++++++--- src/components/place/placemap/Map/Map.jsx | 88 +++++++++---- 4 files changed, 356 insertions(+), 65 deletions(-) diff --git a/src/app/place/placedetail/page.jsx b/src/app/place/placedetail/page.jsx index cf00fd7..fe32a56 100644 --- a/src/app/place/placedetail/page.jsx +++ b/src/app/place/placedetail/page.jsx @@ -25,7 +25,7 @@ const PlaceDetailPage = () => { const ActualPlaceDetailPage = () => { const searchParams = useSearchParams(); - const id = parseInt(searchParams.get("id"), 10); + const placeId = parseInt(searchParams.get("placeId"), 10); const router = useRouter(); const [isClient, setIsClient] = useState(false); const [isMapBottomSheetOpen, setIsMapBottomSheetOpen] = useState(false); @@ -36,7 +36,8 @@ const ActualPlaceDetailPage = () => { const [isUploadAction, setIsUploadAction] = useState(false); const fileInputRef = useRef(null); const { setPlaceName, setVisitDate, setCategory } = useReviewStore(); - const selectedCard = cards.find((card) => card.id === id); + const [selectedCard, setSelectedCard] = useState(null); + const [isLoading, setIsLoading] = useState(true); const { isLoaded } = useJsApiLoader({ googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY, @@ -54,21 +55,37 @@ const ActualPlaceDetailPage = () => { if (data.status === "OK" && data.results.length > 0) { const { lat, lng } = data.results[0].geometry.location; setCenter({ lat, lng }); - } else { - alert("주소에 대한 좌표를 가져올 수 없습니다."); } } catch (error) { console.error("Error fetching coordinates:", error); } }; + useEffect(() => { + if (placeId) { + fetchPlaceDetails(placeId); + } + }, [placeId]); + const fetchPlaceDetails = async (placeId) => { + try { + const response = await axios.get(`https://api.daengplace.com/places/${placeId}`); + console.log(response.data); + setSelectedCard(response.data.data); + } catch (error) { + console.error("Error fetching place details:", error); + setSelectedCard(null); + } finally { + setIsLoading(false); + } + }; + useEffect(() => { if (selectedCard) { - setPlaceName(selectedCard.title); + setPlaceName(selectedCard.name); setCategory(selectedCard.category); setIsClient(true); - setAddress(selectedCard.address); - fetchCoordinates(selectedCard.address); + setAddress(selectedCard.location); + fetchCoordinates(selectedCard.location); } }, [selectedCard]); @@ -168,12 +185,18 @@ const ActualPlaceDetailPage = () => { diff --git a/src/app/place/placemap/page.jsx b/src/app/place/placemap/page.jsx index de4bb91..b54f760 100644 --- a/src/app/place/placemap/page.jsx +++ b/src/app/place/placemap/page.jsx @@ -4,6 +4,7 @@ import styled from "styled-components"; import { useRouter, useSearchParams } from "next/navigation"; import { useJsApiLoader } from "@react-google-maps/api"; import { sidoOptions, gunguOptions } from "@/data/data"; +import axios from "axios"; import SearchBar from "@/components/place/placemap/SearchBar/SearchBar"; import Tabs from "@/components/place/placemap/Tabs/Tabs"; import FilterButtons from "@/components/place/placemap/FilterButtons/FilterButtons"; @@ -40,11 +41,13 @@ const ActualPlaceMap = () => { const [selectedSido, setSelectedSido] = useState(""); const [selectedGungu, setSelectedGungu] = useState(""); const [showGunguDropdown, setShowGunguDropdown] = useState(false); + const [allMarkers, setAllMarkers] = useState([]); + const [markers, setMarkers] = useState([]); const { isLoaded } = useJsApiLoader({ googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY, }); - + useEffect(() => { if (!isNaN(lat) && !isNaN(lng)) { setCenter({ lat, lng }); @@ -53,6 +56,38 @@ const ActualPlaceMap = () => { } }, [lat, lng]); + useEffect(() => { + if (userLocation) { + setSelectedCategory("전체"); + fetchPlaces(userLocation.lat, userLocation.lng); + } + }, [userLocation]); + + const fetchPlaces = async (latitude, longitude) => { + try { + const response = await axios.get("https://api.daengplace.com/places", { + params: { latitude, longitude, page: 1, size: 20 }, + }); + const places = response.data.data.places || []; + const fetchedMarkers = places.map((place) => ({ + id: place.placeId, + position: { lat: place.latitude, lng: place.longitude }, + name: place.name, + category: place.category, + is_parking: place.is_parking, + inside: place.inside, + outside: place.outside, + weight_limit: place.weight_limit, + pet_fee: place.pet_fee, + operationHour: place.operationHour, + })); + setAllMarkers(fetchedMarkers); + setMarkers(fetchedMarkers); + console.log(places); + } catch (error) { + console.error("Error fetching places:", error); + } + }; const handleSidoChange = (event) => { const value = event.target.value; setSelectedSido(value); @@ -63,12 +98,96 @@ const ActualPlaceMap = () => { const handleGunguChange = (event) => setSelectedGungu(event.target.value); const handleBackToList = () => { - router.push("/place/placesearch"); + if (!isNaN(lat) && !isNaN(lng)) { + router.push(`/place/placesearch?lat=${lat}&lng=${lng}`); + } else { + alert("지도 데이터를 불러올 수 없습니다."); + } + }; + + const categoryMapping = { + 미용: "서비스", + 반려동물용품: "서비스", + 위탁관리: "서비스", + 식당: "음식점", + 카페: "음식점", + 문예회관: "문화시설", + 박물관: "문화시설", + 미술관: "문화시설", + 여행지: "문화시설", + 동물병원: "의료시설", + 동물약국: "의료시설", }; const handleCategoryClick = (category) => { setSelectedCategory(category); - setSelectedFilters([]); + const updatedFilters = [...selectedFilters]; + filterMarkers(category, selectedFilters); + }; + + const handleFilterClick = (filter) => { + setSelectedFilters((prevFilters) => { + const updatedFilters = prevFilters.includes(filter) + ? prevFilters.filter((f) => f !== filter) + : [...prevFilters, filter]; + filterMarkers(selectedCategory, updatedFilters); + return updatedFilters; + }); + }; + + const filterMarkers = (category, filters) => { + let filteredMarkers = allMarkers; + + if (category !== "전체") { + filteredMarkers = filteredMarkers.filter((marker) => { + const mainCategory = categoryMapping[marker.category]; + return mainCategory === category; + }); + } + + if (filters.length > 0) { + filters.forEach((filter) => { + if (filter === "주차 가능") { + filteredMarkers = filteredMarkers.filter((marker) => marker.is_parking); + } else if (filter === "실내공간") { + filteredMarkers = filteredMarkers.filter((marker) => marker.inside); + } else if (filter === "야외공간") { + filteredMarkers = filteredMarkers.filter((marker) => marker.outside); + } else if (filter === "무게 제한 없음") { + filteredMarkers = filteredMarkers.filter((marker) => marker.weight_limit === 0); + } else if (filter === "애견 동반 요금 없음") { + filteredMarkers = filteredMarkers.filter((marker) => marker.pet_fee === 0); + } else if (filter === "영업중") { + filteredMarkers = filteredMarkers.filter((marker) => { + const { todayOpen, todayClose } = marker.operationHour || {}; + if (!todayOpen || !todayClose) return false; + + const now = new Date(); + const [openHour, openMinute] = todayOpen.split(":").map(Number); + const [closeHour, closeMinute] = todayClose.split(":").map(Number); + + const openTime = new Date(); + openTime.setHours(openHour, openMinute); + + const closeTime = new Date(); + closeTime.setHours(closeHour, closeMinute); + + if (closeTime <= openTime) { + closeTime.setDate(closeTime.getDate() + 1); + } + + return now >= openTime && now <= closeTime; + }); + } + }); + } + + setMarkers(filteredMarkers); + console.log("Filtered Markers:", filteredMarkers); + console.log("All Markers:", allMarkers); +console.log("Selected Filters:", filters); +console.log("All Markers with Parking Info:", allMarkers.map((marker) => marker.pet_fee)); + }; if (!isLoaded) { @@ -98,17 +217,13 @@ const ActualPlaceMap = () => { />
- setSelectedFilters((prev) => - prev.includes(filter) ? prev.filter((f) => f !== filter) : [...prev, filter] - ) - } + onFilterClick={handleFilterClick} hoveredFilter={hoveredFilter} setHoveredFilter={setHoveredFilter} /> - + { const [hoveredCategory, setHoveredCategory] = useState(null); const [selectedFilters, setSelectedFilters] = useState([]); const [hoveredFilter, setHoveredFilter] = useState(null); - const [cards, setCards] = useState(initialCards || []); + const [cards, setCards] = useState([]); + const [allCards, setAllCards] = useState([]); const [userLocation, setUserLocation] = useState(null); const [isLocationPermissionGranted, setIsLocationPermissionGranted] = useState(false); const [showScrollToTop, setShowScrollToTop] = useState(false); @@ -41,6 +42,8 @@ const ActualPlaceSearchPage = () => { const bottomRef = useRef(null); const scrollableRef = useRef(null); const searchParams = useSearchParams(); + const [page, setpage] = useState(1); + const size = 20; const handleOpenBottomSheet = () => setIsBottomSheetOpen(true); const handleCloseBottomSheet = () => setIsBottomSheetOpen(false); @@ -59,26 +62,65 @@ const ActualPlaceSearchPage = () => { setSelectedGungu(event.target.value); }; + const categoryMapping = { + 미용: "서비스", + 반려동물용품: "서비스", + 위탁관리: "서비스", + 식당: "음식점", + 카페: "음식점", + 문예회관: "문화시설", + 박물관: "문화시설", + 미술관: "문화시설", + 여행지: "문화시설", + 동물병원: "의료시설", + 동물약국: "의료시설", + }; + const handleCategoryClick = (category) => { setSelectedCategory(category); - setSelectedFilters([]); + filterCards(category, selectedFilters); }; - const handleHover = (category) => setHoveredCategory(category); + const handleHover = (category) => setHoveredCategory(category); + const handleFilterClick = (filter) => { - setSelectedFilters((prevFilters) => - prevFilters.includes(filter) - ? prevFilters.filter((f) => f !== filter) - : [...prevFilters, filter] - ); + setSelectedFilters((prevFilters) => { + const updatedFilters = prevFilters.includes(filter) + ? prevFilters.filter((f) => f !== filter) + : [...prevFilters, filter]; + filterCards(selectedCategory, updatedFilters); + return updatedFilters; + }); }; - const handleHover2 = (filter) => setHoveredFilter(filter); + const filterCards = (category, filters) => { + let filteredCards = allCards; + if (category !== "전체") { + filteredCards = filteredCards.filter((card) => card.mainCategory === category); + } + if (filters.length > 0) { + filters.forEach((filter) => { + if (filter === "주차 가능") { + filteredCards = filteredCards.filter((card) => card.is_parking); + } else if (filter === "실내공간") { + filteredCards = filteredCards.filter((card) => card.inside); + } else if (filter === "야외공간") { + filteredCards = filteredCards.filter((card) => card.outside); + } else if (filter === "무게 제한 없음") { + filteredCards = filteredCards.filter((card) => !card.weight_limit); + } else if (filter === "애견 동반 요금 없음") { + filteredCards = filteredCards.filter((card) => !card.pet_fee); + } else if (filter === "영업중") { + filteredCards = filteredCards.filter((card) => card.isOpenNow); + } + }); + } - const handleCardClick = (id) => { - router.push(`/place/placedetail?id=${id}`); + setCards(filteredCards); }; + const handleHover2 = (filter) => setHoveredFilter(filter); + const toggleLike = (e, id) => { e.stopPropagation(); setCards((prevCards) => @@ -87,7 +129,7 @@ const ActualPlaceSearchPage = () => { ) ); }; - + useEffect(() => { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( @@ -101,15 +143,92 @@ const ActualPlaceSearchPage = () => { ); } }, []); + + useEffect(() => { + const lat = parseFloat(searchParams.get("lat")); + const lng = parseFloat(searchParams.get("lng")); + + if (!isNaN(lat) && !isNaN(lng)) { + setUserLocation({ lat, lng }); + fetchPlaces(lat, lng, page, size); + } else { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude } = position.coords; + setUserLocation({ lat: latitude, lng: longitude }); + fetchPlaces(latitude, longitude, page, size); + }, + () => { + alert("현재 위치를 가져올 수 없습니다."); + } + ); + } + console.error("Invalid latitude or longitude in URL."); + } + }, [searchParams, page]); + + const fetchPlaces = async (lat, lng, page, size) => { + try { + const response = await axios.get("https://api.daengplace.com/places", { + params: { + latitude: lat, + longitude: lng, + page, + size, + }, + }); + const places = response.data.data.places || []; + const mappedPlaces = places.map((place) => ({ + ...place, + mainCategory: categoryMapping[place.category] || "기타", + isOpenNow: checkIsOpen(place.operationHour), + })); + setAllCards(mappedPlaces); + setCards(mappedPlaces); + console.log(places); + } catch (error) { + console.error("Error fetching places:", error); + } + }; + + const checkIsOpen = (operationHour) => { + if (!operationHour || !operationHour.todayOpen || !operationHour.todayClose) { + return false; + } + + const { todayOpen, todayClose } = operationHour; + + const now = new Date(); + const [openHour, openMinute, openSecond] = todayOpen.split(":").map(Number); + const [closeHour, closeMinute, closeSecond] = todayClose.split(":").map(Number); + + const openTime = new Date(); + openTime.setHours(openHour, openMinute, openSecond || 0); + + const closeTime = new Date(); + closeTime.setHours(closeHour, closeMinute, closeSecond || 0); + + if (closeTime <= openTime) { + closeTime.setDate(closeTime.getDate() + 1); + } + + return now >= openTime && now <= closeTime; + }; + + const handleCardClick = (placeId) => { + router.push(`/place/placedetail?placeId=${placeId}`); + }; const handleMapView = () => { if (userLocation) { - router.push(`/place/placemap?lat=${userLocation.lat}&lng=${userLocation.lng}`); + const { lat, lng } = userLocation; + router.push(`/place/placemap?lat=${lat}&lng=${lng}`); } else { alert("위치를 가져올 수 없습니다."); } }; - + const scrollToTop = () => { const container = document.getElementById("scrollable-container"); if (container) { @@ -162,7 +281,7 @@ const ActualPlaceSearchPage = () => { } }; }, [cards]); - + return ( <>
{ onFilterClick={handleFilterClick} onHover={handleHover2} /> - handleCardClick(id)} toggleLike={toggleLike} /> + handleCardClick(placeId)} toggleLike={toggleLike} keyExtractor={(card) => card.id || card.placeId} /> {/* 리스트 마지막 감지용 */}
diff --git a/src/components/place/placemap/Map/Map.jsx b/src/components/place/placemap/Map/Map.jsx index b82b785..d78d8e6 100644 --- a/src/components/place/placemap/Map/Map.jsx +++ b/src/components/place/placemap/Map/Map.jsx @@ -2,38 +2,72 @@ import React from "react"; import styled from "styled-components"; import { GoogleMap, Circle, Marker } from "@react-google-maps/api"; +import { useRouter } from "next/navigation"; -const Map = ({ center, zoom, userLocation, markers }) => ( - - - {userLocation && ( - { + const router = useRouter(); + + const categoryMapping = { + 미용: "서비스", + 반려동물용품: "서비스", + 위탁관리: "서비스", + 식당: "음식점", + 카페: "음식점", + 문예회관: "문화시설", + 박물관: "문화시설", + 미술관: "문화시설", + 여행지: "문화시설", + 동물병원: "의료시설", + 동물약국: "의료시설", + }; + + const getMarkerIcon = (category) => { + const mainCategory = categoryMapping[category] || "기타"; + const iconUrls = { + 서비스: "http://maps.google.com/mapfiles/ms/icons/red-dot.png", + 음식점: "http://maps.google.com/mapfiles/ms/icons/green-dot.png", + 문화시설: "http://maps.google.com/mapfiles/ms/icons/blue-dot.png", + 의료시설: "http://maps.google.com/mapfiles/ms/icons/yellow-dot.png", + 기타: "http://maps.google.com/mapfiles/ms/icons/purple-dot.png", + }; + + return iconUrls[mainCategory]; + }; + + const handleMarkerClick = (id) => { + router.push(`/place/placedetail?placeId=${id}`); + }; + + return ( + + + {userLocation && ( + - )} - - {markers && - markers.map((marker) => ( - - )) - } - - - -); - + )} + {markers && + markers.map((marker) => ( + handleMarkerClick(marker.id)} + /> + ))} + + + ); +}; export default Map; const MapContainer = styled.div`