어제 만든 캠핑리스트


캠핑장 입지 구분을 아이콘으로 넣고 싶었다.
참고로 활용 메뉴얼 보면 데이터 설명 나와있음.
<일단 완성본>

<코드>
LocationICon.tsx
import styled from "@emotion/styled";
interface IPropsType {
type: string;
}
const Icon = styled.i<IPropsType>`
display: inline-block;
width: 3.2rem;
height: 3.2rem;
background-size: 100%;
background-repeat: no-repeat;
${(props) => {
switch (props.type) {
case "산":
return `background-image: url(/image/icon/icon_mountain.png)`;
case "숲":
return `background-image: url(/image/icon/icon_forest.png)`;
case "강":
return `background-image: url(/image/icon/icon_wave.png)`;
case "해변":
return `background-image: url(/image/icon/icon_beach.png)`;
default:
return `
width: 1rem;
height: .1rem;
background: #67794A;
`;
}
}};
`;
export function LocationIcon({ type }: IPropsType) {
return (
<Icon type={type}>
<span className="sr_only">{type}</span>
</Icon>
);
}
Icon을 따로 컴포넌트로 만들었다.
type에 들어오는 텍스트가 산이면 산 아이콘, 숲이면 숲 아이콘 ~
없으면 그냥 선 (원래 점 세개있는 이미지로 하려다가 버튼 모양 같아서 바꿈)
Icon에 케이스에 따른 스타일을 줘야 하는데 타입에 따라 다르니까
<IPropsType> 스타일에 타입 선언해 줌.
CampingCard.tsx
{list.map((item: ICampingList) => {
// 입지 구분 아이콘 리스트
const icons = item.lctCl ? item.lctCl.split(",") : [];
const iconList = icons
.slice(0, 3)
.concat(
Array(3 - icons.length > 0 ? 3 - icons.length : 0).fill("없음"),
);
return (
<CardWrap key={item.contentId} className={className}>
<CardInner>
<ImgBox>
<LikeBtn className="like" />
<img src={item.firstImageUrl} alt={item.facltNm} />
</ImgBox>
<CardInfo>
<li>
<strong>{item.facltNm}</strong>
</li>
<li>{item.lineIntro ? item.lineIntro : item.themaEnvrnCl}</li>
<li className="address">{item.addr1}</li>
<li>{item.tel ? item.tel : "-"}</li>
</CardInfo>
</CardInner>
<IConWrap>
{iconList.map((icon, index) => (
<li key={index}>
<LocationIcon type={icon} />
</li>
))}
</IConWrap>
</CardWrap>
);
})}
아이콘 리스트가 항상 세개로 맞춰놓을 거라서
const icons = item.lctCl ? item.lctCl.split(",") : [];
const iconList = icons
.slice(0, 3)
.concat(
Array(3 - icons.length > 0 ? 3 - icons.length : 0).fill("없음"),
);
lctCl데이터가 원래 "산, 숲" 이렇게 문자열로 되어있어서
icons에 ,로 쪼개서 배열로 만들어 준다. 없으면 빈 배열.
iconList는 혹시나 3개가 넘어갈 경우가 있을 수도 있으니까 3개로 잘라주고
.concat을 이용해서 아이콘이 3보다 작을 때 "없음"으로 채운다.
이제 페이지네이션 하고..
좋아요도 해보고..
검색도 해보고..
현재 전체 코드
더보기
import styled from "@emotion/styled";
import LikeBtn from "../likeBtn";
import { useEffect, useState } from "react";
import axios from "axios";
import { LocationIcon } from "../locationIcon";
const CardWrap = styled.div`
background: #f8f8f8;
border-radius: 1.5rem;
box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.1);
`;
const CardInner = styled.div`
background: #fff;
border-radius: 1.5rem;
overflow: hidden;
box-shadow: 0 0.4rem 2rem 0 rgba(0, 0, 0, 0.1);
`;
const ImgBox = styled.div`
position: relative;
height: 18rem;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.like {
position: absolute;
right: 1.6rem;
bottom: -1rem;
}
`;
const CardInfo = styled.div`
padding: 1.4rem 1.2rem 2.4rem 1.2rem;
min-height: 14.8rem;
li {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
min-height: 1.6rem;
font-size: 1.4rem;
strong {
font-size: 1.8rem;
}
}
li.address {
margin-top: 2rem;
color: #4a7b33;
}
li ~ li {
margin-top: 1rem;
}
`;
const IConWrap = styled.ul`
display: flex;
justify-content: space-between;
padding: 1rem 1.2rem;
li {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
li ~ li {
border-left: 0.1rem solid #a9a9a9;
}
`;
const Loading = styled.div`
font-size: 4rem;
`;
interface ICampingList {
facltNm: string;
lineIntro?: string;
addr1: string;
firstImageUrl?: string;
themaEnvrnCl?: string;
tel: string;
contentId: number;
lctCl?: string;
}
interface IApiResponse {
response: {
body: {
items: {
item: ICampingList[];
};
};
};
}
interface IPropsList {
className?: string;
}
export default function CampingCard({ className }: IPropsList) {
const [list, setList] = useState<ICampingList[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const SERVICE_KEY = process.env.NEXT_PUBLIC_SERVICE_KEY;
// 데이터 불러오기
useEffect(() => {
async function fetchData(): Promise<void> {
setLoading(true);
try {
const response = await axios.get<IApiResponse>(
`http://apis.data.go.kr/B551011/GoCamping/basedList?serviceKey=${SERVICE_KEY}&numOfRows=12&pageNo=1&MobileOS=AND&MobileApp=appName&_type=json`,
);
setList(response.data.response.body.items.item);
} catch (e) {
console.error(e);
}
setLoading(false);
}
void fetchData();
}, [SERVICE_KEY]);
if (loading) {
return <Loading>대기 중..</Loading>;
}
if (!list) {
return null;
}
return (
<>
{list.map((item: ICampingList) => {
// 입지 구분 아이콘 리스트
const icons = item.lctCl ? item.lctCl.split(",") : [];
const iconList = icons
.slice(0, 3)
.concat(
Array(3 - icons.length > 0 ? 3 - icons.length : 0).fill("없음"),
);
return (
<CardWrap key={item.contentId} className={className}>
<CardInner>
<ImgBox>
<LikeBtn className="like" />
<img src={item.firstImageUrl} alt={item.facltNm} />
</ImgBox>
<CardInfo>
<li>
<strong>{item.facltNm}</strong>
</li>
<li>{item.lineIntro ? item.lineIntro : item.themaEnvrnCl}</li>
<li className="address">{item.addr1}</li>
<li>{item.tel ? item.tel : "-"}</li>
</CardInfo>
</CardInner>
<IConWrap>
{iconList.map((icon, index) => (
<li key={index}>
<LocationIcon type={icon.trim()} />
</li>
))}
</IConWrap>
</CardWrap>
);
})}
</>
);
}
'언어 > Next.js' 카테고리의 다른 글
[Next 고캠핑] 드롭다운 선택으로 검색을 해보자 (타입스크립트) (0) | 2024.07.25 |
---|---|
[Next 고캠핑] 페이지네이션 커스텀하기 (1) | 2024.07.24 |
[Next 고캠핑]공공데이터포털 API 불러오기 (0) | 2024.07.16 |
[react-select] 리액트 셀렉트 커스텀하기 (0) | 2024.07.13 |
[react-select] 리액트 셀렉트 사용해보기 (1) | 2024.07.07 |