D. Product Management

Tutorial Product Management

1. Backend Product Model (src/models/Product.js)

const db = require('../config/database');

const Product = {
  create: async (productData) => {
    const { name, description, price, stock, image_url, category_id } = productData;
    const [result] = await db.execute(
      'INSERT INTO products (name, description, price, stock, image_url, category_id) VALUES (?, ?, ?, ?, ?, ?)',
      [name, description, price, stock, image_url, category_id]
    );
    return result;
  },

  findAll: async () => {
    const [rows] = await db.execute('SELECT * FROM products');
    return rows;
  },

  findById: async (id) => {
    const [rows] = await db.execute('SELECT * FROM products WHERE id = ?', [id]);
    return rows[0];
  },

  update: async (id, productData) => {
    const { name, description, price, stock, image_url, category_id } = productData;
    const [result] = await db.execute(
      'UPDATE products SET name = ?, description = ?, price = ?, stock = ?, image_url = ?, category_id = ? WHERE id = ?',
      [name, description, price, stock, image_url, category_id, id]
    );
    return result;
  },

  delete: async (id) => {
    const [result] = await db.execute('DELETE FROM products WHERE id = ?', [id]);
    return result;
  },

  // Methods tambahan akan diimplementasikan nanti
  /*
  findByCategory: async (categoryId) => {...},
  updateStock: async (id, quantity) => {...},
  searchProducts: async (keyword) => {...},
  */
};

module.exports = Product;

2. Backend Product Controller (src/controllers/productController.js)

const Product = require('../models/Product');
const multer = require('multer');
const path = require('path');

// Upload configuration akan diimplementasikan nanti
/*
const storage = multer.diskStorage({...});
const upload = multer({...});
*/

const createProduct = async (req, res) => {
  try {
    const result = await Product.create(req.body);
    res.status(201).json({ 
      message: 'Product created successfully',
      productId: result.insertId 
    });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const getAllProducts = async (req, res) => {
  try {
    const products = await Product.findAll();
    res.json(products);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const getProductById = async (req, res) => {
  try {
    const product = await Product.findById(req.params.id);
    if (!product) {
      return res.status(404).json({ message: 'Product not found' });
    }
    res.json(product);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const updateProduct = async (req, res) => {
  try {
    const result = await Product.update(req.params.id, req.body);
    if (result.affectedRows === 0) {
      return res.status(404).json({ message: 'Product not found' });
    }
    res.json({ message: 'Product updated successfully' });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const deleteProduct = async (req, res) => {
  try {
    const result = await Product.delete(req.params.id);
    if (result.affectedRows === 0) {
      return res.status(404).json({ message: 'Product not found' });
    }
    res.json({ message: 'Product deleted successfully' });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

module.exports = {
  createProduct,
  getAllProducts,
  getProductById,
  updateProduct,
  deleteProduct
};

3. Backend Product Routes (src/routes/product.routes.js)

const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
const { auth } = require('../middleware/auth');
const { checkRole } = require('../middleware/role');

router.get('/', productController.getAllProducts);
router.get('/:id', productController.getProductById);

// Protected routes
router.post('/', auth, checkRole(['admin', 'petugas']), productController.createProduct);
router.put('/:id', auth, checkRole(['admin', 'petugas']), productController.updateProduct);
router.delete('/:id', auth, checkRole(['admin']), productController.deleteProduct);

// Routes tambahan akan diimplementasikan nanti
/*
router.get('/category/:id', productController.getProductsByCategory);
router.post('/:id/image', auth, upload.single('image'), productController.uploadImage);
router.get('/search', productController.searchProducts);
*/

module.exports = router;

4. Frontend Product Service (src/services/product.service.js)

import api from './api';

const ProductService = {
  getAllProducts: async () => {
    const response = await api.get('/products');
    return response.data;
  },

  getProductById: async (id) => {
    const response = await api.get(`/products/${id}`);
    return response.data;
  },

  createProduct: async (productData) => {
    const response = await api.post('/products', productData);
    return response.data;
  },

  updateProduct: async (id, productData) => {
    const response = await api.put(`/products/${id}`, productData);
    return response.data;
  },

  deleteProduct: async (id) => {
    const response = await api.delete(`/products/${id}`);
    return response.data;
  },

  // Methods tambahan akan diimplementasikan nanti
  /*
  uploadImage: async (id, formData) => {...},
  searchProducts: async (keyword) => {...},
  getProductsByCategory: async (categoryId) => {...},
  */
};

export default ProductService;

5. Frontend Product Slice (src/store/slices/productSlice.js)

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  products: [],
  selectedProduct: null,
  loading: false,
  error: null,
};

const productSlice = createSlice({
  name: 'product',
  initialState,
  reducers: {
    setProducts: (state, action) => {
      state.products = action.payload;
    },
    setSelectedProduct: (state, action) => {
      state.selectedProduct = action.payload;
    },
    addProduct: (state, action) => {
      state.products.push(action.payload);
    },
    updateProduct: (state, action) => {
      const index = state.products.findIndex(p => p.id === action.payload.id);
      if (index !== -1) {
        state.products[index] = action.payload;
      }
    },
    removeProduct: (state, action) => {
      state.products = state.products.filter(p => p.id !== action.payload);
    },
    setLoading: (state, action) => {
      state.loading = action.payload;
    },
    setError: (state, action) => {
      state.error = action.payload;
    },
  },
});

export const {
  setProducts,
  setSelectedProduct,
  addProduct,
  updateProduct,
  removeProduct,
  setLoading,
  setError,
} = productSlice.actions;

export default productSlice.reducer;

6. Frontend Product List Component (src/components/products/ProductList.js)

import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import ProductService from '../../services/product.service';
import { setProducts, setLoading, setError } from '../../store/slices/productSlice';

const ProductList = () => {
  const dispatch = useDispatch();
  const { products, loading, error } = useSelector((state) => state.product);

  useEffect(() => {
    fetchProducts();
  }, []);

  const fetchProducts = async () => {
    dispatch(setLoading(true));
    try {
      const data = await ProductService.getAllProducts();
      dispatch(setProducts(data));
    } catch (err) {
      dispatch(setError(err.message));
    } finally {
      dispatch(setLoading(false));
    }
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  // Template akan diimplementasikan nanti
  return (
    <div>
      <h2>Products</h2>
      {/* Product grid/list akan diimplementasikan nanti */}
    </div>
  );
};

export default ProductList;

7. Frontend Product Form Component (src/components/products/ProductForm.js)

import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import ProductService from '../../services/product.service';
import { addProduct, updateProduct } from '../../store/slices/productSlice';

const ProductForm = ({ editProduct = null, onClose }) => {
  const dispatch = useDispatch();
  const [formData, setFormData] = useState({
    name: '',
    description: '',
    price: '',
    stock: '',
    category_id: '',
  });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (editProduct) {
      setFormData(editProduct);
    }
  }, [editProduct]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    try {
      if (editProduct) {
        const data = await ProductService.updateProduct(editProduct.id, formData);
        dispatch(updateProduct({ id: editProduct.id, ...formData }));
      } else {
        const data = await ProductService.createProduct(formData);
        dispatch(addProduct({ id: data.productId, ...formData }));
      }
      onClose();
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  // Template akan diimplementasikan nanti
  return (
    <div>
      <h3>{editProduct ? 'Edit Product' : 'Add New Product'}</h3>
      {/* Form akan diimplementasikan nanti */}
    </div>
  );
};

export default ProductForm;

8. Frontend Product Detail Component (src/components/products/ProductDetail.js)

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import ProductService from '../../services/product.service';

const ProductDetail = () => {
  const { id } = useParams();
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchProduct();
  }, [id]);

  const fetchProduct = async () => {
    try {
      const data = await ProductService.getProductById(id);
      setProduct(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

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

  // Template akan diimplementasikan nanti
  return (
    <div>
      <h2>Product Detail</h2>
      {/* Detail view akan diimplementasikan nanti */}
    </div>
  );
};

export default ProductDetail;

Catatan Implementasi:

  1. Templates yang perlu dibuat:

    • Product list view (grid/list)

    • Product form modal

    • Product detail page

    • Product search dan filter

    • Product image upload

  2. Features yang perlu ditambahkan:

    • Pagination

    • Search functionality

    • Category management

    • Image handling

    • Stock tracking

    • Price history

  3. Additional Features:

    • Product variants

    • Bulk import/export

    • Product analytics

    • Product reviews

    • Stock alerts

  4. Security Considerations:

    • Input validation

    • Image upload restrictions

    • Role-based access

    • Data sanitization

Langkah testing:

  1. Test CRUD operations

  2. Verify image uploads

  3. Test search functionality

  4. Verify role permissions

  5. Test form validations

  6. Check error handling

Last updated