Jun's Blog

홈페이지 만들어보기[상품 정보 상세 보기](with SpringBoot) - (8) 본문

WEB/React

홈페이지 만들어보기[상품 정보 상세 보기](with SpringBoot) - (8)

luckydadit 2025. 2. 21. 12:40

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>

 

<적용결과>

수량 입력 후, 장바구니 버튼 클릭시

수량 입력 후, 구매하기 버튼 클릭시