diff --git a/src/assets/icons/zoomIn.svg b/src/assets/icons/zoomIn.svg new file mode 100644 index 00000000..6acd831c --- /dev/null +++ b/src/assets/icons/zoomIn.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/MapZoomIn/RouteMapBody/RouteMapBody.module.scss b/src/components/MapZoomIn/RouteMapBody/RouteMapBody.module.scss new file mode 100644 index 00000000..1a3ef647 --- /dev/null +++ b/src/components/MapZoomIn/RouteMapBody/RouteMapBody.module.scss @@ -0,0 +1,7 @@ +@use '@/sass' as *; + +.container, +.map { + width: 100%; + height: 100vh; +} diff --git a/src/components/MapZoomIn/RouteMapBody/RouteMapBody.tsx b/src/components/MapZoomIn/RouteMapBody/RouteMapBody.tsx new file mode 100644 index 00000000..66c87c61 --- /dev/null +++ b/src/components/MapZoomIn/RouteMapBody/RouteMapBody.tsx @@ -0,0 +1,79 @@ +import React, {useEffect, useRef, useState} from 'react'; +import {CustomOverlayMap, Map, Polyline} from 'react-kakao-maps-sdk'; +import {SwiperRef} from 'swiper/react'; + +import styles from './RouteMapBody.module.scss'; + +import RouteMapSlide from '../RouteMapSlide/RouteMapSlide'; +import MapPinActive from '../../CandidatesMap/MapPins/MapPinActive'; +import MapPinNumber from '../../CandidatesMap/MapPins/MapPinNumber'; + +import {Journeys, LatLng} from '@/types/route'; + +const RouteMapBody = ({journeys}: Journeys) => { + const [centerMarker, setCenterMarker] = useState({ + lat: journeys[0].places[0].place.latitude, + lng: journeys[0].places[0].place.longitude, + }); + + const [activeDay, setActiveDay] = useState(0); + const [selectedPinIndex, setSelectedPinIndex] = useState(0); + const swiperRef = useRef(null); + + const latlngs = journeys[activeDay].places.map((place) => ({ + lat: place.place.latitude, + lng: place.place.longitude, + })); + + const handleMapMarkerClick = (latlng: LatLng, i: number) => { + setCenterMarker(latlng); + swiperRef.current?.swiper.slideTo(i); + setSelectedPinIndex(i); + }; + + const handleDayChange = (day: number) => { + setActiveDay(day); + handleMapMarkerClick(latlngs[0], 0); + }; + + useEffect(() => { + setCenterMarker({ + lat: journeys[0].places[0].place.latitude, + lng: journeys[0].places[0].place.longitude, + }); + }, []); + + useEffect(() => { + console.log(selectedPinIndex); + }, [selectedPinIndex]); + + return ( +
+ + {journeys[activeDay].places.map((place, i) => ( + + + +
handleMapMarkerClick({lat: place.place.latitude, lng: place.place.latitude}, i)} + > + {selectedPinIndex === i ? : } +
+
+
+ ))} +
+ +
+ ); +}; + +export default RouteMapBody; diff --git a/src/components/MapZoomIn/RouteMapSlide/RouteMapSlide.module.scss b/src/components/MapZoomIn/RouteMapSlide/RouteMapSlide.module.scss new file mode 100644 index 00000000..8eca0dd8 --- /dev/null +++ b/src/components/MapZoomIn/RouteMapSlide/RouteMapSlide.module.scss @@ -0,0 +1,53 @@ +@use '@/sass' as *; + +.container { + position: fixed; + bottom: 0; + width: 100%; + max-width: 36rem; + max-width: 45rem; + height: 20.5rem; + display: flex; + align-items: flex-end; + padding-bottom: 14px; + box-shadow: $shadow200; + z-index: 2; + background-color: $neutral0; + + .daysButtonContainer { + position: absolute; + top: 0; + display: flex; + gap: 0.8rem; + width: 100%; + max-width: 36rem; + max-width: 45rem; + padding: 24px 20px 0 20px; + + button { + @include typography(button); + display: flex; + padding: 8px 16px; + flex-direction: column; + justify-content: center; + align-items: center; + border-radius: 8px; + } + + .activeDayButton { + border: 1px solid $primary300; + background-color: $primary300; + color: $neutral0; + } + + .inactiveDayButton { + border: 1px solid $neutral300; + background-color: $neutral0; + color: $neutral800; + } + } + + .swiperSlideItem { + padding: 1rem 0; + } +} diff --git a/src/components/MapZoomIn/RouteMapSlide/RouteMapSlide.tsx b/src/components/MapZoomIn/RouteMapSlide/RouteMapSlide.tsx new file mode 100644 index 00000000..a4821121 --- /dev/null +++ b/src/components/MapZoomIn/RouteMapSlide/RouteMapSlide.tsx @@ -0,0 +1,67 @@ +import {Swiper, SwiperClass, SwiperSlide} from 'swiper/react'; +import 'swiper/scss'; +import 'swiper/scss/navigation'; + +import styles from './RouteMapSlide.module.scss'; + +import PlaceCard from '@/components/Route/PlaceCard/PlaceCard'; + +import {RouteMapSlideProps} from '@/types/route'; + +const RouteMapSlide = ({ + journeys, + setSelectedPinIndex, + setCenterMarker, + swiperRef, + activeDay, + onDayChange, +}: RouteMapSlideProps) => { + const dates = journeys.map((journey) => journey.date); + + const handleSlideChange = (swiper: SwiperClass) => { + const activeJourney = journeys[activeDay]; + const center = { + lat: activeJourney.places[swiper.activeIndex].place.latitude, + lng: activeJourney.places[swiper.activeIndex].place.longitude, + }; + setCenterMarker(center); + setSelectedPinIndex(swiper.activeIndex); + }; + + return ( +
+
+ {dates.map((_, i) => ( + + ))} +
+ + {journeys[activeDay].places.map((place, j) => ( + + + + ))} + +
+ ); +}; + +export default RouteMapSlide; diff --git a/src/components/Route/RouteTabPanel/RouteTabPanel.module.scss b/src/components/Route/RouteTabPanel/RouteTabPanel.module.scss index 35a5f7fe..dea14daf 100644 --- a/src/components/Route/RouteTabPanel/RouteTabPanel.module.scss +++ b/src/components/Route/RouteTabPanel/RouteTabPanel.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .panelContainer { -ms-overflow-style: none; @@ -16,6 +16,13 @@ height: 16rem; } + .zoomInbutton { + position: absolute; + top: 16rem; + right: 2rem; + z-index: 10; + } + .routeContainer { .journeysContainer { display: flex; diff --git a/src/components/Route/RouteTabPanel/RouteTabPanel.tsx b/src/components/Route/RouteTabPanel/RouteTabPanel.tsx index aa8d4c47..4115798d 100644 --- a/src/components/Route/RouteTabPanel/RouteTabPanel.tsx +++ b/src/components/Route/RouteTabPanel/RouteTabPanel.tsx @@ -1,32 +1,37 @@ -import styles from "./RouteTabPanel.module.scss"; +import {useNavigate} from 'react-router-dom'; -import DayNavigationBar from "../DayNavigationBar/DayNavigationBar"; -import DayRoute from "../DayRoute/DayRoute"; -import EmptyDate from "../EmptyDate/EmptyDate"; -import MapInTrip from "../MapInTrip/MapInTrip"; +import styles from './RouteTabPanel.module.scss'; -import { DateItem, MapInTripProps } from "@/types/route"; +import ZoomInIcon from '@/assets/icons/zoomIn.svg?react'; +import {getSpaceId} from '@/utils/getSpaceId'; -function RouteTabPanel({ mapRef, center }: MapInTripProps) { +import DayNavigationBar from '../DayNavigationBar/DayNavigationBar'; +import DayRoute from '../DayRoute/DayRoute'; +import EmptyDate from '../EmptyDate/EmptyDate'; +import MapInTrip from '../MapInTrip/MapInTrip'; + +import {DateItem, MapInTripProps} from '@/types/route'; + +function RouteTabPanel({mapRef, center}: MapInTripProps) { const data = { journeys: [ { id: 0, - date: "2024-01-16", + date: '2024-01-16', places: [ { id: 0, Order: 0, place: { id: 0, - title: "씨티 호텔", + title: '씨티 호텔', thumbnail: - "https://images.trvl-media.com/lodging/28000000/27440000/27434200/27434198/cb31822f.jpg?impolicy=resizecrop&rw=1200&ra=fit", - address: "강원도 강릉시", - addressDetail: "교동광장로 112", + 'https://images.trvl-media.com/lodging/28000000/27440000/27434200/27434198/cb31822f.jpg?impolicy=resizecrop&rw=1200&ra=fit', + address: '강원도 강릉시', + addressDetail: '교동광장로 112', latitude: 37.76437082535426, longitude: 128.87675285339355, - category: "숙소", + category: '숙소', }, }, { @@ -34,14 +39,14 @@ function RouteTabPanel({ mapRef, center }: MapInTripProps) { Order: 1, place: { id: 1, - title: "동화가든", + title: '동화가든', thumbnail: - "https://search.pstatic.net/common/?src=http%3A%2F%2Fblogfiles.naver.net%2FMjAyMzEyMjRfMjgg%2FMDAxNzAzMzk1OTQ3NzIx.PD8Sif-ZdTdc9tugl9qh9Izb91v0tK_OD1IJPvgVEbAg.oc9JBSNBPc6WjsJOFhCcXXBoG2Qg318fhOveoAyqbvog.JPEG.rhwpgus90%2FIMG_3224.JPG", - address: "강원도 강릉시", - addressDetail: "초당순두부길77번길 15", + 'https://search.pstatic.net/common/?src=http%3A%2F%2Fblogfiles.naver.net%2FMjAyMzEyMjRfMjgg%2FMDAxNzAzMzk1OTQ3NzIx.PD8Sif-ZdTdc9tugl9qh9Izb91v0tK_OD1IJPvgVEbAg.oc9JBSNBPc6WjsJOFhCcXXBoG2Qg318fhOveoAyqbvog.JPEG.rhwpgus90%2FIMG_3224.JPG', + address: '강원도 강릉시', + addressDetail: '초당순두부길77번길 15', latitude: 37.7911054, longitude: 128.9149116, - category: "맛집", + category: '맛집', }, }, { @@ -49,35 +54,35 @@ function RouteTabPanel({ mapRef, center }: MapInTripProps) { Order: 2, place: { id: 2, - title: "테라로사", + title: '테라로사', thumbnail: - "https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20200422_278%2F1587531042172TgXbr_JPEG%2FV-ta0vOWwlwKQkmnI-B9s7ja.jpg", - address: "강원도 강릉시", - addressDetail: "구정면 현천길 7", + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20200422_278%2F1587531042172TgXbr_JPEG%2FV-ta0vOWwlwKQkmnI-B9s7ja.jpg', + address: '강원도 강릉시', + addressDetail: '구정면 현천길 7', latitude: 37.6964635, longitude: 128.890664, - category: "카페", + category: '카페', }, }, ], }, { id: 1, - date: "2024-01-17", + date: '2024-01-17', places: [ { id: 3, Order: 0, place: { id: 3, - title: "안목해변", + title: '안목해변', thumbnail: - "https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20190130_26%2F1548818549792K262M_JPEG%2FyOtLXHFaaCdCC6c9frIgwJTB.jpeg.jpg", - address: "강원도 강릉시", - addressDetail: "창해로14번길 20-1", + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20190130_26%2F1548818549792K262M_JPEG%2FyOtLXHFaaCdCC6c9frIgwJTB.jpeg.jpg', + address: '강원도 강릉시', + addressDetail: '창해로14번길 20-1', latitude: 37.7725926, longitude: 128.9473204, - category: "관광", + category: '관광', }, }, { @@ -85,14 +90,14 @@ function RouteTabPanel({ mapRef, center }: MapInTripProps) { Order: 1, place: { id: 4, - title: "카페오션스강릉버거", + title: '카페오션스강릉버거', thumbnail: - "https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240102_48%2F17041673424734KIm1_JPEG%2F%25B0%25AD%25B8%25AA_%25B8%25C0%25C1%25FD_%25286%2529.jpg", - address: "강원도 강릉시", - addressDetail: "해안로 355", + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240102_48%2F17041673424734KIm1_JPEG%2F%25B0%25AD%25B8%25AA_%25B8%25C0%25C1%25FD_%25286%2529.jpg', + address: '강원도 강릉시', + addressDetail: '해안로 355', latitude: 37.8952651, longitude: 128.8292485, - category: "맛집", + category: '맛집', }, }, ], @@ -100,6 +105,9 @@ function RouteTabPanel({ mapRef, center }: MapInTripProps) { ], }; + const navigate = useNavigate(); + const spaceId = getSpaceId(); + if (!data.journeys || data.journeys.length === 0) { return ; } @@ -113,17 +121,15 @@ function RouteTabPanel({ mapRef, center }: MapInTripProps) {
+
{data.journeys && data.journeys.map((journey, index) => ( - + ))}
diff --git a/src/components/TripSpace/EditTripSpace/EditTripSpace.tsx b/src/components/TripSpace/EditTripSpace/EditTripSpace.tsx index 79fc20ba..67999f6c 100644 --- a/src/components/TripSpace/EditTripSpace/EditTripSpace.tsx +++ b/src/components/TripSpace/EditTripSpace/EditTripSpace.tsx @@ -1,27 +1,22 @@ -import { useDisclosure } from "@chakra-ui/react"; -import { useNavigate } from "react-router-dom"; +import {useDisclosure} from '@chakra-ui/react'; +import {useNavigate} from 'react-router-dom'; -import styles from "./EditTripSpace.module.scss"; +import styles from './EditTripSpace.module.scss'; -import LeaveTripModal from "../LeaveTripModal/LeaveTripModal"; +import {getSpaceId} from '@/utils/getSpaceId'; + +import LeaveTripModal from '../LeaveTripModal/LeaveTripModal'; function EditTripSpace() { const navigate = useNavigate(); - const { - isOpen: isModalOpen, - onOpen: onModalOpen, - onClose: onModalClose, - } = useDisclosure(); + const {isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose} = useDisclosure(); + const spaceId = getSpaceId(); return ( <>
- - + +
diff --git a/src/pages/MapZoomIn/MapZoomIn.module.scss b/src/pages/MapZoomIn/MapZoomIn.module.scss new file mode 100644 index 00000000..94e8e1d7 --- /dev/null +++ b/src/pages/MapZoomIn/MapZoomIn.module.scss @@ -0,0 +1,19 @@ +@use '@/sass' as *; + +.navigationTitleContainer { + position: fixed; + top: 0; + display: flex; + gap: 1.2rem; + width: 100%; + min-width: 36rem; + max-width: 45rem; + padding: 16px 20px; + background-color: $neutral0; + z-index: 2; + + h1 { + @include typography(button); + color: $neutral900; + } +} diff --git a/src/pages/MapZoomIn/MapZoomIn.tsx b/src/pages/MapZoomIn/MapZoomIn.tsx new file mode 100644 index 00000000..2b890d8f --- /dev/null +++ b/src/pages/MapZoomIn/MapZoomIn.tsx @@ -0,0 +1,117 @@ +import './MapZoomIn.module.scss'; +import styles from './MapZoomIn.module.scss'; + +import useGoBack from '@/hooks/useGoBack'; + +import RouteMapBody from '@/components/MapZoomIn/RouteMapBody/RouteMapBody'; + +import BackIcon from '@/assets/back.svg?react'; + +function MapZoomIn() { + const goBack = useGoBack(); + const data = { + journeys: [ + { + id: 0, + date: '2024-01-16', + places: [ + { + id: 0, + Order: 0, + place: { + id: 0, + title: '씨티 호텔', + thumbnail: + 'https://images.trvl-media.com/lodging/28000000/27440000/27434200/27434198/cb31822f.jpg?impolicy=resizecrop&rw=1200&ra=fit', + address: '강원도 강릉시', + addressDetail: '교동광장로 112', + latitude: 37.76437082535426, + longitude: 128.87675285339355, + category: '숙소', + }, + }, + { + id: 1, + Order: 1, + place: { + id: 1, + title: '동화가든', + thumbnail: + 'https://search.pstatic.net/common/?src=http%3A%2F%2Fblogfiles.naver.net%2FMjAyMzEyMjRfMjgg%2FMDAxNzAzMzk1OTQ3NzIx.PD8Sif-ZdTdc9tugl9qh9Izb91v0tK_OD1IJPvgVEbAg.oc9JBSNBPc6WjsJOFhCcXXBoG2Qg318fhOveoAyqbvog.JPEG.rhwpgus90%2FIMG_3224.JPG', + address: '강원도 강릉시', + addressDetail: '초당순두부길77번길 15', + latitude: 37.7911054, + longitude: 128.9149116, + category: '맛집', + }, + }, + { + id: 2, + Order: 2, + place: { + id: 2, + title: '테라로사', + thumbnail: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20200422_278%2F1587531042172TgXbr_JPEG%2FV-ta0vOWwlwKQkmnI-B9s7ja.jpg', + address: '강원도 강릉시', + addressDetail: '구정면 현천길 7', + latitude: 37.6964635, + longitude: 128.890664, + category: '카페', + }, + }, + ], + }, + { + id: 1, + date: '2024-01-17', + places: [ + { + id: 3, + Order: 0, + place: { + id: 3, + title: '안목해변', + thumbnail: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20190130_26%2F1548818549792K262M_JPEG%2FyOtLXHFaaCdCC6c9frIgwJTB.jpeg.jpg', + address: '강원도 강릉시', + addressDetail: '창해로14번길 20-1', + latitude: 37.7725926, + longitude: 128.9473204, + category: '관광', + }, + }, + { + id: 4, + Order: 1, + place: { + id: 4, + title: '카페오션스강릉버거', + thumbnail: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240102_48%2F17041673424734KIm1_JPEG%2F%25B0%25AD%25B8%25AA_%25B8%25C0%25C1%25FD_%25286%2529.jpg', + address: '강원도 강릉시', + addressDetail: '해안로 355', + latitude: 37.8952651, + longitude: 128.8292485, + category: '맛집', + }, + }, + ], + }, + ], + }; + + return ( +
+
+ +

스페이스 타이틀

+
+ +
+ ); +} + +export default MapZoomIn; diff --git a/src/routes/MainRouter/MainRouter.tsx b/src/routes/MainRouter/MainRouter.tsx index 44c2cf74..28becf32 100644 --- a/src/routes/MainRouter/MainRouter.tsx +++ b/src/routes/MainRouter/MainRouter.tsx @@ -1,21 +1,22 @@ import {Route, Routes} from 'react-router-dom'; -import AddPlaceFromVote from "@/pages/AddPlaceFromVote/AddPlaceFromVote"; -import FindPassword from "@/pages/Auth/FindPassword/FindPassword"; -import Login from "@/pages/Auth/Login/Login"; -import AgreePrivacy from "@/pages/Auth/Signup/Agree/AgreePrivacy"; -import AgreeService from "@/pages/Auth/Signup/Agree/AgreeService"; -import Signup from "@/pages/Auth/Signup/Signup"; -import Calendar from "@/pages/Calendar/Calendar"; +import AddPlaceFromVote from '@/pages/AddPlaceFromVote/AddPlaceFromVote'; +import FindPassword from '@/pages/Auth/FindPassword/FindPassword'; +import Login from '@/pages/Auth/Login/Login'; +import AgreePrivacy from '@/pages/Auth/Signup/Agree/AgreePrivacy'; +import AgreeService from '@/pages/Auth/Signup/Agree/AgreeService'; +import Signup from '@/pages/Auth/Signup/Signup'; +import Calendar from '@/pages/Calendar/Calendar'; import CandidatesMap from '@/pages/CandidatesMap/CandidatesMap'; -import Detail from "@/pages/Detail/Detail"; -import Home from "@/pages/Home/Home"; -import RegionSearch from "@/pages/RegionSearch/RegionSearch"; -import SearchFromHome from "@/pages/SearchFromHome/SearchFromHome"; -import Trip from "@/pages/Trip/Trip"; -import Vote from "@/pages/Vote/Vote"; +import Detail from '@/pages/Detail/Detail'; +import Home from '@/pages/Home/Home'; +import MapZoomIn from '@/pages/MapZoomIn/MapZoomIn'; +import RegionSearch from '@/pages/RegionSearch/RegionSearch'; +import SearchFromHome from '@/pages/SearchFromHome/SearchFromHome'; +import Trip from '@/pages/Trip/Trip'; +import Vote from '@/pages/Vote/Vote'; import VoteMemo from '@/pages/Vote/VoteMemo/VoteMemo'; -import Dashboard from "@/routes/Dashboard/Dashboard"; +import Dashboard from '@/routes/Dashboard/Dashboard'; function MainRouter() { return ( @@ -36,9 +37,10 @@ function MainRouter() { } /> } /> } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> ); } diff --git a/src/types/route.ts b/src/types/route.ts index 8c6fe013..ac947097 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -1,3 +1,6 @@ +import {Dispatch} from 'react'; +import {SwiperRef} from 'swiper/react'; + export interface PlaceCardProps { index: number; name: string; @@ -5,25 +8,27 @@ export interface PlaceCardProps { address: string; } +export interface PlaceOrder { + id: number; + Order: number; + place: Place; +} + export interface Place { id: number; - order: number; - place: { - id: number; - title: string; - thumbnail: string; - address: string; - addressDetail: string; - latitude: number; - longitude: number; - category: string; - }; + title: string; + thumbnail: string; + address: string; + addressDetail: string; + latitude: number; + longitude: number; + category: string; } export interface Journey { id: number; date: string; - places: Place[]; + places: PlaceOrder[]; } export interface DayRouteProps { @@ -78,3 +83,16 @@ export interface MapInTripProps { mapRef: React.RefObject; center: LatLng; } + +export interface Journeys { + journeys: Journey[]; +} + +export interface RouteMapSlideProps { + journeys: Journey[]; + setSelectedPinIndex: Dispatch>; + setCenterMarker: Dispatch>; + swiperRef: React.RefObject; + activeDay: number; + onDayChange: (day: number) => void; +} diff --git a/src/utils/getSpaceId.ts b/src/utils/getSpaceId.ts new file mode 100644 index 00000000..a47e92e7 --- /dev/null +++ b/src/utils/getSpaceId.ts @@ -0,0 +1,6 @@ +export const getSpaceId = (): number => { + const splitUrl = window.location.pathname.split('/'); + const spaceId = splitUrl[splitUrl.length - 1]; + + return parseInt(spaceId); +};