diff --git a/frontend/src/component/canvasWithMap/canvasWithMapforDraw/MapCanvasForDraw.tsx b/frontend/src/component/canvasWithMap/canvasWithMapforDraw/MapCanvasForDraw.tsx index e6601602..82320089 100644 --- a/frontend/src/component/canvasWithMap/canvasWithMapforDraw/MapCanvasForDraw.tsx +++ b/frontend/src/component/canvasWithMap/canvasWithMapforDraw/MapCanvasForDraw.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { ButtonState } from '@/component/common/enums.ts'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { ButtonState } from '@/component/common/enums'; import classNames from 'classnames'; import { MdArrowCircleLeft, MdArrowCircleRight } from 'react-icons/md'; import { FloatingButton } from '@/component/common/floatingbutton/FloatingButton.tsx'; @@ -9,6 +9,9 @@ import { ICanvasPoint, IMapCanvasProps, IPoint } from '@/lib/types/canvasInterfa import { useUndoRedo } from '@/hooks/useUndoRedo.ts'; import startmarker from '@/assets/startmarker.png'; import endmarker from '@/assets/endmarker.png'; +import { CurrentUserContext } from '@/context/CurrentUserContext'; +import { ToolDescription } from '@/component/tooldescription/ToolDescription'; +import { SearchBox } from '@/component/searchbox/SearchBox'; export const MapCanvasForDraw = ({ width, @@ -37,6 +40,8 @@ export const MapCanvasForDraw = ({ const { isMenuOpen, toolType, toggleMenu, handleMenuClick } = useFloatingButton(); const { pathPoints, addPoint, undo, redo, undoStack, redoStack } = useUndoRedo([]); + const { setCurrentUser } = useContext(CurrentUserContext); + const startImageRef = useRef(null); const endImageRef = useRef(null); @@ -48,6 +53,31 @@ export const MapCanvasForDraw = ({ endImageRef.current.src = endmarker; }, []); + useEffect(() => { + const updateUser = () => { + setCurrentUser(prevUser => { + return { + ...prevUser, + start_location: { + ...prevUser.start_location, // 기존 start_location 유지 + title: prevUser.start_location.title ?? '', + lat: startMarker?.lat ?? prevUser.start_location.lat, + lng: startMarker?.lng ?? prevUser.start_location.lng, + }, + end_location: { + ...prevUser.end_location, // 기존 end_location 유지 + title: prevUser.end_location.title ?? '', + lat: endMarker?.lat ?? prevUser.end_location.lat, + lng: endMarker?.lng ?? prevUser.end_location.lng, + }, + path: pathPoints, // 경로 포인트들 + }; + }); + }; + + updateUser(); // 상태 업데이트 함수 호출 + }, [startMarker, endMarker, pathPoints]); // 필요한 의존성만 포함 + useEffect(() => { if (!mapRef.current) return; @@ -187,9 +217,27 @@ export const MapCanvasForDraw = ({ if (!clickedPoint) return; switch (toolType) { case ButtonState.START_MARKER: + setCurrentUser(prevUser => ({ + ...prevUser, + start_location: { + ...prevUser.start_location, + title: '', // title을 빈 문자열로 초기화 -> 검색창에 보이게 하려고 + lat: clickedPoint.lat, + lng: clickedPoint.lng, + }, + })); setStartMarker(clickedPoint); break; case ButtonState.DESTINATION_MARKER: + setCurrentUser(prevUser => ({ + ...prevUser, + end_location: { + ...prevUser.end_location, + title: '', // title을 빈 문자열로 초기화 -> 검색창에 보이게 하려고 + lat: clickedPoint.lat, + lng: clickedPoint.lng, + }, + })); setEndMarker(clickedPoint); break; case ButtonState.LINE_DRAWING: @@ -355,6 +403,33 @@ export const MapCanvasForDraw = ({ } }; + const handleCreateMarker = (point: IPoint) => { + if (toolType === ButtonState.START_MARKER) { + setStartMarker(point); + setCurrentUser(prevUser => ({ + ...prevUser, + start_location: { + ...prevUser.start_location, + title: '', + }, + })); + } else { + setEndMarker(point); + setCurrentUser(prevUser => ({ + ...prevUser, + end_location: { + ...prevUser.end_location, + title: '', + }, + })); + } + }; + + const handleDeleteMarker = () => { + if (toolType === ButtonState.START_MARKER) setStartMarker(null); + else setEndMarker(null); + }; + useEffect(() => { if (isDragging) { if (canvasRef.current) { @@ -386,6 +461,16 @@ export const MapCanvasForDraw = ({ onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} > + {(toolType === ButtonState.START_MARKER || toolType === ButtonState.DESTINATION_MARKER) && ( +
+ +
+ )}
{toolType === ButtonState.LINE_DRAWING ? (
@@ -431,6 +516,9 @@ export const MapCanvasForDraw = ({ handleMenuClick={handleMenuClick} />
+
+ +
{/* TODO: 줌인 줌아웃 버튼으로도 접근 가능하도록 추가 */} {/*
*/} diff --git a/frontend/src/component/common/InputBox.tsx b/frontend/src/component/common/InputBox.tsx index 3452bec7..ad7e3da2 100644 --- a/frontend/src/component/common/InputBox.tsx +++ b/frontend/src/component/common/InputBox.tsx @@ -6,6 +6,7 @@ export interface IInputBoxProps { onChange?: (event: ChangeEvent) => void; onFocus?: (event: FocusEvent) => void; onBlur?: (event: FocusEvent) => void; + value?: string; className?: string; } @@ -15,6 +16,7 @@ export const InputBox = ({ onChange, onFocus, onBlur, + value = '', className = '', }: IInputBoxProps) => ( ); diff --git a/frontend/src/component/header/Header.tsx b/frontend/src/component/header/Header.tsx index 9ce59ffd..13b77cfa 100644 --- a/frontend/src/component/header/Header.tsx +++ b/frontend/src/component/header/Header.tsx @@ -24,7 +24,7 @@ export const Header = (props: IHeaderProps) => { return (
diff --git a/frontend/src/component/routebutton/RouteResultButton.tsx b/frontend/src/component/routebutton/RouteResultButton.tsx index 1c468564..59cf11e7 100644 --- a/frontend/src/component/routebutton/RouteResultButton.tsx +++ b/frontend/src/component/routebutton/RouteResultButton.tsx @@ -1,7 +1,5 @@ import { IUser } from '@/context/UserContext'; -import { getAddressFromCoordinates } from '@/utils/map/getAddress'; import classNames from 'classnames'; -import { useEffect, useState } from 'react'; import { GoArrowRight } from 'react-icons/go'; import { IoClose } from 'react-icons/io5'; import { useNavigate } from 'react-router-dom'; @@ -13,25 +11,6 @@ interface IRouteResultButtonProps { export const RouteResultButton = (props: IRouteResultButtonProps) => { const navigate = useNavigate(); - const [start, setStart] = useState(''); - const [end, setEnd] = useState(''); - useEffect(() => { - // Fetch the addresses asynchronously when component mounts - const fetchAddresses = async () => { - const startAddress = await getAddressFromCoordinates( - props.user.start_location.lat, - props.user.start_location.lng, - ); - const endAddress = await getAddressFromCoordinates( - props.user.end_location.lat, - props.user.end_location.lng, - ); - setStart(startAddress); // Set start address - setEnd(endAddress); // Set end address - }; - - fetchAddresses(); - }, [props.user.start_location, props.user.end_location]); const goToUserDrawRoute = (user: string) => { navigate(`/add-channel/${user}/draw`); @@ -40,7 +19,7 @@ export const RouteResultButton = (props: IRouteResultButtonProps) => { return (
- {props.user.name} +

{props.user.name}

diff --git a/frontend/src/component/routebutton/RouteSettingButton.tsx b/frontend/src/component/routebutton/RouteSettingButton.tsx index 06d0bebf..ebae2bfe 100644 --- a/frontend/src/component/routebutton/RouteSettingButton.tsx +++ b/frontend/src/component/routebutton/RouteSettingButton.tsx @@ -18,7 +18,7 @@ export const RouteSettingButton = (props: IRouteSettingButtonProps) => { return (
- {props.user.name} +

{props.user.name}

+
+ + {/* 검색 결과 리스트 */} + {searchResults.length > 0 && ( +
+ {loading &&

로딩 중...

} + {error &&

{error}

} + {searchResults.map(result => ( + + ))} +
+ )} +
+ ); +}; diff --git a/frontend/src/component/tooldescription/ToolDescription.tsx b/frontend/src/component/tooldescription/ToolDescription.tsx new file mode 100644 index 00000000..d0235825 --- /dev/null +++ b/frontend/src/component/tooldescription/ToolDescription.tsx @@ -0,0 +1,19 @@ +import { useContext } from 'react'; +import { ToolTypeContext } from '@/context/ToolTypeContext'; +import { ButtonStateDescriptions } from './constant/DescriptionConst'; + +export const ToolDescription = () => { + const { toolType } = useContext(ToolTypeContext); + const description = ButtonStateDescriptions[toolType]; + + return ( + // 조건부 렌더링: description이 빈 문자열이 아니면 렌더링 + description ? ( +
+

+ {description} +

+
+ ) : null + ); +}; diff --git a/frontend/src/component/tooldescription/constant/DescriptionConst.ts b/frontend/src/component/tooldescription/constant/DescriptionConst.ts new file mode 100644 index 00000000..21fd541d --- /dev/null +++ b/frontend/src/component/tooldescription/constant/DescriptionConst.ts @@ -0,0 +1,18 @@ +import { ButtonState } from '@/component/common/enums'; + +export enum DescriptionText { + CLOSE = '', + OPEN = '', + START_MARKER = '출발지를 설정합니다.\n터치를 해서 출발 지점을 선택해주세요.', + DESTINATION_MARKER = '도착지를 설정합니다.\n터치를 해서 도착 지점을 선택해주세요.', + LINE_DRAWING = '경로를 설정합니다.\n터치를 해서 경로를 그려주세요.', +} + +// 매핑 객체로 연결 +export const ButtonStateDescriptions: Record = { + [ButtonState.CLOSE]: DescriptionText.CLOSE, + [ButtonState.OPEN]: DescriptionText.OPEN, + [ButtonState.START_MARKER]: DescriptionText.START_MARKER, + [ButtonState.DESTINATION_MARKER]: DescriptionText.DESTINATION_MARKER, + [ButtonState.LINE_DRAWING]: DescriptionText.LINE_DRAWING, +}; diff --git a/frontend/src/context/CurrentUserContext.tsx b/frontend/src/context/CurrentUserContext.tsx new file mode 100644 index 00000000..cd1d3a9c --- /dev/null +++ b/frontend/src/context/CurrentUserContext.tsx @@ -0,0 +1,36 @@ +import React, { createContext, ReactNode, useMemo, useState } from 'react'; +import { IUser } from './UserContext'; + +interface ICurrentUserContextProps { + children: ReactNode; +} + +interface ICurrentUserOptionContext { + currentUser: IUser; + setCurrentUser: React.Dispatch>; +} + +const defaultUserContext: ICurrentUserOptionContext = { + currentUser: { + id: 1, + name: '사용자1', + start_location: { title: '', lat: 0, lng: 0 }, + end_location: { title: '', lat: 0, lng: 0 }, + path: [], + marker_style: { color: '' }, + }, + setCurrentUser: () => {}, +}; + +export const CurrentUserContext = createContext(defaultUserContext); + +export const CurrentUserProvider = (props: ICurrentUserContextProps) => { + const [currentUser, setCurrentUser] = useState(defaultUserContext.currentUser); + const contextValue = useMemo( + () => ({ currentUser, setCurrentUser }), + [currentUser, setCurrentUser], + ); + return ( + {props.children} + ); +}; diff --git a/frontend/src/context/ToolTypeContext.tsx b/frontend/src/context/ToolTypeContext.tsx new file mode 100644 index 00000000..cdce41ec --- /dev/null +++ b/frontend/src/context/ToolTypeContext.tsx @@ -0,0 +1,24 @@ +import { ButtonState } from '@/component/common/enums'; +import { createContext, ReactNode, useMemo, useState } from 'react'; + +interface IToolContextType { + toolType: ButtonState; + setToolType: (toolType: ButtonState) => void; +} +interface IToolTypeProps { + children: ReactNode; // ReactNode 타입 추가 +} + +const defaultToolContext: IToolContextType = { + toolType: ButtonState.CLOSE, + setToolType: () => {}, +}; + +export const ToolTypeContext = createContext(defaultToolContext); + +export const ToolTypeProvider = (props: IToolTypeProps) => { + const [toolType, setToolType] = useState(ButtonState.OPEN); + const contextValue = useMemo(() => ({ toolType, setToolType }), [toolType, setToolType]); + + return {props.children}; +}; diff --git a/frontend/src/context/UserContext.tsx b/frontend/src/context/UserContext.tsx index 326831c0..e2c284a3 100644 --- a/frontend/src/context/UserContext.tsx +++ b/frontend/src/context/UserContext.tsx @@ -4,10 +4,12 @@ export interface IUser { id: number; name: string; start_location: { + title: string; lat: number; lng: number; }; end_location: { + title: string; lat: number; lng: number; }; @@ -38,8 +40,8 @@ export const UserProvider = (props: IUserContextProps) => { { id: 1, name: '사용자1', - start_location: { lat: 0, lng: 0 }, - end_location: { lat: 0, lng: 0 }, + start_location: { title: '', lat: 0, lng: 0 }, + end_location: { title: '', lat: 0, lng: 0 }, path: [], marker_style: { color: '' }, }, diff --git a/frontend/src/hooks/useFloatingButton.ts b/frontend/src/hooks/useFloatingButton.ts index f46a402f..b542936e 100644 --- a/frontend/src/hooks/useFloatingButton.ts +++ b/frontend/src/hooks/useFloatingButton.ts @@ -1,9 +1,14 @@ import { ButtonState } from '@/component/common/enums'; -import { useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; +import { ToolTypeContext } from '@/context/ToolTypeContext'; export const useFloatingButton = () => { const [isMenuOpen, setIsMenuOpen] = useState(true); - const [toolType, setToolType] = useState(ButtonState.OPEN); + const { toolType, setToolType } = useContext(ToolTypeContext); + + useEffect(() => { + setIsMenuOpen(toolType === ButtonState.OPEN); + }, [toolType]); const toggleMenu = () => { setIsMenuOpen(prev => !prev); diff --git a/frontend/src/pages/DrawRoute.tsx b/frontend/src/pages/DrawRoute.tsx index 23a30e2a..502036b6 100644 --- a/frontend/src/pages/DrawRoute.tsx +++ b/frontend/src/pages/DrawRoute.tsx @@ -1,73 +1,138 @@ import { useContext, useEffect } from 'react'; import { FooterContext } from '@/component/layout/footer/LayoutFooterProvider'; -import { useParams } from 'react-router-dom'; -import { UserContext } from '@/context/UserContext'; +import { useNavigate, useParams } from 'react-router-dom'; +import { IUser, UserContext } from '@/context/UserContext'; +import { ToolTypeProvider } from '@/context/ToolTypeContext'; +import { buttonActiveType } from '@/component/layout/enumTypes'; import { MapProviderForDraw } from '@/component/canvasWithMap/canvasWithMapforDraw/MapProviderForDraw.tsx'; +import { CurrentUserContext } from '@/context/CurrentUserContext'; +import { getAddressFromCoordinates } from '@/utils/map/getAddress'; export const DrawRoute = () => { const { users, setUsers } = useContext(UserContext); - const { setFooterTitle } = useContext(FooterContext); + const { setFooterTitle, setFooterActive, setFooterOnClick } = useContext(FooterContext); + const { currentUser, setCurrentUser } = useContext(CurrentUserContext); const params = useParams>(); // userName을 URL 파라미터로 가져옴 + const navigate = useNavigate(); - // URL에서 'user' 파라미터를 받아 해당 사용자를 찾음 - const getUser = (name: string | undefined) => { - return users.find(user => user.name === name); + const goToAddChannelRoute = () => { + navigate(`/add-channel/`); }; - const user = getUser(params.user); // params.user에 해당하는 사용자 찾기 + const getUser = () => { + return users.find(user => user.name === params.user); + }; - const addMockData = () => { - if (user) { - const updatedUser = { - ...user, - start_location: { lat: 37.5665, lng: 126.978 }, // 임시 mock 데이터 - end_location: { lat: 35.1796, lng: 129.0756 }, - path: [ - { lat: 37.5665, lng: 126.978 }, - { lat: 36.5, lng: 127.5 }, - { lat: 35.1796, lng: 129.0756 }, - ], - marker_style: { color: 'blue' }, - }; + const isUserDataComplete = (user: IUser): boolean => { + return ( + user.start_location.lat !== 0 && + user.start_location.lng !== 0 && + user.end_location.lat !== 0 && + user.end_location.lng !== 0 && + user.path.length > 0 && + user.marker_style.color !== '' + ); + }; - // 해당 user의 state를 업데이트 - const updatedUsers = users.map(u => (u.name === params.user ? updatedUser : u)); // params.user를 사용해서 업데이트 - setUsers(updatedUsers); + useEffect(() => { + if (currentUser) { + const UsersComplete = isUserDataComplete(currentUser); + + // 모든 사용자가 완전한 데이터라면 Footer를 활성화 + if (UsersComplete) { + setFooterActive(buttonActiveType.ACTIVE); + } else { + setFooterActive(buttonActiveType.PASSIVE); + } } - }; - const resetMockData = () => { + }, [users, currentUser, setFooterActive]); // currentUser가 변경될 때마다 실행 + + useEffect(() => { + setFooterTitle('사용자 경로 추가 완료'); + setFooterActive(buttonActiveType.PASSIVE); + const user = getUser(); if (user) { + // userId에 따른 Tailwind 색상 클래스를 설정 + const markerColors: { [key: number]: string } = { + 1: 'marker:user1', // tailwind의 custom color class로 설정 + 2: 'marker:user2', + 3: 'marker:user3', + 4: 'marker:user4', + 5: 'marker:user5', + }; + + // user.id에 맞는 marker 스타일 적용 const updatedUser = { ...user, - start_location: { lat: 0, lng: 0 }, // 초기값으로 리셋 - end_location: { lat: 0, lng: 0 }, - path: [], - marker_style: { color: '' }, + marker_style: { + ...user.marker_style, + color: markerColors[user.id] || 'marker:user1', // 기본값은 user1 색상 + }, }; - // 해당 user의 state를 초기 상태로 업데이트 - const updatedUsers = users.map(u => (u.name === params.user ? updatedUser : u)); - setUsers(updatedUsers); + setCurrentUser(updatedUser); } - }; + }, []); useEffect(() => { - setFooterTitle('사용자 경로 추가 완료'); - }, [setFooterTitle]); + console.log(currentUser); + if (currentUser) { + setFooterOnClick(() => { + if (currentUser) { + const updateUserLocation = async () => { + const updatedUser = { ...currentUser }; // currentUser 복사 + + // start_location.title이 비어 있으면 주소를 업데이트 + if (!updatedUser.start_location.title) { + try { + const startAddress = await getAddressFromCoordinates( + updatedUser.start_location.lat, + updatedUser.start_location.lng, + ); + updatedUser.start_location.title = startAddress; // 주소 업데이트 + } catch (error) { + console.error('Error fetching start location address:', error); + } + } + + // end_location.title이 비어 있으면 주소를 업데이트 + if (!updatedUser.end_location.title) { + try { + const endAddress = await getAddressFromCoordinates( + updatedUser.end_location.lat, + updatedUser.end_location.lng, + ); + updatedUser.end_location.title = endAddress; // 주소 업데이트 + } catch (error) { + console.error('Error fetching end location address:', error); + } + } + + // user 정보를 업데이트 + const updatedUsers = users.map(user => { + if (user.name === updatedUser.name) { + return updatedUser; // 주소가 업데이트된 currentUser로 업데이트 + } + return user; + }); + + setUsers(updatedUsers); // users 배열 업데이트 + goToAddChannelRoute(); // 경로 추가 페이지로 이동 + }; + + updateUserLocation(); // 위치 업데이트 함수 호출 + } + }); + } + }, [currentUser, users]); return ( -
- - - -
- {/* TODO: 동율님 mock 데이터 관련 버튼 없애고 나서, height={window.innerHeight - 180} 으로 변경해주시면 됩니다! */} - + +
+
+ +
-
+ ); }; diff --git a/frontend/src/routes/IndexRoutes.tsx b/frontend/src/routes/IndexRoutes.tsx index 4ae240a9..2c59bc4c 100644 --- a/frontend/src/routes/IndexRoutes.tsx +++ b/frontend/src/routes/IndexRoutes.tsx @@ -8,35 +8,38 @@ import { HostView } from '@/pages/HostView'; import { GuestView } from '@/pages/GuestView'; import { Layout } from '@/component/layout/Layout'; import { UserProvider } from '@/context/UserContext'; +import { CurrentUserProvider } from '@/context/CurrentUserContext'; export const IndexRoutes = () => ( - - }> - {/* 메인 페이지를 위한 인덱스 라우트 */} - } /> + + + }> + {/* 메인 페이지를 위한 인덱스 라우트 */} + } /> - {/* 공개 라우트 */} - } /> + {/* 공개 라우트 */} + } /> - {/* 채널 추가를 위한 중첩 라우트 */} - - } /> - - } /> - } /> + {/* 채널 추가를 위한 중첩 라우트 */} + + } /> + + } /> + } /> + - - {/* 채널별 뷰를 위한 중첩 라우트 */} - - } /> - } /> - + {/* 채널별 뷰를 위한 중첩 라우트 */} + + } /> + } /> + - {/* TODO : 정의되지 않은 경로에 대한 폴백 라우트 (선택사항) */} - {/* } /> */} - - + {/* TODO : 정의되지 않은 경로에 대한 폴백 라우트 (선택사항) */} + {/* } /> */} + + + ); diff --git a/frontend/src/utils/map/getAddress.ts b/frontend/src/utils/map/getAddress.ts index 93180e92..0e9e1eda 100644 --- a/frontend/src/utils/map/getAddress.ts +++ b/frontend/src/utils/map/getAddress.ts @@ -10,7 +10,6 @@ export const getAddressFromCoordinates = (lat: number, lng: number): Promise