diff --git a/uniro_frontend/package-lock.json b/uniro_frontend/package-lock.json
index 9fb6f47..8e59f44 100644
--- a/uniro_frontend/package-lock.json
+++ b/uniro_frontend/package-lock.json
@@ -9,10 +9,13 @@
"version": "0.0.0",
"dependencies": {
"@googlemaps/js-api-loader": "^1.16.8",
+ "@react-spring/web": "^9.7.5",
"@tailwindcss/vite": "^4.0.0",
+ "framer-motion": "^12.0.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^7.1.3",
+ "react-sheet-slide": "^1.5.0",
"tailwindcss": "^4.0.0"
},
"devDependencies": {
@@ -1048,6 +1051,78 @@
"url": "https://opencollective.com/unts"
}
},
+ "node_modules/@react-spring/animated": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz",
+ "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/shared": "~9.7.5",
+ "@react-spring/types": "~9.7.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/core": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz",
+ "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/animated": "~9.7.5",
+ "@react-spring/shared": "~9.7.5",
+ "@react-spring/types": "~9.7.5"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-spring/donate"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/rafz": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz",
+ "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==",
+ "license": "MIT"
+ },
+ "node_modules/@react-spring/shared": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz",
+ "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/rafz": "~9.7.5",
+ "@react-spring/types": "~9.7.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/types": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz",
+ "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==",
+ "license": "MIT"
+ },
+ "node_modules/@react-spring/web": {
+ "version": "9.7.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz",
+ "integrity": "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/animated": "~9.7.5",
+ "@react-spring/core": "~9.7.5",
+ "@react-spring/shared": "~9.7.5",
+ "@react-spring/types": "~9.7.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/@rollup/pluginutils": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
@@ -2098,6 +2173,26 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@use-gesture/core": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
+ "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@use-gesture/react": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
+ "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@use-gesture/core": "10.3.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
"node_modules/@vitejs/plugin-react": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
@@ -2552,6 +2647,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
@@ -2579,15 +2683,6 @@
}
}
},
- "node_modules/cookie": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
- "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3462,6 +3557,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.0.6",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.0.6.tgz",
+ "integrity": "sha512-LmrXbXF6Vv5WCNmb+O/zn891VPZrH7XbsZgRLBROw6kFiP+iTK49gxTv2Ur3F0Tbw6+sy9BVtSqnWfMUpH+6nA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.0.0",
+ "motion-utils": "^12.0.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -4675,6 +4797,21 @@
"node": "*"
}
},
+ "node_modules/motion-dom": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.0.0.tgz",
+ "integrity": "sha512-CvYd15OeIR6kHgMdonCc1ihsaUG4MYh/wrkz8gZ3hBX/uamyZCXN9S9qJoYF03GqfTt7thTV/dxnHYX4+55vDg==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.0.0"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.0.0.tgz",
+ "integrity": "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5174,6 +5311,18 @@
}
}
},
+ "node_modules/react-sheet-slide": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/react-sheet-slide/-/react-sheet-slide-1.5.0.tgz",
+ "integrity": "sha512-mCq+/e0HQJp1QoVfpugWAlSEgJyQ/Vit0hJYNa7zrUSCB2VXmQKIubJEg2CJQtokzi1BwLaMU4tqEJbw4XwJlw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@react-spring/web": "^9.0.0",
+ "@use-gesture/react": "^10.0.0",
+ "react": "^16.8.0 || >=17",
+ "react-dom": "^16.8.0 || >=17"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -5773,7 +5922,6 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
"license": "0BSD"
},
"node_modules/turbo-stream": {
diff --git a/uniro_frontend/package.json b/uniro_frontend/package.json
index 6d9da40..d854e29 100644
--- a/uniro_frontend/package.json
+++ b/uniro_frontend/package.json
@@ -11,10 +11,13 @@
},
"dependencies": {
"@googlemaps/js-api-loader": "^1.16.8",
+ "@react-spring/web": "^9.7.5",
"@tailwindcss/vite": "^4.0.0",
+ "framer-motion": "^12.0.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^7.1.3",
+ "react-sheet-slide": "^1.5.0",
"tailwindcss": "^4.0.0"
},
"devDependencies": {
diff --git a/uniro_frontend/src/App.tsx b/uniro_frontend/src/App.tsx
index 26e9e4d..41eb866 100644
--- a/uniro_frontend/src/App.tsx
+++ b/uniro_frontend/src/App.tsx
@@ -3,6 +3,7 @@ import "./App.css";
import Demo from "./pages/demo";
import LandingPage from "./pages/landing";
import UniversitySearchPage from "./pages/search";
+import NavigationResultPage from "./pages/navigationResult";
function App() {
return (
@@ -10,6 +11,7 @@ function App() {
} />
} />
} />
+ } />
);
}
diff --git a/uniro_frontend/src/assets/icon/cautionText.svg b/uniro_frontend/src/assets/icon/cautionText.svg
new file mode 100644
index 0000000..1862757
--- /dev/null
+++ b/uniro_frontend/src/assets/icon/cautionText.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_frontend/src/assets/icon/close.svg b/uniro_frontend/src/assets/icon/close.svg
new file mode 100644
index 0000000..2a1889d
--- /dev/null
+++ b/uniro_frontend/src/assets/icon/close.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_frontend/src/assets/icon/destination.svg b/uniro_frontend/src/assets/icon/destination.svg
new file mode 100644
index 0000000..b356c81
--- /dev/null
+++ b/uniro_frontend/src/assets/icon/destination.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_frontend/src/assets/icon/goBack.svg b/uniro_frontend/src/assets/icon/goBack.svg
new file mode 100644
index 0000000..3a3e03b
--- /dev/null
+++ b/uniro_frontend/src/assets/icon/goBack.svg
@@ -0,0 +1,5 @@
+
diff --git a/uniro_frontend/src/assets/icon/resultDivider.svg b/uniro_frontend/src/assets/icon/resultDivider.svg
new file mode 100644
index 0000000..d8576d1
--- /dev/null
+++ b/uniro_frontend/src/assets/icon/resultDivider.svg
@@ -0,0 +1,3 @@
+
diff --git a/uniro_frontend/src/assets/icon/safeText.svg b/uniro_frontend/src/assets/icon/safeText.svg
new file mode 100644
index 0000000..20e1ea5
--- /dev/null
+++ b/uniro_frontend/src/assets/icon/safeText.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_frontend/src/assets/icon/start.svg b/uniro_frontend/src/assets/icon/start.svg
new file mode 100644
index 0000000..e1fb5c7
--- /dev/null
+++ b/uniro_frontend/src/assets/icon/start.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_frontend/src/assets/markers/building.svg b/uniro_frontend/src/assets/markers/building.svg
new file mode 100644
index 0000000..4fc91f3
--- /dev/null
+++ b/uniro_frontend/src/assets/markers/building.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_frontend/src/assets/markers/caution.svg b/uniro_frontend/src/assets/markers/caution.svg
new file mode 100644
index 0000000..556f369
--- /dev/null
+++ b/uniro_frontend/src/assets/markers/caution.svg
@@ -0,0 +1,16 @@
+
diff --git a/uniro_frontend/src/assets/markers/danger.svg b/uniro_frontend/src/assets/markers/danger.svg
new file mode 100644
index 0000000..5508d10
--- /dev/null
+++ b/uniro_frontend/src/assets/markers/danger.svg
@@ -0,0 +1,16 @@
+
diff --git a/uniro_frontend/src/assets/markers/destination.svg b/uniro_frontend/src/assets/markers/destination.svg
new file mode 100644
index 0000000..80cbe8d
--- /dev/null
+++ b/uniro_frontend/src/assets/markers/destination.svg
@@ -0,0 +1,15 @@
+
diff --git a/uniro_frontend/src/assets/markers/origin.svg b/uniro_frontend/src/assets/markers/origin.svg
new file mode 100644
index 0000000..d633fcd
--- /dev/null
+++ b/uniro_frontend/src/assets/markers/origin.svg
@@ -0,0 +1,15 @@
+
diff --git a/uniro_frontend/src/assets/markers/selectedBuilding.svg b/uniro_frontend/src/assets/markers/selectedBuilding.svg
new file mode 100644
index 0000000..1187dc7
--- /dev/null
+++ b/uniro_frontend/src/assets/markers/selectedBuilding.svg
@@ -0,0 +1,4 @@
+
diff --git a/uniro_frontend/src/assets/markers/waypoint.svg b/uniro_frontend/src/assets/markers/waypoint.svg
new file mode 100644
index 0000000..5107e6a
--- /dev/null
+++ b/uniro_frontend/src/assets/markers/waypoint.svg
@@ -0,0 +1,3 @@
+
diff --git a/uniro_frontend/src/assets/route/dest.svg b/uniro_frontend/src/assets/route/dest.svg
new file mode 100644
index 0000000..90b6d7d
--- /dev/null
+++ b/uniro_frontend/src/assets/route/dest.svg
@@ -0,0 +1,10 @@
+
diff --git a/uniro_frontend/src/assets/route/left.svg b/uniro_frontend/src/assets/route/left.svg
new file mode 100644
index 0000000..8ae9bd7
--- /dev/null
+++ b/uniro_frontend/src/assets/route/left.svg
@@ -0,0 +1,3 @@
+
diff --git a/uniro_frontend/src/assets/route/right.svg b/uniro_frontend/src/assets/route/right.svg
new file mode 100644
index 0000000..427acf4
--- /dev/null
+++ b/uniro_frontend/src/assets/route/right.svg
@@ -0,0 +1,3 @@
+
diff --git a/uniro_frontend/src/assets/route/start.svg b/uniro_frontend/src/assets/route/start.svg
new file mode 100644
index 0000000..afaa65c
--- /dev/null
+++ b/uniro_frontend/src/assets/route/start.svg
@@ -0,0 +1,3 @@
+
diff --git a/uniro_frontend/src/assets/route/straight.svg b/uniro_frontend/src/assets/route/straight.svg
new file mode 100644
index 0000000..e485217
--- /dev/null
+++ b/uniro_frontend/src/assets/route/straight.svg
@@ -0,0 +1,3 @@
+
diff --git "a/uniro_frontend/src/assets/\352\263\240\353\240\244\353\214\200\355\225\231\352\265\220.svg" "b/uniro_frontend/src/assets/university/\352\263\240\353\240\244\353\214\200\355\225\231\352\265\220.svg"
similarity index 100%
rename from "uniro_frontend/src/assets/\352\263\240\353\240\244\353\214\200\355\225\231\352\265\220.svg"
rename to "uniro_frontend/src/assets/university/\352\263\240\353\240\244\353\214\200\355\225\231\352\265\220.svg"
diff --git "a/uniro_frontend/src/assets/\354\204\234\354\232\270\354\213\234\353\246\275\353\214\200\355\225\231\352\265\220.svg" "b/uniro_frontend/src/assets/university/\354\204\234\354\232\270\354\213\234\353\246\275\353\214\200\355\225\231\352\265\220.svg"
similarity index 100%
rename from "uniro_frontend/src/assets/\354\204\234\354\232\270\354\213\234\353\246\275\353\214\200\355\225\231\352\265\220.svg"
rename to "uniro_frontend/src/assets/university/\354\204\234\354\232\270\354\213\234\353\246\275\353\214\200\355\225\231\352\265\220.svg"
diff --git "a/uniro_frontend/src/assets/\354\235\264\355\231\224\354\227\254\354\236\220\353\214\200\355\225\231\352\265\220.svg" "b/uniro_frontend/src/assets/university/\354\235\264\355\231\224\354\227\254\354\236\220\353\214\200\355\225\231\352\265\220.svg"
similarity index 100%
rename from "uniro_frontend/src/assets/\354\235\264\355\231\224\354\227\254\354\236\220\353\214\200\355\225\231\352\265\220.svg"
rename to "uniro_frontend/src/assets/university/\354\235\264\355\231\224\354\227\254\354\236\220\353\214\200\355\225\231\352\265\220.svg"
diff --git "a/uniro_frontend/src/assets/\354\235\270\355\225\230\353\214\200\355\225\231\352\265\220.svg" "b/uniro_frontend/src/assets/university/\354\235\270\355\225\230\353\214\200\355\225\231\352\265\220.svg"
similarity index 100%
rename from "uniro_frontend/src/assets/\354\235\270\355\225\230\353\214\200\355\225\231\352\265\220.svg"
rename to "uniro_frontend/src/assets/university/\354\235\270\355\225\230\353\214\200\355\225\231\352\265\220.svg"
diff --git "a/uniro_frontend/src/assets/\355\225\234\354\226\221\353\214\200\355\225\231\352\265\220.svg" "b/uniro_frontend/src/assets/university/\355\225\234\354\226\221\353\214\200\355\225\231\352\265\220.svg"
similarity index 100%
rename from "uniro_frontend/src/assets/\355\225\234\354\226\221\353\214\200\355\225\231\352\265\220.svg"
rename to "uniro_frontend/src/assets/university/\355\225\234\354\226\221\353\214\200\355\225\231\352\265\220.svg"
diff --git a/uniro_frontend/src/component/Map.tsx b/uniro_frontend/src/component/Map.tsx
index 1b1ad43..14517f5 100644
--- a/uniro_frontend/src/component/Map.tsx
+++ b/uniro_frontend/src/component/Map.tsx
@@ -1,9 +1,19 @@
+import { useEffect } from "react";
import useMap from "../hooks/useMap";
-const Map = () => {
- const { mapRef } = useMap();
+type MapProps = {
+ style?: React.CSSProperties;
+};
+const Map = ({ style }: MapProps) => {
+ const { mapRef, map, overlay, AdvancedMarker, Polyline, mapLoaded } = useMap();
+
+ if (!style) {
+ style = { height: "100%", width: "100%" };
+ }
+
+ useEffect(() => {}, []);
- return
;
+ return ;
};
export default Map;
diff --git a/uniro_frontend/src/component/NavgationMap.tsx b/uniro_frontend/src/component/NavgationMap.tsx
new file mode 100644
index 0000000..1756403
--- /dev/null
+++ b/uniro_frontend/src/component/NavgationMap.tsx
@@ -0,0 +1,104 @@
+import { useEffect, useRef } from "react";
+import useMap from "../hooks/useMap";
+import { NavigationRoute } from "../data/types/route";
+import createAdvancedMarker from "../utils/markers/createAdvanedMarker";
+import createMarkerElement from "../components/map/mapMarkers";
+import { Markers } from "../constant/enums";
+
+type MapProps = {
+ style?: React.CSSProperties;
+ routes: NavigationRoute;
+ /** 바텀시트나 상단 UI에 의해 가려지는 영역이 있을 경우, 지도 fitBounds에 추가할 패딩값 */
+ topPadding?: number;
+ bottomPadding?: number;
+};
+
+const NavigationMap = ({ style, routes, topPadding = 0, bottomPadding = 0 }: MapProps) => {
+ const { mapRef, map, AdvancedMarker, Polyline } = useMap();
+
+ const boundsRef = useRef(null);
+
+ if (!style) {
+ style = { height: "100%", width: "100%" };
+ }
+
+ useEffect(() => {
+ if (!map || !AdvancedMarker || !routes || !Polyline) return;
+
+ const { route: routeList } = routes;
+ if (!routeList || routeList.length === 0) return;
+ const bounds = new google.maps.LatLngBounds();
+
+ new Polyline({
+ path: [...routeList.map((edge) => edge.startNode), routeList[routeList.length - 1].endNode],
+ map,
+ strokeColor: "#000000",
+ strokeWeight: 2.0,
+ });
+
+ routeList.forEach((edge, index) => {
+ const { startNode, endNode } = edge;
+ const startCoordinate = new google.maps.LatLng(startNode.lat, startNode.lng);
+ const endCoordinate = new google.maps.LatLng(endNode.lat, endNode.lng);
+ bounds.extend(startCoordinate);
+ bounds.extend(endCoordinate);
+ if (index !== 0 && index !== routeList.length - 1) {
+ const markerElement = createMarkerElement({
+ type: Markers.WAYPOINT,
+ className: "translate-waypoint",
+ });
+ if (index === 1) {
+ createAdvancedMarker(AdvancedMarker, map, startCoordinate, markerElement);
+ }
+ createAdvancedMarker(AdvancedMarker, map, endCoordinate, markerElement);
+ }
+ });
+
+ const edgeRoutes = [routeList[0], routeList[routeList.length - 1]];
+
+ edgeRoutes.forEach((edge, index) => {
+ const { startNode, endNode } = edge;
+ if (index === 0) {
+ const startCoordinate = new google.maps.LatLng(startNode.lat, startNode.lng);
+ const markerElement = createMarkerElement({
+ type: Markers.ORIGIN,
+ title: routes.originBuilding.buildingName,
+ className: "translate-routemarker",
+ });
+ createAdvancedMarker(AdvancedMarker, map, startCoordinate, markerElement);
+ bounds.extend(startCoordinate);
+ } else {
+ const endCoordinate = new google.maps.LatLng(endNode.lat, endNode.lng);
+ const markerElement = createMarkerElement({
+ type: Markers.DESTINATION,
+ title: routes.destinationBuilding.buildingName,
+ className: "translate-routemarker",
+ });
+ createAdvancedMarker(AdvancedMarker, map, endCoordinate, markerElement);
+ bounds.extend(endCoordinate);
+ }
+ });
+
+ boundsRef.current = bounds;
+ map.fitBounds(bounds, {
+ top: topPadding,
+ right: 50,
+ bottom: bottomPadding,
+ left: 50,
+ });
+ }, [map, AdvancedMarker, Polyline, routes]);
+
+ useEffect(() => {
+ if (!map || !boundsRef.current) return;
+ map.fitBounds(boundsRef.current, {
+ top: topPadding,
+ right: 50,
+ bottom: bottomPadding,
+ left: 50,
+ });
+ }, [map, bottomPadding, topPadding]);
+
+ return ;
+};
+
+export default NavigationMap;
diff --git a/uniro_frontend/src/components/map/mapMarkers.tsx b/uniro_frontend/src/components/map/mapMarkers.tsx
new file mode 100644
index 0000000..9e4eaf9
--- /dev/null
+++ b/uniro_frontend/src/components/map/mapMarkers.tsx
@@ -0,0 +1,83 @@
+import { MarkerTypes } from "../../data/types/marker";
+import { Markers } from "../../constant/enums";
+
+function createTextElement(type: MarkerTypes, title: string): HTMLElement {
+ const markerTitle = document.createElement("p");
+ markerTitle.innerText = title;
+
+ switch (type) {
+ case Markers.CAUTION:
+ markerTitle.className =
+ "h-[38px] py-2 px-4 mb-2 text-kor-body3 font-semibold text-gray-100 bg-system-orange text-center rounded-200";
+ return markerTitle;
+ case Markers.DANGER:
+ markerTitle.className =
+ "h-[38px] py-2 px-4 mb-2 text-kor-body3 font-semibold text-gray-100 bg-system-red text-center rounded-200";
+ return markerTitle;
+ case Markers.BUILDING:
+ markerTitle.className =
+ "py-1 px-3 text-kor-caption font-medium text-gray-100 bg-gray-900 text-center rounded-200";
+ return markerTitle;
+ case Markers.SELECTED_BUILDING:
+ markerTitle.className =
+ "py-1 px-3 text-kor-caption font-medium text-gray-100 bg-primary-500 text-center rounded-200";
+ return markerTitle;
+ case Markers.ORIGIN:
+ markerTitle.className =
+ "py-1 px-3 text-kor-caption font-medium text-gray-100 bg-primary-500 text-center rounded-200";
+ return markerTitle;
+ case Markers.DESTINATION:
+ markerTitle.className =
+ "py-1 px-3 text-kor-caption font-medium text-gray-100 bg-primary-500 text-center rounded-200";
+ return markerTitle;
+ default:
+ return markerTitle;
+ }
+}
+
+function createImageElement(type: MarkerTypes): HTMLElement {
+ const markerImage = document.createElement("img");
+
+ markerImage.src = `/src/assets/markers/${type}.svg`;
+ return markerImage;
+}
+
+function createContainerElement(className?: string) {
+ const container = document.createElement("div");
+ console.log(className);
+ container.className = `flex flex-col items-center space-y-[7px] ${className}`;
+
+ return container;
+}
+
+export default function createMarkerElement({
+ type,
+ title,
+ className,
+ hasTopContent = false,
+}: {
+ type: MarkerTypes;
+ className?: string;
+ title?: string;
+ hasTopContent?: boolean;
+}): HTMLElement {
+ const container = createContainerElement(className);
+
+ const markerImage = createImageElement(type);
+
+ if (title) {
+ const markerTitle = createTextElement(type, title);
+ if (hasTopContent) {
+ container.appendChild(markerTitle);
+ container.appendChild(markerImage);
+ return container;
+ }
+
+ container.appendChild(markerImage);
+ container.appendChild(markerTitle);
+ return container;
+ }
+
+ container.appendChild(markerImage);
+ return container;
+}
diff --git a/uniro_frontend/src/components/navigation/bottomSheet/bottomSheetHandle.tsx b/uniro_frontend/src/components/navigation/bottomSheet/bottomSheetHandle.tsx
new file mode 100644
index 0000000..9fdb66e
--- /dev/null
+++ b/uniro_frontend/src/components/navigation/bottomSheet/bottomSheetHandle.tsx
@@ -0,0 +1,20 @@
+import { DragControls } from "framer-motion";
+import React from "react";
+
+type HandleProps = {
+ dragControls: DragControls;
+};
+
+const BottomSheetHandle = ({ dragControls }: HandleProps) => {
+ return (
+ dragControls.start(e)}
+ >
+
+
+ );
+};
+
+export default BottomSheetHandle;
diff --git a/uniro_frontend/src/components/navigation/navigationDescription.tsx b/uniro_frontend/src/components/navigation/navigationDescription.tsx
new file mode 100644
index 0000000..e3ab1e0
--- /dev/null
+++ b/uniro_frontend/src/components/navigation/navigationDescription.tsx
@@ -0,0 +1,60 @@
+import React, { useState } from "react";
+import Cancel from "../../assets/icon/close.svg?react";
+import CautionIcon from "../../assets/icon/cautionText.svg?react";
+import SafeIcon from "../../assets/icon/safeText.svg?react";
+import DestinationIcon from "../../assets/icon/destination.svg?react";
+import OriginIcon from "../../assets/icon/start.svg?react";
+import ResultDivider from "../../assets/icon/resultDivider.svg?react";
+import { NavigationRoute } from "../../data/types/route";
+import { mockNavigationRoute } from "../../data/mock/hanyangRoute";
+const TITLE = "전동휠체어 예상소요시간";
+
+type TopBarProps = {
+ isDetailView: boolean;
+};
+
+const NavigationDescription = ({ isDetailView }: TopBarProps) => {
+ const [route, _] = useState(mockNavigationRoute);
+
+ return (
+
+
+ {TITLE}
+ {!isDetailView && }
+
+
+
+
+
+ {route.totalCost}
+
+
분
+
+
+
+ {`${route.totalDistance}m`}
+
+
+
+ {route.hasCaution ? : }
+
+ 가는 길에 주의 요소가 {route.hasCaution ? "있어요" : "없어요"}
+
+
+
+
+
+
+ {route.originBuilding.buildingName}
+
+
+
+
+ {route.destinationBuilding.buildingName}
+
+
+
+ );
+};
+
+export default NavigationDescription;
diff --git a/uniro_frontend/src/components/navigation/route/routeCard.tsx b/uniro_frontend/src/components/navigation/route/routeCard.tsx
new file mode 100644
index 0000000..d00f175
--- /dev/null
+++ b/uniro_frontend/src/components/navigation/route/routeCard.tsx
@@ -0,0 +1,128 @@
+import OriginIcon from "../../../assets/route/start.svg?react";
+import DestinationIcon from "../../../assets/route/dest.svg?react";
+import StraightIcon from "../../../assets/route/straight.svg?react";
+import RightIcon from "../../../assets/route/right.svg?react";
+import LeftIcon from "../../../assets/route/left.svg?react";
+import CautionText from "../../../assets/icon/cautionText.svg?react";
+import { RouteEdge } from "../../../data/types/edge";
+import { Building } from "../../../data/types/node";
+
+const NumberIcon = ({ index }: { index: number }) => {
+ return (
+
+ );
+};
+
+export const RouteCard = ({
+ index,
+ route,
+ originBuilding,
+ destinationBuilding,
+}: {
+ index: number;
+ route: RouteEdge;
+ originBuilding: Building;
+ destinationBuilding: Building;
+}) => {
+ switch (route.direction) {
+ case "straight":
+ return (
+
+ );
+ case "right":
+ return (
+
+ );
+ case "left":
+ return (
+
+ );
+ case "uturn":
+ return (
+
+ );
+ case "origin":
+ return (
+
+
+
+
{originBuilding.buildingName}
+
{originBuilding.address}
+
+
+ );
+ case "destination":
+ return (
+
+
+
+
{destinationBuilding.buildingName}
+
{destinationBuilding.address}
+
+
+ );
+ case "caution":
+ return (
+
+ );
+ default:
+ return (
+
+ 알 수 없음
+
+ );
+ }
+};
diff --git a/uniro_frontend/src/components/navigation/route/routeList.tsx b/uniro_frontend/src/components/navigation/route/routeList.tsx
new file mode 100644
index 0000000..6c27124
--- /dev/null
+++ b/uniro_frontend/src/components/navigation/route/routeList.tsx
@@ -0,0 +1,34 @@
+import { Fragment } from "react";
+import { RouteEdge } from "../../../data/types/edge";
+import { Building } from "../../../data/types/node";
+import { RouteCard } from "./routeCard";
+
+type RouteListProps = {
+ routes: RouteEdge[];
+ originBuilding: Building;
+ destinationBuilding: Building;
+};
+
+const Divider = () => ;
+
+const RouteList = ({ routes, originBuilding, destinationBuilding }: RouteListProps) => {
+ return (
+
+ {routes.map((route, index) => (
+
+
+
+
+
+
+ ))}
+
+ );
+};
+
+export default RouteList;
diff --git a/uniro_frontend/src/components/universityButton.tsx b/uniro_frontend/src/components/universityButton.tsx
index dbc0dca..2dd99bf 100644
--- a/uniro_frontend/src/components/universityButton.tsx
+++ b/uniro_frontend/src/components/universityButton.tsx
@@ -7,19 +7,23 @@ interface UniversityButtonProps {
onClick: () => void;
}
+const svgModules = import.meta.glob("/src/assets/university/*.svg", { eager: true });
+
export default function UniversityButton({ name, img, selected, onClick }: UniversityButtonProps) {
const handleClick = (e: MouseEvent) => {
e.stopPropagation();
onClick();
};
+ const svgPath = (svgModules[`/src/assets/university/${img}`] as { default: string })?.default;
+
return (
diff --git a/uniro_frontend/src/constant/enums.ts b/uniro_frontend/src/constant/enums.ts
new file mode 100644
index 0000000..e816ddb
--- /dev/null
+++ b/uniro_frontend/src/constant/enums.ts
@@ -0,0 +1,15 @@
+export const enum Markers {
+ CAUTION = "caution",
+ DANGER = "danger",
+ BUILDING = "building",
+ ORIGIN = "origin",
+ DESTINATION = "destination",
+ SELECTED_BUILDING = "selectedBuilding",
+ WAYPOINT = "waypoint",
+ NUMBERED_WAYPOINT = "numberedWayPoint",
+}
+
+export const enum RoutePoint {
+ ORIGIN = "origin",
+ DESTINATION = "destination",
+}
diff --git a/uniro_frontend/src/container/animatedContainer.tsx b/uniro_frontend/src/container/animatedContainer.tsx
new file mode 100644
index 0000000..657afde
--- /dev/null
+++ b/uniro_frontend/src/container/animatedContainer.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import { AnimatePresence, motion, MotionProps } from "framer-motion";
+
+type Props = {
+ isVisible: boolean;
+ children: React.ReactNode;
+ positionDelta: number;
+ className: string;
+ isTop?: boolean;
+ transition?: MotionProps["transition"];
+ motionProps?: MotionProps;
+};
+
+// default는 바텀에서 시작
+const AnimatedContainer = ({
+ isVisible,
+ children,
+ positionDelta,
+ className,
+ isTop = false,
+ transition = { duration: 0.3, type: "tween" },
+ motionProps = {},
+}: Props) => {
+ return (
+
+ {isVisible && (
+
+ {children}
+
+ )}
+
+ );
+};
+
+export default AnimatedContainer;
diff --git a/uniro_frontend/src/data/factory/edgeFactory.ts b/uniro_frontend/src/data/factory/edgeFactory.ts
index 445d21f..77ca341 100644
--- a/uniro_frontend/src/data/factory/edgeFactory.ts
+++ b/uniro_frontend/src/data/factory/edgeFactory.ts
@@ -1,4 +1,4 @@
-import { HazardEdge } from "../types/edge";
+import { Direction, HazardEdge, RouteEdge } from "../types/edge";
import { CautionFactor, DangerFactor } from "../types/factor";
import { CustomNode } from "../types/node";
@@ -15,3 +15,20 @@ export const createHazardEdge = (
cautionFactors,
dangerFactors,
});
+
+export const createRouteEdges = (edges: HazardEdge[]): RouteEdge[] => {
+ const routeEdges: RouteEdge[] = [];
+ edges.forEach((edge, index) => {
+ const routeEdge: RouteEdge = {
+ ...edge,
+ distance: 100,
+ direction: ["right", "straight", "left"][index % 3] as Direction,
+ };
+ routeEdges.push(routeEdge);
+ });
+
+ routeEdges[0].direction = "origin";
+ routeEdges[routeEdges.length - 1].direction = "destination";
+
+ return routeEdges;
+};
diff --git a/uniro_frontend/src/data/factory/navigationFactory.ts b/uniro_frontend/src/data/factory/navigationFactory.ts
index 900ae92..d62f342 100644
--- a/uniro_frontend/src/data/factory/navigationFactory.ts
+++ b/uniro_frontend/src/data/factory/navigationFactory.ts
@@ -1,11 +1,15 @@
-import { HazardEdge } from "../types/edge";
+import { hanyangBuildings } from "../mock/hanyangBuildings";
+import { RouteEdge } from "../types/edge";
import { NavigationRoute } from "../types/route";
-export const createNavigationRoute = (edges: HazardEdge[]): NavigationRoute => {
+// TODO: Distance를 m-> km로 자동 변환해주는 util
+export const createNavigationRoute = (edges: RouteEdge[]): NavigationRoute => {
return {
route: edges,
hasCaution: edges.some((edge) => edge.cautionFactors !== undefined),
- totalDistance: 1.5,
+ totalDistance: 635,
totalCost: 10,
+ originBuilding: hanyangBuildings[0],
+ destinationBuilding: hanyangBuildings[1],
};
};
diff --git a/uniro_frontend/src/data/mock/hanyangBuildings.ts b/uniro_frontend/src/data/mock/hanyangBuildings.ts
index a75dcd5..38f6d64 100644
--- a/uniro_frontend/src/data/mock/hanyangBuildings.ts
+++ b/uniro_frontend/src/data/mock/hanyangBuildings.ts
@@ -1,12 +1,12 @@
import { Building } from "../types/node";
-export const buildings: Building[] = [
+export const hanyangBuildings: Building[] = [
{
id: "101",
lng: 127.044755,
lat: 37.555994,
isCore: true,
- buildingName: "역사관",
+ buildingName: "한양대학교 법학관",
buildingImageUrl: "https://upload.wikimedia.org/wikipedia/commons/6/69/Hanyang_University_008.JPG",
phoneNumber: "02-2220-0114",
address: "서울특별시 성동구 왕십리로 222",
@@ -16,7 +16,7 @@ export const buildings: Building[] = [
lng: 127.0455,
lat: 37.5565,
isCore: true,
- buildingName: "본관",
+ buildingName: "한양대학교 제2공학관",
buildingImageUrl: "https://upload.wikimedia.org/wikipedia/commons/6/69/Hanyang_University_008.JPG",
phoneNumber: "02-2220-0114",
address: "서울특별시 성동구 왕십리로 222",
diff --git a/uniro_frontend/src/data/mock/hanyangRoute.ts b/uniro_frontend/src/data/mock/hanyangRoute.ts
index 544b166..40c9594 100644
--- a/uniro_frontend/src/data/mock/hanyangRoute.ts
+++ b/uniro_frontend/src/data/mock/hanyangRoute.ts
@@ -1,4 +1,4 @@
-import { createHazardEdge } from "../factory/edgeFactory";
+import { createHazardEdge, createRouteEdges } from "../factory/edgeFactory";
import { createNavigationRoute } from "../factory/navigationFactory";
import { createNode } from "../factory/nodeFactory";
import { HazardEdge } from "../types/edge";
@@ -17,6 +17,8 @@ const edges: HazardEdge[] = [
createHazardEdge("route2", nodes[1], nodes[2], ["도로에 균열이 있어요"]),
createHazardEdge("route3", nodes[2], nodes[3]),
createHazardEdge("route4", nodes[3], nodes[4]),
+ createHazardEdge("route5", nodes[3], nodes[4]),
+ createHazardEdge("route6", nodes[3], nodes[4]),
];
-export const mockNavigationRoute = createNavigationRoute(edges);
+export const mockNavigationRoute = createNavigationRoute(createRouteEdges(edges));
diff --git a/uniro_frontend/src/data/types/edge.d.ts b/uniro_frontend/src/data/types/edge.d.ts
index 585186e..bf2271f 100644
--- a/uniro_frontend/src/data/types/edge.d.ts
+++ b/uniro_frontend/src/data/types/edge.d.ts
@@ -7,9 +7,16 @@ export interface Edge {
endNode: CustomNode;
}
+export type Direction = "origin" | "right" | "straight" | "left" | "uturn" | "destination" | "caution";
+
// 위험 요소 & 주의 요소
// 마커를 표시하거나, 길 찾기 결과의 경로를 그릴 때 사용
export interface HazardEdge extends Edge {
dangerFactors?: DangerFactor[];
cautionFactors?: CautionFactor[];
}
+
+export interface RouteEdge extends HazardEdge {
+ distance: number;
+ direction: Direction;
+}
diff --git a/uniro_frontend/src/data/types/marker.d.ts b/uniro_frontend/src/data/types/marker.d.ts
new file mode 100644
index 0000000..866d06c
--- /dev/null
+++ b/uniro_frontend/src/data/types/marker.d.ts
@@ -0,0 +1,13 @@
+import { Markers } from "../../constant/enums";
+
+export type AdvancedMarker = google.maps.marker.AdvancedMarkerElement;
+
+export type MarkerTypes =
+ | Markers.BUILDING
+ | Markers.CAUTION
+ | Markers.DANGER
+ | Markers.DESTINATION
+ | Markers.ORIGIN
+ | Markers.NUMBERED_WAYPOINT
+ | Markers.WAYPOINT
+ | Markers.SELECTED_BUILDING;
diff --git a/uniro_frontend/src/data/types/route.d.ts b/uniro_frontend/src/data/types/route.d.ts
index a335dc5..e8f0532 100644
--- a/uniro_frontend/src/data/types/route.d.ts
+++ b/uniro_frontend/src/data/types/route.d.ts
@@ -1,11 +1,14 @@
-import { HazardEdge } from "./edge";
+import { RouteEdge } from "./edge";
+import { Building } from "./node";
export interface Route {
- route: HazardEdge[];
+ route: RouteEdge[];
}
export interface NavigationRoute extends Route {
hasCaution: boolean;
totalDistance: number;
totalCost: number;
+ originBuilding: Building;
+ destinationBuilding: Building;
}
diff --git a/uniro_frontend/src/hooks/useScrollControl.tsx b/uniro_frontend/src/hooks/useScrollControl.tsx
new file mode 100644
index 0000000..271854a
--- /dev/null
+++ b/uniro_frontend/src/hooks/useScrollControl.tsx
@@ -0,0 +1,31 @@
+import React, { useEffect, useState } from "react";
+
+// 스크롤 비활성화, 맵을 움직일 때, 다른 부분들도 같이 움직이는 것들을 제어함.
+// 전역 적용이 필요하다면 다른 방식을 사용할 예정.
+const useScrollControl = () => {
+ const [scrollState, setScrollState] = useState(false);
+
+ const enableScroll = () => {
+ setScrollState(true);
+ };
+
+ const disableScroll = () => {
+ setScrollState(false);
+ };
+
+ useEffect(() => {
+ if (scrollState) {
+ document.body.style.overflow = "auto";
+ } else {
+ document.body.style.overflow = "hidden";
+ }
+
+ return () => {
+ document.body.style.overflow = "hidden";
+ };
+ }, [scrollState]);
+
+ return { enableScroll, disableScroll };
+};
+
+export default useScrollControl;
diff --git a/uniro_frontend/src/index.css b/uniro_frontend/src/index.css
index 0dadffa..914f9ac 100644
--- a/uniro_frontend/src/index.css
+++ b/uniro_frontend/src/index.css
@@ -40,7 +40,7 @@
}
* {
- font-family: "SF Pro Display", "AppleSDGothicNeo";
+ font-family: "SF Pro Display", "AppleSDGothicNeo" !important;
outline: none;
--webkit-scrollbar-width: none;
}
@@ -123,3 +123,13 @@
--text-eng-caption: 12px;
--text-eng-caption--line-height: 160%;
}
+
+@utility translate-marker {
+ transform: translateY(calc(100% - 10px));
+}
+@utility translate-routemarker {
+ transform: translateY(calc(100% - 40px));
+}
+@utility translate-waypoint {
+ transform: translateY(+4px);
+}
diff --git a/uniro_frontend/src/map/initializer/googleMapInitializer.ts b/uniro_frontend/src/map/initializer/googleMapInitializer.ts
index edfb317..2bcdc23 100644
--- a/uniro_frontend/src/map/initializer/googleMapInitializer.ts
+++ b/uniro_frontend/src/map/initializer/googleMapInitializer.ts
@@ -1,5 +1,4 @@
import { HanyangUniversity } from "../../constant/university";
-import { HanyangUniversityBounds } from "../../constant/bounds";
import loadGoogleMapsLibraries from "../loader/googleMapLoader";
@@ -25,10 +24,12 @@ export const initializeMap = async (mapElement: HTMLElement | null): Promise