This commit is contained in:
Muzakki Parsaoran Siregar 2025-10-03 14:15:04 +07:00
parent 97d79b4a84
commit 54c237d211
12 changed files with 260 additions and 70 deletions

View File

@ -11,8 +11,8 @@ class ReviewController extends Controller
// Ambil semua ulasan
public function index()
{
$reviews = Review::all();
return response()->json($reviews, 200);
$reviews = Review::latest()->paginate(10);
return view('admin.reviews.index', compact('reviews'));
}

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Models\Template;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class TemplateController extends Controller
{
@ -20,15 +21,28 @@ class TemplateController extends Controller
return view('admin.template.edit', compact('template'));
}
// Update template (nama, harga, paket)
// Update template (nama, harga, paket, foto)
public function update(Request $request, Template $template)
{
$validated = $request->validate([
'nama_template' => 'required|string|max:255',
'harga' => 'required|numeric|min:0',
'paket' => 'required|in:starter,basic,premium',
'foto' => 'nullable|image|mimes:jpg,jpeg,png|max:5120 ',
]);
// kalau ada upload foto baru
if ($request->hasFile('foto')) {
// hapus foto lama kalau ada
if ($template->foto && Storage::exists('public/' . $template->foto)) {
Storage::delete('public/' . $template->foto);
}
// simpan foto baru
$path = $request->file('foto')->store('templates', 'public');
$validated['foto'] = $path;
}
$template->update($validated);
return redirect()->route('admin.template.index')

View File

@ -24,7 +24,7 @@
<table class="w-full border border-gray-200 text-left border-collapse">
<thead class="bg-gray-100">
<tr>
<th class="px-3 py-2 border border-gray-200 w-[50px] text-center">#</th>
<th class="px-3 py-2 border border-gray-200 w-[50px] text-center">No.</th>
<th class="px-3 py-2 border border-gray-200">Nama</th>
<th class="px-3 py-2 border border-gray-200">Deskripsi</th>
<th class="px-3 py-2 border border-gray-200 text-center w-[100px]">Foto</th>

View File

@ -0,0 +1,92 @@
@extends('layouts.app')
@section('title', 'Daftar Pesanan Pelanggan')
@section('content')
<div class="container mx-auto py-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">Daftar Pesanan</h2>
</div>
<!-- Flash Message -->
@if(session('success'))
<div class="mb-4 p-3 rounded bg-green-100 text-green-800 border border-green-300 shadow">
{{ session('success') }}
</div>
@endif
<!-- Tabel -->
<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="w-full border border-gray-200 text-left border-collapse">
<thead class="bg-gray-100">
<tr>
<th class="px-3 py-2 border border-gray-200 w-[50px] text-center">No.</th>
<th class="px-3 py-2 border border-gray-200">Nama Pemesan</th>
<th class="px-3 py-2 border border-gray-200">Email</th>
<th class="px-3 py-2 border border-gray-200">Template</th>
<th class="px-3 py-2 border border-gray-200">Status</th>
<th class="px-3 py-2 border border-gray-200">Harga</th>
<th class="px-3 py-2 border border-gray-200 text-center w-[180px]">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($pelanggans as $index => $pelanggan)
<tr class="hover:bg-gray-50">
<td class="px-3 py-2 border border-gray-200 text-center">{{ $index + 1 }}</td>
<td class="px-3 py-2 border border-gray-200">{{ $pelanggan->nama_pemesan }}</td>
<td class="px-3 py-2 border border-gray-200">{{ $pelanggan->email }}</td>
<td class="px-3 py-2 border border-gray-200">{{ $pelanggan->template->nama_template ?? '-' }}</td>
<td class="px-3 py-2 border border-gray-200">
<span class="px-2 py-1 rounded text-sm
{{ $pelanggan->status == 'diterima' ? 'bg-green-100 text-green-700' : ($pelanggan->status == 'ditolak' ? 'bg-red-100 text-red-700' : 'bg-yellow-100 text-yellow-700') }}">
{{ ucfirst($pelanggan->status) }}
</span>
</td>
<td class="px-3 py-2 border border-gray-200">Rp {{ number_format($pelanggan->harga, 0, ',', '.') }}</td>
<td class="px-3 py-2 border border-gray-200 text-center">
<div class="flex justify-center gap-2">
<!-- Detail -->
<a href="{{ route('admin.pelanggan.show', $pelanggan->id) }}"
class="px-3 py-1 rounded bg-blue-500 text-white text-sm hover:bg-blue-600 flex items-center">
<i class="bi bi-eye mr-1"></i> Detail
</a>
<!-- Form Update Status -->
<form action="{{ route('admin.pelanggan.update', $pelanggan->id) }}" method="POST" class="d-inline">
@csrf
@method('PUT')
<select name="status" onchange="this.form.submit()"
class="text-sm border rounded px-2 py-1">
<option value="menunggu" {{ $pelanggan->status == 'menunggu' ? 'selected' : '' }}>Menunggu</option>
<option value="diterima" {{ $pelanggan->status == 'diterima' ? 'selected' : '' }}>Diterima</option>
<option value="ditolak" {{ $pelanggan->status == 'ditolak' ? 'selected' : '' }}>Ditolak</option>
</select>
</form>
<!-- Hapus -->
<form action="{{ route('admin.pelanggan.destroy', $pelanggan->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Yakin hapus pesanan?')">
@csrf
@method('DELETE')
<button class="px-3 py-1 rounded bg-red-500 text-white text-sm hover:bg-red-600 flex items-center">
<i class="bi bi-trash mr-1"></i> Hapus
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-3 py-4 text-center text-gray-500 border border-gray-200">
Belum ada pesanan.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">
{{ $pelanggans->links() }}
</div>
</div>
@endsection

View File

@ -0,0 +1,94 @@
@extends('layouts.app')
@section('title', 'Manajemen Ulasan')
@section('content')
<div class="container mx-auto py-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">Daftar Ulasan</h2>
</div>
<!-- Flash Message -->
@if(session('success'))
<div class="mb-4 p-3 rounded bg-green-100 text-green-800 border border-green-300 shadow">
{{ session('success') }}
</div>
@endif
<!-- Tabel -->
<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="w-full border border-gray-200 text-left border-collapse">
<thead class="bg-gray-100">
<tr>
<th class="px-3 py-2 border border-gray-200 w-[50px] text-center">No.</th>
<th class="px-3 py-2 border border-gray-200">Nama</th>
<th class="px-3 py-2 border border-gray-200">Kota</th>
<th class="px-3 py-2 border border-gray-200 text-center">Penilaian</th>
<th class="px-3 py-2 border border-gray-200">Pesan</th>
<th class="px-3 py-2 border border-gray-200 text-center w-[120px]">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($reviews as $index => $review)
<tr class="hover:bg-gray-50">
<td class="px-3 py-2 border border-gray-200 text-center">{{ $index + 1 }}</td>
<td class="px-3 py-2 border border-gray-200">{{ $review->name }}</td>
<td class="px-3 py-2 border border-gray-200">{{ $review->city }}</td>
<td class="px-3 py-2 border border-gray-200 text-center">
@for ($s = 1; $s <= 5; $s++)
<i class="bi {{ $s <= $review->rating ? 'bi-star-fill text-yellow-500' : 'bi-star text-gray-400' }}"></i>
@endfor
</td>
<td class="px-3 py-2 border border-gray-200">{{ $review->message }}</td>
<td class="px-3 py-2 border border-gray-200 text-center">
<div class="flex justify-center gap-2">
<form action="{{ route('admin.reviews.destroy', $review->id) }}" method="POST" onsubmit="return confirm('Hapus ulasan dari {{ $review->name }}?')">
@csrf
@method('DELETE')
<button type="submit" class="px-3 py-1 rounded bg-red-600 text-white text-sm hover:bg-red-700 flex items-center">
<i class="bi bi-trash mr-1"></i> Hapus
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-3 py-4 text-center text-gray-500 border border-gray-200">
Belum ada ulasan.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const modal = document.getElementById('modalDelete');
const overlay = document.getElementById('modalDeleteOverlay');
const closeBtn = document.getElementById('closeDeleteModal');
const deleteForm = document.getElementById('deleteForm');
const deleteName = document.getElementById('deleteName');
document.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', () => {
deleteForm.action = btn.dataset.action;
deleteName.textContent = btn.dataset.name || 'pengguna ini';
modal.classList.remove('hidden');
modal.classList.add('flex');
});
});
const closeModal = () => {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
closeBtn.addEventListener('click', closeModal);
overlay.addEventListener('click', closeModal);
});
</script>
@endsection

View File

@ -20,7 +20,8 @@
</div>
@endif
<form action="{{ route('admin.template.update', $template->id) }}" method="POST">
<form action="{{ route('admin.template.update', $template->id) }}"
method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@ -59,6 +60,26 @@
@enderror
</div>
{{-- Foto / Gambar --}}
<div class="mb-3">
<label for="foto" class="form-label">Gambar Template</label>
<div class="mb-2">
@if($template->foto)
<img src="{{ asset('storage/' . $template->foto) }}"
alt="{{ $template->nama_template }}"
class="rounded border" style="max-height: 120px">
@else
<p class="text-muted">Belum ada gambar</p>
@endif
</div>
<input type="file" name="foto" id="foto"
class="form-control @error('foto') is-invalid @enderror">
<small class="text-muted">Kosongkan jika tidak ingin mengganti gambar.</small>
@error('foto')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
{{-- Tombol --}}
<button type="submit" class="btn btn-primary">Simpan Perubahan</button>
<a href="{{ route('admin.template.index') }}" class="btn btn-secondary">Batal</a>

View File

@ -20,7 +20,8 @@
<table class="w-full border border-gray-200 text-left border-collapse">
<thead class="bg-gray-100">
<tr>
<th class="px-3 py-2 border border-gray-200 w-[50px] text-center">#</th>
<th class="px-3 py-2 border border-gray-200 w-[50px] text-center">No.</th>
<th class="px-3 py-2 border border-gray-200">Gambar</th>
<th class="px-3 py-2 border border-gray-200">Nama</th>
<th class="px-3 py-2 border border-gray-200">Kategori</th>
<th class="px-3 py-2 border border-gray-200">Paket</th>
@ -32,6 +33,18 @@
@forelse($templates as $index => $t)
<tr class="hover:bg-gray-50">
<td class="px-3 py-2 border border-gray-200 text-center">{{ $index + 1 }}</td>
<!-- Kolom gambar -->
<td class="px-3 py-2 border border-gray-200 text-center">
@if($t->foto)
<img src="{{ asset('storage/' . $t->foto) }}"
alt="{{ $t->nama_template }}"
class="w-16 h-16 object-cover rounded">
@else
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-3 py-2 border border-gray-200">{{ $t->nama_template }}</td>
<td class="px-3 py-2 border border-gray-200">{{ $t->kategori->nama ?? '-' }}</td>
<td class="px-3 py-2 border border-gray-200">{{ ucfirst($t->paket) }}</td>
@ -47,7 +60,7 @@
</tr>
@empty
<tr>
<td colspan="6"
<td colspan="7"
class="px-3 py-4 text-center text-gray-500 border border-gray-200">
Belum ada template.
</td>

View File

@ -12,6 +12,7 @@
background-color: #f1f5f9;
font-family: 'Poppins', sans-serif;
}
.sidebar {
width: 250px;
height: 100vh;
@ -54,19 +55,23 @@
<li>
<a href="{{ route('admin.template.index') }}"
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->routeIs('admin.template.*') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
<i class="bi bi-diagram-3 me-2"></i> Template
<i class="bi bi-layout-text-window me-2"></i> Template
</a>
</li>
<li>
<a href="#" class="flex items-center py-2 px-3 rounded hover:bg-blue-50 text-gray-700">
<a href="{{ route('admin.pelanggan.index') }}"
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->routeIs('admin.pelanggan.*') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
<i class="bi bi-people me-2"></i> Pelanggan
</a>
</li>
<li>
<a href="#" class="flex items-center py-2 px-3 rounded hover:bg-blue-50 text-gray-700">
<i class="bi bi-chat-dots me-2"></i> Ulasan
<a href="{{ route('admin.reviews.index') }}"
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->routeIs('admin.reviews.*') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
<i class="bi bi-people me-2"></i> Ulasan
</a>
</li>
</ul>
<p class="text-gray-500 uppercase text-xs font-semibold px-3 mt-4 mb-2">Akun</p>
@ -89,4 +94,5 @@
@yield('content')
</div>
</body>
</html>

View File

@ -1,58 +0,0 @@
@extends('layouts.app')
@section('title', 'Daftar Pesanan Pelanggan')
@section('content')
<div class="container mt-4">
<h2 class="mb-3">Daftar Pesanan</h2>
<a href="{{ route('pelanggans.create') }}" class="btn btn-primary mb-3">+ Buat Pesanan</a>
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>Nama Pemesan</th>
<th>Email</th>
<th>Template</th>
<th>Status</th>
<th>Harga</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@forelse($pelanggans as $index => $pelanggan)
<tr>
<td>{{ $index + 1 }}</td>
<td>{{ $pelanggan->nama_pemesan }}</td>
<td>{{ $pelanggan->email }}</td>
<td>{{ $pelanggan->template->nama_template ?? '-' }}</td>
<td>
<span class="badge bg-{{ $pelanggan->status == 'diterima' ? 'success' : ($pelanggan->status == 'ditolak' ? 'danger' : 'warning') }}">
{{ ucfirst($pelanggan->status) }}
</span>
</td>
<td>Rp {{ number_format($pelanggan->harga, 0, ',', '.') }}</td>
<td>
<a href="{{ route('pelanggans.show', $pelanggan->id) }}" class="btn btn-sm btn-info">Detail</a>
<a href="{{ route('pelanggans.edit', $pelanggan->id) }}" class="btn btn-sm btn-warning">Edit</a>
<form action="{{ route('pelanggans.destroy', $pelanggan->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Yakin hapus pesanan?')">
@csrf
@method('DELETE')
<button class="btn btn-sm btn-danger">Hapus</button>
</form>
</td>
</tr>
@empty
<tr><td colspan="7" class="text-center">Belum ada pesanan.</td></tr>
@endforelse
</tbody>
</table>
{{ $pelanggans->links() }}
</div>
@endsection

View File

@ -5,7 +5,9 @@ use App\Http\Controllers\AdminAuthController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\KategoriController;
use App\Http\Controllers\TemplateController;
use App\Http\Controllers\PelangganController;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Api\ReviewController;
// logout umum
Route::post('/logout', function () {
@ -42,5 +44,11 @@ Route::prefix('admin')->name('admin.')->group(function () {
Route::get('/template', [TemplateController::class, 'index'])->name('template.index');
Route::get('/template/{template}/edit', [TemplateController::class, 'edit'])->name('template.edit');
Route::put('/template/{template}', [TemplateController::class, 'update'])->name('template.update');
// Pelanggan routes
Route::resource('pelanggan', PelangganController::class)->except(['create', 'edit']);
// Ulasan routes
Route::resource('reviews', ReviewController::class)->except(['create', 'edit', 'update', 'show', 'store']);
});
});