언어/Next.js

[Next 고캠핑] 입지 구분 아이콘으로 하기

홍시_코딩기록 2024. 7. 17. 23:21

어제 만든 캠핑리스트 

아이콘 작업 전/ 고캠핑 활용 메뉴얼

캠핑장 입지 구분을 아이콘으로 넣고 싶었다.

참고로 활용 메뉴얼 보면 데이터 설명 나와있음.

 

 

 

<일단 완성본>

 

 


<코드>

 

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>
        );
      })}
    </>
  );
}