Skip to content

Commit beaf9a4

Browse files
authored
[UNI-370] feat : 검색 리스트 로딩 + NotFound 화면 적용 (#226)
1 parent 1570509 commit beaf9a4

File tree

7 files changed

+107
-54
lines changed

7 files changed

+107
-54
lines changed

uniro_frontend/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/webp" href="/logo.webp" />
66
<link href="/dist/styles.css" rel="text/html" />
7-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
88
<title>UNIRO</title>
99
<meta
1010
name="description"

uniro_frontend/src/components/building/buildingList.tsx

+13-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getSearchBuildings } from "../../api/nodes";
33
import { University } from "../../data/types/university";
44
import useSearchBuilding from "../../hooks/useSearchBuilding";
55
import BuildingCard from "./buildingCard";
6+
import BuildingNotFound from "./buildingNotFound";
67

78
interface BuildingListProps {
89
university: University;
@@ -18,14 +19,18 @@ export default function BuildingList({ university, input }: BuildingListProps) {
1819
});
1920

2021
return (
21-
<ul className="px-4 pt-1 space-y-1">
22-
{(buildings ?? []).map((building) => (
23-
<BuildingCard
24-
onClick={() => setBuilding(building)}
25-
key={`building-${building.buildingName}`}
26-
building={building}
27-
/>
28-
))}
22+
<ul className="w-full h-full px-4 pt-1 space-y-1">
23+
{buildings.length === 0 ? (
24+
<BuildingNotFound />
25+
) : (
26+
buildings.map((building) => (
27+
<BuildingCard
28+
onClick={() => setBuilding(building)}
29+
key={`building-${building.buildingName}`}
30+
building={building}
31+
/>
32+
))
33+
)}
2934
</ul>
3035
);
3136
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import SearchNull from "../error/SearchNull";
2+
3+
export default function BuildingNotFound() {
4+
return (
5+
<div className="h-full w-full flex justify-center items-center">
6+
<SearchNull message="해당 건물을 찾을 수 없습니다." />
7+
</div>
8+
);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
2+
import React, { MouseEvent, Dispatch, SetStateAction } from "react";
3+
import { getUniversityList } from "../../api/search";
4+
import UniversityButton from "../universityButton";
5+
import { University } from "../../data/types/university";
6+
import { useNavigate } from "react-router";
7+
import useUniversityInfo from "../../hooks/useUniversityInfo";
8+
import UniversityNotFound from "./universityNotFound";
9+
10+
interface UniveristyListProps {
11+
query: string;
12+
selectedUniv: University | undefined;
13+
setSelectedUniv: Dispatch<SetStateAction<University | undefined>>;
14+
}
15+
16+
export default function UniversityList({ query, selectedUniv, setSelectedUniv }: UniveristyListProps) {
17+
const { data: universityList } = useSuspenseQuery({
18+
queryKey: ["university", query],
19+
queryFn: () => getUniversityList(query),
20+
});
21+
const { university, setUniversity } = useUniversityInfo();
22+
const navigation = useNavigate();
23+
24+
const handleClick = (e: MouseEvent<HTMLButtonElement>, univ: University) => {
25+
setSelectedUniv(univ);
26+
};
27+
28+
const handleDbClick = (e: MouseEvent<HTMLButtonElement>, univ: University) => {
29+
setSelectedUniv(univ);
30+
setUniversity(univ);
31+
navigation("/map");
32+
};
33+
34+
return (
35+
<ul className="w-full h-full px-[14px] py-[6px]">
36+
{universityList.length === 0 ? (
37+
<UniversityNotFound />
38+
) : (
39+
universityList.map((univ) => (
40+
<UniversityButton
41+
key={`university-${univ.id}`}
42+
selected={selectedUniv?.id === univ.id}
43+
onClick={handleClick}
44+
onDbClick={handleDbClick}
45+
university={univ}
46+
/>
47+
))
48+
)}
49+
</ul>
50+
);
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import SearchNull from "../error/SearchNull";
2+
3+
export default function UniversityNotFound() {
4+
return (
5+
<div className="h-full w-full flex justify-center items-center">
6+
<SearchNull message="해당 학교를 찾을 수 없습니다." />
7+
</div>
8+
);
9+
}

uniro_frontend/src/components/universityButton.tsx

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
import { MouseEvent, useState } from "react";
2+
import { University } from "../data/types/university";
23

34
interface UniversityButtonProps {
4-
name: string;
5-
img: string;
5+
university: University;
66
selected: boolean;
77
loading?: boolean;
8-
onClick: () => void;
8+
onClick: (e: MouseEvent<HTMLButtonElement>, univ: University) => void;
9+
onDbClick: (e: MouseEvent<HTMLButtonElement>, univ: University) => void;
910
}
1011

11-
export default function UniversityButton({ name, img, selected, onClick }: UniversityButtonProps) {
12+
export default function UniversityButton({ university, selected, onClick, onDbClick }: UniversityButtonProps) {
1213
const [imageLoaded, setImageLoaded] = useState(false);
14+
15+
const { imageUrl, name } = university;
16+
1317
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
1418
e.stopPropagation();
15-
onClick();
19+
onClick(e, university);
20+
};
21+
22+
const handleDbClick = (e: MouseEvent<HTMLButtonElement>) => {
23+
onDbClick(e, university);
1624
};
1725

1826
return (
1927
<li className="my-[6px]">
2028
<button
29+
onDoubleClick={handleDbClick}
2130
onClick={handleClick}
2231
className={`w-full h-full p-6 border rounded-400 text-left ${
2332
selected ? "border-primary-400 bg-system-skyblue text-primary-500" : "border-gray-400"
@@ -26,7 +35,7 @@ export default function UniversityButton({ name, img, selected, onClick }: Unive
2635
<span className="inline-block w-[30px] h-[30px] relative align-middle">
2736
{!imageLoaded && <div className="absolute inset-0 bg-gray-300 rounded-full animate-pulse" />}
2837
<img
29-
src={img}
38+
src={imageUrl}
3039
className={`absolute inset-0 w-full h-full transition-opacity duration-300 ${imageLoaded ? "opacity-100" : "opacity-0"}`}
3140
alt={`${name} 로고`}
3241
onLoad={() => setImageLoaded(true)}

uniro_frontend/src/pages/universitySearch.tsx

+9-39
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
1-
import { useEffect, useState } from "react";
1+
import { Suspense, useEffect, useState } from "react";
22
import Input from "../components/customInput";
3-
import UniversityButton from "../components/universityButton";
43
import Button from "../components/customButton";
5-
import { Link, useNavigate } from "react-router";
4+
import { Link } from "react-router";
65
import useUniversityInfo from "../hooks/useUniversityInfo";
7-
import { useQuery } from "@tanstack/react-query";
8-
import { getUniversityList } from "../api/search";
96
import { University } from "../data/types/university";
107
import useRoutePoint from "../hooks/useRoutePoint";
8+
import UniversityList from "../components/university/universityList";
9+
import InnerLoading from "../components/loading/innerLoading";
1110

1211
export default function UniversitySearchPage() {
1312
const [selectedUniv, setSelectedUniv] = useState<University>();
1413
const { university, setUniversity } = useUniversityInfo();
1514
const { setDestination, setOrigin } = useRoutePoint();
1615
const [input, setInput] = useState<string>("");
17-
const navigation = useNavigate();
18-
19-
const { data: universityList } = useQuery({
20-
queryKey: ["university", input],
21-
queryFn: () => getUniversityList(input),
22-
});
2316

2417
useEffect(() => {
2518
if (university) {
@@ -38,37 +31,14 @@ export default function UniversitySearchPage() {
3831
<div className="h-full w-full" onClick={() => setSelectedUniv(undefined)}>
3932
<div className="relative flex flex-col h-dvh w-full max-w-[450px] mx-auto py-5">
4033
<div className="w-full px-[14px] pb-[17px] border-b-[1px] border-gray-400">
41-
<Input
42-
onChangeDebounce={(e) => setInput(e)}
43-
placeholder="우리 학교를 검색해보세요"
44-
handleVoiceInput={() => {}}
45-
/>
34+
<Input onChangeDebounce={(e) => setInput(e)} placeholder="우리 학교를 검색해보세요" />
4635
</div>
4736
<div className="overflow-y-scroll flex-1">
48-
<ul
49-
className="w-full h-full px-[14px] py-[6px]"
50-
onClick={() => {
51-
setSelectedUniv(undefined);
52-
}}
37+
<Suspense
38+
fallback={<InnerLoading isLoading={true} loadingContent="학교 목록을 불러오고 있습니다." />}
5339
>
54-
{universityList &&
55-
universityList.map((univ) => (
56-
<UniversityButton
57-
key={`university-${univ.id}`}
58-
selected={selectedUniv?.id === univ.id}
59-
onClick={() => {
60-
if (selectedUniv?.id === univ.id) {
61-
setUniversity(univ);
62-
navigation("/map");
63-
return;
64-
}
65-
setSelectedUniv(univ);
66-
}}
67-
name={univ.name}
68-
img={univ.imageUrl}
69-
/>
70-
))}
71-
</ul>
40+
<UniversityList query={input} selectedUniv={selectedUniv} setSelectedUniv={setSelectedUniv} />
41+
</Suspense>
7242
</div>
7343
<div className="px-[14px]">
7444
{selectedUniv && (

0 commit comments

Comments
 (0)