add pagination & filter

This commit is contained in:
Muzakki Parsaoran Siregar 2025-09-12 10:21:59 +07:00
parent ddc24336b1
commit 252f90aa41
6 changed files with 238 additions and 82 deletions

View File

@ -11,7 +11,7 @@ class FiturController extends Controller
// Tampilkan semua fitur (halaman admin)
public function index()
{
$fitur = Fitur::all();
$fitur = \App\Models\Fitur::paginate(5);
return view('admin.fitur.index', compact('fitur'));
}

View File

@ -8,17 +8,21 @@ use Illuminate\Http\Request;
class PelangganController extends Controller
{
// Tampilkan semua pelanggan (admin)
public function index()
{
$pelanggans = Pelanggan::all();
return view('admin.pelanggan.index', compact('pelanggans'));
public function index(Request $request)
{
$query = Pelanggan::query();
if ($request->filled('kategori')) {
$query->where('kategori', $request->kategori);
}
// Detail pelanggan
public function show(Pelanggan $pelanggan)
{
return view('admin.pelanggan.show', compact('pelanggan'));
}
$pelanggans = $query->get();
// Ambil semua kategori untuk dropdown
$kategoris = \App\Models\Kategori::all();
return view('admin.pelanggan.index', compact('pelanggans', 'kategoris'));
}
// Hapus pelanggan
public function destroy(Pelanggan $pelanggan)

View File

@ -13,7 +13,8 @@ class TemplateController extends Controller
public function index()
{
// eager load fiturs & kategori
$templates = Template::with(['kategori','fiturs'])->get();
$templates = Template::with(['kategori', 'fiturs'])
->paginate(5); // <= pagination 10 item per halaman
$kategoris = Kategori::all();
$fiturs = Fitur::all();
@ -96,7 +97,7 @@ class TemplateController extends Controller
public function byKategori($id)
{
$kategori = Kategori::findOrFail($id);
$templates = Template::with(['kategori','fiturs'])
$templates = Template::with(['kategori', 'fiturs'])
->where('kategori_id', $id)
->get();
$kategoris = Kategori::all();

View File

@ -34,47 +34,102 @@
setTimeout(() => document.getElementById('toast-error')?.remove(), 5000);
</script>
@endif
<!-- Tabel Fitur -->
<div class="bg-white rounded-lg shadow-sm">
<div class="p-4 overflow-x-auto">
<table class="w-full table-fixed border border-gray-300 text-left">
<thead class="bg-gray-100">
<tr>
<th class="w-[7%] p-2 border border-gray-300 text-center">Nomor</th>
<th class="w-[70%] p-2 border border-gray-300 text-center">Fitur</th>
<th class="w-[20%] p-2 border border-gray-300 text-center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse ($fitur as $key => $item)
<tr class="hover:bg-gray-50">
<td class="py-5 px-2 border border-gray-300 text-center">{{ $key + 1 }}</td>
<td class="py-5 px-2 border border-gray-300 truncate whitespace-nowrap">{{ $item->deskripsi }}</td>
<td class="py-5 px-2 border border-gray-300 text-center">
<div class="flex justify-center space-x-2">
<button class="text-blue-600 flex items-center pr-4 openEditModalBtn"
data-id="{{ $item->id }}">
<i class="bi bi-pencil mr-1"></i> Ubah
</button>
<button class="text-red-600 flex items-center openDeleteModalBtn"
data-id="{{ $item->id }}">
<i class="bi bi-trash mr-1"></i> Hapus
</button>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="p-2 text-center text-gray-500">Belum ada fitur</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<!-- Tabel Fitur -->
<div class="bg-white rounded-lg shadow-sm">
<div class="p-4 overflow-x-auto">
<table class="w-full table-fixed border border-gray-300 text-left">
<thead class="bg-gray-100">
<tr>
<th class="w-[7%] p-2 border border-gray-300 text-center">Nomor</th>
<th class="w-[70%] p-2 border border-gray-300 text-center">Fitur</th>
<th class="w-[20%] p-2 border border-gray-300 text-center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse ($fitur as $key => $item)
<tr class="hover:bg-gray-50">
<td class="py-5 px-2 border border-gray-300 text-center">
{{ $fitur->firstItem() + $key }}
</td>
<td class="py-5 px-2 border border-gray-300 truncate whitespace-nowrap">{{ $item->deskripsi }}</td>
<td class="py-5 px-2 border border-gray-300 text-center">
<div class="flex justify-center space-x-2">
<button class="text-blue-600 flex items-center pr-4 openEditModalBtn"
data-id="{{ $item->id }}">
<i class="bi bi-pencil mr-1"></i> Ubah
</button>
<button class="text-red-600 flex items-center openDeleteModalBtn"
data-id="{{ $item->id }}">
<i class="bi bi-trash mr-1"></i> Hapus
</button>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="p-2 text-center text-gray-500">Belum ada fitur</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="p-4 flex justify-center">
<div class="flex space-x-1">
{{-- Tombol Previous --}}
@if ($fitur->onFirstPage())
<span class="px-3 py-1 rounded-lg bg-gray-200 text-gray-500 cursor-not-allowed">Prev</span>
@else
<a href="{{ $fitur->previousPageUrl() }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-gray-300">Prev</a>
@endif
@php
$total = $fitur->lastPage();
$current = $fitur->currentPage();
@endphp
{{-- Selalu tampilkan halaman pertama --}}
@if ($current > 2)
<a href="{{ $fitur->url(1) }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-blue-100">1</a>
@if ($current > 3)
<span class="px-3 py-1 text-gray-500">...</span>
@endif
@endif
{{-- Hanya tampilkan 3 halaman di tengah (current-1, current, current+1) --}}
@for ($i = max(1, $current - 1); $i <= min($total, $current + 1); $i++)
@if ($i == $current)
<span class="px-3 py-1 rounded-lg bg-blue-600 text-white font-semibold">{{ $i }}</span>
@else
<a href="{{ $fitur->url($i) }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-blue-100">{{ $i }}</a>
@endif
@endfor
{{-- Selalu tampilkan halaman terakhir --}}
@if ($current < $total - 1)
@if ($current < $total - 2)
<span class="px-3 py-1 text-gray-500">...</span>
@endif
<a href="{{ $fitur->url($total) }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-blue-100">{{ $total }}</a>
@endif
{{-- Tombol Next --}}
@if ($fitur->hasMorePages())
<a href="{{ $fitur->nextPageUrl() }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-gray-300">Next</a>
@else
<span class="px-3 py-1 rounded-lg bg-gray-200 text-gray-500 cursor-not-allowed">Next</span>
@endif
</div>
</div>
<!-- Modal Tambah -->
<div id="modalTambah" class="fixed inset-0 hidden items-center justify-center z-50">
<div class="absolute inset-0 bg-black opacity-50" id="closeTambahModal"></div>

View File

@ -5,10 +5,24 @@
@section('content')
<div class="container mx-auto py-4">
<!-- Header -->
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">Manajemen Pelanggan</h3>
</div>
<!-- Header + Filter -->
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">Manajemen Pelanggan</h3>
<!-- Filter Kategori -->
<form method="GET" action="{{ route('admin.pelanggan.index') }}" class="flex space-x-2">
<select name="kategori" class="border border-gray-300 rounded px-3 py-2">
<option value="">-- Semua Kategori --</option>
@foreach($kategoris as $kategori)
<option value="{{ $kategori->nama }}" {{ request('kategori') == $kategori->nama ? 'selected' : '' }}>
{{ $kategori->nama }}
</option>
@endforeach
</select>
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded">Filter</button>
</form>
</div>
<!-- Flash Message -->
@if (session('success'))

View File

@ -26,7 +26,9 @@
<div id="toast-success" class="mb-4 p-3 rounded bg-green-100 text-green-800 border border-green-300 shadow">
{{ session('success') }}
</div>
<script>setTimeout(() => document.getElementById('toast-success')?.remove(), 3000);</script>
<script>
setTimeout(() => document.getElementById('toast-success')?.remove(), 3000);
</script>
@endif
@if ($errors->any())
@ -37,7 +39,9 @@
@endforeach
</ul>
</div>
<script>setTimeout(() => document.getElementById('toast-error')?.remove(), 5000);</script>
<script>
setTimeout(() => document.getElementById('toast-error')?.remove(), 5000);
</script>
@endif
<!-- Tabel Template -->
@ -63,18 +67,22 @@
<td class="p-2 border border-gray-300 truncate">{{ $template->kategori->nama ?? '-' }}</td>
<!-- Tampilkan banyak fitur -->
<td class="p-2 border border-gray-300 truncate">
@forelse($template->fiturs as $f)
<span class="inline-block bg-gray-200 px-2 py-0.5 rounded text-xs mr-1 mb-1">
{{ \Illuminate\Support\Str::limit($f->deskripsi, 50) }}
</span>
@empty
-
@endforelse
<td class="p-2 border border-gray-300 align-top">
<div class="flex flex-wrap gap-1">
@forelse($template->fiturs as $f)
<span class="inline-block bg-gray-200 px-2 py-0.5 rounded text-xs">
{{ $f->deskripsi }}
</span>
@empty
<span class="text-gray-500">-</span>
@endforelse
</div>
</td>
<td class="p-2 border border-gray-300 text-center">
<div class="w-12 h-12 overflow-hidden rounded bg-gray-100 flex items-center justify-center mx-auto">
<div
class="w-12 h-12 overflow-hidden rounded bg-gray-100 flex items-center justify-center mx-auto">
<img src="{{ $template->foto ? asset('storage/' . $template->foto) : asset('default-image.png') }}"
alt="foto" class="max-w-full max-h-full object-contain">
</div>
@ -101,7 +109,8 @@
</tr>
@empty
<tr>
<td colspan="7" class="p-2 text-center text-gray-500 border border-gray-300">Belum ada template</td>
<td colspan="7" class="p-2 text-center text-gray-500 border border-gray-300">Belum ada
template</td>
</tr>
@endforelse
</tbody>
@ -109,6 +118,64 @@
</div>
</div>
<!-- Pagination -->
@if ($templates->hasPages())
<div class="p-4 flex justify-center">
<div class="flex space-x-1">
{{-- Tombol Previous --}}
@if ($templates->onFirstPage())
<span class="px-3 py-1 rounded-lg bg-gray-200 text-gray-500 cursor-not-allowed">Prev</span>
@else
<a href="{{ $templates->previousPageUrl() }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-gray-300">Prev</a>
@endif
@php
$total = $templates->lastPage();
$current = $templates->currentPage();
@endphp
{{-- Selalu tampilkan halaman pertama --}}
@if ($current > 2)
<a href="{{ $templates->url(1) }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-blue-100">1</a>
@if ($current > 3)
<span class="px-3 py-1 text-gray-500">...</span>
@endif
@endif
{{-- Hanya tampilkan 3 halaman di tengah --}}
@for ($i = max(1, $current - 1); $i <= min($total, $current + 1); $i++)
@if ($i == $current)
<span
class="px-3 py-1 rounded-lg bg-blue-600 text-white font-semibold">{{ $i }}</span>
@else
<a href="{{ $templates->url($i) }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-blue-100">{{ $i }}</a>
@endif
@endfor
{{-- Selalu tampilkan halaman terakhir --}}
@if ($current < $total - 1)
@if ($current < $total - 2)
<span class="px-3 py-1 text-gray-500">...</span>
@endif
<a href="{{ $templates->url($total) }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-blue-100">{{ $total }}</a>
@endif
{{-- Tombol Next --}}
@if ($templates->hasMorePages())
<a href="{{ $templates->nextPageUrl() }}"
class="px-3 py-1 rounded-lg bg-gray-200 text-gray-700 hover:bg-gray-300">Next</a>
@else
<span class="px-3 py-1 rounded-lg bg-gray-200 text-gray-500 cursor-not-allowed">Next</span>
@endif
</div>
</div>
@endif
<!-- Modal Tambah Template -->
@if (!isset($kategori))
<div id="modalTambah" class="fixed inset-0 hidden items-center justify-center z-50">
@ -122,13 +189,15 @@
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium">Nama Template</label>
<input type="text" name="nama_template" class="w-full p-2 border rounded" required value="{{ old('nama_template') }}">
<input type="text" name="nama_template" class="w-full p-2 border rounded" required
value="{{ old('nama_template') }}">
</div>
<div>
<label class="block text-sm font-medium">Kategori</label>
<select name="kategori_id" class="w-full p-2 border rounded" required>
@foreach ($kategoris as $kategoriItem)
<option value="{{ $kategoriItem->id }}" @selected(old('kategori_id') == $kategoriItem->id)>{{ $kategoriItem->nama }}</option>
<option value="{{ $kategoriItem->id }}" @selected(old('kategori_id') == $kategoriItem->id)>
{{ $kategoriItem->nama }}</option>
@endforeach
</select>
</div>
@ -136,12 +205,14 @@
<!-- CHECKBOX FITUR -->
<div>
<label class="block text-sm font-medium">Fitur</label>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-48 overflow-auto p-2 border rounded">
<div
class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-48 overflow-auto p-2 border rounded">
@foreach ($fiturs as $fitur)
<label class="flex items-center space-x-2">
<input type="checkbox" name="fitur_id[]" value="{{ $fitur->id }}"
{{ (is_array(old('fitur_id')) && in_array($fitur->id, old('fitur_id'))) ? 'checked' : '' }}>
<span class="text-sm">{{ \Illuminate\Support\Str::limit($fitur->deskripsi, 80) }}</span>
{{ is_array(old('fitur_id')) && in_array($fitur->id, old('fitur_id')) ? 'checked' : '' }}>
<span
class="text-sm">{{ \Illuminate\Support\Str::limit($fitur->deskripsi, 80) }}</span>
</label>
@endforeach
</div>
@ -149,7 +220,8 @@
<div>
<label class="block text-sm font-medium">Harga</label>
<input type="number" name="harga" class="w-full p-2 border rounded" required min="0" step="1000" value="{{ old('harga') }}">
<input type="number" name="harga" class="w-full p-2 border rounded" required
min="0" step="1000" value="{{ old('harga') }}">
</div>
<div>
<label class="block text-sm font-medium">Foto (opsional)</label>
@ -158,7 +230,8 @@
</div>
</div>
<div class="p-4 border-t flex justify-end space-x-2">
<button type="button" id="closeTambahBtn" class="bg-gray-300 text-black px-3 py-1 rounded">Batal</button>
<button type="button" id="closeTambahBtn"
class="bg-gray-300 text-black px-3 py-1 rounded">Batal</button>
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button>
</div>
</form>
@ -171,7 +244,8 @@
<div id="modalEdit{{ $template->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
<div class="absolute inset-0 bg-black opacity-50 closeEditOverlay" data-id="{{ $template->id }}"></div>
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
<form action="{{ route('templates.update', $template->id) }}" method="POST" enctype="multipart/form-data">
<form action="{{ route('templates.update', $template->id) }}" method="POST"
enctype="multipart/form-data">
@csrf @method('PUT')
<div class="p-4 border-b">
<h5 class="text-lg font-medium">Edit Template</h5>
@ -179,7 +253,9 @@
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium">Nama Template</label>
<input type="text" name="nama_template" value="{{ old('nama_template', $template->nama_template) }}" class="w-full p-2 border rounded" required>
<input type="text" name="nama_template"
value="{{ old('nama_template', $template->nama_template) }}"
class="w-full p-2 border rounded" required>
</div>
<div>
<label class="block text-sm font-medium">Kategori</label>
@ -194,12 +270,14 @@
<!-- EDIT: checkbox fitur dengan checked state -->
<div>
<label class="block text-sm font-medium">Fitur</label>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-48 overflow-auto p-2 border rounded">
<div
class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-48 overflow-auto p-2 border rounded">
@foreach ($fiturs as $fiturItem)
<label class="flex items-center space-x-2">
<input type="checkbox" name="fitur_id[]" value="{{ $fiturItem->id }}"
{{ (is_array(old('fitur_id')) ? in_array($fiturItem->id, old('fitur_id')) : $template->fiturs->contains($fiturItem->id)) ? 'checked' : '' }}>
<span class="text-sm">{{ \Illuminate\Support\Str::limit($fiturItem->deskripsi, 80) }}</span>
<span
class="text-sm">{{ \Illuminate\Support\Str::limit($fiturItem->deskripsi, 80) }}</span>
</label>
@endforeach
</div>
@ -207,15 +285,18 @@
<div>
<label class="block text-sm font-medium">Harga</label>
<input type="number" name="harga" value="{{ old('harga', $template->harga) }}" class="w-full p-2 border rounded" required min="0" step="1000">
<input type="number" name="harga" value="{{ old('harga', $template->harga) }}"
class="w-full p-2 border rounded" required min="0" step="1000">
</div>
<div>
<label class="block text-sm font-medium">Foto (opsional)</label>
@if ($template->foto)
<div class="mb-2">
<small class="text-gray-500">Foto saat ini:</small>
<div class="w-20 h-20 mt-1 overflow-hidden rounded bg-gray-100 flex items-center justify-center border">
<img src="{{ asset('storage/' . $template->foto) }}" alt="foto" class="max-w-full max-h-full object-contain">
<div
class="w-20 h-20 mt-1 overflow-hidden rounded bg-gray-100 flex items-center justify-center border">
<img src="{{ asset('storage/' . $template->foto) }}" alt="foto"
class="max-w-full max-h-full object-contain">
</div>
</div>
@endif
@ -223,7 +304,8 @@
</div>
</div>
<div class="p-4 border-t flex justify-end space-x-2">
<button type="button" class="bg-gray-300 text-black px-3 py-1 rounded closeEditBtn" data-id="{{ $template->id }}">Batal</button>
<button type="button" class="bg-gray-300 text-black px-3 py-1 rounded closeEditBtn"
data-id="{{ $template->id }}">Batal</button>
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button>
</div>
</form>