티스토리 뷰
우선 저번에 검색 API를 뚫고 검색 페이지인 프론트와 연동을 하려다 문제가 생겨서
1. 우선 백에서 3가지 분류를 이용하여 쿼리를 작성한다.
2. 가져올 때는 CourseSearchInfo에 맞게 가져와야한다. -> 그래야 카드 리스트가 보일 것이다.
3. 프론트에서 조건문을 사용하여 검색을 했을 떄 보여지게 하면 된다.
4. 그 후 디테일한 작업을 하자!!
이 순서대로 작업을 하려고 했다.
우선 1번은 내가 사용했던 쿼리를 그대로 이용하면 될 것 같다.
package ssac.LMS.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ssac.LMS.domain.Course;
import java.util.List;
public interface CourseRepository extends JpaRepository<Course, Long> {
@Query("SELECT c FROM Course c " +
"JOIN c.user u " +
"WHERE LOWER(c.title) LIKE LOWER(concat('%', :keyword, '%')) " +
" OR LOWER(c.tags) LIKE LOWER(concat('%', :keyword, '%')) " +
" OR LOWER(u.userName) LIKE LOWER(concat('%', :keyword, '%'))")
List<Course> findByKeyword(@Param("keyword") String keyword);
}
이렇게 하면 검색은 내가 원하는대로 강의 제목, 강의 태그, 강사 이름으로 검색을 할 수 있다.
하지만 프론트에서 보여주게 하려면 CourseSearchInfo를 맞춰야하는데 그러기 위해 우선 DTO를 먼저 수정했다.
package ssac.LMS.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import ssac.LMS.domain.Tags;
import java.util.Date;
@Data
@AllArgsConstructor
public class CourseSearchResponseDto {
private String title;
private String description;
private Long courseId;
private String thumbnailPath;
private Tags tags;
private String userName;
}
이런 식으로 바꿔서 프론트에 어떠한 값을 보낼지 정해놓고 그 다음 프론트에서 CourseSearchInfo를 새로 만들었다.
// 검색 정보에 대한 타입 정의
type CourseSearchInfo = {
title: string;
description: string;
courseId: number;
thumbnailPath: string;
tags: string;
userName: string;
}
위의 DTO와 보면 똑같다는 것을 알 것이다.
console.log를 찍어보면

콘솔에 원하는 값이 잘 나온다. 여기까지 검색은 강의 제목, 강의 태그, 강사 이름으로 검색을 하고 프론트에 보내는 것은 DTO에 있는 것을 잘 보내는 것을 알 수 있다.
여기까지 1, 2번을 완성했다.
3번 프론트에서 조건문을 이용해서 원하는 대로 보여지게 만들어야한다.
import React, { ReactNode, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Box, Container, Flex, SimpleGrid, Input, Button, IconButton, useColorModeValue, background } from "@chakra-ui/react";
import { SearchIcon } from "@chakra-ui/icons"
import axios from "axios";
import ClassCard from "../../components/ClassCard";
import SectionTitle from "../../components/SectionTitle";
import ContentArea from "../../components/ContentArea";
// 강의 정보에 대한 타입 정의
type CourseInfo = {
title: string;
description: string;
classId: number;
thumbnailPath: string;
};
// 검색 정보에 대한 타입 정의
type CourseSearchInfo = {
title: string;
description: string;
courseId: number;
thumbnailPath: string;
tags: string;
userName: string;
}
const ClassCardList = ({ children }: { children: ReactNode }) => (
<SimpleGrid
columns={{ base: 1, sm: 2, md: 3 }}
spacing={5}
gridAutoRows={"1fr"}
borderColor={"none"}
>
{children}
</SimpleGrid>
);
function SearchPage() {
const navigate = useNavigate();
// 최고의 결과를 저장할 상태
const [bestResults, setBestResults] = useState<CourseInfo[]>([]);
// 최신의 결과를 저장할 상태
const [newResults, setNewResults] = useState<CourseInfo[]>([]);
// 검색 결과를 저장할 상태
const [keyword, setKeyword] = useState("");
const [searchResults, setSearchResults] = useState<CourseSearchInfo[]>([]);
// 검색 버튼 클릭 여부를 저장할 상태
const [isSearchButtonClick, setIsSearchButtonClick] = useState<boolean>(false);
// 페이지 로드 시 최고의 결과와 최신의 결과를 가져오는 useEffect
useEffect(() => {
handleBest();
handleNew();
}, []); // 페이지가 로드될 때 한 번만 실행
// 서버에서 최고의 결과를 가져오는 함수
const handleBest = () => {
axios.get('http://localhost:8080/course/best')
.then((response) => {
setBestResults(response.data.data); // 최고의 결과를 state에 저장
})
.catch((error) => {
console.error('Error fetching best results', error);
});
};
// 서버에서 최신의 결과를 가져오는 함수
const handleNew = () => {
axios.get('http://localhost:8080/course/new')
.then((response) => {
setNewResults(response.data.data); // 최신의 결과를 state에 저장
})
.catch((error) => {
console.error('Error fetching new results', error);
});
};
/* ******************** 검색 부분 ***************************/
// handleSearch 함수에서 검색어가 비어있는 경우 처리를 추가합니다.
const handleSearch = () => {
if (!keyword.trim()) { // 검색어가 비어있는 경우 처리
console.log("검색어를 입력하세요.");
return; // 빈 검색어일 경우 함수를 빠르게 종료
}
axios.get('http://localhost:8080/course/search', {
params: {
keyword: keyword
},
})
.then((response) => {
setSearchResults(response.data.data); // response.data -> response.data.data 로 하니까 나온다....
console.log('Search results:', response.data); // 검색 결과를 콘솔에 출력
setIsSearchButtonClick(true); // 검색 버튼 클릭을 true로 설정
// 검색 결과가 나오면 bestResults와 newResults를 비워줍니다.
setBestResults([]);
setNewResults([]);
})
.catch((error) => {
console.error('Error fetching search results', error);
});
};
return (
<>
<ContentArea>
<Flex
flexDirection={"column"}
className={"content-wrapper"}
p={4}
width={"100%"}
gap={4}
>
<Box mx="auto" width="50%">
<Flex align="center" justify="center">
<input
type="text"
placeholder="검색어를 입력하세요"
value={keyword}
onChange={(event) => setKeyword(event.target.value)}
onKeyPress={(event) => {
if (event.key === "Enter") {
handleSearch();
}
}}
style={{ padding: "8px", fontSize: "16px", borderRadius: "999px", width: "100%", paddingLeft: "20px" }}
/>
<Button
onClick={handleSearch}
borderRadius="full"
bgColor={useColorModeValue("gray.200", "gray.700")}
_hover={{ bgColor: useColorModeValue("gray.300", "gray.600")}}
ml={2}
>
<SearchIcon />
</Button>
</Flex>
</Box>
{/* 검색 결과를 보여줄지 여부를 조건부 렌더링으로 설정 */}
{isSearchButtonClick && (
<>
<Box>
<SectionTitle title={"검색 결과"} />
<ClassCardList>
{searchResults.map((item, idx) => (
<ClassCard
key={idx}
title={item.title}
desc={item.description}
onClick={() => navigate(`/class/${item.courseId}`)}
imgSrc={item.thumbnailPath}
/>
))}
</ClassCardList>
</Box>
</>
)}
{/* 최고의 결과 표시 */}
{bestResults.length > 0 && (
<Box>
<SectionTitle title={"BEST"} />
<ClassCardList>
{bestResults.map((item, idx) => (
<ClassCard
key={idx}
title={item.title}
desc={item.description}
onClick={() => navigate(`/class/${item.classId}`)}
imgSrc={item.thumbnailPath}
/>
))}
</ClassCardList>
</Box>
)}
{/* 최신의 결과 표시 */}
{newResults.length > 0 && (
<Box>
<SectionTitle title={"New"} />
<ClassCardList>
{newResults.map((item, idx) => (
<ClassCard
key={idx}
title={item.title}
desc={item.description}
onClick={() => navigate(`/class/${item.classId}`)}
imgSrc={item.thumbnailPath}
/>
))}
</ClassCardList>
</Box>
)}
</Flex>
</ContentArea>
</>
);
}
export default SearchPage;
이렇게 작성하니 원래 best와 new 부분이 있었는데
검색을 하면 프론트에서 받아온 값을 기준으로 검색 결과만 나오고 best와 new 부분이 사라지게 된다.
여기까지는 내가 원하는 부분이다.
4번의 디테일함은...
1. 검색페이지에서 데이터베이스에 해당하는 값이 없으면 값이 없다는 것을 보여줘야한다.
2. 검색페이지에서 빈 값 검색을 하면 다시 검색 페이지로 오는 것
3. 메인페이지에서 검색창 부분 수정 & 키워드 검색 페이지로 넘기는 것
4. nav bar에 검색 탭을 만드는 것
5. 정말 시간되면 페이징까지 하는 것
다시 또 무엇을 해야할지 쪼개진다. 하면서 업데이트하자!
'SeSAC_도봉캠퍼스 > 새싹_도봉캠퍼스_프로젝트 4' 카테고리의 다른 글
2024.04.20_프로젝트 4 (19 일차) (0) | 2024.04.20 |
---|---|
2024.04.19_프로젝트 4 (18 일차) (0) | 2024.04.19 |
2024.04.17_프로젝트 4 (16 일차) (0) | 2024.04.18 |
2024.04.16_프로젝트 4 (15 일차) (0) | 2024.04.17 |
2024.04.04_프로젝트 4 (3 일차) (0) | 2024.04.04 |