diff --git a/uniro_frontend/src/App.tsx b/uniro_frontend/src/App.tsx index cf246e5..df5068e 100644 --- a/uniro_frontend/src/App.tsx +++ b/uniro_frontend/src/App.tsx @@ -8,7 +8,7 @@ import BuildingSearchPage from "./pages/buildingSearch"; import NavigationResultPage from "./pages/navigationResult"; import ReportRoutePage from "./pages/reportRoute"; import ReportForm from "./pages/reportForm"; -import ReportHazardPage from "./pages/reportHazard"; +import ReportRiskPage from "./pages/reportRisk"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useDynamicSuspense } from "./hooks/useDynamicSuspense"; import OfflinePage from "./pages/offline"; @@ -34,7 +34,7 @@ function App() { } /> } /> } /> - } /> + } /> /** 에러 페이지 */ } /> } /> diff --git a/uniro_frontend/src/api/route.ts b/uniro_frontend/src/api/route.ts index 5d9dd4a..c167382 100644 --- a/uniro_frontend/src/api/route.ts +++ b/uniro_frontend/src/api/route.ts @@ -1,4 +1,5 @@ import { IssueTypeKey } from "../constant/enum/reportEnum"; +import { Coord } from "../data/types/coord"; import { CautionIssueType, DangerIssueType } from "../data/types/enum"; import { NodeId } from "../data/types/node"; @@ -44,3 +45,14 @@ export const postReport = ( ): Promise => { return postFetch(`/${univId}/route/risk/${routeId}`, body); }; + +export const postReportRoute = ( + univId: number, + body: { + startNodeId: NodeId; + endNodeId: NodeId | null; + coordinates: Coord[]; + }, +): Promise => { + return postFetch(`/${univId}/route`, body); +}; diff --git a/uniro_frontend/src/constant/enum/messageEnum.ts b/uniro_frontend/src/constant/enum/messageEnum.ts index e591b49..c2f249d 100644 --- a/uniro_frontend/src/constant/enum/messageEnum.ts +++ b/uniro_frontend/src/constant/enum/messageEnum.ts @@ -1,4 +1,4 @@ -export enum ReportHazardMessage { +export enum ReportRiskMessage { DEFAULT = "선 위를 눌러 제보할 지점을 선택하세요", CREATE = "이 지점으로 새로운 제보를 진행할까요?", UPDATE = "이 지점에 제보된 기존 정보를 바꿀까요?", diff --git a/uniro_frontend/src/hooks/useReportHazard.ts b/uniro_frontend/src/hooks/useReportHazard.ts deleted file mode 100644 index 79aea93..0000000 --- a/uniro_frontend/src/hooks/useReportHazard.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { create } from "zustand"; - -interface ReportedHazardEdge { - reportType: "CREATE" | "UPDATE" | undefined; - setReportType: (type: "CREATE" | "UPDATE") => void; - startNode: google.maps.LatLng | google.maps.LatLngLiteral | undefined; - endNode: google.maps.LatLng | google.maps.LatLngLiteral | undefined; - setNode: ( - point1: google.maps.LatLng | google.maps.LatLngLiteral, - point2: google.maps.LatLng | google.maps.LatLngLiteral, - ) => void; -} - -const useReportHazard = create((set) => ({ - reportType: undefined, - setReportType: (newType) => set(() => ({ reportType: newType })), - startNode: undefined, - endNode: undefined, - setNode: (point1, point2) => set(() => ({ startNode: point1, endNode: point2 })), -})); - -export default useReportHazard; diff --git a/uniro_frontend/src/hooks/useReportRisk.ts b/uniro_frontend/src/hooks/useReportRisk.ts new file mode 100644 index 0000000..b4e77a9 --- /dev/null +++ b/uniro_frontend/src/hooks/useReportRisk.ts @@ -0,0 +1,14 @@ +import { create } from "zustand"; +import { RouteId } from "../data/types/route"; + +interface ReportedRiskRoute { + reportRouteId: RouteId | undefined; + setReportRouteId: (selectedRouteId: RouteId) => void; +} + +const useReportRisk = create((set) => ({ + reportRouteId: undefined, + setReportRouteId: (newRouteId: RouteId) => set({ reportRouteId: newRouteId }), +})); + +export default useReportRisk; diff --git a/uniro_frontend/src/pages/reportForm.tsx b/uniro_frontend/src/pages/reportForm.tsx index 966e1af..552f03d 100644 --- a/uniro_frontend/src/pages/reportForm.tsx +++ b/uniro_frontend/src/pages/reportForm.tsx @@ -17,6 +17,8 @@ import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-q import { getSingleRouteRisk, postReport } from "../api/route"; import { University } from "../data/types/university"; import { useNavigate } from "react-router"; +import useReportRisk from "../hooks/useReportRisk"; +import { RouteId } from "../data/types/route"; const ReportForm = () => { useScrollControl(); @@ -33,10 +35,13 @@ const ReportForm = () => { const [errorTitle, setErrorTitle] = useState(""); const { university } = useUniversityInfo(); + const { reportRouteId: routeId } = useReportRisk(); - useRedirectUndefined([university]); + useRedirectUndefined([university, routeId]); - const routeId = 1; + console.log(routeId) + + if (!routeId) return; const { data } = useSuspenseQuery({ queryKey: ["report", university?.id ?? 1001, routeId], diff --git a/uniro_frontend/src/pages/reportHazard.tsx b/uniro_frontend/src/pages/reportHazard.tsx deleted file mode 100644 index 4b2be9d..0000000 --- a/uniro_frontend/src/pages/reportHazard.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { useEffect, useState } from "react"; -import useMap from "../hooks/useMap"; -import createAdvancedMarker from "../utils/markers/createAdvanedMarker"; -import createMarkerElement from "../components/map/mapMarkers"; -import { CoreRoutesList } from "../data/types/route"; -import { Markers } from "../constant/enum/markerEnum"; -import { mockHazardEdges } from "../data/mock/hanyangHazardEdge"; -import { ClickEvent } from "../data/types/event"; -import createSubNodes from "../utils/polylines/createSubnodes"; -import { LatLngToLiteral } from "../utils/coordinates/coordinateTransform"; -import findNearestSubEdge from "../utils/polylines/findNearestEdge"; -import centerCoordinate from "../utils/coordinates/centerCoordinate"; -import { MarkerTypesWithElement } from "../data/types/marker"; -import Button from "../components/customButton"; -import { Link } from "react-router"; -import { ReportHazardMessage } from "../constant/enum/messageEnum"; -import { motion } from "framer-motion"; -import useReportHazard from "../hooks/useReportHazard"; -import AnimatedContainer from "../container/animatedContainer"; - -import BackButton from "../components/map/backButton"; - -import useUniversityInfo from "../hooks/useUniversityInfo"; -import useRedirectUndefined from "../hooks/useRedirectUndefined"; -import { University } from "../data/types/university"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { getAllRoutes } from "../api/route"; - -interface reportMarkerTypes extends MarkerTypesWithElement { - edge: [google.maps.LatLng | google.maps.LatLngLiteral, google.maps.LatLng | google.maps.LatLngLiteral]; -} - -export default function ReportHazardPage() { - const { map, mapRef, AdvancedMarker, Polyline } = useMap({ zoom: 18, minZoom: 17 }); - const [reportMarker, setReportMarker] = useState(); - const { setReportType, setNode } = useReportHazard(); - - const [message, setMessage] = useState(ReportHazardMessage.DEFAULT); - - const { university } = useUniversityInfo(); - useRedirectUndefined([university]); - - const { data } = useSuspenseQuery({ - queryKey: ["routes", university?.id], - queryFn: () => getAllRoutes(university?.id ?? 1001), - }); - - const resetMarker = (prevMarker: MarkerTypesWithElement) => { - if (prevMarker.type === Markers.REPORT) { - prevMarker.element.map = null; - return; - } else { - prevMarker.element.content = createMarkerElement({ type: prevMarker.type }); - } - }; - - const addHazardMarker = () => { - if (AdvancedMarker === null || map === null) return; - for (const edge of mockHazardEdges) { - const { id, startNode, endNode, dangerFactors, cautionFactors } = edge; - - const type = dangerFactors ? Markers.DANGER : Markers.CAUTION; - - const hazardMarker = createAdvancedMarker( - AdvancedMarker, - map, - new google.maps.LatLng({ - lat: (startNode.lat + endNode.lat) / 2, - lng: (startNode.lng + endNode.lng) / 2, - }), - createMarkerElement({ type }), - () => { - hazardMarker.content = createMarkerElement({ - type, - title: dangerFactors ? dangerFactors[0] : cautionFactors && cautionFactors[0], - hasTopContent: true, - }); - setMessage(ReportHazardMessage.UPDATE); - setReportMarker((prevMarker) => { - if (prevMarker) { - resetMarker(prevMarker); - } - - return { - type, - element: hazardMarker, - edge: [startNode, endNode], - }; - }); - }, - ); - } - }; - - const drawRoute = (coreRouteList: CoreRoutesList) => { - if (!Polyline || !AdvancedMarker || !map) return; - - for (const coreRoutes of coreRouteList) { - // 가장 끝쪽 Core Node 그리기 - const endNode = coreRoutes.routes[coreRoutes.routes.length - 1].node2; - - createAdvancedMarker( - AdvancedMarker, - map, - endNode, - createMarkerElement({ type: Markers.WAYPOINT, className: "translate-waypoint" }), - ); - - for (const coreRoute of coreRoutes.routes) { - const routePolyLine = new Polyline({ - map: map, - path: [coreRoute.node1, coreRoute.node2], - strokeColor: "#808080", - }); - - routePolyLine.addListener("click", (e: ClickEvent) => { - const subNodes = createSubNodes(routePolyLine); - - alert(coreRoute.routeId); - - const edges = subNodes - .map( - (node, idx) => - [node, subNodes[idx + 1]] as [google.maps.LatLngLiteral, google.maps.LatLngLiteral], - ) - .slice(0, -1); - - const point = LatLngToLiteral(e.latLng); - - const { edge: nearestEdge, point: nearestPoint } = findNearestSubEdge(edges, point); - - const newReportMarker = createAdvancedMarker( - AdvancedMarker, - map, - centerCoordinate(nearestEdge[0], nearestEdge[1]), - createMarkerElement({ - type: Markers.REPORT, - className: "translate-routemarker", - hasAnimation: true, - }), - ); - - setMessage(ReportHazardMessage.CREATE); - - setReportMarker((prevMarker) => { - if (prevMarker) { - resetMarker(prevMarker); - } - - return { - type: Markers.REPORT, - element: newReportMarker, - edge: nearestEdge, - }; - }); - }); - } - // 시작하는 쪽의 Core Node 그리기 - const startNode = coreRoutes.routes[0].node1; - createAdvancedMarker( - AdvancedMarker, - map, - startNode, - createMarkerElement({ type: Markers.WAYPOINT, className: "translate-waypoint" }), - ); - } - }; - - const reportHazard = () => { - if (!reportMarker) return; - - setReportType(reportMarker.type === Markers.REPORT ? "CREATE" : "UPDATE"); - - setNode(...reportMarker.edge); - }; - - useEffect(() => { - drawRoute(data); - addHazardMarker(); - - if (map) { - map.addListener("click", () => { - setReportMarker((prevMarker) => { - if (prevMarker) { - setMessage(ReportHazardMessage.DEFAULT); - resetMarker(prevMarker); - } else setMessage(ReportHazardMessage.ERROR); - - return undefined; - }); - }); - } - }, [map, AdvancedMarker, Polyline]); - - useEffect(() => { - if (message === ReportHazardMessage.ERROR) { - setTimeout(() => { - setMessage(ReportHazardMessage.DEFAULT); - }, 1000); - } - }, [message]); - - return ( -
-
- - {message} - -
- -
- - - - - -
- ); -} diff --git a/uniro_frontend/src/pages/reportRisk.tsx b/uniro_frontend/src/pages/reportRisk.tsx new file mode 100644 index 0000000..ab99561 --- /dev/null +++ b/uniro_frontend/src/pages/reportRisk.tsx @@ -0,0 +1,305 @@ +import { MouseEvent, useEffect, useState } from "react"; +import useMap from "../hooks/useMap"; +import createAdvancedMarker from "../utils/markers/createAdvanedMarker"; +import createMarkerElement from "../components/map/mapMarkers"; +import { CoreRoute, CoreRoutesList, RouteId } from "../data/types/route"; +import { Markers } from "../constant/enum/markerEnum"; +import { ClickEvent } from "../data/types/event"; +import { LatLngToLiteral } from "../utils/coordinates/coordinateTransform"; +import findNearestSubEdge from "../utils/polylines/findNearestEdge"; +import centerCoordinate from "../utils/coordinates/centerCoordinate"; +import { MarkerTypesWithElement } from "../data/types/marker"; +import Button from "../components/customButton"; +import { Link } from "react-router"; +import { ReportRiskMessage } from "../constant/enum/messageEnum"; +import { motion } from "framer-motion"; +import AnimatedContainer from "../container/animatedContainer"; + +import BackButton from "../components/map/backButton"; + +import useUniversityInfo from "../hooks/useUniversityInfo"; +import useRedirectUndefined from "../hooks/useRedirectUndefined"; +import { University } from "../data/types/university"; +import { useSuspenseQueries } from "@tanstack/react-query"; +import { getAllRoutes } from "../api/route"; +import { getAllRisks } from "../api/routes"; +import useReportRisk from "../hooks/useReportRisk"; +import { CautionIssueType, DangerIssueType } from "../data/types/enum"; +import { CautionIssue, DangerIssue } from "../constant/enum/reportEnum"; + +interface reportMarkerTypes extends MarkerTypesWithElement { + route: RouteId; + factors?: DangerIssueType[] | CautionIssueType[]; +} + +export default function ReportRiskPage() { + const { map, mapRef, AdvancedMarker, Polyline } = useMap({ zoom: 18, minZoom: 17 }); + + const [reportMarker, setReportMarker] = useState(); + + const [message, setMessage] = useState(ReportRiskMessage.DEFAULT); + const { setReportRouteId } = useReportRisk(); + const { university } = useUniversityInfo(); + + useRedirectUndefined([university]); + + if (!university) return; + + const result = useSuspenseQueries({ + queries: [ + { + queryKey: ["routes", university.id], + queryFn: () => getAllRoutes(university.id), + }, + { queryKey: [university.id, 'risks'], queryFn: () => getAllRisks(university.id) }, + ] + }); + + const [routes, risks] = result; + + const reportRisk = (e: MouseEvent) => { + if (!reportMarker) { + e.preventDefault(); + return; + } + + setReportRouteId(reportMarker.route) + } + + const resetMarker = (prevMarker: MarkerTypesWithElement) => { + if (prevMarker.type === Markers.REPORT) { + prevMarker.element.map = null; + return; + } else { + prevMarker.element.content = createMarkerElement({ type: prevMarker.type }); + } + }; + + const addRiskMarker = () => { + if (AdvancedMarker === null || map === null) return; + const { dangerRoutes, cautionRoutes } = risks.data; + + for (const route of dangerRoutes) { + const { routeId, node1, node2, dangerTypes } = route; + const type = Markers.DANGER; + + const dangerMarker = createAdvancedMarker( + AdvancedMarker, + map, + new google.maps.LatLng({ + lat: (node1.lat + node2.lat) / 2, + lng: (node1.lng + node2.lng) / 2, + }), + createMarkerElement({ type }), + () => { + setReportMarker((prevMarker) => { + if (prevMarker) resetMarker(prevMarker); + + return { + type: Markers.DANGER, + element: dangerMarker, + route: routeId, + factors: dangerTypes, + } + }) + }, + ); + } + + for (const route of cautionRoutes) { + const { routeId, node1, node2, cautionTypes } = route; + const type = Markers.CAUTION; + + const cautionMarker = createAdvancedMarker( + AdvancedMarker, + map, + new google.maps.LatLng({ + lat: (node1.lat + node2.lat) / 2, + lng: (node1.lng + node2.lng) / 2, + }), + createMarkerElement({ type }), + () => { + setReportMarker((prevMarker) => { + if (prevMarker) resetMarker(prevMarker); + + return { + type: Markers.CAUTION, + element: cautionMarker, + route: routeId, + factors: cautionTypes, + } + }) + }, + ); + } + }; + + const drawRoute = (coreRouteList: CoreRoutesList) => { + if (!Polyline || !AdvancedMarker || !map) return; + + for (const coreRoutes of coreRouteList) { + const { coreNode1Id, coreNode2Id, routes: subRoutes } = coreRoutes; + + // 가장 끝쪽 Core Node 그리기 + const endNode = subRoutes[subRoutes.length - 1].node2; + + createAdvancedMarker( + AdvancedMarker, + map, + endNode, + createMarkerElement({ type: Markers.WAYPOINT, className: "translate-waypoint" }), + ); + + const subNodes = [subRoutes[0].node1, ...subRoutes.map((el) => el.node2)]; + + const routePolyLine = new Polyline({ + map: map, + path: subNodes.map((el) => { + return { lat: el.lat, lng: el.lng }; + }), + strokeColor: "#808080", + }); + + + + routePolyLine.addListener("click", (e: ClickEvent) => { + const edges: CoreRoute[] = subRoutes.map(({ routeId, node1, node2 }) => { + return { routeId, node1, node2 }; + }); + + const point = LatLngToLiteral(e.latLng); + const { edge: nearestEdge, point: nearestPoint } = findNearestSubEdge(edges, point); + + const newReportMarker = createAdvancedMarker( + AdvancedMarker, + map, + centerCoordinate(nearestEdge.node1, nearestEdge.node2), + createMarkerElement({ + type: Markers.REPORT, + className: "translate-routemarker", + hasAnimation: true, + }), + ); + + setReportMarker((prevMarker) => { + if (prevMarker) resetMarker(prevMarker); + + return { + type: Markers.REPORT, + element: newReportMarker, + route: nearestEdge.routeId + } + }) + + }); + + const startNode = subRoutes[0].node1; + + createAdvancedMarker( + AdvancedMarker, + map, + startNode, + createMarkerElement({ type: Markers.WAYPOINT, className: "translate-waypoint" }), + ); + } + }; + + + useEffect(() => { + drawRoute(routes.data); + addRiskMarker(); + + if (map) { + map.addListener("click", () => { + setReportMarker((prevMarker) => { + if (prevMarker) { + setMessage(ReportRiskMessage.DEFAULT); + resetMarker(prevMarker); + } else setMessage(ReportRiskMessage.ERROR); + + return undefined; + }); + }); + + } + }, [map, AdvancedMarker, Polyline]); + + useEffect(() => { + if (message === ReportRiskMessage.ERROR) { + setTimeout(() => { + setMessage(ReportRiskMessage.DEFAULT); + }, 1000); + } + }, [message]); + + /** isSelect(Marker 선택 시) Marker Content 변경, 지도 이동, BottomSheet 열기 */ + const changeMarkerStyle = (marker: reportMarkerTypes | undefined, isSelect: boolean) => { + if (!map || !marker) return; + + + if (isSelect) { + if (marker.type === Markers.DANGER) { + const key = marker.factors && marker.factors[0] as DangerIssueType; + marker.element.content = createMarkerElement({ + type: marker.type, + title: key && DangerIssue[key], + hasTopContent: true, + }); + } + else if (marker.type === Markers.CAUTION) { + const key = marker.factors && marker.factors[0] as CautionIssueType; + marker.element.content = createMarkerElement({ + type: marker.type, + title: key && CautionIssue[key], + hasTopContent: true, + }); + } + return; + } + + marker.element.content = createMarkerElement({ + type: marker.type, + }); + }; + + /** 선택된 마커가 있는 경우 */ + useEffect(() => { + if (!reportMarker) return; + + if (reportMarker.type === Markers.REPORT) setMessage(ReportRiskMessage.CREATE) + else if (reportMarker.type === Markers.DANGER || reportMarker.type === Markers.CAUTION) setMessage(ReportRiskMessage.UPDATE) + else setMessage(ReportRiskMessage.DEFAULT); + + changeMarkerStyle(reportMarker, true); + return () => { + changeMarkerStyle(reportMarker, false); + }; + }, [reportMarker]); + + return ( +
+
+ + {message} + +
+ +
+ + + + + +
+ ); +} diff --git a/uniro_frontend/src/pages/reportRoute.tsx b/uniro_frontend/src/pages/reportRoute.tsx index 1a834ff..55b55aa 100644 --- a/uniro_frontend/src/pages/reportRoute.tsx +++ b/uniro_frontend/src/pages/reportRoute.tsx @@ -10,87 +10,188 @@ import findNearestSubEdge from "../utils/polylines/findNearestEdge"; import { AdvancedMarker } from "../data/types/marker"; import Button from "../components/customButton"; import { CautionToggleButton, DangerToggleButton } from "../components/map/floatingButtons"; -import { mockHazardEdges } from "../data/mock/hanyangHazardEdge"; import toggleMarkers from "../utils/markers/toggleMarkers"; import BackButton from "../components/map/backButton"; import useUniversityInfo from "../hooks/useUniversityInfo"; import useRedirectUndefined from "../hooks/useRedirectUndefined"; import { University } from "../data/types/university"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { getAllRoutes } from "../api/route"; -import { CoreRoutesList } from "../data/types/route"; - -const colors = [ - "#f1a2b3", - "#4c8dff", - "#e67e22", - "#2ecc71", - "#9b59b6", - "#34495e", - "#f39c12", - "#1abc9c", - "#d35400", - "#c0392b", -]; +import { useMutation, useQueryClient, useSuspenseQueries } from "@tanstack/react-query"; +import { getAllRoutes, postReportRoute } from "../api/route"; +import { CoreRoute, CoreRoutesList, Route, RouteId } from "../data/types/route"; +import { Node, NodeId } from "../data/types/node"; +import { Coord } from "../data/types/coord"; +import useModal from "../hooks/useModal"; +import { useNavigate } from "react-router"; +import { getAllRisks } from "../api/routes"; +import { CautionIssueType, DangerIssueType } from "../data/types/enum"; +import { CautionIssue, DangerIssue } from "../constant/enum/reportEnum"; + +type SelectedMarkerTypes = { + type: Markers.CAUTION | Markers.DANGER; + id: RouteId; + element: AdvancedMarker; + factors: DangerIssueType[] | CautionIssueType[]; +}; export default function ReportRoutePage() { + const navigate = useNavigate(); + const queryClient = useQueryClient(); const { map, mapRef, AdvancedMarker, Polyline } = useMap({ zoom: 18, minZoom: 17 }); - const originPoint = useRef<{ point: google.maps.LatLngLiteral; element: AdvancedMarker } | undefined>(); - const [newPoints, setNewPoints] = useState<{ element: AdvancedMarker | null; coords: google.maps.LatLngLiteral[] }>( - { - element: null, - coords: [], - }, - ); + const originPoint = useRef<{ point: Node; element: AdvancedMarker } | undefined>(); + const [newPoints, setNewPoints] = useState<{ element: AdvancedMarker | null; coords: (Coord | Node)[] }>({ + element: null, + coords: [], + }); const newPolyLine = useRef(); const [isActive, setIsActive] = useState(false); - const [dangerMarkers, setDangerMarkers] = useState([]); - const [isDangerAcitve, setIsDangerActive] = useState(true); + const [dangerMarkers, setDangerMarkers] = useState<{ element: AdvancedMarker, routeId: RouteId }[]>([]); + const [isDangerAcitve, setIsDangerActive] = useState(false); - const [cautionMarkers, setCautionMarkers] = useState([]); - const [isCautionAcitve, setIsCautionActive] = useState(true); + const [cautionMarkers, setCautionMarkers] = useState<{ element: AdvancedMarker, routeId: RouteId }[]>([]); + const [isCautionAcitve, setIsCautionActive] = useState(false); const { university } = useUniversityInfo(); useRedirectUndefined([university]); - const { data } = useSuspenseQuery({ - queryKey: ["routes", university?.id], - queryFn: () => getAllRoutes(university?.id ?? 1001), + const [selectedMarker, setSelectedMarker] = useState(); + const [SuccessModal, isSuccessOpen, openSuccess, closeSuccess] = useModal(() => { navigate('/map') }); + const [FailModal, isFailOpen, openFail, closeFail] = useModal(); + + if (!university) return; + + const result = useSuspenseQueries({ + queries: [ + { + queryKey: ["routes", university.id], + queryFn: () => getAllRoutes(university.id), + }, + { queryKey: [university.id, 'risks'], queryFn: () => getAllRisks(university.id) }, + ] + }); + + const [routes, risks] = result; + + + const { mutate } = useMutation({ + mutationFn: ({ + startNodeId, + coordinates, + endNodeId, + }: { + startNodeId: NodeId; + endNodeId: NodeId | null; + coordinates: Coord[]; + }) => postReportRoute(university.id, { startNodeId, endNodeId, coordinates }), + onSuccess: () => { + openSuccess(); + if (newPoints.element) newPoints.element.map = null; + if (originPoint.current) { + originPoint.current.element.map = null; + originPoint.current = undefined; + } + setNewPoints({ + element: null, + coords: [], + }); + if (newPolyLine.current) newPolyLine.current.setPath([]); + queryClient.invalidateQueries({ queryKey: ["routes", university.id], }); + }, + onError: () => { + openFail(); + if (newPoints.element) newPoints.element.map = null; + if (originPoint.current) { + originPoint.current.element.map = null; + originPoint.current = undefined; + } + setNewPoints({ + element: null, + coords: [], + }); + if (newPolyLine.current) newPolyLine.current.setPath([]); + }, }); - const addHazardMarker = () => { + const addRiskMarker = () => { if (AdvancedMarker === null || map === null) return; - for (const edge of mockHazardEdges) { - const { id, startNode, endNode, dangerFactors, cautionFactors } = edge; - const hazardMarker = createAdvancedMarker( + const { dangerRoutes, cautionRoutes } = risks.data; + + /** 위험 마커 생성 */ + const dangerMarkersWithId: { routeId: RouteId; element: AdvancedMarker }[] = []; + + for (const route of dangerRoutes) { + const { routeId, node1, node2, dangerTypes } = route; + const type = Markers.DANGER; + + const dangerMarker = createAdvancedMarker( AdvancedMarker, - map, + null, new google.maps.LatLng({ - lat: (startNode.lat + endNode.lat) / 2, - lng: (startNode.lng + endNode.lng) / 2, + lat: (node1.lat + node2.lat) / 2, + lng: (node1.lng + node2.lng) / 2, }), - createMarkerElement({ type: dangerFactors ? Markers.DANGER : Markers.CAUTION }), + createMarkerElement({ type }), + () => { + setSelectedMarker((prevMarker) => { + if (prevMarker?.id === routeId) return undefined; + return { + type: Markers.DANGER, + element: dangerMarker, + factors: dangerTypes, + id: routeId + } + }) + }, ); - if (dangerFactors) { - setDangerMarkers((prevMarkers) => [...prevMarkers, hazardMarker]); - } else { - setCautionMarkers((prevMarkers) => [...prevMarkers, hazardMarker]); - } + + dangerMarkersWithId.push({ routeId, element: dangerMarker }); } + setDangerMarkers(dangerMarkersWithId); + + /** 주의 마커 생성 */ + const cautionMarkersWithId: { routeId: RouteId; element: AdvancedMarker }[] = []; + + for (const route of cautionRoutes) { + const { routeId, node1, node2, cautionTypes } = route; + const type = Markers.CAUTION; + + const cautionMarker = createAdvancedMarker( + AdvancedMarker, + null, + new google.maps.LatLng({ + lat: (node1.lat + node2.lat) / 2, + lng: (node1.lng + node2.lng) / 2, + }), + createMarkerElement({ type }), + () => { + setSelectedMarker((prevMarker) => { + if (prevMarker?.id === routeId) return undefined; + return { + type: Markers.CAUTION, + element: cautionMarker, + factors: cautionTypes, + id: routeId + } + }) + }, + ); + cautionMarkersWithId.push({ routeId, element: cautionMarker }); + } + + setCautionMarkers(cautionMarkersWithId); }; const toggleCautionButton = () => { if (!map) return; setIsCautionActive((isActive) => { - toggleMarkers(!isActive, cautionMarkers, map); + toggleMarkers(!isActive, cautionMarkers.map(marker => marker.element), map); return !isActive; }); }; const toggleDangerButton = () => { if (!map) return; setIsDangerActive((isActive) => { - toggleMarkers(!isActive, dangerMarkers, map); + toggleMarkers(!isActive, dangerMarkers.map(marker => marker.element), map); return !isActive; }); }; @@ -103,35 +204,29 @@ export default function ReportRoutePage() { for (const edge of edges) { const subNode = createSubNodes(new Polyline({ path: edge })); - const subEdges = subNode.map((node, idx) => [node, subNode[idx + 1]]).slice(0, -1); - - for (let i = 0; i < subEdges.length; i++) { - new Polyline({ - map: map, - path: subEdges[i], - strokeColor: colors[i % 10], - }); - } - subNodes.push(...subNode); } - console.log(subNodes); + if (!originPoint.current) return; + const lastPoint = newPoints.coords[newPoints.coords.length - 1] as Node | Coord; - setNewPoints({ - element: null, - coords: [], - }); - originPoint.current = undefined; - newPolyLine.current.setPath([]); + if ("nodeId" in lastPoint) { + mutate({ + startNodeId: originPoint.current.point.nodeId, + endNodeId: lastPoint.nodeId, + coordinates: subNodes, + }); + } else mutate({ startNodeId: originPoint.current.point.nodeId, coordinates: subNodes, endNodeId: null }); }; const drawRoute = (coreRouteList: CoreRoutesList) => { if (!Polyline || !AdvancedMarker || !map) return; for (const coreRoutes of coreRouteList) { + const { coreNode1Id, coreNode2Id, routes: subRoutes } = coreRoutes; + // 가장 끝쪽 Core Node 그리기 - const endNode = coreRoutes.routes[coreRoutes.routes.length - 1].node2; + const endNode = subRoutes[subRoutes.length - 1].node2; createAdvancedMarker( AdvancedMarker, @@ -139,77 +234,75 @@ export default function ReportRoutePage() { endNode, createMarkerElement({ type: Markers.WAYPOINT, className: "translate-waypoint" }), ); - for (const coreRoute of coreRoutes.routes) { - const routePolyLine = new Polyline({ - map: map, - path: [coreRoute.node1, coreRoute.node2], - strokeColor: "#808080", + + const subNodes = [subRoutes[0].node1, ...subRoutes.map((el) => el.node2)]; + + const routePolyLine = new Polyline({ + map: map, + path: subNodes.map((el) => { + return { lat: el.lat, lng: el.lng }; + }), + strokeColor: "#808080", + }); + + routePolyLine.addListener("click", (e: ClickEvent) => { + const edges: CoreRoute[] = subRoutes.map(({ routeId, node1, node2 }) => { + return { routeId, node1, node2 }; }); - routePolyLine.addListener("click", (e: ClickEvent) => { - const subNodes = createSubNodes(routePolyLine); - const edges = subNodes - .map( - (node, idx) => - [node, subNodes[idx + 1]] as [google.maps.LatLngLiteral, google.maps.LatLngLiteral], - ) - .slice(0, -1); + const point = LatLngToLiteral(e.latLng); + const { edge: nearestEdge, point: nearestPoint } = findNearestSubEdge(edges, point); - const point = LatLngToLiteral(e.latLng); + const tempWaypointMarker = createAdvancedMarker( + AdvancedMarker, + map, + nearestPoint, + createMarkerElement({ + type: Markers.WAYPOINT, + className: "translate-waypoint", + }), + ); - const { edge: nearestEdge, point: nearestPoint } = findNearestSubEdge(edges, point); - - const tempWaypointMarker = createAdvancedMarker( - AdvancedMarker, - map, - nearestPoint, - createMarkerElement({ - type: Markers.WAYPOINT, - className: "translate-waypoint", - }), - ); - if (originPoint.current) { - setNewPoints((prevPoints) => { - if (prevPoints.element) { - prevPoints.element.position = nearestPoint; - return { - ...prevPoints, - coords: [...prevPoints.coords, nearestPoint], - }; - } else { - setIsActive(true); - return { - element: new AdvancedMarker({ - map: map, - position: nearestPoint, - content: createMarkerElement({ - type: Markers.DESTINATION, - }), + if (originPoint.current) { + setNewPoints((prevPoints) => { + if (prevPoints.element) { + prevPoints.element.position = nearestPoint; + return { + ...prevPoints, + coords: [...prevPoints.coords, nearestPoint], + }; + } else { + setIsActive(true); + return { + element: new AdvancedMarker({ + map: map, + position: nearestPoint, + content: createMarkerElement({ + type: Markers.DESTINATION, }), - coords: [...prevPoints.coords, nearestPoint], - }; - } - }); - } else { - const originMarker = new AdvancedMarker({ - map: map, - position: nearestPoint, - content: createMarkerElement({ type: Markers.ORIGIN }), - }); - - originPoint.current = { - point: nearestPoint, - element: originMarker, - }; - - setNewPoints({ - element: null, - coords: [nearestPoint], - }); - } - }); - } - const startNode = coreRoutes.routes[0].node1; + }), + coords: [...prevPoints.coords, nearestPoint], + }; + } + }); + } else { + const originMarker = new AdvancedMarker({ + map: map, + position: nearestPoint, + content: createMarkerElement({ type: Markers.ORIGIN }), + }); + originPoint.current = { + point: nearestPoint, + element: originMarker, + }; + setNewPoints({ + element: null, + coords: [nearestPoint], + }); + } + }); + + const startNode = subRoutes[0].node1; createAdvancedMarker( AdvancedMarker, @@ -227,14 +320,15 @@ export default function ReportRoutePage() { }, [newPoints]); useEffect(() => { - drawRoute(data); - addHazardMarker(); + drawRoute(routes.data); + addRiskMarker(); if (Polyline) { newPolyLine.current = new Polyline({ map: map, path: [], strokeColor: "#0367FB" }); } if (map && AdvancedMarker) { map.addListener("click", (e: ClickEvent) => { + setSelectedMarker(undefined) if (originPoint.current) { const point = LatLngToLiteral(e.latLng); setNewPoints((prevPoints) => { @@ -263,6 +357,44 @@ export default function ReportRoutePage() { } }, [map]); + /** isSelect(Marker 선택 시) Marker Content 변경, 지도 이동, BottomSheet 열기 */ + const changeMarkerStyle = (marker: SelectedMarkerTypes | undefined, isSelect: boolean) => { + if (!map || !marker) return; + + + if (isSelect) { + if (marker.type === Markers.DANGER) { + const key = marker.factors[0] as DangerIssueType; + marker.element.content = createMarkerElement({ + type: marker.type, + title: key && DangerIssue[key], + hasTopContent: true, + }); + } + else if (marker.type === Markers.CAUTION) { + const key = marker.factors[0] as CautionIssueType; + marker.element.content = createMarkerElement({ + type: marker.type, + title: key && CautionIssue[key], + hasTopContent: true, + }); + } + return; + } + + marker.element.content = createMarkerElement({ + type: marker.type, + }); + }; + + /** 선택된 마커가 있는 경우 */ + useEffect(() => { + changeMarkerStyle(selectedMarker, true); + return () => { + changeMarkerStyle(selectedMarker, false); + }; + }, [selectedMarker]); + return (
@@ -281,6 +413,30 @@ export default function ReportRoutePage() {
+ {isSuccessOpen && ( + +

새로운 길 제보를 완료했어요!

+
+

제보는 바로 반영되지만,

+

+ 더 정확한 정보를 위해 추후 수정될 수 있어요. +

+
+
+ )} + {isFailOpen && ( + +

경로를 생성하는데 실패하였습니다!

+
+

+ 선택하신 경로는 생성이 불가능합니다. +

+

+ 선 위에서 시작하여, 빈 곳을 이어주시기 바랍니다. +

+
+
+ )}
); } diff --git a/uniro_frontend/src/utils/coordinates/centerCoordinate.ts b/uniro_frontend/src/utils/coordinates/centerCoordinate.ts index 8fc52a2..1e16b47 100644 --- a/uniro_frontend/src/utils/coordinates/centerCoordinate.ts +++ b/uniro_frontend/src/utils/coordinates/centerCoordinate.ts @@ -1,7 +1,6 @@ -export default function centerCoordinate( - point1: google.maps.LatLngLiteral, - point2: google.maps.LatLngLiteral, -): google.maps.LatLngLiteral { +import { Coord } from "../../data/types/coord"; + +export default function centerCoordinate(point1: Coord, point2: Coord): Coord { return { lat: (point1.lat + point2.lat) / 2, lng: (point1.lng + point2.lng) / 2, diff --git a/uniro_frontend/src/utils/markers/createAdvanedMarker.ts b/uniro_frontend/src/utils/markers/createAdvanedMarker.ts index 27509ca..28fb887 100644 --- a/uniro_frontend/src/utils/markers/createAdvanedMarker.ts +++ b/uniro_frontend/src/utils/markers/createAdvanedMarker.ts @@ -30,8 +30,11 @@ export function createUniversityMarker( "py-1 px-3 text-kor-caption font-medium text-gray-100 bg-primary-500 text-center rounded-200"; container.appendChild(markerTitle); const markerImage = document.createElement("img"); + + const markerImages = import.meta.glob("/src/assets/markers/*.svg", { eager: true }); + markerImage.className = "border-0 translate-y-[-1px]"; - markerImage.src = "/src/assets/markers/university.svg"; + markerImage.src = (markerImages[`/src/assets/markers/university.svg`] as { default: string })?.default; container.appendChild(markerImage); const newMarker = new AdvancedMarker({ diff --git a/uniro_frontend/src/utils/polylines/createSubnodes.ts b/uniro_frontend/src/utils/polylines/createSubnodes.ts index dac974d..4560fd3 100644 --- a/uniro_frontend/src/utils/polylines/createSubnodes.ts +++ b/uniro_frontend/src/utils/polylines/createSubnodes.ts @@ -1,9 +1,10 @@ import { EDGE_LENGTH } from "../../constant/edge"; +import { Coord } from "../../data/types/coord"; import { LatLngToLiteral } from "../coordinates/coordinateTransform"; import distance from "../coordinates/distance"; /** 구면 보간 없이 계산한 결과 */ -export default function createSubNodes(polyLine: google.maps.Polyline): google.maps.LatLngLiteral[] { +export default function createSubNodes(polyLine: google.maps.Polyline): Coord[] { const paths = polyLine.getPath(); const [startNode, endNode] = paths.getArray().map((el) => LatLngToLiteral(el)); @@ -19,7 +20,7 @@ export default function createSubNodes(polyLine: google.maps.Polyline): google.m const subNodes = []; for (let i = 0; i < subEdgesCount; i++) { - const subNode: google.maps.LatLngLiteral = { + const subNode: Coord = { lat: startNode.lat + interval.lat * i, lng: startNode.lng + interval.lng * i, }; diff --git a/uniro_frontend/src/utils/polylines/findNearestEdge.ts b/uniro_frontend/src/utils/polylines/findNearestEdge.ts index ceeaf80..64ccc99 100644 --- a/uniro_frontend/src/utils/polylines/findNearestEdge.ts +++ b/uniro_frontend/src/utils/polylines/findNearestEdge.ts @@ -1,18 +1,21 @@ +import { Coord } from "../../data/types/coord"; +import { Node } from "../../data/types/node"; +import { CoreRoute } from "../../data/types/route"; import centerCoordinate from "../coordinates/centerCoordinate"; import distance from "../coordinates/distance"; export default function findNearestSubEdge( - edges: [google.maps.LatLngLiteral, google.maps.LatLngLiteral][], - point: google.maps.LatLngLiteral, + edges: CoreRoute[], + point: Coord, ): { - edge: [google.maps.LatLngLiteral, google.maps.LatLngLiteral]; - point: google.maps.LatLngLiteral; + edge: CoreRoute; + point: Node; } { const edgesWithDistance = edges - .map(([startNode, endNode]) => { + .map((edge) => { return { - edge: [startNode, endNode] as [google.maps.LatLngLiteral, google.maps.LatLngLiteral], - distance: distance(point, centerCoordinate(startNode, endNode)), + edge, + distance: distance(point, centerCoordinate(edge.node1, edge.node2)), }; }) .sort((a, b) => { @@ -21,9 +24,10 @@ export default function findNearestSubEdge( const nearestEdge = edgesWithDistance[0].edge; - const distance0 = distance(nearestEdge[0], point); - const distance1 = distance(nearestEdge[1], point); - const nearestPoint = distance0 > distance1 ? nearestEdge[1] : nearestEdge[0]; + const { node1, node2 } = nearestEdge; + const distance0 = distance(node1, point); + const distance1 = distance(node2, point); + const nearestPoint = distance0 > distance1 ? node2 : node1; return { edge: nearestEdge,