미새문지

24.09.05 day61 작업 일지 본문

개발 TIL

24.09.05 day61 작업 일지

문미새 2024. 9. 5. 23:41
728x90

특정 루틴을 눌렀을 때의 루틴 페이지에 데이터를 받을 준비를 했다.

import { useEffect, useState } from "react";
import AboutHeader from "../components/common/aboutHeader";
import DetailHeader from "../components/detail/detailHeader";
import DetailInfo from "../components/detail/detailInfo";
import axios from "axios";

const DetailRoutine: React.FC = () => {
  const [data, setData] = useState<any>(null);

  useEffect(() => {
    axios
      .get("/api/routine/{routine_key}")
      .then((response) => {
        setData(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  return (
    <>
      <DetailHeader />
      <DetailInfo data={data} />
    </>
  );
};
export default DetailRoutine;

메인 컴포넌트에는 header와 info가 있는데 header는 좋아요를 받을 버튼이 있어 해당 루틴의 번호값을 보내야 하고 Info는 루틴의 데이터와 제품의 데이터를 넘겨야 한다.

import "../../scss/detail/detailInfo.scss";
import DetailProfile from "./detailProfile";
import DetailTitle from "./detailTitle";
import DetailCheck from "./detailCheck";
import DetailTag from "./detailTag";
import DetailGrade from "./detailGrade";
import DetailBtnBox from "./detailBtnBox";
import { useEffect, useState } from "react";
import axios from "axios";

interface routineObject {
  id: number;
  brand: string;
  name: string;
  size: number;
  price: number;
  itemImg: string;
}

interface routineData {
  nickname: string;
  reviewPoint: string;
  reviewMember: number;
  profileImg: string;
  check: string[];
  tag: string[];
  routineGrade: number;
  routineList: routineObject[];
  itemList: string[];
}

interface detailRoutineData {
  data: routineData[];
}

const DetailInfo: React.FC<detailRoutineData> = ({ data }) => {
  const hasData = data && data.length > 0;

  return (
    <>
      {hasData ? (
        <div className="detailInfoWrapper">
          <h4>{data[0]?.nickname}의 루틴</h4>
          <DetailTitle
            reviewPoint={data[0]?.reviewPoint}
            reviewMember={data[0]?.reviewMember}
          />
          <DetailProfile
            profileImg={data[0]?.profileImg}
            profileNickname={data[0]?.nickname}
          />
          <DetailCheck check={data[0]?.check} />
          <DetailTag tag={data[0]?.tag} />
          <DetailGrade
            routineGrade={data[0]?.routineGrade}
            routineList={data[0]?.routineList}
          />
          <DetailBtnBox itemList={data[0]?.itemList} />
        </div>
      ) : (
        <div>루틴 정보가 없습니다.</div>
      )}
    </>
  );
};
export default DetailInfo;

벌써 인터페이스가 3중이 됐다.이렇게 많은 데이터가 한번에 들어올 지는 모르지만 기본적으로 루틴 데이터는 한번에 들어오니까 루틴의 제품 데이터를 다른  api로 받아올 수도 있으니 interface로 분리해줬다.

그리고 배열의 경우에는 항상 값이 존재할 수는 없어 값이 없을 때의 처리도 있어야 하기 때문에 hasData라는 boolean값을 만들어서 true값일 떈 기존에 있는 div를 false값일 땐 정보가 없다는 예외의 div를 화면에 출력시킨다.

 

각 구조별로 컴포넌트를 나눠놨으니 해당 컴포넌트에 맞는 데이터를 전달해주고 받아야 한다.

import Star from "../../img/star.png";
import "../../scss/detail/detailInfo.scss";

interface detailTitleData {
  reviewPoint: string;
  reviewMember: number;
}

const DetailTitle: React.FC<detailTitleData> = ({
  reviewPoint,
  reviewMember,
}) => {
  return (
    <>
      <div className="detailTitleReview">
        <img src={Star} alt="평점" />
        <span>
          {reviewPoint} <span>({reviewMember})</span>
        </span>
        <span>리뷰 조회</span>
      </div>
    </>
  );
};
export default DetailTitle;

title의 경우는 평점과 리뷰를 쓴 멤버의 수를

import "../../scss/detail/detailInfo.scss";

interface detailProfileData {
  profileImg: string;
  profileNickname: string;
}

const DetailProfile: React.FC<detailProfileData> = ({
  profileImg,
  profileNickname,
}) => {
  return (
    <>
      <div className="detailProfileWrapper">
        <div>{/* <img src={profileImg} alt="프로필" /> */}</div>
        <div>
          <span>{profileNickname}님의 루틴</span>
          <span>
            피부타입 <span>64%</span> 일치
          </span>
        </div>
      </div>
    </>
  );
};
export default DetailProfile;

profile에는 프로필 이미지와 닉네임을 전달해줘야 하고 피부 타입 일치 부분은 현재 본인의 트러블 리스트와 이 화장품의 트러블리스트를 대조해야 하는데 이건 아직 감이 안온다.

 

그 아래 컴포넌트들은 트러블리스트와 태그리스트를 배열로 받아서 map으로 배열만큼 뿌려줘야 한다. 코드는 짧고 반복적이라 패스

 

import ReviewPoint from "../common/reviewPoint";

interface routineObject {
  id: number;
  brand: string;
  name: string;
  size: number;
  price: number;
  itemImg: string;
}

interface detailGradeData {
  routineGrade: number;
  routineList: routineObject[];
}

const DetailGrade: React.FC<detailGradeData> = ({
  routineGrade,
  routineList,
}) => {
  const hasRoutine = routineList && routineList.length > 0;

  return (
    <>
      <div className="detailGradeWrapper">
        <div className="detailGradeTitle">
          <div>
            <span>취침 전 : {routineGrade}단계 루틴</span>
            <span>2/{routineGrade} 제품 보유중</span>
          </div>
          <div>
            <span>보유중인 제품 제외하기</span>
            <input type="checkbox" />
          </div>
        </div>
        <div className="detailGradeItem">
          {hasRoutine ? (
            <div className="detailGradeBox">
              <span>1단계:세안</span>
              <div className="detailItemInfo">
                <div>
                  <img src={routineList[0].itemImg} alt="" />
                </div>
                <div>
                  <span>{routineList[0].brand}</span>
                  <span>
                    {routineList[0]?.name} & {routineList[0]?.size}
                  </span>
                  <span>₩ {routineList[0]?.price}</span>
                  <ReviewPoint />
                </div>
              </div>
              <div className="detailItemEffect">제품 효능 박스(일단 보류)</div>
            </div>
          ) : (
            <div>루틴 정보가 없습니다.</div>
          )}
        </div>
      </div>
    </>
  );
};
export default DetailGrade;

이 컴포넌트는 해당 루틴의 개수만큼 제품을 화면에 뿌려줘야 하며 이 후 map으로 전부 출력해줄 예정이다.

 

import { useNavigate } from "react-router-dom";

interface itemBox {
  itemList: string[];
}

const DetailBtnBox: React.FC<itemBox> = ({ itemList }) => {
  const navigate = useNavigate();

  const handleAddItem = () => {
    navigate("/order", { state: { itemList } });
  };

  return (
    <>
      <div className="detailBtnBoxWrapper">
        <button onClick={handleAddItem}>장바구니 추가</button>
        <button>구매하기</button>
      </div>
    </>
  );
};
export default DetailBtnBox;

장바구니 추가와 구매 버튼이 있는 컴포넌트는 제품의 리스트를 넘기며 장바구니 추가 시 해당 제품의 리스트를 넘겨줘야 한다.

여기서 useNavigate는 다른 페이지로 이동될 때 특정 값을 전달할 수 있는 기능이 있어 유용하게 쓰인다.

그리고 찾아본 결과 라우팅을 사용하는 SPA경우에는 navigate를 사용해서 페이지가 변경되며 이 개념은 페이지 이동으로 들어가지 않고 컴포넌트 이동으로 적용되는 것 같다.

 

구매하기 버튼은 결제 api 기능이 추가됐을 때 연결해서 사용할 예정이다.

장바구니 추가를 누르면 라우팅으로 설정한 /order 페이지로 넘어가며, 이전 페이지에서 navigate로 보낸 값을 받아줘야 한다.

import { useLocation } from "react-router-dom";
import AboutHeader from "../components/common/aboutHeader";
import BuyBtn from "../components/mart/buyBtn";
import Caution from "../components/mart/caution";
import MartList from "../components/mart/martList";
import PriceList from "../components/mart/priceList";

const Mart: React.FC = () => {
  const location = useLocation();
  const { itemList } = location.state || { itemList: [] };

  const handleBack = () => {
    return;
  };

  return (
    <>
      <AboutHeader Title={"장바구니"} onBack={handleBack} />
      <MartList itemList={itemList} />
      <PriceList itemList={itemList} />
      <Caution />
      <BuyBtn />
    </>
  );
};
export default Mart;

값을 받을 때는 useLocation이라는 라우팅의 기능을 사용하며 해당 값이 비어있을 수 있기 때문에 값이 없으면 빈 배열로 받을 수 있게 처리해준다. 그리고 값은 객체 형태이기 때문에 중괄호를 사용해 상수값을 감싸줘야 한다.

해당 받아온 값을 데이터가 필요한 컴포넌트에 똑같이 전달하며 MartList에는 제품의 목록을, PriceList에는 제품들의 가격을 보내게 된다.

728x90

'개발 TIL' 카테고리의 다른 글

24.09.09 day63 작업 일지  (0) 2024.09.09
24.09.06 day62 작업 일지  (2) 2024.09.06
24.09.04 day60 jwt 오류 해결, 카카오 로그인 구현 중  (3) 2024.09.04
24.09.03 day59 작업 일지  (1) 2024.09.03
24.09.02 day58 작업 일지  (4) 2024.09.02