Berikut adalah langkah-langkah membuat CRUD Kategori Product yang memiliki field name, slug dan image dan nantinya kita akan mengintegrasikannya dengan modul produk yang telah dibuat sebelumnya agar setiap produk dapat memiliki kategori.
Langkah 1: Buat Model & Migrasi Category
Langkah ini sebenarnya sudah kita buat di part 3 sebelumnya. namun agar tutorial lengkap, kita tulis ulang, jika sudah berhasil silahkan skip langkah ini:
php artisan make:model Category -m
Edit file migrasi database/migrations/xxxx_xx_xx_create_categories_table.php:
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->string('image')->nullable();
$table->timestamps();
});
}
Lakukan Migrasi:
php artisan migrate
Edit model app/Models/Category.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
protected $fillable = [
'name', 'slug', 'image'
];
public function products()
{
return $this->hasMany(Product::class);
}
}
Langkah 2: Buat Controller Category
Buat controller:
php artisan make:controller CategoryController
Edit app/Http/Controllers/CategoryController.php:
<?php
namespace App\Http\Controllers;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Inertia\Inertia;
use Illuminate\Support\Str;
class CategoryController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
$categories = Category::latest()->paginate(10);
return Inertia::render('Categories/Index', [
'categories' => $categories,
]);
}
public function create()
{
return Inertia::render('Categories/Create');
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'slug' => 'required|string|unique:categories,slug',
'image' => 'nullable|image|max:2048',
]);
$path = null;
if ($request->hasFile('image')) {
$path = $request->file('image')->store('categories', 'public');
}
Category::create([
'name' => $validated['name'],
'slug' => $validated['slug'],
'image' => $path
]);
return redirect()->route('categories.index')->with('success', 'Category created successfully.');
}
public function edit(Category $category)
{
return Inertia::render('Categories/Edit', [
'category' => [
'id' => $category->id,
'name' => $category->name,
'slug' => $category->slug,
'image' => $category->image,
'image_url' => $category->image ? asset('storage/'.$category->image) : null,
]
]);
}
public function update(Request $request, Category $category)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'slug' => 'required|string|unique:categories,slug,' . $category->id,
'image' => 'nullable|image|max:2048',
]);
if ($request->hasFile('image')) {
if ($category->image && Storage::disk('public')->exists($category->image)) {
Storage::disk('public')->delete($category->image);
}
$validated['image'] = $request->file('image')->store('categories', 'public');
}
$category->update($validated);
return redirect()->route('categories.index')->with('success', 'Category updated successfully.');
}
public function destroy(Category $category)
{
if ($category->image && Storage::disk('public')->exists($category->image)) {
Storage::disk('public')->delete($category->image);
}
$category->delete();
return redirect()->route('categories.index')->with('success', 'Category deleted successfully.');
}
}
Langkah 3: Routes Category
Tambahkan route categories Di routes/web.php:
use App\Http\Controllers\CategoryController;
Route::middleware(['auth', 'verified'])->group(function () {
Route::resource('categories', CategoryController::class);
});
Langkah 5: Buat Halaman Vue untuk Category
Buat folder resources/js/Pages/Categories dan file Index.vue, Create.vue, Edit.vue.
Index.vue
<template>
<div class="flex min-h-screen bg-gray-100">
<!-- Sidebar -->
<aside class="w-64 bg-blue-600 text-white flex-shrink-0">
<div class="p-6">
<h2 class="text-2xl font-bold">Admin Panel</h2>
</div>
<nav class="mt-6">
<ul>
<li>
<Link href="/dashboard"
class="block px-4 py-2 hover:bg-blue-700"
>
Dashboard
</Link>
</li>
<li>
<Link href="/categories"
class="block px-4 py-2 hover:bg-blue-700"
>
Categories
</Link>
</li>
<li>
<Link href="/products"
class="block px-4 py-2 hover:bg-blue-700"
>
Products
</Link>
</li>
<li>
<Link href="/orders"
class="block px-4 py-2 hover:bg-blue-700"
>
Orders
</Link>
</li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 p-6">
<h1 class="text-2xl font-bold text-gray-800 mb-6">Category Management</h1>
<Link href="/categories/create" class="px-4 py-2 bg-blue-600 text-white rounded">Create Category</Link>
<table class="min-w-full mt-4 bg-white border">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left">Name</th>
<th class="px-4 py-2 text-left">Slug</th>
<th class="px-4 py-2 text-left">Image</th>
<th class="px-4 py-2 text-center">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="cat in categories.data" :key="cat.id" class="border-t">
<td class="px-4 py-2">{{ cat.name }}</td>
<td class="px-4 py-2">{{ cat.slug }}</td>
<td class="px-4 py-2">
<img v-if="cat.image" :src="`/storage/${cat.image}`" class="w-16 h-16 object-cover" />
</td>
<td class="px-4 py-2 text-center">
<Link :href="`/categories/${cat.id}/edit`" class="px-2 py-1 bg-green-600 text-white rounded">Edit</Link>
<button @click="destroy(cat.id)" class="ml-2 px-2 py-1 bg-red-600 text-white rounded">Delete</button>
</td>
</tr>
</tbody>
</table>
</main>
</div>
</template>
<script>
import { Link} from '@inertiajs/inertia-vue3';
import { Inertia } from '@inertiajs/inertia';
export default {
props: {
categories: Object,
},
components: { Link },
methods: {
destroy(id) {
if (confirm('Are you sure?')) {
Inertia.delete(`/categories/${id}`);
}
}
}
}
</script>
Create.vue
<template>
<div class="flex min-h-screen bg-gray-100">
<!-- Sidebar -->
<aside class="w-64 bg-blue-600 text-white flex-shrink-0">
<div class="p-6">
<h2 class="text-2xl font-bold">Admin Panel</h2>
</div>
<nav class="mt-6">
<ul>
<li>
<Link href="/dashboard"
class="block px-4 py-2 hover:bg-blue-700"
>
Dashboard
</Link>
</li>
<li>
<Link href="/categories"
class="block px-4 py-2 hover:bg-blue-700"
>
Categories
</Link>
</li>
<li>
<Link href="/products"
class="block px-4 py-2 hover:bg-blue-700"
>
Products
</Link>
</li>
<li>
<Link href="/orders"
class="block px-4 py-2 hover:bg-blue-700"
>
Orders
</Link>
</li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 p-6">
<h1 class="text-2xl font-bold text-gray-800 mb-6">Add New Category</h1>
<div class="bg-white p-6 rounded-lg shadow">
<form @submit.prevent="submit" class="space-y-4">
<div>
<label class="block font-medium">Name</label>
<input v-model="form.name" type="text" class="w-full border px-3 py-2 rounded" />
<div v-if="errors.name" class="text-red-500 text-sm">{{ errors.name }}</div>
</div>
<div>
<label class="block font-medium">Slug</label>
<input v-model="form.slug" type="text" class="w-full border px-3 py-2 rounded" />
<div v-if="errors.slug" class="text-red-500 text-sm">{{ errors.slug }}</div>
</div>
<div>
<label class="block font-medium">Image</label>
<input type="file" @change="handleFile" class="w-full border px-3 py-2 rounded" />
<div v-if="errors.image" class="text-red-500 text-sm">{{ errors.image }}</div>
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded">Save</button>
</form>
</div>
</main>
</div>
</template>
<script>
import { useForm,Link } from '@inertiajs/inertia-vue3';
export default {
components: { Link },
setup() {
const form = useForm({
name: '',
slug: '',
image: null
});
const handleFile = (e) => {
form.image = e.target.files[0];
};
const submit = () => {
form.post('/categories');
};
return { form, handleFile, submit, errors: form.errors };
}
};
</script>
Edit.vue
<template>
<div class="flex min-h-screen bg-gray-100">
<!-- Sidebar -->
<aside class="w-64 bg-blue-600 text-white flex-shrink-0">
<div class="p-6">
<h2 class="text-2xl font-bold">Admin Panel</h2>
</div>
<nav class="mt-6">
<ul>
<li>
<Link href="/dashboard"
class="block px-4 py-2 hover:bg-blue-700"
>
Dashboard
</Link>
</li>
<li>
<Link href="/categories"
class="block px-4 py-2 hover:bg-blue-700"
>
Categories
</Link>
</li>
<li>
<Link href="/products"
class="block px-4 py-2 hover:bg-blue-700"
>
Products
</Link>
</li>
<li>
<Link href="/orders"
class="block px-4 py-2 hover:bg-blue-700"
>
Orders
</Link>
</li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 p-6">
<h1 class="text-2xl font-bold text-gray-800 mb-6">Add New Product</h1>
<div class="bg-white p-6 rounded-lg shadow">
<form @submit.prevent="submit" class="space-y-4">
<div>
<label class="block font-medium">Name</label>
<input v-model="form.name" type="text" class="w-full border px-3 py-2 rounded" />
<div v-if="errors.name" class="text-red-500 text-sm">{{ errors.name }}</div>
</div>
<div>
<label class="block font-medium">Slug</label>
<input v-model="form.slug" type="text" class="w-full border px-3 py-2 rounded" />
<div v-if="errors.slug" class="text-red-500 text-sm">{{ errors.slug }}</div>
</div>
<div>
<label class="block font-medium">Image</label>
<input type="file" @change="handleFile" class="w-full border px-3 py-2 rounded" />
<div v-if="errors.image" class="text-red-500 text-sm">{{ errors.image }}</div>
<div v-if="category.image_url" class="mt-2">
<p class="font-medium">Current Image:</p>
<img :src="category.image_url" class="w-32 h-32 object-cover mt-1" />
</div>
<div v-if="imagePreview" class="mt-2">
<p class="font-medium">New Image Preview:</p>
<img :src="imagePreview" class="w-32 h-32 object-cover mt-1" />
</div>
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded">Update</button>
</form>
</div>
</main>
</div>
</template>
<script>
import { ref } from 'vue';
import { useForm,Link } from '@inertiajs/inertia-vue3';
export default {
props: {
category: Object,
},
components: { Link },
setup(props) {
const form = useForm({
name: props.category.name,
slug: props.category.slug,
image: null
});
const imagePreview = ref(null);
const handleFile = (e) => {
const file = e.target.files[0];
form.image = file;
imagePreview.value = URL.createObjectURL(file);
};
const submit = () => {
form.transform((data) => {
data._method = 'PUT'; // Gunakan PUT untuk update
return data;
});
form.post(`/categories/${props.category.id}`, {
onSuccess: () => {
alert('Category updated successfully!');
}
});
};
return { form, handleFile, submit, errors: form.errors, imagePreview };
},
};
</script>
Jika berhasil, akan seperti berikut:
Silahkan lakukan CRUD. Selamat mencoba!!!