diff --git a/.github/workflows/fe-deploy.yml b/.github/workflows/fe-deploy.yml
index 86395ac..17836b4 100644
--- a/.github/workflows/fe-deploy.yml
+++ b/.github/workflows/fe-deploy.yml
@@ -1,66 +1,66 @@
name: FE CI / CD
on:
- push:
- branches:
- - fe
+ push:
+ branches:
+ - fe
jobs:
- CI:
- runs-on: ubuntu-latest
+ CI:
+ runs-on: ubuntu-latest
- env:
- GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
- IMAGE_NAME: uniro-fe
- IMAGE_TAG: ${{ github.sha }}
+ env:
+ GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
+ IMAGE_NAME: uniro-fe
+ IMAGE_TAG: ${{ github.sha }}
- steps:
- - name: 코드 체크아웃
- uses: actions/checkout@v4
+ steps:
+ - name: 코드 체크아웃
+ uses: actions/checkout@v4
- - name: Google Cloud SDK 설정
- uses: "google-github-actions/auth@v2"
- with:
- credentials_json: ${{ secrets.GCP_SA_KEY }}
+ - name: Google Cloud SDK 설정
+ uses: "google-github-actions/auth@v2"
+ with:
+ credentials_json: ${{ secrets.GCP_SA_KEY }}
- - name: Docker를 위한 gcloud 인증 설정
- run: gcloud auth configure-docker --quiet
+ - name: Docker를 위한 gcloud 인증 설정
+ run: gcloud auth configure-docker --quiet
- - name: Create .env from secret
- run: |
- echo "${{ secrets.FE_ENV }}" > uniro_frontend/.env
+ - name: Create .env from secret
+ run: |
+ echo "${{ secrets.FE_ENV }}" > uniro_frontend/.env
- - name: Docker 이미지 빌드 및 푸시
- run: |
- docker build -t gcr.io/${{ env.GCP_PROJECT_ID }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} -f uniro_frontend/Dockerfile .
- docker push gcr.io/${{ env.GCP_PROJECT_ID }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
+ - name: Docker 이미지 빌드 및 푸시
+ run: |
+ docker build -t gcr.io/${{ env.GCP_PROJECT_ID }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} -f uniro_frontend/Dockerfile .
+ docker push gcr.io/${{ env.GCP_PROJECT_ID }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
- CD:
- runs-on: ubuntu-latest
- needs: CI
+ CD:
+ runs-on: ubuntu-latest
+ needs: CI
- env:
- GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
- IMAGE_NAME: uniro-fe
- IMAGE_TAG: ${{ github.sha }}
- DEPLOY_PATH: ${{ secrets.DEPLOY_SERVER_PATH }}
+ env:
+ GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
+ IMAGE_NAME: uniro-fe
+ IMAGE_TAG: ${{ github.sha }}
+ DEPLOY_PATH: ${{ secrets.DEPLOY_SERVER_PATH }}
- steps:
- - name: 배포 서버에 SSH로 연결하여 배포
- uses: appleboy/ssh-action@v0.1.5
- with:
- host: ${{ secrets.DEPLOY_SERVER_HOST }}
- username: ${{ secrets.DEPLOY_SERVER_USER }}
- key: ${{ secrets.DEPLOY_SSH_KEY }}
- envs: GCP_PROJECT_ID, IMAGE_NAME, IMAGE_TAG, DEPLOY_PATH, TEST
- script: |
- cd ${DEPLOY_PATH}
- sudo docker ps -a --format '{{.ID}} {{.Names}}' \
- | grep -v 'nginx-container' \
- | awk '{print $1}' \
- | xargs -r sudo docker stop || true
- sudo docker rm $(sudo docker ps -a -q) || true
- sudo docker login -u _json_key --password-stdin https://gcr.io <<< '${{ secrets.GCP_SA_KEY }}'
- sudo docker pull gcr.io/${GCP_PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG}
- sudo docker run -d --name ${IMAGE_NAME} -p 3000:3000 gcr.io/${GCP_PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG}
- sudo docker network connect nginx_app-network ${IMAGE_NAME}
+ steps:
+ - name: 배포 서버에 SSH로 연결하여 배포
+ uses: appleboy/ssh-action@v0.1.5
+ with:
+ host: ${{ secrets.DEPLOY_SERVER_HOST }}
+ username: ${{ secrets.DEPLOY_SERVER_USER }}
+ key: ${{ secrets.DEPLOY_SSH_KEY }}
+ envs: GCP_PROJECT_ID, IMAGE_NAME, IMAGE_TAG, DEPLOY_PATH, TEST
+ script: |
+ cd ${DEPLOY_PATH}
+ sudo docker ps -a --format '{{.ID}} {{.Names}}' \
+ | egrep -v 'nginx-container|uniro-backoffice' \
+ | awk '{print $1}' \
+ | xargs -r sudo docker stop || true
+ sudo docker rm $(sudo docker ps -a -q) || true
+ sudo docker login -u _json_key --password-stdin https://gcr.io <<< '${{ secrets.GCP_SA_KEY }}'
+ sudo docker pull gcr.io/${GCP_PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG}
+ sudo docker run -d --name ${IMAGE_NAME} -p 3000:3000 gcr.io/${GCP_PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG}
+ sudo docker network connect nginx_app-network ${IMAGE_NAME}
diff --git a/uniro_frontend/src/App.tsx b/uniro_frontend/src/App.tsx
index deda414..c0face0 100644
--- a/uniro_frontend/src/App.tsx
+++ b/uniro_frontend/src/App.tsx
@@ -8,6 +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";
function App() {
return (
@@ -19,7 +20,8 @@ function App() {
} />
} />
} />
- } />
+ } />
+ } />
);
}
diff --git a/uniro_frontend/src/assets/markers/report.svg b/uniro_frontend/src/assets/markers/report.svg
new file mode 100644
index 0000000..6fd82e6
--- /dev/null
+++ b/uniro_frontend/src/assets/markers/report.svg
@@ -0,0 +1,20 @@
+
diff --git a/uniro_frontend/src/constant/enum/markerEnum.ts b/uniro_frontend/src/constant/enum/markerEnum.ts
index 8fc891c..9487ca9 100644
--- a/uniro_frontend/src/constant/enum/markerEnum.ts
+++ b/uniro_frontend/src/constant/enum/markerEnum.ts
@@ -7,4 +7,5 @@ export const enum Markers {
SELECTED_BUILDING = "selectedBuilding",
WAYPOINT = "waypoint",
NUMBERED_WAYPOINT = "numberedWayPoint",
+ REPORT = "report",
}
diff --git a/uniro_frontend/src/constant/enum/messageEnum.ts b/uniro_frontend/src/constant/enum/messageEnum.ts
new file mode 100644
index 0000000..e591b49
--- /dev/null
+++ b/uniro_frontend/src/constant/enum/messageEnum.ts
@@ -0,0 +1,6 @@
+export enum ReportHazardMessage {
+ DEFAULT = "선 위를 눌러 제보할 지점을 선택하세요",
+ CREATE = "이 지점으로 새로운 제보를 진행할까요?",
+ UPDATE = "이 지점에 제보된 기존 정보를 바꿀까요?",
+ ERROR = "선 위에서만 선택 가능해요",
+}
diff --git a/uniro_frontend/src/data/types/marker.d.ts b/uniro_frontend/src/data/types/marker.d.ts
index 06c1bc4..4782ede 100644
--- a/uniro_frontend/src/data/types/marker.d.ts
+++ b/uniro_frontend/src/data/types/marker.d.ts
@@ -10,4 +10,10 @@ export type MarkerTypes =
| Markers.ORIGIN
| Markers.NUMBERED_WAYPOINT
| Markers.WAYPOINT
- | Markers.SELECTED_BUILDING;
+ | Markers.SELECTED_BUILDING
+ | Markers.REPORT;
+
+export type MarkerTypesWithElement = {
+ type: MarkerTypes;
+ element: AdvancedMarker;
+};
diff --git a/uniro_frontend/src/hooks/useReportHazard.ts b/uniro_frontend/src/hooks/useReportHazard.ts
new file mode 100644
index 0000000..79aea93
--- /dev/null
+++ b/uniro_frontend/src/hooks/useReportHazard.ts
@@ -0,0 +1,22 @@
+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/pages/reportForm.tsx b/uniro_frontend/src/pages/reportForm.tsx
index 436c444..2e6e856 100644
--- a/uniro_frontend/src/pages/reportForm.tsx
+++ b/uniro_frontend/src/pages/reportForm.tsx
@@ -11,6 +11,7 @@ import Button from "../components/customButton";
import useScrollControl from "../hooks/useScrollControl";
import useModal from "../hooks/useModal";
+import useReportHazard from "../hooks/useReportHazard";
const ReportForm = () => {
useScrollControl();
@@ -28,7 +29,11 @@ const ReportForm = () => {
const [FailModal, isFailOpen, openFail, closeFail] = useModal();
const [SuccessModal, isSuccessOpen, openSuccess, closeSuccess] = useModal();
+ const { reportType, startNode, endNode } = useReportHazard();
+
useEffect(() => {
+ console.log(reportType, startNode, endNode);
+
setTimeout(() => {
setReportMode("update");
}, 2000);
diff --git a/uniro_frontend/src/pages/reportHazard.tsx b/uniro_frontend/src/pages/reportHazard.tsx
new file mode 100644
index 0000000..6e7adc0
--- /dev/null
+++ b/uniro_frontend/src/pages/reportHazard.tsx
@@ -0,0 +1,207 @@
+import { useEffect, useState } from "react";
+import useMap from "../hooks/useMap";
+import { mockNavigationRoute } from "../data/mock/hanyangRoute";
+import createAdvancedMarker from "../utils/markers/createAdvanedMarker";
+import createMarkerElement from "../components/map/mapMarkers";
+import { RouteEdge } from "../data/types/edge";
+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";
+
+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 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 = (routes: RouteEdge[]) => {
+ if (!Polyline || !AdvancedMarker || !map) return;
+
+ for (const route of routes) {
+ const coreNode = route.endNode;
+
+ createAdvancedMarker(
+ AdvancedMarker,
+ map,
+ coreNode,
+ createMarkerElement({ type: Markers.WAYPOINT, className: "translate-waypoint" }),
+ );
+
+ const routePolyLine = new Polyline({
+ map: map,
+ path: [route.startNode, route.endNode],
+ strokeColor: "#808080",
+ });
+
+ 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 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,
+ };
+ });
+ });
+ }
+
+ createAdvancedMarker(
+ AdvancedMarker,
+ map,
+ routes[0].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(mockNavigationRoute.route);
+ 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}
+
+
+
+
+
+
+
+
+
+ );
+}