이번에는 Goods Page에 대해 알아보자.
Ranking 페이지와 구조는 비슷하나 이번에는 Dropdown이 있고 최신순, 추천순으로 정렬도 가능하다.
Ranking 페이지와 똑같이 Grid 방식을 이용했고 컴포넌트도 동일하게 이용을 하였다.
또, Goods 상품을 클릭 시 구매할 수 있는 GoodsBuy 페이지로도 이동이 가능하다.
<Goods.jsx>
import React, { useState } from 'react';
import all_filter from '../assets/ranking-all-filter.svg';
import pet_filter from '../assets/ranking-pet-filter.svg';
import ocean_filter from '../assets/ranking-ocean-filter.svg';
import hill_filter from '../assets/ranking-hill-filter.svg';
import camping_filter from '../assets/ranking-camping-filter.svg';
import season_filter from '../assets/ranking-season-filter.svg';
import array_drop_down from '../assets/arrow_drop_down.svg';
import array_drop_up from '../assets/arrow_drop_up.svg';
import Foot from '../components/Foot.jsx';
import GoodsImg from '../components/GoodsImg.jsx';
import GoodsRanking from '../components/GoodsRanking';
import FilterButton from '../components/FilterButton.jsx';
import Dropdown from '../components/Dropdown.jsx';
import '../styles/Goods.css';
const goodsFilterButtons = [
{ id: 'all-button', label: '전체', icon: all_filter },
{ id: 'pet-button', label: '반려동물', icon: pet_filter },
{ id: 'ocean-button', label: '바다', icon: ocean_filter },
{ id: 'hill-button', label: '산', icon: hill_filter },
{ id: 'camping-button', label: '캠핑', icon: camping_filter },
{ id: 'season-button', label: '계절', icon: season_filter },
{ id: 'character-button', label: '캐릭터', icon: season_filter },
{ id: 'animal-button', label: '동물', icon: season_filter },
{ id: 'sight-button', label: '풍경', icon: season_filter },
{ id: 'etc-button', label: '기타', icon: season_filter }
];
export default function Goods() {
const [view, setView] = useState(false);
const [selectedSort, setSelectedSort] = useState('최신순');
const [selectedCategory, setSelectedCategory] = useState('all-button');
const list = ['최신순', '추천순'];
const handleSortChange = (sortOption) => {
setSelectedSort(sortOption);
setView(false);
};
const handleCategoryChange = (categoryId) => {
setSelectedCategory(categoryId);
};
return (
<div className='Goods-wrap'>
<div className="Goods-main-best">
<GoodsRanking count={3} />
</div>
<div className="goods-filter">
<FilterButton
filterButtons={goodsFilterButtons}
setCategory={handleCategoryChange}
/>
</div>
<div className='goods-sort' onClick={() => setView(!view)}>
<p style={{ color: 'rgba(45, 45, 45, 0.40)', fontSize: '1.3rem', fontStyle: 'normal', fontWeight: '400', position:'relative'}}>
정렬 방식: <span style={{ color: '#000', fontSize: '1.3rem', fontStyle: 'normal', fontWeight: '500' }}>{selectedSort}</span>
{view ? <img src={array_drop_up} alt="dropdown up" /> : <img src={array_drop_down} alt="dropdown down" />}
{view && (
<Dropdown style={{position:'absolute'}}onSortChange={handleSortChange} selectedSort={selectedSort} list = {list}/>
)}
</p>
</div>
<div className="Goods-main-goods">
<GoodsImg selectedCategory={selectedCategory} selectedSort={selectedSort} />
</div>
<footer className="Goods-foot">
<Foot />
</footer>
</div>
);
}
<GoodsRanking.jsx>
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import rankingShadow from '../assets/ranking_shadow.png';
import '../styles/RankingImg.css';
const BASE_URL = "http://3.39.26.152:8000";
const GoodsRankingBox = ({ index }) => {
const [ranking, setRanking] = useState([]);
// 상품 순위 데이터를 가져오는 비동기 함수
const fetchGoodsRanking = async () => {
try {
const response = await axios.get(`${BASE_URL}/api/goods/ranking`);
if (Array.isArray(response.data)) {
setRanking(response.data); // 배열로 설정
} else {
console.error("응답이 배열이 아닙니다:", response.data);
}
} catch (error) {
console.error("Error fetching ranking data:", error);
}
};
useEffect(() => {
fetchGoodsRanking();
}, []);
// 데이터가 로드되지 않았을 경우 로딩 상태 처리
if (!ranking) {
return <div>Loading...</div>;
}
return (
<div className="rankingImg-box">
<div className="number-container">
<p className='best-ranking-number'>{index + 1}</p>
</div>
<div className='ranking-box-container'>
<img src={rankingShadow} alt="Ranking Shadow" className="best-ranking-shadow" />
<img src={`http://${ranking[index]?.image}`} alt="Best Ranking" className="best-ranking" />
</div>
</div>
);
};
const GoodsRanking = ({ count }) => {
return (
<div className="rankingImg-wrap">
{Array.from({ length: count }, (_, index) => (
<GoodsRankingBox key={index} index={index} />
))}
</div>
);
};
export default GoodsRanking;
<Dropdown.jsx>
import React from 'react';
import '../styles/Dropdown.css';
export default function Dropdown({ onSortChange, selectedSort, list }) {
const sortOptions = list
const handleClick = (option) => {
onSortChange(option);
};
return (
<ul className='dropdown'>
{sortOptions.map(option => (
<li
key={option}
onClick={() => handleClick(option)}
style={{ backgroundColor: selectedSort === option ? '#F0F0F0' : '#FFF', width : '120px' }}
>
{option}
</li>
))}
</ul>
);
}
<GoodsBuy.jsx>
import React, { useState } from "react";
import { useLocation } from "react-router-dom";
import goodsBuyplus from "../assets/goodsBuy-plus.svg";
import goodsBuyminus from "../assets/goodsBuy-minus.svg";
import "../styles/GoodsBuy.css";
export default function GoodsBuy() {
const location = useLocation();
const { imgSrc, itemName, itemPrice } = location.state || {};
const [count, setCount] = useState(1);
function handleBuy() {
alert(`${count}개 구매되었습니다.`);
}
function handleValue(increment) {
setCount((prevCount) => Math.max(1, prevCount + increment)); // 최소 1개 유지
}
const formatPrice = (price) => {
return new Intl.NumberFormat("ko-KR").format(price);
};
return (
<div className="GoodsBuy-wrap">
<main className="GoodsBuy-main">
<div className="GoodsBuy-left">
<img
src={`http://${imgSrc}`}
alt="굿즈 사진"
className="GoodsBuy-img"
/>
</div>
<div className="GoodsBuy-right">
<div className="GoodsBuy-description">
<p className="GoodsBuy-producer">{itemName}</p>
<p className="GoodsBuy-introduction">{itemName}</p>
<p className="GoodsBuy-introduction-price">
{`${formatPrice(itemPrice)}원`}
</p>
</div>
<div className="GoodsBuy-line"></div>
<div className="GoodsBuy-countBox">
<p className="GoodsBuy-small-title">{itemName}</p>
<div className="GoodsBuy-count">
<button className="plus-minus" onClick={() => handleValue(1)}>
<img src={goodsBuyplus} alt="플러스기호" />
</button>
{count}
<button
className="plus-minus"
onClick={() => handleValue(-1)}
disabled={count <= 1}
>
<img src={goodsBuyminus} alt="마이너스기호" />
</button>
<span className="GoodsBuy-countPrice">
{`${formatPrice(count * itemPrice)}원`}
</span>
</div>
</div>
<div className="GoodsBuy-totalPrice-box">
<div className="GoodsBuy-price-description">총 가격</div>
<div className="GoodsBuy-totalPrice">
{`${formatPrice(count * itemPrice)}원`}
</div>
</div>
<button className="GoodsBuy-button" onClick={handleBuy}>
<p className="GoodsBuy-button-p">바로 구매하기</p>
</button>
</div>
</main>
</div>
);
}
GoodsImg -> GoodsBuy로 이동하는 과정에서는 useNavigation, useLocation을 사용하여 이미지, 제목, 가격정도를 넘겨주고
GoodsBuy에서 임의로 count 값에 따라 가격이 달라지게 만들어 놓았다.