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:
Templates yang perlu dibuat:
Product list view (grid/list)
Product form modal
Product detail page
Product search dan filter
Product image upload
Features yang perlu ditambahkan:
Pagination
Search functionality
Category management
Image handling
Stock tracking
Price history
Additional Features:
Product variants
Bulk import/export
Product analytics
Product reviews
Stock alerts
Security Considerations:
Input validation
Image upload restrictions
Role-based access
Data sanitization
Langkah testing:
Test CRUD operations
Verify image uploads
Test search functionality
Verify role permissions
Test form validations
Check error handling
Last updated