Nextjs show list sản phẩm và show modal khi ấn vào 1 sản phẩm để hiển thị

Nextjs show list sản phẩm và show modal khi ấn vào 1 sản phẩm để hiển thị

Đầu tiên chúng ta tạo List product có cản search :

'use client';

import Pagination from '@/components/list/Pagination';
import ListProductItem from '@/components/list/productitem';
import { ProductType } from '@/type/ItemType';
import React, { useEffect, useState } from 'react';
import { Button, Form, InputGroup } from 'react-bootstrap';

const ProductListPage = () => {
     const [products, setProducts] = useState<ProductType[]>([]);
     const [total, setTotal] = useState(0);
     const [skip, setSkip] = useState(0);
     const [localSearch, setLocalSearch] = useState('');
     const [searchTerm, setSearchTerm] = useState('');
     const limit = 10;


     useEffect(() => {

          const fetchProducts = async () => {
               let url = '';
               if (searchTerm.trim()) {
                    // Gọi API tìm kiếm
                    url = `https://dummyjson.com/products/search?q=${encodeURIComponent(searchTerm)}`;
               } else {
                    // Gọi API phân trang mặc định
                    url = `https://dummyjson.com/products?limit=${limit}&skip=${skip}`;
               }

               const res = await fetch(url);
               const data = await res.json();

               setProducts(data.products);
               setTotal(data.total);
          };
          fetchProducts();

     }, [skip, searchTerm]);

     //const totalPages = Math.ceil(total / limit);

     const handleSubmit = (e: React.FormEvent) => {
          e.preventDefault();
          setSkip(0); // reset về trang đầu khi tìm kiếm
          setSearchTerm(localSearch);
     };
     return (
          <div className="container mt-4">
               <h2>Product List (Pagination + Search)</h2>
               <Form onSubmit={handleSubmit} className="mb-3">
                    <InputGroup>
                         <Form.Control
                              type="text"
                              placeholder="Search..."
                              value={localSearch}
                              onChange={(e) => setLocalSearch(e.target.value)}
                         />
                         <Button type="submit" variant="primary">
                              Search
                         </Button>
                    </InputGroup>
               </Form>

               <ul className="list-group mb-3">
                    {products.map(product => (
                         <li key={product.id} className="list-group-item d-flex justify-content-between">
                              <ListProductItem data={product} />

                         </li>
                    ))}
               </ul>

               {!searchTerm && (
                    <Pagination
                         skip={skip}
                         limit={limit}
                         total={total}
                         onPageChange={setSkip}
                    />
               )}
          </div>
     );
};

export default ProductListPage;

Ở đây chúng ta có ListproductItem :

'use client';

import { ProductType } from '@/type/ItemType';
import React, { useState } from 'react';
import { Modal, Button, Spinner } from 'react-bootstrap'; // Nếu dùng Bootstrap
import { ProductDetailType } from './item';

const ListProductItem = ({ data }: { data: ProductType }) => {
     const [showModal, setShowModal] = useState<boolean>(false);
     const [productDetail, setProductDetail] = useState<ProductDetailType | null>(null);
     const [loading, setLoading] = useState(false);

     const handleOpen = async () => {
          setShowModal(true);
          setLoading(true)
          try {
               const res = await fetch(`https://dummyjson.com/products/${data.id}`);
               const detail = await res.json();
               setProductDetail(detail)
          } catch (error) {
               console.error('Failed to fetch product details:', error);
          } finally {
               setLoading(false);
          }
     };
     const handleClose = () => {
          setShowModal(false)
          setProductDetail(null);
     };

     return (
          <>
               <div
                    className="d-flex justify-content-between align-items-center p-2 border-bottom cursor-pointer"
                    onClick={handleOpen}
                    style={{ cursor: 'pointer' }}
               >
                    <div className="title">{data.title}</div>
                    <div className="price">{data.price}$</div>
               </div>

               <Modal show={showModal} onHide={handleClose}>
                    <Modal.Header closeButton>
                         <Modal.Title>{data.title}</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                         {loading ? (
                              <div className="text-center">
                                   <Spinner animation="border" />
                              </div>
                         ) : productDetail ? (
                              <>
                                   <p><strong>Price:</strong> {productDetail.price}$</p>
                                   <p><strong>Description:</strong> {productDetail.description}</p>
                                   {productDetail.images && productDetail.images.length > 0 && (
                                        <img
                                             src={productDetail.images[0]}
                                             alt={productDetail.title}
                                             style={{ width: '100%', maxHeight: '200px', objectFit: 'cover' }}
                                        />
                                   )}
                              </>
                         ) : (
                              <p>Error loading product details.</p>
                         )}
                    </Modal.Body>
                    <Modal.Footer>
                         <Button variant="secondary" onClick={handleClose}>
                              Close
                         </Button>
                    </Modal.Footer>
               </Modal>
          </>
     );
};
export default ListProductItem

Trong component Paginate như sau :

'use client';

import React from 'react';
import { Button } from 'react-bootstrap';

type PaginationProps = {
     skip: number;
     limit: number;
     total: number;
     onPageChange: (newSkip: number) => void;
};

const Pagination = ({ skip, limit, total, onPageChange }: PaginationProps) => {
     const totalPages = Math.ceil(total / limit);
     const currentPage = skip / limit + 1;

     const handlePrev = () => {
          onPageChange(Math.max(0, skip - limit));
     };

     const handleNext = () => {
          onPageChange(skip + limit);
     };

     return (
          <div className="d-flex justify-content-between align-items-center py-3">
               <Button disabled={skip === 0} onClick={handlePrev}>
                    Previous
               </Button>
               <div>
                    Page {currentPage} of {totalPages}
               </div>
               <Button disabled={skip + limit >= total} onClick={handleNext}>
                    Next
               </Button>
          </div>
     );
};

export default Pagination;

truy cập sau khi chạy : http://localhost:3000/product

Bạn có thể download code về : https://drive.google.com/file/d/1DeWk-3zqHZTE5cCtEX5A_r92w1vV8_DT/view?usp=sharing