Untitled

 avatar
unknown
plain_text
2 months ago
32 kB
3
Indexable
import { useParams, useNavigate } from "react-router-dom";
import "../assets/styles/ProductDetail.scss";
import Main from "../utils/container";
import { useState, useEffect } from "react";
import { Icon } from "../assets/icon/icons";
import { productService, cartService } from "../services/api.service";
import { useNotification } from "../components/Notification/Notification";
import OptionCart from "../pages/User/OptionCart";

export default function ProductDetail() {
  const { productId } = useParams();
  const navigate = useNavigate();
  const { showNotification } = useNotification();

  // All state hooks
  const [activeTab, setActiveTab] = useState('details');
  const [selectedFilter, setSelectedFilter] = useState("All");
  const [showWithMedia, setShowWithMedia] = useState(false);
  const [liked, setLiked] = useState(false);
  const [product, setProduct] = useState(null);
  const [variants, setVariants] = useState([]);
  const [selectedVariant, setSelectedVariant] = useState(null);
  const [showOptions, setShowOptions] = useState(false);
  const [selectedTags, setSelectedTags] = useState({
    Size: "",
    Color: "",
    Material: ""
  });
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [quantity, setQuantity] = useState(1);
  const [currentImageIndex, setCurrentImageIndex] = useState(0);

  // useEffect hook
  useEffect(() => {
    const fetchProduct = async () => {
      try {
        const [productData, variantsData] = await Promise.all([
          productService.getProductDetail(productId),
          cartService.getProductVariants(productId)
        ]);
        
        setProduct(productData);
        const variantArray = variantsData?.productVariants || [];
        setVariants(variantArray);
        if (variantArray.length > 0) {
          setSelectedVariant(variantArray[0]);
          setSelectedTags({
            Size: variantArray[0].optionValue1,
            Color: variantArray[0].optionValue2,
            Material: variantArray[0].optionValue3
          });
        }

        const userId = localStorage.getItem('userId');
        if (userId) {
          const wishlistData = await productService.getWishlistByUser(userId);
          setLiked(wishlistData.some(item => item.productId === productId));
        }
      } catch (err) {
        console.error('Failed to fetch product:', err);
        setError(err.message || 'Failed to load product details');
      } finally {
        setLoading(false);
      }
    };

    fetchProduct();
  }, [productId]);

  // Computed values
  const filteredReviews = product?.reviews ? product.reviews.filter((review) => {
    const matchRating = selectedFilter === "All" || review.rating === Number(selectedFilter);
    const matchMedia = !showWithMedia || review.images?.length > 0;
    return matchRating && matchMedia;
  }) : [];

  // Event handlers and other functions
  const getCategories = () => {
    if (!variants || variants.length === 0) return [];

    const categories = [];
    const optionMap = new Map();

    // Collect all unique options and their values from variants
    variants.forEach(variant => {
      for (let i = 1; i <= 5; i++) {
        const optionName = variant[`option${i}`];
        const optionValue = variant[`optionValue${i}`];
        
        if (optionName && optionValue) {
          if (!optionMap.has(optionName)) {
            optionMap.set(optionName, new Set());
          }
          optionMap.get(optionName).add(optionValue);
        }
      }
    });

    // Convert to categories format
    optionMap.forEach((values, name) => {
      // Filter variants based on currently selected options
      const compatibleVariants = variants.filter(variant => {
        return Object.entries(selectedTags).every(([tagName, tagValue]) => {
          if (!tagValue || tagName === name) return true;
          
          // Find which option number this tag corresponds to
          for (let i = 1; i <= 5; i++) {
            if (variant[`option${i}`] === tagName) {
              return variant[`optionValue${i}`] === tagValue;
            }
          }
          return true;
        });
      });

      // Get available values for this option based on compatible variants
      const availableValues = new Set();
      compatibleVariants.forEach(variant => {
        for (let i = 1; i <= 5; i++) {
          if (variant[`option${i}`] === name && variant.quantity > 0) {
            availableValues.add(variant[`optionValue${i}`]);
          }
        }
      });

      categories.push({
        name,
        tags: Array.from(values),
        availableTags: Array.from(availableValues)
      });
    });

    return categories;
  };

  const handleOptionChange = (optionName, value) => {
    const newSelectedTags = {
      ...selectedTags,
      [optionName]: value
    };

    // Reset all options that come after the current one in the variant's order
    const currentOptionIndex = variants.find(v => {
      for (let i = 1; i <= 5; i++) {
        if (v[`option${i}`] === optionName) return true;
      }
      return false;
    });

    if (currentOptionIndex) {
      variants.forEach(variant => {
        for (let i = 1; i <= 5; i++) {
          const optName = variant[`option${i}`];
          if (optName && optName !== optionName) {
            let shouldReset = true;
            // Check if this option comes after the current one in any variant
            for (let j = 1; j <= 5; j++) {
              if (variant[`option${j}`] === optionName) {
                shouldReset = false;
                break;
              }
              if (variant[`option${j}`] === optName) {
                shouldReset = true;
                break;
              }
            }
            if (shouldReset) {
              newSelectedTags[optName] = "";
            }
          }
        }
      });
    }

    setSelectedTags(newSelectedTags);
  };

  const handleOptionSave = () => {
    const matchingVariant = variants.find(variant => {
      return Object.entries(selectedTags).every(([tagName, tagValue]) => {
        if (!tagValue) return true;
        
        // Find which option number this tag corresponds to
        for (let i = 1; i <= 5; i++) {
          if (variant[`option${i}`] === tagName) {
            return variant[`optionValue${i}`] === tagValue;
          }
        }
        return true;
      });
    });

    if (matchingVariant) {
      setSelectedVariant(matchingVariant);
      setShowOptions(false);
      showNotification("Options updated successfully", "success");
    }
  };

  const handleAddToCart = async () => {
    const userId = localStorage.getItem('userId');
    if (!userId) {
      showNotification("Please login to add items to cart!", "error");
      navigate('/login');
      return;
    }

    if (!selectedVariant) {
      showNotification("Please select product options!", "error");
      return;
    }

    if (quantity > selectedVariant.quantity) {
      showNotification("Sorry, we don't have enough stock for that quantity.", "error");
      return;
    }

    try {
      await cartService.addCart({
        productId: product.productId,
        variantId: selectedVariant.variantId,
        quantity: quantity
      });
      showNotification("Product added to cart successfully!", "success");
      navigate("/cart");
    } catch (err) {
      console.error('Failed to add to cart:', err);
      showNotification(err.message || "Failed to add to cart. Please try again.", "error");
    }
  };

  const handleLike = async () => {
    const userId = localStorage.getItem('userId');
    if (!userId) {
      showNotification("Please login to add items to wishlist", "error");
      navigate('/login');
      return;
    }

    try {
      if (!liked) {
        await productService.createWishlist(userId, product.productId);
        setLiked(true);
        showNotification("Product added to Wishlist!", "success");
      } else {
        await productService.removeWishlist(userId, product.productId);
        setLiked(false);
        showNotification("Product removed from Wishlist!", "success");
      }
    } catch (err) {
      console.error('Failed to update wishlist:', err);
      showNotification(err.message || "Failed to update wishlist. Please try again.", "error");
    }
  };

  // Loading and error states
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  if (!product) return <p>Product not found</p>;

  return (
    <Main>
      <div className="product-container">
        {/* Breadcrumb Navigation */}
        <div className="breadcrumb">
          <span onClick={() => navigate('/')}>Home</span>
          <span className="separator">/</span>
          <span onClick={() => navigate('/products')}>Products</span>
          <span className="separator">/</span>
          <span className="current">{product.productName}</span>
        </div>

        {/* Product Main Section */}
        <div className="product-detail">
          {/* Left Column - Images */}
          <div className="product-detail__image">
            {selectedVariant ? (
              <>
                <div className="main-image">
                  <img
                    src={selectedVariant.urlImage}
                    alt={`${product.productName} - ${selectedVariant.optionValue2}`}
                  />
                </div>
              </>
            ) : (
              <div className="no-image-message">
                <span className="no-image-icon">🖼️</span>
                <p>Please select product options to view image</p>
              </div>
            )}
          </div>

          {/* Right Column - Product Info */}
          <div className="product-detail__info">
            <div className="info-header">
              <h1 className="product-name">{product.productName}</h1>
              <div className="price-container">
                <span className="price-label">Price:</span>
                <span className="price-value">
                  {selectedVariant ? `${selectedVariant.price.toLocaleString()} VND` : 'Select options'}
                </span>
              </div>
            </div>

            <div className="info-section description-section">
              <h2 className="section-title">Description</h2>
              <p className="description-text">{product.description}</p>
            </div>

            <div className="info-section options-section">
              <h2 className="section-title" style={{ 
                textAlign: "center", 
                marginBottom: "20px",
                fontSize: "20px",
                color: "#333"
              }}>Product Options</h2>
              {variants && variants.length > 0 ? (
                <>
                  <button 
                    className="select-options-button"
                    onClick={() => setShowOptions(true)}
                    style={{
                      border: selectedVariant 
                        ? "1px solid #ff385c"
                        : "1px solid #d9d9d9",
                      padding: "4px 12px",
                      borderRadius: "16px",
                      cursor: "pointer",
                      transition: "all 0.3s",
                      backgroundColor: selectedVariant 
                        ? "#ff385c"
                        : "transparent",
                      color: selectedVariant ? "white" : "#333",
                      marginBottom: "4px",
                      fontSize: "14px",
                      display: "flex",
                      alignItems: "center",
                      gap: "8px",
                      width: "fit-content"
                    }}
                  >
                    <span className="button-icon">⚙️</span>
                    <span className="button-text">
                      {selectedVariant ? 'Change Options' : 'Select Options'}
                    </span>
                  </button>

                  {showOptions && (
                    <div className="options-overlay">
                      <OptionCart
                        categories={getCategories()}
                        selectedTags={selectedTags}
                        onChange={handleOptionChange}
                        onSave={handleOptionSave}
                        onCancel={() => setShowOptions(false)}
                        currentVariants={variants}
                      />
                    </div>
                  )}
                  
                  {selectedVariant && (
                    <div className="selected-options" style={{
                      marginTop: "1rem",
                      padding: "12px",
                      backgroundColor: "#f5f5f5",
                      borderRadius: "8px"
                    }}>
                      {selectedVariant.optionValue1 && (
                        <div className="option-item" style={{
                          display: "flex",
                          justifyContent: "space-between",
                          marginBottom: "8px",
                          padding: "4px 0"
                        }}>
                          <span className="option-label" style={{ 
                            color: "#333", 
                            fontWeight: "500" 
                          }}>Size:</span>
                          <span className="option-value" style={{ 
                            color: "#ff385c", 
                            fontWeight: "600" 
                          }}>{selectedVariant.optionValue1}</span>
                        </div>
                      )}
                      {selectedVariant.optionValue2 && (
                        <div className="option-item" style={{
                          display: "flex",
                          justifyContent: "space-between",
                          marginBottom: "8px",
                          padding: "4px 0"
                        }}>
                          <span className="option-label" style={{ 
                            color: "#333", 
                            fontWeight: "500" 
                          }}>Color:</span>
                          <span className="option-value" style={{ 
                            color: "#ff385c", 
                            fontWeight: "600" 
                          }}>{selectedVariant.optionValue2}</span>
                        </div>
                      )}
                      {selectedVariant.optionValue3 && (
                        <div className="option-item" style={{
                          display: "flex",
                          justifyContent: "space-between",
                          marginBottom: "8px",
                          padding: "4px 0"
                        }}>
                          <span className="option-label" style={{ 
                            color: "#333", 
                            fontWeight: "500" 
                          }}>Material:</span>
                          <span className="option-value" style={{ 
                            color: "#ff385c", 
                            fontWeight: "600" 
                          }}>{selectedVariant.optionValue3}</span>
                        </div>
                      )}
                      <div style={{
                        display: "flex",
                        justifyContent: "space-between",
                        marginTop: "12px",
                        paddingTop: "12px",
                        borderTop: "1px solid #ddd"
                      }}>
                        <span style={{ color: "#333", fontWeight: "500" }}>Available:</span>
                        <span style={{ color: "#333" }}>
                          {selectedVariant.quantity} items
                        </span>
                      </div>
                    </div>
                  )}
                </>
              ) : (
                <div className="no-options-message" style={{
                  padding: "1.5rem",
                  background: "#f8f8f8",
                  borderRadius: "8px",
                  textAlign: "center",
                  margin: "1rem 0",
                  border: "1px dashed #ddd"
                }}>
                  <span className="no-options-icon" style={{
                    fontSize: "2rem",
                    display: "block",
                    marginBottom: "0.5rem"
                  }}>ℹ️</span>
                  <p style={{
                    margin: 0,
                    color: "#666",
                    fontSize: "0.95rem"
                  }}>This product does not have any variants or options available.</p>
                </div>
              )}
              
              <div className="quantity-section" style={{
                marginTop: "1.5rem"
              }}>
                <div className="quantity-container" style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "1rem"
                }}>
                  <label className="quantity-label" style={{
                    display: "flex",
                    alignItems: "center",
                    gap: "0.5rem"
                  }}>
                    <span className="label-text" style={{
                      color: "#333",
                      fontWeight: "500"
                    }}>Quantity:</span>
                    <div className="quantity-input-group" style={{
                      display: "flex",
                      alignItems: "center",
                      border: "1px solid #d9d9d9",
                      borderRadius: "16px",
                      overflow: "hidden"
                    }}>
                      <button 
                        className="quantity-btn"
                        onClick={() => quantity > 1 && setQuantity(quantity - 1)}
                        disabled={quantity <= 1}
                        style={{
                          padding: "4px 12px",
                          border: "none",
                          background: "transparent",
                          cursor: "pointer",
                          color: "#ff385c"
                        }}
                      >
                        -
                      </button>
                      <input
                        type="number"
                        min="1"
                        max={selectedVariant?.quantity || 1}
                        value={quantity}
                        onChange={(e) => setQuantity(Number(e.target.value))}
                        className="quantity-input"
                        style={{
                          width: "50px",
                          border: "none",
                          textAlign: "center",
                          fontSize: "14px"
                        }}
                      />
                      <button 
                        className="quantity-btn"
                        onClick={() => quantity < (selectedVariant?.quantity || 1) && setQuantity(quantity + 1)}
                        disabled={quantity >= (selectedVariant?.quantity || 1)}
                        style={{
                          padding: "4px 12px",
                          border: "none",
                          background: "transparent",
                          cursor: "pointer",
                          color: "#ff385c"
                        }}
                      >
                        +
                      </button>
                    </div>
                  </label>
                </div>
              </div>
            </div>

            <div className="info-section action-section" style={{
              marginTop: "1.5rem",
              display: "flex",
              gap: "1rem"
            }}>
              <button
                className="add-to-cart-button"
                onClick={handleAddToCart}
                disabled={!selectedVariant || selectedVariant.quantity === 0}
                style={{
                  flex: 1,
                  padding: "12px 24px",
                  borderRadius: "20px",
                  border: "none",
                  background: selectedVariant ? "#ff385c" : "#f5f5f5",
                  color: selectedVariant ? "white" : "#999",
                  cursor: selectedVariant ? "pointer" : "not-allowed",
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  gap: "8px",
                  fontSize: "16px",
                  fontWeight: "500",
                  transition: "all 0.3s"
                }}
              >
                <span className="button-icon">🛒</span>
                <span className="button-text">
                  {!selectedVariant ? "Select Options" : 
                   selectedVariant.quantity === 0 ? "Out of Stock" : 
                   "Add to Cart"}
                </span>
              </button>

              <div className="action-buttons" style={{
                display: "flex",
                gap: "0.5rem"
              }}>
                <button
                  className="like-button"
                  onClick={handleLike}
                  title={liked ? "Remove from Wishlist" : "Add to Wishlist"}
                  style={{
                    padding: "12px",
                    borderRadius: "50%",
                    border: "1px solid #d9d9d9",
                    background: liked ? "#ff385c" : "transparent",
                    color: liked ? "white" : "#666",
                    cursor: "pointer",
                    transition: "all 0.3s"
                  }}
                >
                  <span className="button-icon">{liked ? '❤️' : '🤍'}</span>
                </button>
                <button
                  className="share-button"
                  onClick={() => showNotification("Sharing feature coming soon!", "info")}
                  title="Share Product"
                  style={{
                    padding: "12px",
                    borderRadius: "50%",
                    border: "1px solid #d9d9d9",
                    background: "transparent",
                    color: "#666",
                    cursor: "pointer",
                    transition: "all 0.3s"
                  }}
                >
                  <span className="button-icon">🔗</span>
                </button>
              </div>
            </div>
          </div>
        </div>

        {/* Product Details Tabs */}
        <div className="product-tabs">
          <div className="tab-headers">
            <button 
              className={`tab-button ${activeTab === 'details' ? 'active' : ''}`}
              onClick={() => setActiveTab('details')}
            >
              Product Details
            </button>
            <button 
              className={`tab-button ${activeTab === 'reviews' ? 'active' : ''}`}
              onClick={() => setActiveTab('reviews')}
            >
              Reviews ({filteredReviews.length})
            </button>
            <button 
              className={`tab-button ${activeTab === 'shipping' ? 'active' : ''}`}
              onClick={() => setActiveTab('shipping')}
            >
              Shipping Info
            </button>
          </div>

          <div className="tab-content">
            {activeTab === 'details' && (
              <div className="details-tab">
                <div className="product-features">
                  <h3>Product Features</h3>
                  <ul>
                    <li>High-quality materials</li>
                    <li>Comfortable fit</li>
                    <li>Easy to maintain</li>
                    <li>Stylish design</li>
                  </ul>
                </div>
                <div className="product-specs">
                  <h3>Specifications</h3>
                  <table>
                    <tbody>
                      <tr>
                        <td>Brand</td>
                        <td>{product.brand}</td>
                      </tr>
                      <tr>
                        <td>Material</td>
                        <td>{selectedVariant?.optionValue3 || 'Various options available'}</td>
                      </tr>
                      <tr>
                        <td>Available Colors</td>
                        <td>{Array.from(new Set(variants.map(v => v.optionValue2))).join(', ')}</td>
                      </tr>
                    </tbody>
                  </table>
                </div>
              </div>
            )}

            {activeTab === 'reviews' && (
              <div className="reviews-tab">
                <div className="reviews-overview">
                  <div className="review-header">
                    <h2>Reviews</h2>
                    <div className="rating-summary">
                      <h3>Average Rating</h3>
                      {filteredReviews.length > 0 ? (
                        <div className="rating-stats">
                          <div className="rating-number">
                            {(
                              filteredReviews.reduce(
                                (total, review) => total + review.rating,
                                0
                              ) / filteredReviews.length
                            ).toFixed(1)}
                            <span className="rating-max">★/5.0★</span>
                          </div>
                          <div className="review-count">
                            ({filteredReviews.length} reviews)
                          </div>
                          <div className="star-display">
                            {Array.from({ length: 5 }).map((_, index) => {
                              const rating =
                                filteredReviews.reduce(
                                  (total, review) => total + review.rating,
                                  0
                                ) / filteredReviews.length;
                              return (
                                <span key={index} className="star">
                                  {index < Math.floor(rating) ? "★" : "☆"}
                                </span>
                              );
                            })}
                          </div>
                        </div>
                      ) : (
                        <div className="no-ratings">No ratings yet</div>
                      )}
                    </div>
                  </div>

                  <div className="filters">
                    <div className="filter-group">
                      <label className="filter-rating">
                        <span className="filter-label">Filter by Rating:</span>
                        <select
                          className="rating-select"
                          value={selectedFilter}
                          onChange={(e) => setSelectedFilter(e.target.value)}
                        >
                          <option value="All">All Ratings</option>
                          {[5, 4, 3, 2, 1].map((star) => {
                            const count = filteredReviews.filter(
                              (review) => review.rating === star
                            ).length;
                            return (
                              <option key={star} value={star}>
                                {star} ★ ({count})
                              </option>
                            );
                          })}
                        </select>
                      </label>

                      <label className="show-with-media-container">
                        <input
                          type="checkbox"
                          className="show-with-media"
                          checked={showWithMedia}
                          onChange={(e) => setShowWithMedia(e.target.checked)}
                        />
                        <span>Show Reviews with Media</span>
                      </label>
                    </div>
                  </div>

                  <div className="reviews-list">
                    {filteredReviews.length > 0 ? (
                      filteredReviews.map((review, index) => (
                        <div key={index} className="review-card">
                          <div className="review-header">
                            <div className="reviewer-info">
                              <strong>{review.username}</strong>
                              <div className="review-rating">
                                {Array.from({ length: 5 }).map((_, i) => (
                                  <span key={i} className={`star ${i < review.rating ? 'filled' : ''}`}>
                                    {i < review.rating ? '★' : '☆'}
                                  </span>
                                ))}
                              </div>
                            </div>
                          </div>
                          <div className="review-content">
                            <p>{review.comment}</p>
                            {review.images?.length > 0 && (
                              <div className="review-images">
                                {review.images.map((img, imgIndex) => (
                                  <img 
                                    key={imgIndex} 
                                    src={img} 
                                    alt="Review Media"
                                    onClick={() => {/* TODO: Add image preview */}}
                                  />
                                ))}
                              </div>
                            )}
                          </div>
                        </div>
                      ))
                    ) : (
                      <div className="no-reviews">
                        <p>No reviews match your filters.</p>
                      </div>
                    )}
                  </div>
                </div>
              </div>
            )}

            {activeTab === 'shipping' && (
              <div className="shipping-tab">
                <h3>Shipping Information</h3>
                <div className="shipping-info">
                  <div className="shipping-item">
                    <span className="icon">🚚</span>
                    <h4>Free Shipping</h4>
                    <p>On orders over 1,000,000 VND</p>
                  </div>
                  <div className="shipping-item">
                    <span className="icon">⏱️</span>
                    <h4>Delivery Time</h4>
                    <p>2-4 business days</p>
                  </div>
                  <div className="shipping-item">
                    <span className="icon">↩️</span>
                    <h4>Returns</h4>
                    <p>30-day easy returns</p>
                  </div>
                </div>
              </div>
            )}
          </div>
        </div>

        {/* Related Products Section */}
        <div className="related-products">
          <h2>You May Also Like</h2>
          <div className="related-products-grid">
            {/* Add related products here */}
          </div>
        </div>
      </div>
    </Main>
  );
}
Editor is loading...
Leave a Comment