캠핑장 카드가 잘 나와서 검색 기능을 시작했다.
dropdown.tsx
// 옵션 타입 정의
interface IPropsSelect {
isMain: boolean;
onChangeSearch?: (region: string, subRegion: string | null) => void;
}
export default function DropDown({ isMain, onChangeSearch }: IPropsSelect) {
// 광역시
const [selectedRegion, setSelectedRegion] =
useState<SingleValue<Option>>(null);
// 하위지역
const [subRegions, setSubRegions] = useState<Option[]>([]);
// 광역시 선택시 하위지역 리셋
const [subRegionReset, setSubRegionReset] =
useState<SingleValue<Option>>(null);
// 하위지역 disabled
const [subDisabled, setSubDisabled] = useState(true);
// 광역시도 onChange
const onChangeRegion = (selectedOption: SingleValue<Option>) => {
setSelectedRegion(selectedOption);
setSubRegionReset(null); // 광역시가 선택되면 서브는 리셋
if (selectedOption) {
onChangeSearch?.(selectedOption.value, null); // 지역 검색 값 저장
if (selectedOption.value === "전체") {
// 전체일 떄는 서브드롭박스를 disabled
setSubDisabled(true);
} else {
// 전체가 아닐 때 서브 드롭박스에 광역시에 맞는 지역으로 매핑
const subAreas =
regionMapping[selectedOption.value as keyof typeof regionMapping];
setSubRegions(createOptions(subAreas));
setSubDisabled(false);
}
} else {
setSubRegions([]);
}
};
// 하위지역 onChange
const onChangeSubRegion = (selectedOption: SingleValue<Option>) => {
setSubRegionReset(selectedOption);
onChangeSearch?.(
// 지역 검색 값 저장
selectedRegion?.value ?? "",
selectedOption?.value ?? null,
);
};
- onChangeSerach(광역시, 하위지역)으로 전달될 수 있게 추가하였다.
- 광역시도 onChangeRegion 에도 지역검색값을 저장해주고 (하위지역은 null로)
- 하위지역 onChangeSubRegion 에도 저장
(광역시는 기본값으로 " " 을 넣어줬고 하위지역은 null일 수도 있어서 null을 넣어줬다.)
(광역시를 "전체"로 선택하면 하위지역은 null)
selectedOption.value = 내가 선택한 광역시도
키값인 광역시도를 상수타입으로 사용해야하기 때문에 keyof typeof regionMapping을 해줬다.
regionMapping이 이런식으로 되어있기 때문에
regionMapping["대전시"] 이렇게 들어오는 것
그럼 대전시에 맞는 AREA3이 들어오게 subAreas로 들어오게 된다.
검색창
index.tsx
export default function Home(): JSX.Element {
const [region, setRegion] = useState<string>("");
const [subRegion, setSubRegion] = useState<string | null>(null);
const router = useRouter();
const onChangeSearch = (region: string, subRegion: string | null) => {
setRegion(region);
setSubRegion(subRegion);
};
const onClickList = async (): Promise<void> => {
if ((region !== null && subRegion !== null) || region === "전체") {
await router.push({
pathname: "/campingList",
query: { region, subRegion },
});
} else {
alert("지역을 선택해주세요!");
}
};
return (
<>
<Head>
<title>Go Camping</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Wrap>
<MainWrap>
<Button className="review">요즘 캠핑 후기 보기</Button>
<SearchBox>
<h2>Dayily camping</h2>
<div className="search">
<DropDown isMain={true} onChangeSearch={onChangeSearch} />
<Button onClick={onClickList} className="search_btn">
<span className="mobile">검색하기</span>
<span className="sr_only">검색</span>
</Button>
</div>
</SearchBox>
</MainWrap>
</Wrap>
</>
);
}
- onChangeSearch : 광역시, 하위지역 저장해줌
- onClickList: 광역시가 전체를 선택하거나, 드롭박스 두개를 모두 선택할 경우에만 페이지 전환된다.
query 파라미터로 광역시, 하위지역 전달
router.push 사용
https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object
campingCardList.tsx
interface ICampingList {
facltNm: string;
lineIntro?: string;
addr1: string;
firstImageUrl?: string;
themaEnvrnCl?: string;
tel: string;
contentId: number;
lctCl?: string;
doNm: string;
sigunguNm: string;
}
interface IApiResponse {
response: {
body: {
items: {
item: ICampingList[];
};
totalCount: number;
};
};
}
interface IPropsList {
className?: string;
}
export default function CampingCardList({ className }: IPropsList) {
const [list, setList] = useState<ICampingList[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [currentPage, setCurrentPage] = useState<number>(1); // 현재 페이지 번호
const [totalCount, setTotalCount] = useState<number>(0); // 전체 캠핑 아이템 수
const router = useRouter();
const { region, subRegion } = router.query;
const SERVICE_KEY = process.env.NEXT_PUBLIC_SERVICE_KEY;
// 데이터 불러오기
useEffect(() => {
async function fetchData(): Promise<void> {
setLoading(true);
try {
const response = await axios.get<IApiResponse>(
`https://apis.data.go.kr/B551011/GoCamping/basedList?serviceKey=${SERVICE_KEY}&numOfRows=4000&pageNo=1&MobileOS=AND&MobileApp=appName&_type=json`,
);
const items = response.data.response?.body?.items.item || []; // 데이터 없을 경우 추가 수정
// 지역 필터
const filteredItems = items.filter((item) => {
// 전체 지역일 경우
if (region === "전체") {
return true;
}
// 도는 선택 시는 전체
if (region !== "전체" && subRegion === "전체") {
return item.doNm === region;
}
// 도 선택 && 시 선택
if (region !== "전체" && subRegion !== "전체") {
return item.doNm === region && item.sigunguNm === subRegion;
}
return false;
});
setTotalCount(filteredItems.length);
const paginatedItems = filteredItems.slice(
(currentPage - 1) * PER_PAGE,
currentPage * PER_PAGE,
);
// 데이터 없을 때
if (paginatedItems.length === 0) {
setList([]);
} else {
setList(paginatedItems);
}
} catch (e) {
console.error(e);
}
setLoading(false);
}
void fetchData();
}, [SERVICE_KEY, currentPage]);
if (loading) {
return <Loading>대기 중..</Loading>;
}
const PER_PAGE = 8; // 한 페이지에 보여줄 아이템 수
const pageCount = Math.ceil(totalCount / PER_PAGE); // 전체 페이지 수 계산
const onClickPage = (selected: number) => {
setCurrentPage(selected);
};
return (
<>
<Wrap>
{list.length > 0 ? (
<CampingCard className="card" list={list} />
) : (
<div>노데이타</div>
)}
</Wrap>
{/* 페이지네이션 */}
{pageCount > 0 && (
<Pagination
totalItems={totalCount}
onClick={onClickPage}
currentPage={currentPage}
pageCount={5}
itemCountPerPage={PER_PAGE}
/>
)}
</>
);
}
- 지금 numOfRows를 4000으로 설정 하고 모든 데이터를 받아와서 필터링하는 방법으로 사용하였다.
이 방법보다 더 나은 방법이 있을 것 같아서 찾아봤지만 고캠핑 api에는 지역 검색하는 파라미터가 없어서 데이터를 우선 모두 받아와서 필터링 하는 방법으로 진행하려 한다...
- 전체 지역일경우, 도만 선택한경우, 도&시를 선택한 경우로 나눠서 필터링을 해주고
setTotalCount는 전체 캠핑장 수를 계산해서 페이지네이션 계산,
paginatedItems는 filterItems를 8개씩 잘라서 보여준다(캠핑장)
해야 할 것
- 캠핑 리스트 화면에서 드롭다운 기능 쓰기
- 데이터 없음 화면, 로딩 화면
- 좋아요 기능
'언어 > Next.js' 카테고리의 다른 글
[Next 고캠핑] 모달 만들기 (1) | 2024.07.31 |
---|---|
[Next 고캠핑] 커스텀훅으로 alert창 만들기 (0) | 2024.07.27 |
[Next 고캠핑] 페이지네이션 커스텀하기 (1) | 2024.07.24 |
[Next 고캠핑] 입지 구분 아이콘으로 하기 (0) | 2024.07.17 |
[Next 고캠핑]공공데이터포털 API 불러오기 (0) | 2024.07.16 |