add template dll.

This commit is contained in:
Muzakki Parsaoran Siregar 2025-09-09 11:40:57 +07:00
parent 44dfdec0b0
commit 8a7d2eab98
13 changed files with 937 additions and 428 deletions

View File

@ -11,51 +11,46 @@ class FiturController extends Controller
// Tampilkan semua fitur (halaman admin) // Tampilkan semua fitur (halaman admin)
public function index() public function index()
{ {
$fiturs = Fitur::all(); $fitur = Fitur::all();
return view('fiturs.index', compact('fiturs')); return view('admin.fitur.index', compact('fitur'));
} }
// Form tambah fitur // Form tambah fitur
public function create() public function create()
{ {
return view('fiturs.create'); return view('admin.fitur.create');
} }
// Simpan fitur baru // Simpan fitur baru
public function store(Request $request) public function store(Request $request)
{ {
$data = $request->validate([ $data = $request->validate([
'deskripsi' => 'required|string', 'deskripsi' => 'required|string',
]); ]);
Fitur::create($data); Fitur::create($data);
return redirect()->route('fiturs.index')->with('success', 'Fitur berhasil ditambahkan!'); return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil ditambahkan!');
} }
// Form edit fitur public function update(Request $request, Fitur $fitur)
public function edit(Fitur $fitur) {
{ $data = $request->validate([
return view('fiturs.edit', compact('fitur')); 'deskripsi' => 'required|string',
} ]);
// Update fitur $fitur->update($data);
public function update(Request $request, Fitur $fitur)
{ return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil diperbarui!');
$data = $request->validate([ }
'deskripsi' => 'required|string',
]);
$fitur->update($data);
return redirect()->route('fiturs.index')->with('success', 'Fitur berhasil diperbarui!');
}
// Hapus fitur // Hapus fitur
public function destroy(Fitur $fitur) public function destroy(Fitur $fitur)
{ {
$fitur->delete(); $fitur->delete();
return redirect()->route('fiturs.index')->with('success', 'Fitur berhasil dihapus!'); return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil dihapus!');
} }
} }

View File

@ -10,13 +10,13 @@ class KategoriController extends Controller
{ {
public function index() public function index()
{ {
$kategoris = Kategori::all(); $kategori = Kategori::all();
return view('kategoris.index', compact('kategoris')); return view('admin.kategori.index', compact('kategori'));
} }
public function create() public function create()
{ {
return view('kategoris.create'); return view('admin.kategori.create');
} }
public function store(Request $request) public function store(Request $request)
@ -24,16 +24,20 @@ class KategoriController extends Controller
$data = $request->validate([ $data = $request->validate([
'nama' => 'required|string|max:255', 'nama' => 'required|string|max:255',
'deskripsi' => 'nullable|string', 'deskripsi' => 'nullable|string',
'foto' => 'nullable|string', 'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
]); ]);
if ($request->hasFile('foto')) {
$data['foto'] = $request->file('foto')->store('kategori', 'public');
}
Kategori::create($data); Kategori::create($data);
return redirect()->route('kategoris.index')->with('success', 'Kategori berhasil ditambahkan!'); return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil ditambahkan!');
} }
public function edit(Kategori $kategori) public function edit(Kategori $kategori)
{ {
return view('kategoris.edit', compact('kategori')); return view('admin.kategori.edit', compact('kategori'));
} }
public function update(Request $request, Kategori $kategori) public function update(Request $request, Kategori $kategori)
@ -41,16 +45,20 @@ class KategoriController extends Controller
$data = $request->validate([ $data = $request->validate([
'nama' => 'required|string|max:255', 'nama' => 'required|string|max:255',
'deskripsi' => 'nullable|string', 'deskripsi' => 'nullable|string',
'foto' => 'nullable|string', 'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
]); ]);
if ($request->hasFile('foto')) {
$data['foto'] = $request->file('foto')->store('kategori', 'public');
}
$kategori->update($data); $kategori->update($data);
return redirect()->route('kategoris.index')->with('success', 'Kategori berhasil diperbarui!'); return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil diperbarui!');
} }
public function destroy(Kategori $kategori) public function destroy(Kategori $kategori)
{ {
$kategori->delete(); $kategori->delete();
return redirect()->route('kategoris.index')->with('success', 'Kategori berhasil dihapus!'); return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil dihapus!');
} }
} }

View File

@ -10,20 +10,20 @@ class PelangganController extends Controller
// Tampilkan semua pelanggan (admin) // Tampilkan semua pelanggan (admin)
public function index() public function index()
{ {
$pelanggans = Pelanggan::with('details')->get(); $pelanggans = Pelanggan::all();
return view('pelanggans.index', compact('pelanggans')); return view('admin.pelanggan.index', compact('pelanggans'));
} }
// Detail pelanggan // Detail pelanggan
public function show(Pelanggan $pelanggan) public function show(Pelanggan $pelanggan)
{ {
return view('pelanggans.show', compact('pelanggan')); return view('admin.pelanggan.show', compact('pelanggan'));
} }
// Hapus pelanggan // Hapus pelanggan
public function destroy(Pelanggan $pelanggan) public function destroy(Pelanggan $pelanggan)
{ {
$pelanggan->delete(); $pelanggan->delete();
return redirect()->route('pelanggans.index')->with('success', 'Pelanggan berhasil dihapus!'); return redirect()->route('admin.pelanggan.index')->with('success', 'Pelanggan berhasil dihapus!');
} }
} }

View File

@ -6,20 +6,17 @@ use App\Models\Template;
use App\Models\Kategori; use App\Models\Kategori;
use App\Models\Fitur; use App\Models\Fitur;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class TemplateController extends Controller class TemplateController extends Controller
{ {
public function index() public function index()
{ {
$templates = Template::with(['kategori','fitur'])->get(); $templates = Template::with(['kategori','fitur'])->get();
return view('templates.index', compact('templates'));
}
public function create()
{
$kategoris = Kategori::all(); $kategoris = Kategori::all();
$fiturs = Fitur::all(); $fiturs = Fitur::all();
return view('templates.create', compact('kategoris', 'fiturs'));
return view('admin.templates.index', compact('templates', 'kategoris', 'fiturs'));
} }
public function store(Request $request) public function store(Request $request)
@ -28,36 +25,56 @@ class TemplateController extends Controller
'nama_template' => 'required|string|max:255', 'nama_template' => 'required|string|max:255',
'kategori_id' => 'required|exists:kategoris,id', 'kategori_id' => 'required|exists:kategoris,id',
'fitur_id' => 'required|exists:fiturs,id', 'fitur_id' => 'required|exists:fiturs,id',
'foto' => 'nullable|string', 'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
]); ]);
if ($request->hasFile('foto')) {
$data['foto'] = $request->file('foto')->store('templates', 'public');
}
Template::create($data); Template::create($data);
return redirect()->route('templates.index')->with('success', 'Template berhasil ditambahkan!'); return redirect()->route('templates.index')->with('success', 'Template berhasil ditambahkan!');
} }
public function edit(Template $template)
{
$kategoris = Kategori::all();
$fiturs = Fitur::all();
return view('templates.edit', compact('template','kategoris','fiturs'));
}
public function update(Request $request, Template $template) public function update(Request $request, Template $template)
{ {
$data = $request->validate([ $data = $request->validate([
'nama_template' => 'required|string|max:255', 'nama_template' => 'required|string|max:255',
'kategori_id' => 'required|exists:kategoris,id', 'kategori_id' => 'required|exists:kategoris,id',
'fitur_id' => 'required|exists:fiturs,id', 'fitur_id' => 'required|exists:fiturs,id',
'foto' => 'nullable|string', 'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
]); ]);
if ($request->hasFile('foto')) {
if ($template->foto && Storage::disk('public')->exists($template->foto)) {
Storage::disk('public')->delete($template->foto);
}
$data['foto'] = $request->file('foto')->store('templates', 'public');
}
$template->update($data); $template->update($data);
return redirect()->route('templates.index')->with('success', 'Template berhasil diperbarui!'); return redirect()->route('templates.index')->with('success', 'Template berhasil diperbarui!');
} }
public function destroy(Template $template) public function destroy(Template $template)
{ {
if ($template->foto && Storage::disk('public')->exists($template->foto)) {
Storage::disk('public')->delete($template->foto);
}
$template->delete(); $template->delete();
return redirect()->route('templates.index')->with('success', 'Template berhasil dihapus!'); return redirect()->route('templates.index')->with('success', 'Template berhasil dihapus!');
} }
public function byKategori($id)
{
$kategori = Kategori::findOrFail($id);
$templates = Template::with(['kategori','fitur'])
->where('kategori_id', $id)
->get();
$kategoris = Kategori::all();
$fiturs = Fitur::all();
return view('admin.templates.index', compact('templates', 'kategoris', 'fiturs', 'kategori'));
}
} }

View File

@ -1,4 +1,4 @@
<? <?php
// database/migrations/2025_09_08_000001_create_kategoris_table.php // database/migrations/2025_09_08_000001_create_kategoris_table.php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;

View File

@ -3,107 +3,162 @@
@section('title', 'Manajemen Fitur') @section('title', 'Manajemen Fitur')
@section('content') @section('content')
<div class="container mx-auto py-4"> <div class="container mx-auto py-4">
<!-- Header --> <!-- Header -->
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">Manajemen Fitur</h3> <h3 class="text-xl font-bold">Manajemen Fitur</h3>
<button class="bg-blue-600 text-white px-3 py-1 rounded" data-bs-toggle="modal" data-bs-target="#modalTambah"> <button id="openTambahModal" class="bg-blue-600 text-white px-3 py-1 rounded flex items-center">
<i class="bi bi-plus-lg mr-1"></i> Tambah Fitur <i class="bi bi-plus-lg mr-1"></i> Tambah Fitur
</button> </button>
</div> </div>
<!-- Tabel Fitur -->
<div class="bg-white rounded-lg shadow-sm">
<div class="p-4">
<table class="w-full table-fixed border border-gray-300 text-left">
<thead class="bg-gray-100">
<tr>
<th class="w-[10%] p-2 border border-gray-300">Nomor</th>
<th class="w-[70%] p-2 border border-gray-300">Fitur</th>
<th class="w-[20%] p-2 border border-gray-300 text-center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse ($fiturs as $key => $fitur)
<tr class="hover:bg-gray-50">
<td class="p-2 border border-gray-300">{{ $key + 1 }}</td>
<td class="p-2 border border-gray-300 truncate whitespace-nowrap">{{ $fitur->nama_fitur }}</td>
<td class="p-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" data-bs-toggle="modal" data-bs-target="#modalEdit{{ $fitur->id }}">
<i class="bi bi-pencil mr-1"></i> Ubah
</button>
<form action="{{ route('admin.fitur.destroy', $fitur->id) }}"
method="POST"
class="inline"
onsubmit="return confirm('Yakin mau hapus fitur ini?')">
@csrf
@method('DELETE')
<button class="text-red-600 flex items-center">
<i class="bi bi-trash mr-1"></i> Hapus
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="p-2 text-center text-gray-500">Belum ada fitur</td>
</tr>
@endforelse
</tbody>
</table>
<!-- 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-[10%] p-2 border border-gray-300">Nomor</th>
<th class="w-[70%] p-2 border border-gray-300">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="p-2 border border-gray-300">{{ $key + 1 }}</td>
<td class="p-2 border border-gray-300 truncate whitespace-nowrap">{{ $item->deskripsi }}
</td>
<td class="p-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>
<form action="{{ route('admin.fitur.destroy', $item->id) }}" method="POST"
class="inline" onsubmit="return confirm('Yakin mau hapus fitur ini?')">
@csrf
@method('DELETE')
<button class="text-red-600 flex items-center">
<i class="bi bi-trash mr-1"></i> Hapus
</button>
</form>
</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> </div>
</div> </div>
</div>
<!-- Modal Tambah --> <!-- Modal Tambah -->
<div class="modal fade" id="modalTambah" tabindex="-1"> <div id="modalTambah" class="fixed inset-0 hidden items-center justify-center z-50">
<div class="modal-dialog"> <div class="absolute inset-0 bg-black opacity-50" id="closeTambahModal"></div>
<form action="{{ route('admin.fitur.store') }}" method="POST" class="modal-content"> <div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
@csrf <form action="{{ route('admin.fitur.store') }}" method="POST">
<div class="modal-header"> @csrf
<h5 class="modal-title text-lg font-medium">Tambah Fitur</h5> <div class="p-4 border-b">
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <h5 class="text-lg font-medium">Tambah Fitur</h5>
</div>
<div class="modal-body">
<div class="mb-4">
<label class="block text-sm font-medium">Nama Fitur</label>
<input type="text" name="nama_fitur" class="w-full p-2 border rounded" required>
</div> </div>
</div> <div class="p-4 space-y-4">
<div class="modal-footer"> <div>
<button class="bg-gray-300 text-black px-3 py-1 rounded" data-bs-dismiss="modal">Batal</button> <label class="block text-sm font-medium">Nama Fitur</label>
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button> <input type="text" name="deskripsi" class="w-full p-2 border rounded" required>
</div> </div>
</form> </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 class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button>
</div>
</form>
</div>
</div> </div>
</div>
<!-- Modal Edit --> <!-- Modal Edit -->
@foreach ($fiturs as $fitur) @foreach ($fitur as $item)
<div class="modal fade" id="modalEdit{{ $fitur->id }}" tabindex="-1"> <div id="modalEdit{{ $item->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
<div class="modal-dialog"> <div class="absolute inset-0 bg-black opacity-50 closeEditOverlay" data-id="{{ $item->id }}"></div>
<form action="{{ route('admin.fitur.update', $fitur->id) }}" method="POST" class="modal-content"> <div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
@csrf <form action="{{ route('admin.fitur.update', $item->id) }}" method="POST">
@method('PUT') @csrf
<div class="modal-header"> @method('PUT')
<h5 class="modal-title text-lg font-medium">Edit Fitur</h5> <div class="p-4 border-b">
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <h5 class="text-lg font-medium">Edit Fitur</h5>
</div>
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium">Nama Fitur</label>
<input type="text" name="deskripsi" value="{{ $item->deskripsi }}"
class="w-full p-2 border rounded" required>
</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="{{ $item->id }}">Batal</button>
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button>
</div>
</form>
</div> </div>
<div class="modal-body"> </div>
<div class="mb-4"> @endforeach
<label class="block text-sm font-medium">Nama Fitur</label>
<input type="text" name="nama_fitur" value="{{ $fitur->nama_fitur }}" class="w-full p-2 border rounded" required> <script>
</div> // Modal Tambah
</div> const openTambahModal = document.getElementById('openTambahModal');
<div class="modal-footer"> const modalTambah = document.getElementById('modalTambah');
<button class="bg-gray-300 text-black px-3 py-1 rounded" data-bs-dismiss="modal">Batal</button> const closeTambahBtn = document.getElementById('closeTambahBtn');
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button> const closeTambahOverlay = document.getElementById('closeTambahModal');
</div>
</form> openTambahModal.addEventListener('click', () => {
</div> modalTambah.classList.remove('hidden');
</div> modalTambah.classList.add('flex');
@endforeach });
closeTambahBtn.addEventListener('click', () => {
modalTambah.classList.add('hidden');
modalTambah.classList.remove('flex');
});
closeTambahOverlay.addEventListener('click', () => {
modalTambah.classList.add('hidden');
modalTambah.classList.remove('flex');
});
// Modal Edit
const openEditBtns = document.querySelectorAll('.openEditModalBtn');
const closeEditBtns = document.querySelectorAll('.closeEditBtn');
const closeEditOverlays = document.querySelectorAll('.closeEditOverlay');
openEditBtns.forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.dataset.id;
const modal = document.getElementById('modalEdit' + id);
modal.classList.remove('hidden');
modal.classList.add('flex');
});
});
closeEditBtns.forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.dataset.id;
const modal = document.getElementById('modalEdit' + id);
modal.classList.add('hidden');
modal.classList.remove('flex');
});
});
closeEditOverlays.forEach(overlay => {
overlay.addEventListener('click', () => {
const id = overlay.dataset.id;
const modal = document.getElementById('modalEdit' + id);
modal.classList.add('hidden');
modal.classList.remove('flex');
});
});
</script>
@endsection @endsection

View File

@ -7,7 +7,7 @@
<!-- Header --> <!-- Header -->
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">Manajemen Kategori</h3> <h3 class="text-xl font-bold">Manajemen Kategori</h3>
<button class="bg-blue-600 text-white px-3 py-2.5 rounded" data-bs-toggle="modal" data-bs-target="#modalTambah"> <button id="openTambahModal" class="bg-blue-600 text-white px-3 py-2.5 rounded flex items-center">
<i class="bi bi-plus-lg mr-1"></i> Nambah Kategori <i class="bi bi-plus-lg mr-1"></i> Nambah Kategori
</button> </button>
</div> </div>
@ -41,7 +41,7 @@
<!-- Tabel Kategori --> <!-- Tabel Kategori -->
<div class="bg-white rounded-lg shadow-sm"> <div class="bg-white rounded-lg shadow-sm">
<div class="p-4"> <div class="p-4 overflow-x-auto">
<table class="w-full table-fixed text-left border border-gray-300 border-collapse"> <table class="w-full table-fixed text-left border border-gray-300 border-collapse">
<thead class="bg-gray-100"> <thead class="bg-gray-100">
<tr> <tr>
@ -57,25 +57,19 @@
<tr> <tr>
<td class="p-2 border border-gray-300 text-center truncate">{{ $key + 1 }}</td> <td class="p-2 border border-gray-300 text-center truncate">{{ $key + 1 }}</td>
<td class="p-2 border border-gray-300 truncate">{{ $item->nama }}</td> <td class="p-2 border border-gray-300 truncate">{{ $item->nama }}</td>
<td class="p-2 border border-gray-300 truncate"> <td class="p-2 border border-gray-300 truncate">{{ $item->deskripsi ?? '-' }}</td>
{{ $item->deskripsi ?? '-' }}
</td>
<td class="p-2 border border-gray-300 text-center"> <td class="p-2 border border-gray-300 text-center">
<div <div
class="w-12 h-12 overflow-hidden rounded bg-gray-100 flex items-center justify-center mx-auto"> class="w-12 h-12 overflow-hidden rounded bg-gray-100 flex items-center justify-center mx-auto">
@if ($item->foto) <img src="{{ $item->foto ? asset('storage/' . $item->foto) : asset('default-image.png') }}"
<img src="{{ asset('storage/' . $item->foto) }}" alt="foto" alt="foto" class="max-w-full max-h-full object-contain">
class="max-w-full max-h-full object-contain">
@else
<img src="{{ asset('default-image.png') }}" alt="default"
class="max-w-full max-h-full object-contain">
@endif
</div> </div>
</td> </td>
<td class="p-2 border border-gray-300 text-center"> <td class="p-2 border border-gray-300 text-center">
<div class="flex justify-center space-x-2"> <div class="flex justify-center space-x-2">
<button class="text-blue-600 hover:underline flex items-center pr-4" <button
data-bs-toggle="modal" data-bs-target="#modalEdit{{ $item->id }}"> class="text-blue-600 hover:underline flex items-center pr-4 openEditModalBtn"
data-id="{{ $item->id }}">
<i class="bi bi-pencil mr-1"></i> Ubah <i class="bi bi-pencil mr-1"></i> Ubah
</button> </button>
<form action="{{ route('admin.kategori.destroy', $item->id) }}" method="POST" <form action="{{ route('admin.kategori.destroy', $item->id) }}" method="POST"
@ -97,88 +91,141 @@
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div>
<div class="modal fade" id="modalTambah" tabindex="-1"> <!-- Modal Tambah -->
<div class="modal-dialog"> <div id="modalTambah" class="fixed inset-0 hidden items-center justify-center z-50">
<form action="{{ route('admin.kategori.store') }}" method="POST" enctype="multipart/form-data" <div class="absolute inset-0 bg-black opacity-50" id="closeTambahModal"></div>
class="modal-content"> <div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
@csrf <form action="{{ route('admin.kategori.store') }}" method="POST" enctype="multipart/form-data">
<div class="modal-header"> @csrf
<h5 class="modal-title text-lg font-medium">Tambah Kategori</h5> <div class="p-4 border-b">
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <h5 class="text-lg font-medium">Tambah Kategori</h5>
</div>
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium">Nama</label>
<input type="text" name="nama" class="w-full p-2 border rounded" required>
</div> </div>
<div class="modal-body"> <div>
<div class="mb-4"> <label class="block text-sm font-medium">Deskripsi</label>
<textarea name="deskripsi" class="w-full p-2 border rounded" rows="3"></textarea>
</div>
<div>
<label class="block text-sm font-medium">Foto</label>
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
<small class="text-gray-500">Format yang didukung: JPG, PNG, GIF. Maksimal 5MB.</small>
</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 class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button>
</div>
</form>
</div>
</div>
<!-- Modal Edit -->
@foreach ($kategori as $item)
<div id="modalEdit{{ $item->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
<div class="absolute inset-0 bg-black opacity-50 closeEditOverlay" data-id="{{ $item->id }}"></div>
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
<form action="{{ route('admin.kategori.update', $item->id) }}" method="POST"
enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="p-4 border-b">
<h5 class="text-lg font-medium">Edit Kategori</h5>
</div>
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium">Nama</label> <label class="block text-sm font-medium">Nama</label>
<input type="text" name="nama" class="w-full p-2 border rounded" required> <input type="text" name="nama" value="{{ $item->nama }}"
class="w-full p-2 border rounded" required>
</div> </div>
<div class="mb-4"> <div>
<label class="block text-sm font-medium">Deskripsi</label> <label class="block text-sm font-medium">Deskripsi</label>
<textarea name="deskripsi" class="w-full p-2 border rounded" rows="3"></textarea> <textarea name="deskripsi" class="w-full p-2 border rounded" rows="3">{{ $item->deskripsi }}</textarea>
</div> </div>
<div class="mb-4"> <div>
<label class="block text-sm font-medium">Foto</label> <label class="block text-sm font-medium">Foto</label>
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*"> <input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
<small class="text-gray-500">Format yang didukung: JPG, PNG, GIF. Maksimal 5MB.</small> <small class="text-gray-500">Format yang didukung: JPG, PNG, GIF. Maksimal 5MB.</small>
@if ($item->foto)
<div class="mt-2">
<small class="text-gray-500">Foto saat ini:</small><br>
<div
class="w-20 h-20 mt-1 overflow-hidden rounded bg-gray-100 flex items-center justify-center border">
<img src="{{ asset('storage/' . $item->foto) }}" alt="foto"
class="max-w-full max-h-full object-contain">
</div>
</div>
@endif
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="p-4 border-t flex justify-end space-x-2">
<button class="bg-gray-300 text-black px-3 py-1 rounded" data-bs-dismiss="modal">Batal</button> <button type="button" class="bg-gray-300 text-black px-3 py-1 rounded closeEditBtn"
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button> data-id="{{ $item->id }}">Batal</button>
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
@endforeach
<!-- Modal Edit --> <script>
@foreach ($kategori as $item) // Modal Tambah
<div class="modal fade" id="modalEdit{{ $item->id }}" tabindex="-1"> const openTambahModal = document.getElementById('openTambahModal');
<div class="modal-dialog"> const modalTambah = document.getElementById('modalTambah');
<form action="{{ route('admin.kategori.update', $item->id) }}" method="POST" const closeTambahBtn = document.getElementById('closeTambahBtn');
enctype="multipart/form-data" class="modal-content"> const closeTambahOverlay = document.getElementById('closeTambahModal');
@csrf
@method('PUT') openTambahModal.addEventListener('click', () => {
<div class="modal-header"> modalTambah.classList.remove('hidden');
<h5 class="modal-title text-lg font-medium">Edit Kategori</h5> modalTambah.classList.add('flex');
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> });
</div> closeTambahBtn.addEventListener('click', () => {
<div class="modal-body"> modalTambah.classList.add('hidden');
<div class="mb-4"> modalTambah.classList.remove('flex');
<label class="block text-sm font-medium">Nama</label> });
<input type="text" name="nama" value="{{ $item->nama }}" closeTambahOverlay.addEventListener('click', () => {
class="w-full p-2 border rounded" required> modalTambah.classList.add('hidden');
</div> modalTambah.classList.remove('flex');
<div class="mb-4"> });
<label class="block text-sm font-medium">Deskripsi</label>
<textarea name="deskripsi" class="w-full p-2 border rounded" rows="3">{{ $item->deskripsi }}</textarea> // Modal Edit
</div> const openEditBtns = document.querySelectorAll('.openEditModalBtn');
<div class="mb-4"> const closeEditBtns = document.querySelectorAll('.closeEditBtn');
<label class="block text-sm font-medium">Foto</label> const closeEditOverlays = document.querySelectorAll('.closeEditOverlay');
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
<small class="text-gray-500">Format yang didukung: JPG, PNG, GIF. Maksimal 5MB.</small> openEditBtns.forEach(btn => {
@if ($item->foto) btn.addEventListener('click', () => {
<div class="mt-2"> const id = btn.dataset.id;
<small class="text-gray-500">Foto saat ini:</small><br> const modal = document.getElementById('modalEdit' + id);
<div modal.classList.remove('hidden');
class="w-20 h-20 mt-1 overflow-hidden rounded bg-gray-100 flex items-center justify-center border"> modal.classList.add('flex');
<img src="{{ asset('storage/' . $item->foto) }}" alt="foto" });
class="max-w-full max-h-full object-contain"> });
</div>
</div> closeEditBtns.forEach(btn => {
@endif btn.addEventListener('click', () => {
</div> const id = btn.dataset.id;
</div> const modal = document.getElementById('modalEdit' + id);
<div class="modal-footer"> modal.classList.add('hidden');
<button class="bg-gray-300 text-black px-3 py-1 rounded" modal.classList.remove('flex');
data-bs-dismiss="modal">Batal</button> });
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button> });
</div>
</form> closeEditOverlays.forEach(overlay => {
</div> overlay.addEventListener('click', () => {
</div> const id = overlay.dataset.id;
@endforeach const modal = document.getElementById('modalEdit' + id);
@endsection modal.classList.add('hidden');
modal.classList.remove('flex');
});
});
</script>
@endsection

View File

@ -0,0 +1,133 @@
@extends('layouts.app')
@section('title', 'Manajemen Pelanggan')
@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>
<!-- Flash Message -->
@if (session('success'))
<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>
@endif
<!-- Tabel Pelanggan -->
<div class="bg-white rounded-lg shadow-sm">
<div class="p-4 overflow-x-auto">
<table class="w-full table-fixed text-left border border-gray-300 border-collapse">
<thead class="bg-gray-100">
<tr>
<th class="p-2 border border-gray-300 w-[50px] text-center">No</th>
<th class="p-2 border border-gray-300">Nama</th>
<th class="p-2 border border-gray-300">Email</th>
<th class="p-2 border border-gray-300">No. Telepon</th>
<th class="p-2 border border-gray-300 text-center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($pelanggans as $key => $pelanggan)
<tr>
<td class="p-2 border border-gray-300 text-center">{{ $key + 1 }}</td>
<td class="p-2 border border-gray-300 truncate">{{ $pelanggan->nama_pemesan }}</td>
<td class="p-2 border border-gray-300 truncate">{{ $pelanggan->email }}</td>
<td class="p-2 border border-gray-300 truncate">{{ $pelanggan->no_tlpn ?? '-' }}</td>
<td class="p-2 border border-gray-300 text-center">
<div class="flex justify-center space-x-2">
<a href="{{ route('admin.pelanggan.show', $pelanggan->id) }}"
class="text-blue-600 hover:underline flex items-center">
<i class="bi bi-eye mr-1"></i> Detail
</a>
<button class="text-red-600 hover:underline flex items-center openDeleteModalBtn"
data-id="{{ $pelanggan->id }}">
<i class="bi bi-trash mr-1"></i> Hapus
</button>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="p-2 text-center text-gray-500 border border-gray-300">Belum ada
pelanggan</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal Hapus Pelanggan -->
@foreach ($pelanggans as $pelanggan)
<div id="modalDelete{{ $pelanggan->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
<div class="absolute inset-0 bg-black opacity-50 closeDeleteOverlay" data-id="{{ $pelanggan->id }}"></div>
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
<div class="p-4 border-b">
<h5 class="text-lg font-medium">Hapus Pelanggan</h5>
</div>
<div class="p-4">
<p>Apakah Anda yakin ingin menghapus pelanggan <strong>{{ $pelanggan->nama }}</strong>?</p>
</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 closeDeleteBtn"
data-id="{{ $pelanggan->id }}">Batal</button>
<form action="{{ route('admin.pelanggan.destroy', $pelanggan->id) }}" method="POST" class="inline">
@csrf
@method('DELETE')
<button class="bg-red-600 text-white px-3 py-1 rounded">Hapus</button>
</form>
</div>
</div>
</div>
@endforeach
<script>
document.addEventListener('DOMContentLoaded', () => {
const openDeleteBtns = document.querySelectorAll('.openDeleteModalBtn');
const closeDeleteBtns = document.querySelectorAll('.closeDeleteBtn');
const closeDeleteOverlays = document.querySelectorAll('.closeDeleteOverlay');
openDeleteBtns.forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.dataset.id;
const modal = document.getElementById('modalDelete' + id);
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
}
});
});
closeDeleteBtns.forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.dataset.id;
const modal = document.getElementById('modalDelete' + id);
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
});
});
closeDeleteOverlays.forEach(overlay => {
overlay.addEventListener('click', () => {
const id = overlay.dataset.id;
const modal = document.getElementById('modalDelete' + id);
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
});
});
});
</script>
@endsection

View File

@ -66,43 +66,54 @@
</table> </table>
</div> </div>
</div> </div>
</div>
<!-- Modal Konfirmasi Hapus Tailwind -->
<!-- Modal Konfirmasi Hapus --> <div id="modalDelete" class="fixed inset-0 hidden items-center justify-center z-50">
<div class="modal fade" id="confirmDeleteModal" tabindex="-1"> <div class="absolute inset-0 bg-black opacity-50" id="modalDeleteOverlay"></div>
<div class="modal-dialog"> <div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
<form id="deleteForm" method="POST" class="modal-content"> <form id="deleteForm" method="POST">
@csrf @csrf
@method('DELETE') @method('DELETE')
<div class="modal-header"> <div class="p-4 border-b">
<h5 class="modal-title text-lg font-medium">Hapus Ulasan</h5> <h5 class="text-lg font-medium">Hapus Ulasan</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> </div>
</div> <div class="p-4">
<div class="modal-body"> Apakah Anda yakin ingin menghapus ulasan dari <strong id="deleteName"></strong>?
Apakah Anda yakin ingin menghapus ulasan dari <strong id="deleteName"></strong>? </div>
</div> <div class="p-4 border-t flex justify-end space-x-2">
<div class="modal-footer"> <button type="button" id="closeDeleteModal"
<button type="button" class="bg-gray-300 text-black px-3 py-1 rounded" class="bg-gray-300 text-black px-3 py-1 rounded">Batal</button>
data-bs-dismiss="modal">Batal</button> <button type="submit" class="bg-red-600 text-white px-3 py-1 rounded">Ya, Hapus</button>
<button type="submit" class="bg-red-600 text-white px-3 py-1 rounded">Ya, Hapus</button> </div>
</div> </form>
</form>
</div>
</div> </div>
</div>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const modalEl = document.getElementById('confirmDeleteModal'); const modal = document.getElementById('modalDelete');
const deleteForm = document.getElementById('deleteForm'); const overlay = document.getElementById('modalDeleteOverlay');
const deleteName = document.getElementById('deleteName'); const closeBtn = document.getElementById('closeDeleteModal');
const deleteForm = document.getElementById('deleteForm');
const deleteName = document.getElementById('deleteName');
document.querySelectorAll('.btn-delete').forEach(btn => { document.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
deleteForm.action = btn.dataset.action; deleteForm.action = btn.dataset.action;
deleteName.textContent = btn.dataset.name || 'pengguna ini'; deleteName.textContent = btn.dataset.name || 'pengguna ini';
new bootstrap.Modal(modalEl).show(); modal.classList.remove('hidden');
}); modal.classList.add('flex');
}); });
}); });
</script>
@endsection const closeModal = () => {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
closeBtn.addEventListener('click', closeModal);
overlay.addEventListener('click', closeModal);
});
</script>
@endsection

View File

@ -0,0 +1,277 @@
@extends('layouts.app')
@section('title', 'Manajemen Template')
@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">
@isset($kategori)
Template Kategori: {{ $kategori->nama }}
@else
Semua Template
@endisset
</h3>
@if (!isset($kategori))
<button id="openTambahModal" class="bg-blue-600 text-white px-3 py-2.5 rounded flex items-center">
<i class="bi bi-plus-lg mr-1"></i> Tambah Template
</button>
@endif
</div>
<!-- Flash Message -->
@if (session('success'))
<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>
@endif
@if ($errors->any())
<div id="toast-error" class="mb-4 p-3 rounded bg-red-100 text-red-800 border border-red-300 shadow">
<ul class="list-disc ml-5">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
<script>
setTimeout(() => document.getElementById('toast-error')?.remove(), 5000);
</script>
@endif
<!-- Tabel Template -->
<div class="bg-white rounded-lg shadow-sm">
<div class="p-4 overflow-x-auto">
<table class="w-full table-fixed text-left border border-gray-300 border-collapse">
<thead class="bg-gray-100">
<tr>
<th class="p-2 border border-gray-300 w-[50px] text-center">No</th>
<th class="p-2 border border-gray-300 w-[200px]">Nama Template</th>
<th class="p-2 border border-gray-300 w-[150px]">Kategori</th>
<th class="p-2 border border-gray-300 w-[150px]">Fitur</th>
<th class="p-2 border border-gray-300 w-[90px] text-center">Foto</th>
<th class="p-2 border border-gray-300 w-[130px] text-center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($templates as $key => $template)
<tr>
<td class="p-2 border border-gray-300 text-center">{{ $key + 1 }}</td>
<td class="p-2 border border-gray-300 truncate">{{ $template->nama_template }}</td>
<td class="p-2 border border-gray-300 truncate">{{ $template->kategori->nama ?? '-' }}</td>
<td class="p-2 border border-gray-300 truncate">{{ $template->fitur->deskripsi ?? '-' }}
</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">
<img src="{{ $template->foto ? asset('storage/' . $template->foto) : asset('default-image.png') }}"
alt="foto" class="max-w-full max-h-full object-contain">
</div>
</td>
<td class="p-2 border border-gray-300 text-center">
<div class="flex justify-center space-x-2">
<button class="text-blue-600 hover:underline flex items-center openEditModalBtn"
data-id="{{ $template->id }}">
<i class="bi bi-pencil mr-1"></i> Edit
</button>
<form action="{{ route('templates.destroy', $template->id) }}" method="POST"
class="inline" onsubmit="return confirm('Hapus template ini?')">
@csrf
@method('DELETE')
<button class="text-red-600 hover:underline flex items-center">
<i class="bi bi-trash mr-1"></i> Hapus
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="p-2 text-center text-gray-500 border border-gray-300">Belum ada
template</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal Tambah Template -->
@if (!isset($kategori))
<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>
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
<form action="{{ route('templates.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="p-4 border-b">
<h5 class="text-lg font-medium">Tambah Template</h5>
</div>
<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>
</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 $kategori)
<option value="{{ $kategori->id }}">{{ $kategori->nama }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium">Fitur</label>
<select name="fitur_id" class="w-full p-2 border rounded" required>
@foreach ($fiturs as $fitur)
<option value="{{ $fitur->id }}">{{ $fitur->deskripsi }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium">Foto (opsional)</label>
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
<small class="text-gray-500">Format yang didukung: JPG, PNG, GIF. Maksimal 5MB.</small>
</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 class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button>
</div>
</form>
</div>
</div>
@endif
<!-- Modal Edit Template -->
@foreach ($templates as $template)
<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">
@csrf @method('PUT')
<div class="p-4 border-b">
<h5 class="text-lg font-medium">Edit Template</h5>
</div>
<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="{{ $template->nama_template }}"
class="w-full p-2 border rounded" required>
</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 $kategori)
<option value="{{ $kategori->id }}" @selected($kategori->id == $template->kategori_id)>{{ $kategori->nama }}
</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium">Fitur</label>
<select name="fitur_id" class="w-full p-2 border rounded" required>
@foreach ($fiturs as $fitur)
<option value="{{ $fitur->id }}" @selected($fitur->id == $template->fitur_id)>
{{ $fitur->deskripsi }}</option>
@endforeach
</select>
</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>
</div>
@endif
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
</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 class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button>
</div>
</form>
</div>
</div>
@endforeach
<script>
document.addEventListener('DOMContentLoaded', () => {
// Modal Tambah
const openTambahModal = document.getElementById('openTambahModal');
const modalTambah = document.getElementById('modalTambah');
const closeTambahBtn = document.getElementById('closeTambahBtn');
const closeTambahOverlay = document.getElementById('closeTambahModal');
if (openTambahModal) {
openTambahModal.addEventListener('click', () => {
modalTambah.classList.remove('hidden');
modalTambah.classList.add('flex');
});
}
if (closeTambahBtn) {
closeTambahBtn.addEventListener('click', () => {
modalTambah.classList.add('hidden');
modalTambah.classList.remove('flex');
});
}
if (closeTambahOverlay) {
closeTambahOverlay.addEventListener('click', () => {
modalTambah.classList.add('hidden');
modalTambah.classList.remove('flex');
});
}
// Modal Edit
const openEditBtns = document.querySelectorAll('.openEditModalBtn');
const closeEditBtns = document.querySelectorAll('.closeEditBtn');
const closeEditOverlays = document.querySelectorAll('.closeEditOverlay');
openEditBtns.forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.dataset.id;
const modal = document.getElementById('modalEdit' + id);
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
}
});
});
closeEditBtns.forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.dataset.id;
const modal = document.getElementById('modalEdit' + id);
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
});
});
closeEditOverlays.forEach(overlay => {
overlay.addEventListener('click', () => {
const id = overlay.dataset.id;
const modal = document.getElementById('modalEdit' + id);
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
});
});
});
</script>
@endsection

View File

@ -5,16 +5,15 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title', 'Admin Panel')</title> <title>@yield('title', 'Admin Panel')</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style> <style>
body { body {
background-color: #f1f5f9; background-color: #f1f5f9;
font-family: 'Poppins', sans-serif; font-family: 'Poppins', sans-serif;
} }
/* SIDEBAR */ /* Sidebar */
.sidebar { .sidebar {
width: 250px; width: 250px;
height: 100vh; height: 100vh;
@ -29,169 +28,122 @@
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.sidebar .logo {
text-align: center;
padding: 20px 0;
border-bottom: 1px solid #eee;
}
.sidebar .logo img {
width: 130px;
}
.sidebar .menu-title {
font-size: 11px;
font-weight: 600;
padding: 15px 20px 5px;
color: #6c757d;
text-transform: uppercase;
letter-spacing: .5px;
}
.sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar ul li a {
display: flex;
align-items: center;
padding: 12px 20px;
color: #495057;
text-decoration: none;
font-weight: 500;
font-size: 14px;
border-left: 3px solid transparent;
transition: all 0.25s ease-in-out;
}
.sidebar ul li a:hover {
background-color: #f1f5ff;
color: #0d6efd;
border-left: 3px solid #0d6efd;
}
.sidebar ul li a.active {
background-color: #eef4ff;
color: #0d6efd;
border-left: 3px solid #0d6efd;
}
.sidebar ul li a i {
font-size: 18px;
margin-right: 10px;
}
/* Submenu */
.submenu { .submenu {
padding-left: 55px; display: none;
} }
.submenu a { .submenu.show {
font-size: 13px; display: block;
padding: 8px 20px;
color: #6c757d;
}
.submenu a:hover {
color: #0d6efd;
background: transparent;
border-left: none;
}
/* MAIN CONTENT */
.main-content {
margin-left: 250px;
padding: 25px;
min-height: 100vh;
} }
</style> </style>
</head> </head>
<body> <body class="flex">
<!-- Sidebar -->
<div class="sidebar flex flex-col">
<!-- LOGO -->
<div class="text-center py-4 border-b">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="mx-auto mb-2" style="max-height: 80px;">
</div>
<!-- Sidebar --> <!-- MENU -->
<div id="sidebar" class="sidebar bg-white d-flex flex-column"> <div class="flex-1 overflow-y-auto">
<p class="text-gray-500 uppercase text-xs font-semibold px-3 mt-4 mb-2">Menu Utama</p>
<ul class="px-2 space-y-1">
<li>
<a href="{{ route('admin.dashboard') }}"
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->is('admin/dashboard') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
<i class="bi bi-house-door me-2"></i> Dasbor
</a>
</li>
<li>
<a href="{{ route('admin.kategori.index') }}"
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->is('admin/kategori*') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
<i class="bi bi-diagram-3 me-2"></i> Kategori
</a>
</li>
<li>
<a href="{{ route('admin.fitur.index') }}"
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->is('admin/fitur*') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
<i class="bi bi-grid me-2"></i> Fitur
</a>
</li>
<!-- Dropdown Templat -->
<li>
@php
$isTemplatePage = request()->is('templates*'); // cek apakah sedang di halaman template
@endphp
<button
class="w-full flex items-center justify-between py-2 px-3 text-gray-700 rounded hover:bg-blue-50 {{ $isTemplatePage ? 'bg-blue-100 text-blue-600' : '' }}"
id="templatBtn">
<span><i class="bi bi-card-list me-2"></i> Templat</span>
<i class="bi bi-chevron-down transition-transform {{ $isTemplatePage ? 'rotate-180' : '' }}"
id="templatIcon"></i>
</button>
<ul class="submenu pl-6 space-y-1 {{ $isTemplatePage ? 'show' : '' }}" id="templatSubmenu">
<li>
<a href="{{ route('templates.index') }}"
class="block py-2 px-2 rounded {{ request()->is('templates') ? 'bg-blue-100 text-blue-600' : 'text-gray-500 hover:text-blue-600 hover:bg-blue-50' }}">
Semua Template
</a>
</li>
@foreach (\App\Models\Kategori::all() as $kategori)
<li>
<a href="{{ route('templates.byKategori', $kategori->id) }}"
class="block py-2 px-2 rounded {{ request()->is('templates/kategori/' . $kategori->id) ? 'bg-blue-100 text-blue-600' : 'text-gray-500 hover:text-blue-600 hover:bg-blue-50' }}">
{{ $kategori->nama }}
</a>
</li>
@endforeach
</ul>
</li>
<li>
<a href="{{ route('admin.pelanggan.index') }}"
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 text-gray-700">
<i class="bi bi-people me-2"></i> Pelanggan
</a>
</li>
<!-- LOGO --> <li>
<div class="sidebar-header text-center py-4 px-10"> <a href="{{ route('admin.reviews.index') }}"
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid mb-2" style="max-height: 80px;"> class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->is('admin/ulasan') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
<i class="bi bi-chat-dots 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>
<ul class="px-2 space-y-1">
<li>
<a href="{{ route('admin.logout') }}"
onclick="event.preventDefault(); document.getElementById('logout-form').submit();"
class="flex items-center py-2 px-3 text-red-600 font-semibold hover:bg-red-50 rounded">
<i class="bi bi-box-arrow-right me-2"></i> Keluar
</a>
<form id="logout-form" action="{{ route('admin.logout') }}" method="POST" class="hidden">
@csrf
</form>
</li>
</ul>
</div>
</div> </div>
<!-- MENU --> <!-- MAIN CONTENT -->
<div class="sidebar-menu flex-grow-1"> <div class="flex-1 ml-[250px] p-6">
<p class="menu-title text-muted px-3 mb-2">Menu Utama</p> @yield('content')
<ul class="list-unstyled px-2">
<li>
<a href="{{ route('admin.dashboard') }}"
class="d-flex align-items-center py-2 px-3 {{ request()->is('admin/dashboard') ? 'active' : '' }}">
<i class="bi bi-house-door me-2"></i> Dasbor
</a>
</li>
<li>
<a href="{{ route('admin.kategori.index') }}"
class="d-flex align-items-center py-2 px-3 {{ request()->is('admin/kategori*') ? 'active' : '' }}">
<i class="bi bi-diagram-3 me-2"></i> Kategori
</a>
</li>
<li>
<a href="{{ route('admin.fitur.index') }}"
class="d-flex align-items-center py-2 px-3 {{ request()->is('admin/fitur*') ? 'active' : '' }}">
<i class="bi bi-grid me-2"></i> Fitur
</a>
</li>
<!-- Dropdown Templat -->
<li>
<a href="#templatSubmenu" data-bs-toggle="collapse" aria-expanded="false"
class="d-flex align-items-center py-2 px-3">
<i class="bi bi-card-list me-2"></i> Templat
<i class="bi bi-chevron-down ms-auto"></i>
</a>
<ul id="templatSubmenu" class="submenu collapse list-unstyled ms-4">
<li><a href="javascript:void(0)" class="d-block py-2">Pernikahan</a></li>
<li><a href="javascript:void(0)" class="d-block py-2">Ulang Tahun</a></li>
<li><a href="javascript:void(0)" class="d-block py-2">Khitan</a></li>
</ul>
</li>
<li>
<a href="javascript:void(0)" class="d-flex align-items-center py-2 px-3">
<i class="bi bi-people me-2"></i> Pelanggan
</a>
</li>
<li>
<a href="{{ route('admin.reviews.index') }}"
class="d-flex align-items-center py-2 px-3 {{ request()->is('admin/ulasan') ? 'active' : '' }}">
<i class="bi bi-chat-dots me-2"></i> Ulasan
</a>
</li>
</ul>
<!-- MENU AKUN -->
<p class="menu-title text-muted px-3 mt-4 mb-2">Akun</p>
<ul class="list-unstyled px-2">
<li>
<a href="{{ route('admin.logout') }}"
onclick="event.preventDefault(); document.getElementById('logout-form').submit();"
class="d-flex align-items-center py-2 px-3 text-danger fw-bold">
<i class="bi bi-box-arrow-right me-2 text-danger"></i> Keluar
</a>
<form id="logout-form" action="{{ route('admin.logout') }}" method="POST" class="d-none">
@csrf
</form>
</li>
</ul>
</div> </div>
</div>
<!-- MAIN CONTENT --> <script>
<div class="main-content"> // Dropdown Templat
@yield('content') const templatBtn = document.getElementById('templatBtn');
</div> const templatSubmenu = document.getElementById('templatSubmenu');
const templatIcon = document.getElementById('templatIcon');
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> templatBtn.addEventListener('click', () => {
templatSubmenu.classList.toggle('show');
templatIcon.classList.toggle('rotate-180');
});
</script>
</body> </body>
</html> </html>

View File

@ -3,11 +3,11 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\ReviewController; use App\Http\Controllers\Api\ReviewController;
use App\Http\Controllers\Api\TemplateController;
use App\Http\Controllers\Api\KategoriApiController; use App\Http\Controllers\Api\KategoriApiController;
use App\Http\Controllers\Api\PernikahanApiController; use App\Http\Controllers\Api\PernikahanApiController;
use App\Http\Controllers\Api\UlangTahunApiController; use App\Http\Controllers\Api\UlangTahunApiController;
use App\Http\Controllers\Api\KhitanApiController; use App\Http\Controllers\Api\KhitanApiController;
use App\Http\Controllers\Api\TemplateApiController;
// Form API (user) // Form API (user)
Route::post('form/pernikahan', [PernikahanApiController::class, 'store']); Route::post('form/pernikahan', [PernikahanApiController::class, 'store']);

View File

@ -27,18 +27,34 @@ Route::prefix('admin')->name('admin.')->group(function () {
}); });
//Kategori //Kategori
Route::resource('kategoris', KategoriController::class)->middleware('auth'); Route::prefix('admin')->name('admin.')->group(function () {
Route::resource('kategori', KategoriController::class);
});
// Route Admin Fitur // Route Admin Fitur
Route::resource('fiturs', FiturController::class)->middleware('auth'); Route::prefix('admin')->name('admin.')->group(function () {
Route::resource('fitur', FiturController::class);
});
// Template
Route::prefix('admin')->group(function () {
Route::get('templates', [TemplateController::class, 'index'])->name('templates.index');
Route::post('templates', [TemplateController::class, 'store'])->name('templates.store');
Route::put('templates/{template}', [TemplateController::class, 'update'])->name('templates.update');
Route::delete('templates/{template}', [TemplateController::class, 'destroy'])->name('templates.destroy');
Route::get('templates/kategori/{id}', [TemplateController::class, 'byKategori'])->name('templates.byKategori');
});
// Route Admin Template
Route::resource('templates', TemplateController::class)->middleware('auth');
// Route Admin Pelanggan // Route Admin Pelanggan
Route::resource('pelanggans', PelangganController::class)->only(['index', 'show', 'destroy'])->middleware('auth'); Route::prefix('admin')->name('admin.')->group(function () {
Route::resource('pelanggan', PelangganController::class)->only([
'index',
'show',
'destroy'
]);
});
@ -65,5 +81,3 @@ Route::prefix('admin')->name('admin.')->middleware('auth:admin')->group(function
return redirect()->route('admin.reviews.index')->with('success', 'Ulasan berhasil dihapus'); return redirect()->route('admin.reviews.index')->with('success', 'Ulasan berhasil dihapus');
})->name('reviews.destroy'); })->name('reviews.destroy');
}); });