Jun's Blog
홈페이지 만들어보기[상품 정보 상세 보기](with SpringBoot) - (8) 본문
1. 상세보기 이벤트 처리하기
1.1 상세보기 버튼 변경 및 이벤트 처리하기
기존 상세보기 버튼을 제거하고 해당 아이템을 클릭하면 상세보기 이벤트가 실행되도록 변경합니다.
https://www.w3schools.com/css/css3_flexbox_container.asp
W3Schools.com
W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.
www.w3schools.com
ProductList.js에서 수정한 부분(with. VS Code)
const navigate = useNavigate();
아래 style에 바깥쪽의 {은 jsx의 중괄호이고 안쪽의 {은 JavaScrpit의 중괄호입니다.
<Col key={product.id} md={3} className="mb-3">
<Card className="h-100" onClick={() => navigate(`/product/detail/${product.id}`)}
style={{cursor:"pointer"}}>
<Card.Body>
<Card.Title>{product.name}</Card.Title>
<Card.Text>가격 : {product.price}원</Card.Text>
<div className="d-flex justify-content-center">
<Button
variant="warning"
className="me-2"
onClick={(event) => {
event.stopPropagation(); // Card Click Event 방지
navigate(`/product/update/${product.id}`)
}}
>
수정
</Button>
<Button variant="danger" className="me-2">삭제</Button>
</div>
</Card.Body>
<Container className="my-4">
<h1 className="my-4">상품 목록 페이지</h1>
<Link to={`product/insert`}>
<Button variant ="primary" className="mb-3">
상품 등록
</Button>
</Link>
ProductList.js에서 수정완료한 소스 내용(with. VS Code)
import axios from "axios";
import { useEffect, useState } from "react";
import { Button, Card, Col, Container, Row } from "react-bootstrap";
import { Link, useNavigate } from "react-router-dom";
function App() {
const [products, setProducts] = useState([]); // 상품 목록 state
useEffect(() => { // BackEnd 서버에세 데이터를 읽어 오기
axios.get("http://localhost:9000/product/list")
.then((response) => {
console.log('response.data');
console.log(response.data);
setProducts(response.data || []); // 데이터가 안올 경우, []로 대체한다는 의미임.
})
.catch((error) => {
console.error(error);
});
}, []); // 2번째 매개 변수 []로 인하여 딱 1번만 rendering합니다.
const navigate = useNavigate();
return (
<Container className="my-4">
<h1 className="my-4">상품 목록 페이지</h1>
<Link to={`product/insert`}>
<Button variant ="primary" className="mb-3">
상품 등록
</Button>
</Link>
<Row>
{products.map((product) => (
<Col key={product.id} md={3} className="mb-3">
<Card className="h-100" onClick={() => navigate(`/product/detail/${product.id}`)}
style={{cursor:"pointer"}}>
<Card.Img
variant="top"
src={`http://localhost:9000/images/${product.image}`} // image는 DB 컬럼명
alt={product.name}
style={{ width: '100%', height: '200px', objectFit: 'cover' }} />
<Card.Body>
<Card.Title>{product.name}</Card.Title>
<Card.Text>가격 : {product.price}원</Card.Text>
<div className="d-flex justify-content-center">
<Button
variant="warning"
className="me-2"
onClick={(event) => {
event.stopPropagation(); // Card Click Event 방지
navigate(`/product/update/${product.id}`)
}}
>
수정
</Button>
<Button variant="danger" className="me-2">삭제</Button>
</div>
</Card.Body>
</Card>
</Col>
))}
</Row>
</Container>
);
};
export default App;
<적용결과>
App.js에서 수정한 부분(with. VS Code)
<Routes>
{/* path에는 요청 url 정보, element에는 컴포넌트 이름 */}
<Route path='/' element={<HomePage/>}/>
<Route path='/member/login' element={<LoginPage setUser={handleLoginSuccess} />}/>
<Route path='/member/signup' element={<SignupPage/>}/>
<Route path='/product/list' element={<ProductList/>}/>
<Route path='/product/detail/:id' element={<ProductDetail/>}/>
</Routes>
import ProductDetail from './pages/ProductDetail';
ProductDetail.js파일 추가(with. VS Code)
import { useParams } from "react-router-dom";
function App(){
/* useParams : 파라미터를 처리해주는 hook */
/* 주의 사항 : 넘기는 쪽과 받는 쪽의 이름이 일치해야 합니다. */
const {id} = useParams();
console.log(id);
return(
<>
상세 보기 페이지 : {id}
</>
);
};
export default App ;
<목록에서 특정 상품(id:296)을 클릭했을 때, 상세 보기 페이지로 이동한 결과>
ProductDetail.js파일 추가(with. VS Code)
return(
<Container className="my-4">
<Card>
<Row className="g-0">
{/* 좌측 상품 이미지 */}
<Col md={4}>
좌측 이미지
</Col>
{/* 우측 상품 세부 정보 */}
<Col md={4}>
우측 상품 세부 정보
</Col>
</Row>
</Card>
</Container>
);
<적용결과>
https://www.w3schools.com/react/react_useeffect.asp
W3Schools.com
W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.
www.w3schools.com
ProductDetail.js에 수정 및 추가 부분(with. VS Code)
import axios from "axios";
import { useEffect, useState } from "react";
import { Card, Col, Container, Row, Table } from "react-bootstrap";
import { useNavigate, useParams } from "react-router-dom";
function App(){
/* useParams : 파라미터를 처리해주는 hook */
/* 주의 사항 : 넘기는 쪽과 받는 쪽의 이름이 일치해야 합니다. */
const {id} = useParams();
console.log(id);
// get Product from Backend
const [product, setProduct] = useState(null);
// 로딩 상태를 나타내는 스테이트로, true이면 현재 로딩중입니다.
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
useEffect(() => {
axios.get(`http://localhost:9000/product/detail/${id}`) // SpringBoot에 넘기는 부분
.then((response) => {
setProduct(response.data);
setLoading(false); // 완전히 읽혀 지면 false로 변경
})
.catch((error) => {
console.log(error);
window.alert("상품 정보를 불러오는 중 오류가 발생하였습니다."); // 경고 알림창 표시
navigate(-1); // 이전 페이지로 이동
});
}, [id, product]);
// 카테고리 정보 보여 주기 (예시 : BREAD → 빵(BREAD))
const getCategoryDesc = (category) => {
switch(category){
case 'BREAD' : return `빵(${category})`
case 'BEVERAGE' : return `음료수(${category})`
case 'CAKE' : return `케익(${category})`
default : return `기타(${category})`
}
};
if(loading === true){
return <Container className="my-4 text-center"><h3>상품 정보를 읽어 오는 중입니다.</h3></Container>
}
if(!product){
return <Container className="my-4 text-center"><h3>상품 정보를 찾을 수 없습니다.</h3></Container>
}
return(
<Container className="my-4">
<Card>
<Row className="g-0">
{/* 좌측 상품 이미지 */}
<Col md={4}>
<Card.Img
variant="top"
src={`http://localhost:9000/images/${product.image}`}
alt={product.name}
style={{ width:'100%', height:'400px', objectFit:'cover' }}
/>
</Col>
{/* 우측 상품 세부 정보 */}
<Col md={8}>
<Card.Body>
<Card.Title className="fs-3">
{product.name}
</Card.Title>
<Table striped bordered hover>
<tbody>
<tr>
<td className="text-center"><strong>가격</strong></td>
<td>{product.price.toLocaleString()}원</td>
</tr>
<tr>
<td className="text-center"><strong>카테고리</strong></td>
<td>{getCategoryDesc(product.category)}</td>
</tr>
<tr>
<td className="text-center"><strong>재고</strong></td>
<td>{product.stock.toLocaleString()}개</td>
</tr>
<tr>
<td className="text-center"><strong>설명</strong></td>
<td>{product.description}</td>
</tr>
<tr>
<td className="text-center"><strong>등록일자</strong></td>
<td>{product.inputdate}</td>
</tr>
</tbody>
</Table>
</Card.Body>
</Col>
</Row>
</Card>
</Container>
);
};
export default App ;
<실행결과>
아직 SpringBoot쪽(BackEnd) 처리를 하지 않아 상세보기 이벤트 실행시,
response data가 없으므로 아래와 같이 에러가 발생합니다.
ProductController.java에 추가 및 수정 부분 (with. IntelliJ)
// @PathVariable : url 경로 내의 변수 값을 파라미터로 매핑할 때 사용합니다.
// http://localhost:9000/product/detail/상품아이디
@GetMapping("/detail/{id}")
public ResponseEntity<Product> detail(@PathVariable Long id){
Product product = this.productService.getProductById(id);
if(product != null){
return ResponseEntity.ok(product); // 200 Ok 응답
}else{ // 404 응답
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
ProductService.java에 추가 및 수정 부분 (with. IntelliJ)
public Product getProductById(Long id) {
Optional<Product> product = this.productRepository.findById(id);
return product.orElse(null); // 해당 상품이 존재하지 않으면 null 값을 반환합니다.
}
<적용결과>
특정 상품을 클릭하여 상품 상세보기 이벤트 처리 결과(정상)
상품 정보가 없을 경우를 가정하여, url 에 없는 아이디 정보를 입력하여 상품 상세보기 이벤트를 처리한 결과(오류)
ex) url - http://localhost:3000/product/detail/3004
ProductDetail.js에 수정 및 추가 부분(with. VS Code)
{/* 구매 수량 입력 */}
<Form.Group as={Row} className="mb-3 align-items-center">
<Col xs={3} className="text-center">
<strong>구매 수량</strong>
</Col>
<Col xs={5}>
<Form.Control
type="number"
value={`quantity`}
onChange={`handleQuantityChange`}
min="1"
/>
</Col>
</Form.Group>
{/* 버튼 (목록으로 & 장바구니 & 구매하기) */}
<div className="d-flex justify-content-center mt-3">
<Button variant="primary" className="me-3 px-4" href="/product/list">목록으로</Button>
<Button variant="success" className="me-3 px-4" onClick={`addToCart`}>
장바구니
</Button>
<Button variant="danger" className="px-4" onClick={`buyNow`}>
구매하기
</Button>
</div>
<적용 결과>
ProductDetail.js에 수정 및 추가 부분(with. VS Code)
// 구매 수량 관련 항목들
const [quantity, setQuantity] = useState(0); // 구매 수량 State
const handleQuantityChange = (event) => {
const newValue = parseInt(event.target.value);
setQuantity(newValue);
};
<Form.Control
type="number"
value={quantity}
onChange={handleQuantityChange}
min="1"
/>
// 장바구니에 추가하기
const addToCart = () => {
alert(`${product.name} ${quantity}개를 장바구니에 담기`); // 추후 구현 예정
};
// 바로 구매하기
const buyNow = () => {
alert(`${product.name} ${quantity}개를 지금 구매하기`); // 추후 구현 예정
};
{/* 버튼 (목록으로 & 장바구니 & 구매하기) */}
<div className="d-flex justify-content-center mt-3">
<Button variant="primary" className="me-3 px-4" href="/product/list">목록으로</Button>
<Button variant="success" className="me-3 px-4" onClick={addToCart}>
장바구니
</Button>
<Button variant="danger" className="px-4" onClick={buyNow}>
구매하기
</Button>
</div>
<적용결과>
수량 입력 후, 장바구니 버튼 클릭시
수량 입력 후, 구매하기 버튼 클릭시
'WEB > React' 카테고리의 다른 글
홈페이지 만들어보기[상품 수정](with SpringBoot) - (10) (2) | 2025.02.21 |
---|---|
홈페이지 만들어보기[상품 등록](with SpringBoot) - (9) (1) | 2025.02.21 |
홈페이지 만들어보기[상품 목록 조회](with SpringBoot) - (7) (2) | 2025.02.20 |
홈페이지 메인화면 꾸미기(with SpringBoot) - (6.5) (extra) (2) | 2025.02.20 |
임의 서비스 포트 강제 종료하는 방법 (0) | 2025.02.19 |