Skip to content

Commit bb6186a

Browse files
authored
[UNI-196] feat : 길찾기 결과 경로 없음 에러(422) 화면 및 커스텀 훅 추가 (#109)
* [UNI-197] feat : 길찾기 결과 경로 없음 에러 (422) 추가 * [UNI-198] feat : 지도 메인 페이지, 길 찾기 요청 적용
1 parent 23461c0 commit bb6186a

File tree

6 files changed

+146
-7
lines changed

6 files changed

+146
-7
lines changed

uniro_frontend/src/components/navigation/navigationDescription.tsx

+11-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { NavigationRouteList } from "../../data/types/route";
99
import useRoutePoint from "../../hooks/useRoutePoint";
1010
import { formatDistance } from "../../utils/navigation/formatDistance";
1111
import { Link } from "react-router";
12+
import useUniversityInfo from "../../hooks/useUniversityInfo";
13+
import { useQueryClient } from "@tanstack/react-query";
1214

1315
const TITLE = "전동휠체어 예상소요시간";
1416

@@ -21,12 +23,20 @@ const NavigationDescription = ({ isDetailView, navigationRoute }: TopBarProps) =
2123
const { origin, destination } = useRoutePoint();
2224
const { totalCost, totalDistance, hasCaution } = navigationRoute;
2325

26+
const { university } = useUniversityInfo();
27+
const queryClient = useQueryClient();
28+
29+
/** 지도 페이지로 돌아가게 될 경우, 캐시를 삭제하기 */
30+
const removeQuery = () => {
31+
queryClient.removeQueries({ queryKey: ['fastRoute', university?.id, origin?.nodeId, destination?.nodeId] })
32+
}
33+
2434
return (
2535
<div className="w-full p-5">
2636
<div className={`w-full flex flex-row items-center ${isDetailView ? "justify-start" : "justify-between"}`}>
2737
<span className="text-left text-kor-body3 text-primary-500 flex-1 font-semibold">{TITLE}</span>
2838
{!isDetailView && (
29-
<Link to="/map">
39+
<Link to="/map" onClick={removeQuery}>
3040
<Cancel />
3141
</Link>
3242
)}

uniro_frontend/src/constant/error.ts

+8
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@ export class BadRequestError extends Error {
1212
}
1313
}
1414

15+
export class UnProcessableError extends Error {
16+
constructor(message: string) {
17+
super(message);
18+
this.name = "UnProcessable";
19+
}
20+
}
21+
1522
export enum ERROR_STATUS {
1623
NOT_FOUND = 404,
1724
BAD_REQUEST = 400,
1825
INTERNAL_ERROR = 500,
26+
UNPROCESSABLE = 422,
1927
}

uniro_frontend/src/hooks/useMutationError.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default function useMutationError<TData, TError, TVariables, TContext>(
5757
else throw error;
5858

5959
return (
60-
<div className="fixed inset-0 flex items-center justify-center bg-[rgba(0,0,0,0.2)]">
60+
<div className="fixed inset-0 flex items-center justify-center bg-[rgba(0,0,0,0.2)] z-100">
6161
< div className="w-full max-w-[365px] flex flex-col bg-gray-100 rounded-400 overflow-hidden" >
6262
<div className="flex flex-col justify-center space-y-1 py-[25px]">
6363
<p className="text-kor-body1 font-bold text-system-red">{title.mainTitle}</p>
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { QueryClient, useQuery, UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
2+
import { NotFoundError, BadRequestError, ERROR_STATUS, UnProcessableError } from "../constant/error";
3+
import { useEffect, useState } from "react";
4+
5+
type Fallback = {
6+
[K in Exclude<ERROR_STATUS, ERROR_STATUS.INTERNAL_ERROR>]?: {
7+
mainTitle: string;
8+
subTitle: string[];
9+
};
10+
};
11+
12+
type HandleError = {
13+
fallback: Fallback;
14+
onClose?: () => void;
15+
}
16+
17+
type UseQueryErrorReturn<TData, TError> = [
18+
React.FC,
19+
UseQueryResult<TData, TError>
20+
];
21+
22+
export default function useQueryError<TQueryFnData, TError, TData>(
23+
options: UseQueryOptions<TQueryFnData, TError, TData>,
24+
queryClient?: QueryClient,
25+
handleSuccess?: () => void,
26+
handleError?: HandleError,
27+
): UseQueryErrorReturn<TData, TError> {
28+
const [isOpen, setOpen] = useState<boolean>(false);
29+
const result = useQuery<TQueryFnData, TError, TData, readonly unknown[]>(options, queryClient);
30+
31+
const { isError, error, status } = result;
32+
33+
useEffect(() => {
34+
setOpen(isError)
35+
}, [isError])
36+
37+
/** 페이지 이동하여도 status는 success로 남아있으므로 필요시, removeQueries하여 캐시 삭제 */
38+
/** 추가적인 로직 고민하기 */
39+
useEffect(() => {
40+
if (status === "success" && handleSuccess) handleSuccess();
41+
}, [status])
42+
43+
const close = () => {
44+
if (handleError?.onClose) handleError?.onClose();
45+
setOpen(false);
46+
}
47+
48+
const Modal: React.FC = () => {
49+
if (!isOpen || !handleError || !error) return null;
50+
51+
const { fallback } = handleError;
52+
53+
let title: { mainTitle: string, subTitle: string[] } = {
54+
mainTitle: "",
55+
subTitle: [],
56+
}
57+
58+
if (error instanceof NotFoundError) {
59+
title = fallback[404] ?? title;
60+
}
61+
else if (error instanceof BadRequestError) {
62+
title = fallback[400] ?? title;
63+
}
64+
else if (error instanceof UnProcessableError) {
65+
title = fallback[422] ?? title;
66+
}
67+
68+
else throw error;
69+
70+
return (
71+
<div className="fixed inset-0 flex items-center justify-center bg-[rgba(0,0,0,0.2)] z-100">
72+
< div className="w-full max-w-[365px] flex flex-col bg-gray-100 rounded-400 overflow-hidden" >
73+
<div className="flex flex-col justify-center space-y-1 py-[25px]">
74+
<p className="text-kor-body1 font-bold text-system-red">{title.mainTitle}</p>
75+
<div className="space-y-0">
76+
{title.subTitle.map((_subtitle, index) =>
77+
<p key={`error-modal-subtitle-${index}`} className="text-kor-body3 font-regular text-gray-700">{_subtitle}</p>)}
78+
</div>
79+
</div>
80+
<button
81+
onClick={close}
82+
className="h-[58px] border-t-[1px] border-gray-200 text-kor-body2 font-semibold active:bg-gray-200"
83+
>
84+
확인
85+
</button>
86+
</div >
87+
</div >
88+
)
89+
}
90+
91+
return [Modal, result];
92+
}

uniro_frontend/src/pages/map.tsx

+31-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { RoutePoint } from "../constant/enum/routeEnum";
1717
import { Markers } from "../constant/enum/markerEnum";
1818
import createAdvancedMarker, { createUniversityMarker } from "../utils/markers/createAdvanedMarker";
1919
import toggleMarkers from "../utils/markers/toggleMarkers";
20-
import { Link } from "react-router";
20+
import { useNavigate } from "react-router";
2121
import useModal from "../hooks/useModal";
2222
import ReportModal from "../components/map/reportModal";
2323
import useUniversityInfo from "../hooks/useUniversityInfo";
@@ -28,9 +28,11 @@ import { CautionIssueType, DangerIssueType, MarkerTypes } from "../data/types/en
2828
import { CautionIssue, DangerIssue } from "../constant/enum/reportEnum";
2929

3030
/** API 호출 */
31-
import { useSuspenseQueries } from "@tanstack/react-query";
31+
import { useQuery, useSuspenseQueries } from "@tanstack/react-query";
3232
import { getAllRisks } from "../api/routes";
3333
import { getAllBuildings } from "../api/nodes";
34+
import { getNavigationResult } from "../api/route";
35+
import useQueryError from "../hooks/useQueryError";
3436

3537
export type SelectedMarkerTypes = {
3638
type: MarkerTypes;
@@ -68,8 +70,32 @@ export default function MapPage() {
6870
const { university } = useUniversityInfo();
6971
useRedirectUndefined<University | undefined>([university]);
7072

73+
const navigate = useNavigate();
7174
if (!university) return;
7275

76+
const [FailModal, { status, data, refetch: findFastRoute }] = useQueryError({
77+
queryKey: ['fastRoute', university.id, origin?.nodeId, destination?.nodeId],
78+
queryFn: () => getNavigationResult(university.id, origin ? origin?.nodeId : -1, destination ? destination?.nodeId : -1),
79+
enabled: false,
80+
retry: 0,
81+
},
82+
undefined,
83+
() => { navigate('/result') },
84+
{
85+
fallback: {
86+
400: {
87+
mainTitle: "잘못된 요청입니다.", subTitle: ["새로고침 후 다시 시도 부탁드립니다."]
88+
},
89+
404: {
90+
mainTitle: "해당 경로를 찾을 수 없습니다.", subTitle: ["해당 건물이 길이랑 연결되지 않았습니다."]
91+
},
92+
422: {
93+
mainTitle: "해당 경로를 찾을 수 없습니다.", subTitle: ["위험 요소 버튼을 클릭하여,", "통행할 수 없는 원인을 파악하실 수 있습니다."]
94+
}
95+
}
96+
}
97+
)
98+
7399
const results = useSuspenseQueries({
74100
queries: [
75101
{ queryKey: [university.id, "risks"], queryFn: () => getAllRisks(university.id) },
@@ -495,9 +521,9 @@ export default function MapPage() {
495521
</BottomSheet>
496522
{origin && destination && origin.nodeId !== destination.nodeId ? (
497523
/** 출발지랑 도착지가 존재하는 경우 길찾기 버튼 보이기 */
498-
<Link to="/result" className="absolute bottom-6 space-y-2 w-full px-4">
524+
<div onClick={() => findFastRoute()} className="absolute bottom-6 space-y-2 w-full px-4">
499525
<Button variant="primary">길찾기</Button>
500-
</Link>
526+
</div>
501527
) : (
502528
/** 출발지랑 도착지가 존재하지 않거나, 같은 경우 기존 Button UI 보이기 */
503529
<>
@@ -511,6 +537,7 @@ export default function MapPage() {
511537
</>
512538
)}
513539
{isOpen && <ReportModal close={close} />}
540+
<FailModal />
514541
</div>
515542
);
516543
}

uniro_frontend/src/utils/fetch/fetch.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BadRequestError, NotFoundError } from "../../constant/error";
1+
import { BadRequestError, NotFoundError, UnProcessableError } from "../../constant/error";
22

33
export default function Fetch() {
44
const baseURL = import.meta.env.VITE_REACT_SERVER_BASE_URL;
@@ -17,6 +17,8 @@ export default function Fetch() {
1717
throw new BadRequestError("Bad Request");
1818
} else if (response.status === 404) {
1919
throw new NotFoundError("Not Found");
20+
} else if (response.status === 422) {
21+
throw new UnProcessableError("UnProcessable");
2022
} else {
2123
throw new Error("UnExpected Error");
2224
}

0 commit comments

Comments
 (0)