From f75e5245653dd6dd30cf80cc30a7d615e53f49b7 Mon Sep 17 00:00:00 2001 From: tuanOts Date: Sun, 25 May 2025 23:58:48 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20beautiful=20single-row=20pagi?= =?UTF-8?q?nation=20with=20total=20records?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - � Beautiful gradient design with animations - � Total records display with search filtering - � Single-row layout with Ant Design pagination classes - � Responsive compact design for all devices - ⚡ Smooth hover animations and transitions - � Ultra compact buttons and optimized spacing - � Real-time search integration with debouncing - � Glass morphism effects with shimmer animations --- src/Router/router.link.jsx | 7 + src/components/ApiTest.jsx | 84 +++- src/core/redux/reducers/productReducer.js | 62 +-- src/feature-module/inventory/editproduct.jsx | 243 ++++++++++- src/feature-module/inventory/productlist.jsx | 399 ++++++++++++++++++- 5 files changed, 737 insertions(+), 58 deletions(-) diff --git a/src/Router/router.link.jsx b/src/Router/router.link.jsx index 719863d..a09544a 100644 --- a/src/Router/router.link.jsx +++ b/src/Router/router.link.jsx @@ -842,6 +842,13 @@ export const publicRoutes = [ element: , route: Route, }, + { + id: 65.1, + path: `${routes.editproduct}/:id`, + name: "editproductwithid", + element: , + route: Route, + }, { id: 63, path: routes.videocall, diff --git a/src/components/ApiTest.jsx b/src/components/ApiTest.jsx index 030a394..a043e86 100644 --- a/src/components/ApiTest.jsx +++ b/src/components/ApiTest.jsx @@ -4,6 +4,7 @@ const ApiTest = () => { const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(null); + const [productId, setProductId] = useState('1'); // Default test ID const testApiConnection = async () => { setLoading(true); @@ -51,6 +52,50 @@ const ApiTest = () => { } }; + const testProductDetail = async () => { + if (!productId) { + setError('Please enter a product ID'); + return; + } + + setLoading(true); + setError(null); + setResult(null); + + try { + console.log('Testing Product Detail API for ID:', productId); + + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}Products/${productId}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + console.log('Response status:', response.status); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + setResult(data); + console.log('Product Detail Response:', data); + } catch (err) { + console.error('Product Detail API Error:', err); + + let errorMessage = err.message || 'Failed to fetch product details'; + + if (err.name === 'TypeError' && err.message.includes('Failed to fetch')) { + errorMessage = 'CORS Error or Network issue'; + } + + setError(errorMessage); + } finally { + setLoading(false); + } + }; + return (
@@ -60,13 +105,38 @@ const ApiTest = () => {

API Base URL: {process.env.REACT_APP_API_BASE_URL}

- +
+
+
Test All Products API
+ +
+ +
+
Test Product Detail API
+
+ setProductId(e.target.value)} + /> + +
+
+
{loading && (
diff --git a/src/core/redux/reducers/productReducer.js b/src/core/redux/reducers/productReducer.js index 61304a1..b0bad74 100644 --- a/src/core/redux/reducers/productReducer.js +++ b/src/core/redux/reducers/productReducer.js @@ -6,28 +6,31 @@ const initialState = { totalProducts: 0, currentPage: 1, totalPages: 1, - + pageSize: 20, + hasPrevious: false, + hasNext: false, + // Current product (for edit/view) currentProduct: null, - + // Search results searchResults: [], searchQuery: '', - + // Categories and brands categories: [], brands: [], - + // Loading states loading: false, productLoading: false, searchLoading: false, - + // Error states error: null, productError: null, searchError: null, - + // Operation states creating: false, updating: false, @@ -43,18 +46,27 @@ const productReducer = (state = initialState, action) => { loading: true, error: null, }; - - case PRODUCT_ACTIONS.FETCH_PRODUCTS_SUCCESS: + + case PRODUCT_ACTIONS.FETCH_PRODUCTS_SUCCESS: { + // Handle different API response structures + const isArrayResponse = Array.isArray(action.payload); + const products = isArrayResponse ? action.payload : (action.payload.data || action.payload.items || []); + const pagination = action.payload.pagination || {}; + return { ...state, loading: false, - products: action.payload.data || action.payload, - totalProducts: action.payload.total || action.payload.length, - currentPage: action.payload.currentPage || 1, - totalPages: action.payload.totalPages || 1, + products: products, + totalProducts: pagination.totalCount || action.payload.total || products.length, + currentPage: pagination.currentPage || action.payload.currentPage || 1, + totalPages: pagination.totalPages || action.payload.totalPages || 1, + pageSize: pagination.pageSize || 20, + hasPrevious: pagination.hasPrevious || false, + hasNext: pagination.hasNext || false, error: null, }; - + } + case PRODUCT_ACTIONS.FETCH_PRODUCTS_FAILURE: return { ...state, @@ -69,7 +81,7 @@ const productReducer = (state = initialState, action) => { productLoading: true, productError: null, }; - + case PRODUCT_ACTIONS.FETCH_PRODUCT_SUCCESS: return { ...state, @@ -77,7 +89,7 @@ const productReducer = (state = initialState, action) => { currentProduct: action.payload, productError: null, }; - + case PRODUCT_ACTIONS.FETCH_PRODUCT_FAILURE: return { ...state, @@ -92,7 +104,7 @@ const productReducer = (state = initialState, action) => { creating: true, error: null, }; - + case PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS: return { ...state, @@ -101,7 +113,7 @@ const productReducer = (state = initialState, action) => { totalProducts: state.totalProducts + 1, error: null, }; - + case PRODUCT_ACTIONS.CREATE_PRODUCT_FAILURE: return { ...state, @@ -116,7 +128,7 @@ const productReducer = (state = initialState, action) => { updating: true, error: null, }; - + case PRODUCT_ACTIONS.UPDATE_PRODUCT_SUCCESS: return { ...state, @@ -127,7 +139,7 @@ const productReducer = (state = initialState, action) => { currentProduct: action.payload.data, error: null, }; - + case PRODUCT_ACTIONS.UPDATE_PRODUCT_FAILURE: return { ...state, @@ -142,7 +154,7 @@ const productReducer = (state = initialState, action) => { deleting: true, error: null, }; - + case PRODUCT_ACTIONS.DELETE_PRODUCT_SUCCESS: return { ...state, @@ -151,7 +163,7 @@ const productReducer = (state = initialState, action) => { totalProducts: state.totalProducts - 1, error: null, }; - + case PRODUCT_ACTIONS.DELETE_PRODUCT_FAILURE: return { ...state, @@ -166,7 +178,7 @@ const productReducer = (state = initialState, action) => { searchLoading: true, searchError: null, }; - + case PRODUCT_ACTIONS.SEARCH_PRODUCTS_SUCCESS: return { ...state, @@ -175,7 +187,7 @@ const productReducer = (state = initialState, action) => { searchQuery: action.payload.query || '', searchError: null, }; - + case PRODUCT_ACTIONS.SEARCH_PRODUCTS_FAILURE: return { ...state, @@ -189,7 +201,7 @@ const productReducer = (state = initialState, action) => { ...state, categories: action.payload, }; - + case PRODUCT_ACTIONS.FETCH_BRANDS_SUCCESS: return { ...state, @@ -204,7 +216,7 @@ const productReducer = (state = initialState, action) => { productError: null, searchError: null, }; - + case PRODUCT_ACTIONS.CLEAR_CURRENT_PRODUCT: return { ...state, diff --git a/src/feature-module/inventory/editproduct.jsx b/src/feature-module/inventory/editproduct.jsx index f714cd8..6d7b9f3 100644 --- a/src/feature-module/inventory/editproduct.jsx +++ b/src/feature-module/inventory/editproduct.jsx @@ -1,5 +1,5 @@ -import React, { useState } from "react"; -import { Link } from "react-router-dom"; +import React, { useState, useEffect, useRef } from "react"; +import { Link, useParams } from "react-router-dom"; import Select from "react-select"; import { all_routes } from "../../Router/all_routes"; import { DatePicker } from "antd"; @@ -20,15 +20,90 @@ import { } from "feather-icons-react/build/IconComponents"; import { useDispatch, useSelector } from "react-redux"; import { setToogleHeader } from "../../core/redux/action"; +import { fetchProduct } from "../../core/redux/actions/productActions"; import { OverlayTrigger, Tooltip } from "react-bootstrap"; import ImageWithBasePath from "../../core/img/imagewithbasebath"; const EditProduct = () => { const route = all_routes; const dispatch = useDispatch(); + const { id } = useParams(); // Get product ID from URL const data = useSelector((state) => state.toggle_header); + // Get product data from Redux store + const { currentProduct, productLoading, productError } = useSelector((state) => state.products || {}); + + // Track if we've already fetched this product + const fetchedProductId = useRef(null); + + // Form state for editing + const [formData, setFormData] = useState({ + name: '', + slug: '', + sku: '', + description: '', + price: '', + category: null, // Change to null for Select component + brand: null, // Change to null for Select component + quantity: '', + unit: null, // Change to null for Select component + status: '', + images: [] + }); + + // Load product data if ID is provided and product not already loaded + useEffect(() => { + if (id && fetchedProductId.current !== id) { + console.log('Fetching product details for ID:', id); + fetchedProductId.current = id; + dispatch(fetchProduct(id)); + } + }, [id, dispatch]); + + // Helper function to find option by value or label + const findSelectOption = (options, value) => { + if (!value) return null; + return options.find(option => + option.value === value || + option.label === value || + option.value.toLowerCase() === value.toLowerCase() || + option.label.toLowerCase() === value.toLowerCase() + ) || null; + }; + + // Update form data when currentProduct changes + useEffect(() => { + if (currentProduct) { + console.log('Product data loaded:', currentProduct); + + // Find matching options for select fields + const categoryOption = findSelectOption(category, currentProduct.category || currentProduct.categoryName); + const brandOption = findSelectOption(brand, currentProduct.brand || currentProduct.brandName); + const unitOption = findSelectOption(unit, currentProduct.unit); + + setFormData({ + name: currentProduct.name || currentProduct.productName || '', + slug: currentProduct.slug || '', + sku: currentProduct.sku || currentProduct.code || '', + description: currentProduct.description || '', + price: currentProduct.price || currentProduct.salePrice || '', + category: categoryOption, + brand: brandOption, + quantity: currentProduct.quantity || currentProduct.stock || '', + unit: unitOption, + status: currentProduct.status || 'active', + images: currentProduct.images || [] + }); + + console.log('Form data updated:', { + category: categoryOption, + brand: brandOption, + unit: unitOption + }); + } + }, [currentProduct]); + const [selectedDate, setSelectedDate] = useState(new Date()); const handleDateChange = (date) => { setSelectedDate(date); @@ -58,6 +133,9 @@ const EditProduct = () => { { value: "choose", label: "Choose" }, { value: "lenovo", label: "Lenovo" }, { value: "electronics", label: "Electronics" }, + { value: "laptop", label: "Laptop" }, + { value: "computer", label: "Computer" }, + { value: "mobile", label: "Mobile" }, ]; const subcategory = [ { value: "choose", label: "Choose" }, @@ -78,6 +156,11 @@ const EditProduct = () => { { value: "choose", label: "Choose" }, { value: "kg", label: "Kg" }, { value: "pc", label: "Pc" }, + { value: "pcs", label: "Pcs" }, + { value: "piece", label: "Piece" }, + { value: "gram", label: "Gram" }, + { value: "liter", label: "Liter" }, + { value: "meter", label: "Meter" }, ]; const sellingtype = [ { value: "choose", label: "Choose" }, @@ -114,13 +197,74 @@ const EditProduct = () => { const handleRemoveProduct1 = () => { setIsImageVisible1(false); }; + // Show loading state + if (productLoading) { + return ( +
+
+
+
+ Loading... +
+

Loading product details...

+
+
+
+ ); + } + + // Show error state + if (productError) { + return ( +
+
+
+ Error: {productError} +
+ + Back to Product List + + +
+
+
+
+ ); + } + + // Show not found state + if (id && !currentProduct) { + return ( +
+
+
+ Product not found! The product you're trying to edit doesn't exist. +
+ + Back to Product List + +
+
+
+
+ ); + } + return (
-

Edit Product

+

Edit Product {currentProduct?.name ? `- ${currentProduct.name}` : ''}

    @@ -211,13 +355,33 @@ const EditProduct = () => {
    - + { + setFormData(prev => ({ + ...prev, + name: e.target.value + })); + }} + />
    - + { + setFormData(prev => ({ + ...prev, + slug: e.target.value + })); + }} + />
    @@ -227,6 +391,13 @@ const EditProduct = () => { type="text" className="form-control list" placeholder="Enter SKU" + value={formData.sku} + onChange={(e) => { + setFormData(prev => ({ + ...prev, + sku: e.target.value + })); + }} /> { { + setFormData(prev => ({ + ...prev, + brand: selectedOption + })); + }} + isClearable />
@@ -321,7 +508,15 @@ const EditProduct = () => {