Setelah sebelumnya kita buat design schema database, kali jni kita akan implementasi CRUD produk dan kategori. Pembaca harus mengikuti tutorial ini sesuai part agar tidak terjadi error.
A. Menambahkan Model dan Relasi
Model Product dan Category
Buat model Product dan Category dengan perintah berikut:
php artisan make:model Product
php artisan make:model Category
Perintah ini akan menghasilkan dua file model (Product.php dan Category.php) dan file migrasi untuk masing-masing.
Relasi Many-to-Many di Model
Update model Product dan Category untuk mendefinisikan relasi.
Product.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $fillable = ['name', 'slug','description', 'price', 'sku', 'image', 'brand_id','stock'];
// Relasi Many-to-Many ke Category
public function categories()
{
return $this->belongsToMany(Category::class, 'product_category');
}
public function brand()
{
return $this->belongsTo(Brand::class);
}
}
Category.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
protected $fillable = ['name', 'slug'];
// Relasi Many-to-Many ke Product
public function products()
{
return $this->belongsToMany(Product::class, 'product_category');
}
}
B. Membuat Controller dan Views untuk CRUD
Membuat Controller
Gunakan perintah untuk membuat controller ProductController dan CategoryController:
php artisan make:controller ProductController --resource
php artisan make:controller CategoryController --resource
Controller-resource ini otomatis menyediakan method dasar untuk CRUD (index, create, store, edit, update, dan destroy).
Ubah ProductController menjadi sebagai berikut:
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use App\Models\Category;
use App\Models\Brand;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ProductController extends Controller
{
public function index()
{
$products = Product::with('categories')->get();
return view('admin.products.index', compact('products'));
}
public function create()
{
$categories = Category::all();
$brands = Brand::all();
return view('admin.products.create', compact('categories','brands'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric',
'sku' => 'required|string|unique:products',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
'brand_id' => 'nullable|exists:brands,id',
]);
$product = new Product($request->all());
$product->slug = Str::slug($request->name);
if ($request->hasFile('image')) {
$imagePath = $request->file('image')->store('images', 'public');
$product->image = $imagePath;
}
$product->save();
$product->categories()->sync($request->categories);
return redirect()->route('products.index')->with('success', 'Product created successfully');
}
public function edit(Product $product)
{
$categories = Category::all();
$brands = Brand::all();
return view('admin.products.edit', compact('product', 'categories', 'brands'));
}
public function update(Request $request, Product $product)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric',
'sku' => 'required|string|unique:products,sku,' . $product->id,
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
'brand_id' => 'nullable|exists:brands,id',
]);
$product->fill($request->all());
$product->slug = Str::slug($request->name);
if ($request->hasFile('image')) {
$imagePath = $request->file('image')->store('images', 'public');
$product->image = $imagePath;
}
$product->save();
$product->categories()->sync($request->categories);
return redirect()->route('products.index')->with('success', 'Product updated successfully');
}
public function destroy(Product $product)
{
$product->categories()->detach();
$product->delete();
return redirect()->route('products.index')->with('success', 'Product deleted successfully');
}
}
Dan ubah CategoryController menjadi sebagai berikut:
<?php
namespace App\Http\Controllers;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class CategoryController extends Controller
{
public function index()
{
$categories = Category::all();
return view('admin.categories.index', compact('categories'));
}
public function create()
{
return view('admin.categories.create');
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255|unique:categories',
]);
$category = new Category();
$category->name = $request->name;
$category->slug = Str::slug($request->name);
$category->save();
return redirect()->route('categories.index')->with('success', 'Category created successfully');
}
public function edit(Category $category)
{
return view('admin.categories.edit', compact('category'));
}
public function update(Request $request, Category $category)
{
$request->validate([
'name' => 'required|string|max:255|unique:categories,name,' . $category->id,
]);
$category->name = $request->name;
$category->slug = Str::slug($request->name);
$category->save();
return redirect()->route('categories.index')->with('success', 'Category updated successfully');
}
public function destroy(Category $category)
{
$category->products()->detach();
$category->delete();
return redirect()->route('categories.index')->with('success', 'Category deleted successfully');
}
}
Rute untuk Produk dan Kategori di Admin Panel
Buka file routes/web.php dan tambahkan rute untuk admin CRUD:
use App\Http\Controllers\CategoryController;
use App\Http\Controllers\ProductController;
Route::prefix('admin')->middleware('auth')->group(function () {
Route::resource('products', ProductController::class);
Route::resource('categories', CategoryController::class);
});
View untuk CRUD Produk dan Kategori
Buat struktur folder untuk view di resources/views/admin dengan subfolder products dan categories.
C. Membuat views CRUD di Admin Panel untuk Produk dan Kategori
1. Category CRUD
Buat empat file view untuk CRUD di resources/views/admin/categories:
- index.blade.php (Daftar Kategori)
- create.blade.php (Tambah Kategori)
- edit.blade.php (Edit Kategori)
Edit File: resources/views/admin/categories/index.blade.php
@extends('layouts.admin')
@section('content')
<h1>Category List</h1>
<a href="{{ route('categories.create') }}" class="btn btn-primary mb-3">Add New Category</a>
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Slug</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach ($categories as $category)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $category->name }}</td>
<td>{{ $category->slug }}</td>
<td>
<a href="{{ route('categories.edit', $category->id) }}" class="btn btn-secondary">Edit</a>
<form action="{{ route('categories.destroy', $category->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure?')">Delete</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
@endsection
Edit File: resources/views/admin/categories/create.blade.php
@extends('layouts.admin')
@section('content')
<h1>Add New Category</h1>
<form action="{{ route('categories.store') }}" method="POST">
@csrf
<div class="form-group mb-3">
<label for="name">Category Name:</label>
<input type="text" name="name" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Save Category</button>
<a href="{{ route('categories.index') }}" class="btn btn-secondary">Back to Categories</a>
</form>
@endsection
File: resources/views/admin/categories/edit.blade.php
@extends('layouts.admin')
@section('content')
<h1>Edit Category</h1>
<form action="{{ route('categories.update', $category->id) }}" method="POST">
@csrf
@method('PUT')
<div class="form-group mb-3">
<label for="name">Category Name:</label>
<input type="text" name="name" class="form-control" value="{{ $category->name }}" required>
</div>
<button type="submit" class="btn btn-primary">Update Category</button>
<a href="{{ route('categories.index') }}" class="btn btn-secondary">Back to Categories</a>
</form>
@endsection
2. Products views CRUD
Buat empat file view untuk CRUD di resources/views/admin/products:
- index.blade.php (Daftar Produk)
- create.blade.php (Tambah Produk)
- edit.blade.php (Edit Produk)
Edit File: resources/views/admin/products/index.blade.php
@extends('layouts.admin')
@section('content')
<h1>Product List</h1>
<a href="{{ route('products.create') }}" class="btn btn-primary">Add New Product</a>
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Categories</th>
<th>Image</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach ($products as $product)
<tr>
<td>{{ $product->name }}</td>
<td>${{ $product->price }}</td>
<td>{{ $product->categories->pluck('name')->join(', ') }}</td>
<td>
@if($product->image)
<img src="{{ asset('storage/' . $product->image) }}" width="50" alt="{{ $product->name }}">
@endif
</td>
<td>
<a href="{{ route('products.edit', $product->id) }}" class="btn btn-secondary">Edit</a>
<form action="{{ route('products.destroy', $product->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
@endsection
Edit File: resources/views/admin/products/create.blade.php
@extends('layouts.admin')
@section('content')
<h1>Add New Product</h1>
<form action="{{ route('products.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="form-group">
<label for="name">Product Name:</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="form-group">
<label for="name">Product SKU:</label>
<input type="text" name="sku" class="form-control" required>
</div>
<div class="form-group">
<label for="price">Price:</label>
<input type="number" name="price" class="form-control" required>
</div>
<div class="form-group">
<label for="categories">Brands:</label>
<select name="brand_id" id="brand_id" class="form-control">
<option value="">Select Brand</option>
@foreach($brands as $brand)
<option value="{{ $brand->id }}" {{ isset($product) && $product->brand_id == $brand->id ? 'selected' : '' }}>
{{ $brand->name }}
</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="categories">Categories:</label>
<select name="categories[]" class="form-control" multiple>
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea name="description" class="form-control"></textarea>
</div>
<div class="form-group">
<label for="image">Image:</label>
<input type="file" name="image" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Save Product</button>
</form>
@endsection
Edit File: resources/views/admin/products/edit.blade.php
@extends('layouts.admin')
@section('content')
<h1>Edit Product</h1>
<form action="{{ route('products.update', $product->id) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="form-group">
<label for="name">Product Name:</label>
<input type="text" name="name" class="form-control" value="{{ $product->name }}" required>
</div>
<div class="form-group">
<label for="name">Product Name:</label>
<input type="text" name="sku" class="form-control" value="{{ $product->sku }}" required>
</div>
<div class="form-group">
<label for="price">Price:</label>
<input type="number" name="price" class="form-control" value="{{ $product->price }}" required>
</div>
<div class="form-group">
<label for="categories">Brands:</label>
<select name="brand_id" id="brand_id" class="form-control">
<option value="">Select Brand</option>
@foreach($brands as $brand)
<option value="{{ $brand->id }}" {{ isset($product) && $product->brand_id == $brand->id ? 'selected' : '' }}>
{{ $brand->name }}
</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="categories">Categories:</label>
<select name="categories[]" class="form-control" multiple>
@foreach ($categories as $category)
<option value="{{ $category->id }}" {{ in_array($category->id, $product->categories->pluck('id')->toArray()) ? 'selected' : '' }}>
{{ $category->name }}
</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea name="description" class="form-control">{{ $product->description }}</textarea>
</div>
<div class="form-group">
<label for="image">Image:</label>
<input type="file" name="image" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Update Product</button>
</form>
@endsection
D. Admin Panel Design
Menambahkan Sidebar dan Layout
Buat file layouts/admin.blade.php untuk layout utama admin dengan sidebar dan navbar.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<link href="{{ asset('css/admin.css') }}" rel="stylesheet" type="text/css" >
<!-- Tambahkan ini di head atau di bagian bawah file layout Anda -->
</head>
<body>
<!-- Sidebar Toggle Button (visible on mobile) -->
<button id="sidebarToggle" class="btn">
<i class="fa fa-bars"></i>
</button>
<!-- Sidebar -->
<div id="sidebar">
<h4 class="text-center mt-3">Admin Panel</h4>
<nav class="nav flex-column mt-4">
<a class="nav-link {{ request()->is('dashboard') ? 'active' : '' }}" href="{{ route('admin.dashboard') }}">
<i class="fa fa-tachometer-alt"></i> Dashboard
</a>
<a class="nav-link {{ request()->is('brands*') ? 'active' : '' }}" href="{{ route('brands.index') }}">
<i class="fa fa-tags"></i> Brands
</a>
<a class="nav-link {{ request()->is('categories*') ? 'active' : '' }}" href="{{ route('categories.index') }}">
<i class="fa fa-th-list"></i> Categories
</a>
<a class="nav-link {{ request()->is('products*') ? 'active' : '' }}" href="{{ route('products.index') }}">
<i class="fa fa-box-open"></i> Products
</a>
<a class="nav-link" href="{{ route('logout') }}">
<i class="fa fa-sign-out-alt"></i> Logout
</a>
</nav>
</div>
<!-- Main Content -->
<div id="contents">
<div class="container-fluid">
@yield('content')
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Toggle sidebar visibility on mobile
document.getElementById('sidebarToggle').addEventListener('click', function() {
document.getElementById('sidebar').classList.toggle('active');
document.getElementById('contents').classList.toggle('active');
});
</script>
@yield('script')
</body>
</html>
Sidebar dan Navbar Styling (CSS)
Buat file CSS di public/css/admin.css untuk styling sederhana admin panel:
#sidebar {
width: 250px;
height: 100vh;
position: fixed;
top: 0;
left: 0;
background-color: #1e3d59;
color: #fff;
padding-top: 20px;
z-index: 1000;
transition: all 0.3s;
}
#sidebar .nav-link {
color: #cdd3d8;
font-size: 16px;
padding: 15px 20px;
display: flex;
align-items: center;
}
#sidebar .nav-link .fa {
margin-right: 10px;
font-size: 18px;
}
#sidebar .nav-link.active, #sidebar .nav-link:hover {
background-color: #0b2638;
color: #fff;
}
/* Content area */
#contents {
margin-left: 250px;
padding: 20px;
background-color: #f8f9fa;
min-height: 100vh;
transition: margin-left 0.3s;
}
/* Sidebar toggle button for mobile */
#sidebarToggle {
display: none;
}
@media (max-width: 768px) {
#sidebar {
left: -250px;
}
#sidebar.active {
left: 0;
}
#contents {
margin-left: 0;
}
#contents.active {
margin-left: 250px;
}
#sidebarToggle {
display: block;
position: fixed;
top: 10px;
left: 10px;
z-index: 1100;
font-size: 24px;
color: #1e3d59;
background-color: #fff;
border: none;
}
}
Hasil akhir akan seperti berikut:
http://localhost:8000/categories
http://localhost:8000/products/create
http://localhost:8000/products
Selamat Mencoba!! jangan lupa tinggalkan komentar jika ada yang mau ditanyakan :)