Skip to content

Day 12 โ€” CRUD Master Data: Category โ€‹

Fase: 2 โ€” Mini Project Asset Management | Minggu: 3 | Hari: Selasa


๐ŸŽฏ Tujuan Hari Ini โ€‹

Tim menyelesaikan modul CRUD Category secara lengkap: model, controller, route, dan 4 view (index, create, edit, show), dengan validasi data yang rapi.


๐Ÿ“‹ Task Wajib โ€‹

1. Bagi Tugas Hari Ini โ€‹

RoleTugas
Backend DevBuat Model, Controller, dan logic CRUD Category
Frontend DevBuat file-file view (HTML + CSS) untuk Category
QA TesterSiapkan test case dan uji fitur saat sudah selesai
PMKoordinasi + jika selesai, bantu setup layout master yang baru

2. Buat Model Category โ€‹

bash
php artisan make:model Category

Isi app/Models/Category.php:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    protected $fillable = ['name', 'description'];

    // Relasi: 1 kategori punya banyak aset
    public function assets()
    {
        return $this->hasMany(Asset::class);
    }
}

3. Buat Controller โ€‹

bash
php artisan make:controller CategoryController --resource

Isi method-method di CategoryController.php:

php
<?php

namespace App\Http\Controllers;

use App\Models\Category;
use Illuminate\Http\Request;

class CategoryController extends Controller
{
    public function index()
    {
        $categories = Category::withCount('assets')->orderBy('name')->get();
        return view('categories.index', compact('categories'));
    }

    public function create()
    {
        return view('categories.create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'name'        => 'required|min:2|max:100|unique:categories,name',
            'description' => 'nullable|max:500',
        ]);

        Category::create($request->only(['name', 'description']));

        return redirect()->route('categories.index')
            ->with('success', "Kategori '{$request->name}' berhasil ditambahkan!");
    }

    public function show(Category $category)
    {
        $category->load('assets');
        return view('categories.show', compact('category'));
    }

    public function edit(Category $category)
    {
        return view('categories.edit', compact('category'));
    }

    public function update(Request $request, Category $category)
    {
        $request->validate([
            'name'        => "required|min:2|max:100|unique:categories,name,{$category->id}",
            'description' => 'nullable|max:500',
        ]);

        $category->update($request->only(['name', 'description']));

        return redirect()->route('categories.index')
            ->with('success', "Kategori '{$category->name}' berhasil diperbarui!");
    }

    public function destroy(Category $category)
    {
        if ($category->assets()->count() > 0) {
            return redirect()->route('categories.index')
                ->with('error', "Kategori '{$category->name}' tidak bisa dihapus karena masih memiliki aset!");
        }

        $nama = $category->name;
        $category->delete();

        return redirect()->route('categories.index')
            ->with('success', "Kategori '{$nama}' berhasil dihapus!");
    }
}

Perhatikan logika destroy(): Kita cek dulu apakah kategori masih punya aset! Ini adalah business rule penting โ€” tidak boleh hapus kategori yang masih dipakai.

4. Daftarkan Route โ€‹

Di routes/web.php:

php
use App\Http\Controllers\CategoryController;

Route::resource('categories', CategoryController::class);

5. Buat Views (Frontend Dev Mengerjakan Ini) โ€‹

resources/views/categories/index.blade.php:

html
@extends('layouts.app')
@section('title', 'Kategori Aset')

@section('content')
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
    <div>
        <h1>๐Ÿ“‚ Kategori Aset</h1>
        <p style="color:#666;">Total: {{ $categories->count() }} kategori</p>
    </div>
    <a href="{{ route('categories.create') }}" class="btn-primary">+ Tambah Kategori</a>
</div>

@include('partials.alert')

<div style="display:grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap:20px;">
    @forelse($categories as $cat)
    <div style="background:white; border-radius:10px; padding:24px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
        <h3 style="margin-bottom:8px; color:#1a1a2e;">{{ $cat->name }}</h3>
        <p style="color:#666; font-size:14px; margin-bottom:16px;">
            {{ $cat->description ?? 'Tidak ada deskripsi.' }}
        </p>
        <div style="display:flex; justify-content:space-between; align-items:center;">
            <span style="background:#eef2ff; color:#4f46e5; padding:4px 12px; border-radius:20px; font-size:13px;">
                ๐Ÿ—‚๏ธ {{ $cat->assets_count }} aset
            </span>
            <div style="display:flex; gap:10px;">
                <a href="{{ route('categories.edit', $cat->id) }}" style="color:#0066cc; font-size:14px;">Edit</a>
                <form action="{{ route('categories.destroy', $cat->id) }}" method="POST"
                      onsubmit="return confirm('Hapus kategori {{ $cat->name }}?')">
                    @csrf @method('DELETE')
                    <button type="submit" style="border:none; background:none; color:#dc3545; cursor:pointer; font-size:14px;">Hapus</button>
                </form>
            </div>
        </div>
    </div>
    @empty
    <p style="color:#999;">Belum ada kategori. <a href="{{ route('categories.create') }}">Tambahkan sekarang.</a></p>
    @endforelse
</div>
@endsection

resources/views/categories/create.blade.php dan edit.blade.php ikuti pola Day 7โ€“8 (form dengan @extends + validasi error).

6. Buat Partial Alert โ€‹

Buat file resources/views/partials/alert.blade.php:

html
@if(session('success'))
<div style="background:#d4edda; color:#155724; padding:12px 16px; border-radius:6px; margin-bottom:20px;">
    โœ… {{ session('success') }}
</div>
@endif

@if(session('error'))
<div style="background:#f8d7da; color:#721c24; padding:12px 16px; border-radius:6px; margin-bottom:20px;">
    โŒ {{ session('error') }}
</div>
@endif

Gunakan di setiap view dengan: @include('partials.alert')


๐Ÿ“ Laporan ke Mentor โ€‹

๐Ÿ“Œ LAPORAN HARIAN โ€” Day 12
Nama     : [Nama Lengkap]
Role     : [Role Kamu]

โœ… Yang saya kerjakan hari ini:
- [ ] Model Category dengan relasi hasMany selesai
- [ ] CategoryController dengan semua 7 method selesai
- [ ] Validasi unique nama kategori berjalan
- [ ] Logika "tidak bisa hapus jika ada aset" berjalan
- [ ] View index, create, edit selesai
- [ ] Semua CRUD Category diuji manual

๐Ÿ“ธ Screenshot Wajib:
1. Halaman /categories menampilkan kartu-kartu kategori
2. Form tambah kategori
3. Pesan error saat mencoba hapus kategori yang masih punya aset

๐Ÿงช QA Test Result (diisi oleh QA Tester):
| Test Case | Expected | Result | Pass/Fail |
|-----------|----------|--------|-----------|
| Tambah kategori baru | Tersimpan | | |
| Tambah kategori nama duplikat | Validasi error | | |
| Edit nama kategori | Berhasil update | | |
| Hapus kategori tanpa aset | Berhasil hapus | | |
| Hapus kategori dengan aset | Muncul pesan error | | |

โ“ Kendala:
[Tulis jika ada]

Catatan Mentor

Field withCount('assets') di index() adalah cara elegan mendapatkan jumlah relasi tanpa query terpisah. Ini adalah pola yang sangat umum di industri. Jelaskan kepada peserta.

Program Magang SMK RPL โ€” Rekayasa Perangkat Lunak