Compare commits
39 Commits
production
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
72d25ad801 | ||
|
ac2b203f37 | ||
74840a4fcb | |||
ed60c36fa1 | |||
155c1928fd | |||
0d2ab7b957 | |||
55ae830e1d | |||
36dffc322e | |||
779a8c3232 | |||
3c2ca564fb | |||
78357efab7 | |||
0d87a843c2 | |||
|
982aee2a99 | ||
f09d6b8c9c | |||
fb8236daf3 | |||
07aac28e8a | |||
8a7d2eab98 | |||
44dfdec0b0 | |||
|
bfbc8db1fc | ||
|
2b945a5243 | ||
|
68479e8844 | ||
7cba5c3ebd | |||
0252dc8326 | |||
5c59fffc5c | |||
ead36a80de | |||
3b0879934e | |||
f5bab5de8a | |||
657daa7466 | |||
aeb1d56e85 | |||
31a39e9642 | |||
e6565fc33a | |||
bb5315fad5 | |||
43168c4273 | |||
eae9f9fd28 | |||
21d61178ac | |||
c5486575a3 | |||
acc32b08ca | |||
f38f8a286f | |||
6588ee8c46 |
41
backend/app/Http/Controllers/AdminAuthController.php
Normal file
41
backend/app/Http/Controllers/AdminAuthController.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AdminAuthController extends Controller
|
||||
{
|
||||
public function showLogin()
|
||||
{
|
||||
return view('admin.auth.login');
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
$credentials = $request->validate([
|
||||
'email' => ['required','email'],
|
||||
'password' => ['required'],
|
||||
]);
|
||||
|
||||
$remember = $request->boolean('remember');
|
||||
|
||||
if (Auth::guard('admin')->attempt($credentials, $remember)) {
|
||||
$request->session()->regenerate();
|
||||
return redirect()->intended(route('admin.dashboard'));
|
||||
}
|
||||
|
||||
return back()->withErrors([
|
||||
'email' => 'Email atau password salah.',
|
||||
])->onlyInput('email');
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::guard('admin')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect()->route('admin.login');
|
||||
}
|
||||
}
|
21
backend/app/Http/Controllers/Api/KategoriApiController.php
Normal file
21
backend/app/Http/Controllers/Api/KategoriApiController.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Kategori;
|
||||
|
||||
class KategoriApiController extends Controller
|
||||
{
|
||||
// Ambil semua kategori
|
||||
public function index()
|
||||
{
|
||||
return response()->json(Kategori::all());
|
||||
}
|
||||
|
||||
// Ambil detail satu kategori
|
||||
public function show(Kategori $kategori)
|
||||
{
|
||||
return response()->json($kategori);
|
||||
}
|
||||
}
|
65
backend/app/Http/Controllers/Api/KhitanApiController.php
Normal file
65
backend/app/Http/Controllers/Api/KhitanApiController.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Pelanggan;
|
||||
use App\Models\PelangganDetail;
|
||||
use App\Models\Template; // ✅ tambahkan ini
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class KhitanApiController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'template_id' => 'required|exists:templates,id',
|
||||
'nama_pemesan' => 'required|string|max:255',
|
||||
'no_hp' => 'required|string|max:20',
|
||||
'email' => 'required|email',
|
||||
|
||||
// Anak
|
||||
'nama_lengkap_anak' => 'required|string|max:255',
|
||||
'nama_panggilan_anak' => 'required|string|max:255',
|
||||
'bapak_anak' => 'nullable|string|max:255',
|
||||
'ibu_anak' => 'nullable|string|max:255',
|
||||
|
||||
// Jadwal
|
||||
'hari_tanggal_acara' => 'nullable|date',
|
||||
'waktu_acara' => 'nullable|string',
|
||||
'alamat_acara' => 'nullable|string',
|
||||
'maps_acara' => 'nullable|string',
|
||||
|
||||
// Tambahan
|
||||
'no_rekening1' => 'nullable|string',
|
||||
'no_rekening2' => 'nullable|string',
|
||||
'link_musik' => 'nullable|string',
|
||||
'galeri' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// ✅ Ambil template dari database
|
||||
$template = Template::with('kategori')->findOrFail($data['template_id']);
|
||||
|
||||
// ✅ Simpan ke tabel pelanggan
|
||||
$pelanggan = Pelanggan::create([
|
||||
'nama_pemesan' => $data['nama_pemesan'],
|
||||
'nama_template' => $template->nama_template,
|
||||
'kategori' => $template->kategori->nama ?? 'khitan',
|
||||
'email' => $data['email'],
|
||||
'no_tlpn' => $data['no_hp'],
|
||||
'harga' => $template->harga,
|
||||
]);
|
||||
|
||||
// ✅ Simpan detail form ke tabel pelanggan_details
|
||||
PelangganDetail::create([
|
||||
'pelanggan_id' => $pelanggan->id,
|
||||
'detail_form' => $data,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Form khitan berhasil dikirim',
|
||||
'data' => $pelanggan->load('details')
|
||||
], 201);
|
||||
}
|
||||
}
|
86
backend/app/Http/Controllers/Api/PernikahanApiController.php
Normal file
86
backend/app/Http/Controllers/Api/PernikahanApiController.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Pelanggan;
|
||||
use App\Models\PelangganDetail;
|
||||
use App\Models\Template; // ✅ tambahkan ini
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PernikahanApiController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'template_id' => 'required|exists:templates,id',
|
||||
'nama_pemesan' => 'required|string|max:255',
|
||||
'no_hp' => 'required|string|max:20',
|
||||
'email' => 'required|email',
|
||||
|
||||
// Pria
|
||||
'nama_lengkap_pria' => 'required|string|max:255',
|
||||
'nama_panggilan_pria' => 'required|string|max:255',
|
||||
'bapak_pria' => 'nullable|string|max:255',
|
||||
'ibu_pria' => 'nullable|string|max:255',
|
||||
'instagram_pria' => 'nullable|string',
|
||||
'facebook_pria' => 'nullable|string',
|
||||
'twitter_pria' => 'nullable|string',
|
||||
|
||||
// Wanita
|
||||
'nama_lengkap_wanita' => 'required|string|max:255',
|
||||
'nama_panggilan_wanita' => 'required|string|max:255',
|
||||
'bapak_wanita' => 'nullable|string|max:255',
|
||||
'ibu_wanita' => 'nullable|string|max:255',
|
||||
'instagram_wanita' => 'nullable|string',
|
||||
'facebook_wanita' => 'nullable|string',
|
||||
'twitter_wanita' => 'nullable|string',
|
||||
|
||||
// Cerita
|
||||
'cerita_kita' => 'nullable|string',
|
||||
|
||||
// Akad
|
||||
'hari_tanggal_akad' => 'nullable|date',
|
||||
'waktu_akad' => 'nullable|string',
|
||||
'alamat_akad' => 'nullable|string',
|
||||
'maps_akad' => 'nullable|string',
|
||||
|
||||
// Resepsi
|
||||
'hari_tanggal_resepsi' => 'nullable|date',
|
||||
'waktu_resepsi' => 'nullable|string',
|
||||
'alamat_resepsi' => 'nullable|string',
|
||||
'maps_resepsi' => 'nullable|string',
|
||||
|
||||
// Tambahan
|
||||
'no_rekening1' => 'nullable|string',
|
||||
'no_rekening2' => 'nullable|string',
|
||||
'link_musik' => 'nullable|string',
|
||||
'galeri' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// ✅ Ambil template berdasarkan template_id
|
||||
$template = Template::with('kategori')->findOrFail($data['template_id']);
|
||||
|
||||
// ✅ Simpan ke tabel pelanggan
|
||||
$pelanggan = Pelanggan::create([
|
||||
'nama_pemesan' => $data['nama_pemesan'],
|
||||
'nama_template' => $template->nama_template,
|
||||
'kategori' => $template->kategori->nama ?? '-',
|
||||
'email' => $data['email'],
|
||||
'no_tlpn' => $data['no_hp'],
|
||||
'harga' => $template->harga,
|
||||
]);
|
||||
|
||||
// ✅ Simpan detail form ke tabel pelanggan_details
|
||||
PelangganDetail::create([
|
||||
'pelanggan_id' => $pelanggan->id,
|
||||
'detail_form' => $data,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Form pernikahan berhasil dikirim',
|
||||
'data' => $pelanggan->load('details')
|
||||
], 201);
|
||||
}
|
||||
}
|
55
backend/app/Http/Controllers/Api/ReviewController.php
Normal file
55
backend/app/Http/Controllers/Api/ReviewController.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Review;
|
||||
|
||||
class ReviewController extends Controller
|
||||
{
|
||||
// Ambil semua ulasan
|
||||
public function index()
|
||||
{
|
||||
$reviews = Review::all();
|
||||
return view('admin.reviews.index', compact('reviews'));
|
||||
}
|
||||
|
||||
|
||||
// Simpan ulasan baru
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'rating' => 'required|integer|min:1|max:5',
|
||||
'message' => 'required|string',
|
||||
'name' => 'required|string|max:100',
|
||||
'city' => 'required|string|max:100',
|
||||
]);
|
||||
|
||||
$review = Review::create($validated);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Ulasan berhasil disimpan',
|
||||
'data' => $review
|
||||
], 201);
|
||||
}
|
||||
|
||||
// Tampilkan ulasan tertentu
|
||||
public function show($id)
|
||||
{
|
||||
$review = Review::findOrFail($id);
|
||||
return response()->json($review, 200);
|
||||
}
|
||||
|
||||
|
||||
// Hapus ulasan
|
||||
public function destroy($id)
|
||||
{
|
||||
$review = Review::findOrFail($id);
|
||||
$review->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Ulasan berhasil dihapus'
|
||||
], 200);
|
||||
}
|
||||
}
|
21
backend/app/Http/Controllers/Api/TemplateApiController.php
Normal file
21
backend/app/Http/Controllers/Api/TemplateApiController.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Template;
|
||||
|
||||
class TemplateApiController extends Controller
|
||||
{
|
||||
// User hanya bisa lihat semua template
|
||||
public function index()
|
||||
{
|
||||
return response()->json(Template::with(['kategori','fitur'])->get());
|
||||
}
|
||||
|
||||
// User bisa lihat detail 1 template
|
||||
public function show(Template $template)
|
||||
{
|
||||
return response()->json($template->load(['kategori','fitur']));
|
||||
}
|
||||
}
|
63
backend/app/Http/Controllers/Api/UlangTahunApiController.php
Normal file
63
backend/app/Http/Controllers/Api/UlangTahunApiController.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Pelanggan;
|
||||
use App\Models\PelangganDetail;
|
||||
use App\Models\Template; // ✅ tambahkan ini
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UlangTahunApiController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'template_id' => 'required|exists:templates,id',
|
||||
'nama_pemesan' => 'required|string|max:255',
|
||||
'no_hp' => 'required|string|max:20',
|
||||
'email' => 'required|email',
|
||||
|
||||
// Data Anak
|
||||
'nama_lengkap_anak' => 'required|string|max:255',
|
||||
'nama_panggilan_anak' => 'required|string|max:100',
|
||||
'bapak_anak' => 'required|string|max:255',
|
||||
'ibu_anak' => 'required|string|max:255',
|
||||
'umur_dirayakan' => 'required|string|max:10',
|
||||
'anak_ke' => 'required|string|max:5',
|
||||
|
||||
// Jadwal
|
||||
'hari_tanggal_acara' => 'required|date',
|
||||
'waktu_acara' => 'required|string|max:50',
|
||||
'alamat_acara' => 'required|string',
|
||||
'maps_acara' => 'nullable|string',
|
||||
'link_musik' => 'nullable|string',
|
||||
'galeri' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// ✅ Ambil template berdasarkan template_id
|
||||
$template = Template::with('kategori')->findOrFail($data['template_id']);
|
||||
|
||||
// ✅ Simpan ke tabel pelanggan
|
||||
$pelanggan = Pelanggan::create([
|
||||
'nama_pemesan' => $data['nama_pemesan'],
|
||||
'nama_template' => $template->nama_template,
|
||||
'kategori' => $template->kategori->nama ?? 'ulang_tahun',
|
||||
'email' => $data['email'],
|
||||
'no_tlpn' => $data['no_hp'],
|
||||
'harga' => $template->harga,
|
||||
]);
|
||||
|
||||
// ✅ Simpan detail form ke tabel pelanggan_details
|
||||
PelangganDetail::create([
|
||||
'pelanggan_id' => $pelanggan->id,
|
||||
'detail_form' => $data,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Form ulang tahun berhasil dikirim',
|
||||
'data' => $pelanggan->load('details')
|
||||
], 201);
|
||||
}
|
||||
}
|
56
backend/app/Http/Controllers/FiturController.php
Normal file
56
backend/app/Http/Controllers/FiturController.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Fitur;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FiturController extends Controller
|
||||
{
|
||||
// Tampilkan semua fitur (halaman admin)
|
||||
public function index()
|
||||
{
|
||||
$fitur = Fitur::all();
|
||||
return view('admin.fitur.index', compact('fitur'));
|
||||
}
|
||||
|
||||
// Form tambah fitur
|
||||
public function create()
|
||||
{
|
||||
return view('admin.fitur.create');
|
||||
}
|
||||
|
||||
// Simpan fitur baru
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'deskripsi' => 'required|string',
|
||||
]);
|
||||
|
||||
Fitur::create($data);
|
||||
|
||||
return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
public function update(Request $request, Fitur $fitur)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'deskripsi' => 'required|string',
|
||||
]);
|
||||
|
||||
$fitur->update($data);
|
||||
|
||||
return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil diperbarui!');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Hapus fitur
|
||||
public function destroy(Fitur $fitur)
|
||||
{
|
||||
$fitur->delete();
|
||||
|
||||
return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil dihapus!');
|
||||
}
|
||||
}
|
64
backend/app/Http/Controllers/KategoriController.php
Normal file
64
backend/app/Http/Controllers/KategoriController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Kategori;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class KategoriController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$kategori = Kategori::all();
|
||||
return view('admin.kategori.index', compact('kategori'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('admin.kategori.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama' => 'required|string|max:255',
|
||||
'deskripsi' => '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);
|
||||
return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
public function edit(Kategori $kategori)
|
||||
{
|
||||
return view('admin.kategori.edit', compact('kategori'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Kategori $kategori)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama' => 'required|string|max:255',
|
||||
'deskripsi' => '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);
|
||||
return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil diperbarui!');
|
||||
}
|
||||
|
||||
public function destroy(Kategori $kategori)
|
||||
{
|
||||
$kategori->delete();
|
||||
return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil dihapus!');
|
||||
}
|
||||
}
|
29
backend/app/Http/Controllers/PelangganController.php
Normal file
29
backend/app/Http/Controllers/PelangganController.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Pelanggan;
|
||||
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'));
|
||||
}
|
||||
|
||||
// Detail pelanggan
|
||||
public function show(Pelanggan $pelanggan)
|
||||
{
|
||||
return view('admin.pelanggan.show', compact('pelanggan'));
|
||||
}
|
||||
|
||||
// Hapus pelanggan
|
||||
public function destroy(Pelanggan $pelanggan)
|
||||
{
|
||||
$pelanggan->delete();
|
||||
return redirect()->route('admin.pelanggan.index')->with('success', 'Pelanggan berhasil dihapus!');
|
||||
}
|
||||
}
|
82
backend/app/Http/Controllers/TemplateController.php
Normal file
82
backend/app/Http/Controllers/TemplateController.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Template;
|
||||
use App\Models\Kategori;
|
||||
use App\Models\Fitur;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class TemplateController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$templates = Template::with(['kategori','fitur'])->get();
|
||||
$kategoris = Kategori::all();
|
||||
$fiturs = Fitur::all();
|
||||
|
||||
return view('admin.templates.index', compact('templates', 'kategoris', 'fiturs'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama_template' => 'required|string|max:255',
|
||||
'kategori_id' => 'required|exists:kategoris,id',
|
||||
'fitur_id' => 'required|exists:fiturs,id',
|
||||
'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
|
||||
'harga' => 'required|numeric|min:0'
|
||||
]);
|
||||
|
||||
if ($request->hasFile('foto')) {
|
||||
$data['foto'] = $request->file('foto')->store('templates', 'public');
|
||||
}
|
||||
|
||||
Template::create($data);
|
||||
return redirect()->route('templates.index')->with('success', 'Template berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
public function update(Request $request, Template $template)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama_template' => 'required|string|max:255',
|
||||
'kategori_id' => 'required|exists:kategoris,id',
|
||||
'fitur_id' => 'required|exists:fiturs,id',
|
||||
'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
|
||||
'harga' => 'required|numeric|min:0'
|
||||
]);
|
||||
|
||||
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);
|
||||
return redirect()->route('templates.index')->with('success', 'Template berhasil diperbarui!');
|
||||
}
|
||||
|
||||
public function destroy(Template $template)
|
||||
{
|
||||
if ($template->foto && Storage::disk('public')->exists($template->foto)) {
|
||||
Storage::disk('public')->delete($template->foto);
|
||||
}
|
||||
|
||||
$template->delete();
|
||||
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'));
|
||||
}
|
||||
}
|
15
backend/app/Models/Admin.php
Normal file
15
backend/app/Models/Admin.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class Admin extends Authenticatable
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
protected $fillable = ['name','email','password'];
|
||||
|
||||
protected $hidden = ['password','remember_token'];
|
||||
}
|
15
backend/app/Models/Fitur.php
Normal file
15
backend/app/Models/Fitur.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// app/Models/Fitur.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Fitur extends Model
|
||||
{
|
||||
protected $fillable = ['deskripsi'];
|
||||
|
||||
public function templates()
|
||||
{
|
||||
return $this->hasMany(Template::class);
|
||||
}
|
||||
}
|
15
backend/app/Models/Kategori.php
Normal file
15
backend/app/Models/Kategori.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// app/Models/Kategori.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Kategori extends Model
|
||||
{
|
||||
protected $fillable = ['nama', 'deskripsi', 'foto'];
|
||||
|
||||
public function templates()
|
||||
{
|
||||
return $this->hasMany(Template::class);
|
||||
}
|
||||
}
|
35
backend/app/Models/Khitan.php
Normal file
35
backend/app/Models/Khitan.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
// app/Models/Khitan.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Khitan extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'template_id',
|
||||
'nama_pemesan',
|
||||
'no_hp',
|
||||
'email',
|
||||
|
||||
//Data
|
||||
'nama_lengkap_anak',
|
||||
'nama_panggilan_anak',
|
||||
'bapak_anak',
|
||||
'ibu_anak',
|
||||
|
||||
//Jadwal
|
||||
'hari_tanggal_acara',
|
||||
'waktu_acara',
|
||||
'alamat_acara',
|
||||
'maps_acara',
|
||||
'no_rekening1',
|
||||
'no_rekening2',
|
||||
'link_musik',
|
||||
'galeri',
|
||||
];
|
||||
|
||||
public function template() {
|
||||
return $this->belongsTo(Template::class);
|
||||
}
|
||||
}
|
25
backend/app/Models/Pelanggan.php
Normal file
25
backend/app/Models/Pelanggan.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Pelanggan extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'nama_pemesan',
|
||||
'nama_template',
|
||||
'kategori',
|
||||
'email',
|
||||
'no_tlpn',
|
||||
'harga',
|
||||
];
|
||||
|
||||
public function details()
|
||||
{
|
||||
return $this->hasOne(PelangganDetail::class);
|
||||
}
|
||||
}
|
22
backend/app/Models/PelangganDetail.php
Normal file
22
backend/app/Models/PelangganDetail.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PelangganDetail extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['pelanggan_id', 'detail_form'];
|
||||
|
||||
protected $casts = [
|
||||
'detail_form' => 'array',
|
||||
];
|
||||
|
||||
public function pelanggan()
|
||||
{
|
||||
return $this->belongsTo(Pelanggan::class);
|
||||
}
|
||||
}
|
59
backend/app/Models/Pernikahan.php
Normal file
59
backend/app/Models/Pernikahan.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// app/Models/Pernikahan.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Pernikahan extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
|
||||
'template_id',
|
||||
'nama_pemesan',
|
||||
'no_hp',
|
||||
'email',
|
||||
|
||||
// Pria
|
||||
'nama_lengkap_pria',
|
||||
'nama_panggilan_pria',
|
||||
'bapak_pria',
|
||||
'ibu_pria',
|
||||
'instagram_pria',
|
||||
'facebook_pria',
|
||||
'twitter_pria',
|
||||
|
||||
// Wanita
|
||||
'nama_lengkap_wanita',
|
||||
'nama_panggilan_wanita',
|
||||
'bapak_wanita',
|
||||
'ibu_wanita',
|
||||
'instagram_wanita',
|
||||
'facebook_wanita',
|
||||
'twitter_wanita',
|
||||
|
||||
// Cerita
|
||||
'cerita_kita',
|
||||
|
||||
// Akad
|
||||
'hari_tanggal_akad',
|
||||
'waktu_akad',
|
||||
'alamat_akad',
|
||||
'maps_akad',
|
||||
|
||||
// Resepsi
|
||||
'hari_tanggal_resepsi',
|
||||
'waktu_resepsi',
|
||||
'alamat_resepsi',
|
||||
'maps_resepsi',
|
||||
|
||||
// Tambahan
|
||||
'no_rekening1',
|
||||
'no_rekening2',
|
||||
'link_musik',
|
||||
'galeri',
|
||||
];
|
||||
|
||||
public function template() {
|
||||
return $this->belongsTo(Template::class);
|
||||
}
|
||||
}
|
18
backend/app/Models/Review.php
Normal file
18
backend/app/Models/Review.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Review extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'rating',
|
||||
'message',
|
||||
'name',
|
||||
'city',
|
||||
];
|
||||
}
|
30
backend/app/Models/Template.php
Normal file
30
backend/app/Models/Template.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// app/Models/Template.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Template extends Model
|
||||
{
|
||||
protected $fillable = ['nama_template', 'kategori_id', 'fitur_id', 'foto', 'harga'];
|
||||
|
||||
public function kategori() {
|
||||
return $this->belongsTo(Kategori::class);
|
||||
}
|
||||
|
||||
public function fitur() {
|
||||
return $this->belongsTo(Fitur::class);
|
||||
}
|
||||
|
||||
public function pernikahan() {
|
||||
return $this->hasOne(Pernikahan::class);
|
||||
}
|
||||
|
||||
public function ulangTahun() {
|
||||
return $this->hasOne(UlangTahun::class);
|
||||
}
|
||||
|
||||
public function khitan() {
|
||||
return $this->hasOne(Khitan::class);
|
||||
}
|
||||
}
|
36
backend/app/Models/UlangTahun.php
Normal file
36
backend/app/Models/UlangTahun.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// app/Models/UlangTahun.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UlangTahun extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
|
||||
'template_id',
|
||||
'nama_pemesan',
|
||||
'no_hp',
|
||||
'email',
|
||||
|
||||
//Data
|
||||
'nama_lengkap_anak',
|
||||
'nama_panggilan_anak',
|
||||
'bapak_anak',
|
||||
'ibu_anak',
|
||||
'umur_dirayakan',
|
||||
'anak_ke',
|
||||
|
||||
//Jadwal
|
||||
'hari_tanggal_acara',
|
||||
'waktu_acara',
|
||||
'alamat_acara',
|
||||
'maps_acara',
|
||||
'link_musik',
|
||||
'galeri',
|
||||
];
|
||||
|
||||
public function template() {
|
||||
return $this->belongsTo(Template::class);
|
||||
}
|
||||
}
|
@ -2,94 +2,39 @@
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default authentication "guard" and password
|
||||
| reset options for your application. You may change these defaults
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => 'web',
|
||||
'passwords' => 'users',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| here which uses session storage and the Eloquent user provider.
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
// Guard untuk user biasa
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
// Guard untuk admin
|
||||
'admin' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'admins',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| sources which represent each model / table. These sources may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
// Provider untuk user biasa
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\User::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
// Provider untuk admin
|
||||
'admins' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\Admin::class,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may specify multiple password reset configurations if you have more
|
||||
| than one user table or model in the application and you want to have
|
||||
| separate password reset settings based on the specific user types.
|
||||
|
|
||||
| The expire time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
@ -97,19 +42,14 @@ return [
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
'admins' => [
|
||||
'provider' => 'admins',
|
||||
'table' => 'password_reset_tokens',
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the amount of seconds before a password confirmation
|
||||
| times out and the user is prompted to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => 10800,
|
||||
|
||||
];
|
||||
|
23
backend/database/factories/TemplateFactory.php
Normal file
23
backend/database/factories/TemplateFactory.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Template>
|
||||
*/
|
||||
class TemplateFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('admins', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('admins');
|
||||
}
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000001_create_kategoris_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('kategoris', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
$table->text('deskripsi')->nullable();
|
||||
$table->string('foto')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('kategoris');
|
||||
}
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('reviews', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->tinyInteger('rating'); // nilai bintang 1–5
|
||||
$table->text('message');
|
||||
$table->string('name');
|
||||
$table->string('city');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('reviews');
|
||||
}
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000002_create_fiturs_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('fiturs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->text('deskripsi');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('fiturs');
|
||||
}
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000003_create_templates_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('templates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama_template');
|
||||
$table->foreignId('kategori_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('fitur_id')->constrained()->cascadeOnDelete();
|
||||
$table->decimal('harga', 10, 2)->default(0);
|
||||
$table->string('foto')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('templates');
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('pelanggans', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama_pemesan');
|
||||
$table->string('nama_template');
|
||||
$table->string('kategori');
|
||||
$table->string('email');
|
||||
$table->string('no_tlpn');
|
||||
$table->decimal('harga', 15, 2)->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pelanggans');
|
||||
}
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('pelanggan_details', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('pelanggan_id');
|
||||
$table->json('detail_form'); // data sesuai kategori
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('pelanggan_id')->references('id')->on('pelanggans')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pelanggan_details');
|
||||
}
|
||||
};
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000004_create_pernikahans_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('pernikahans', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('nama_pemesan');
|
||||
$table->string('no_hp');
|
||||
$table->string('email');
|
||||
// Data Pria
|
||||
$table->string('nama_lengkap_pria');
|
||||
$table->string('nama_panggilan_pria');
|
||||
$table->string('bapak_pria')->nullable();
|
||||
$table->string('ibu_pria')->nullable();
|
||||
$table->string('instagram_pria')->nullable();
|
||||
$table->string('facebook_pria')->nullable();
|
||||
$table->string('twitter_pria')->nullable();
|
||||
|
||||
// Data Wanita
|
||||
$table->string('nama_lengkap_wanita');
|
||||
$table->string('nama_panggilan_wanita');
|
||||
$table->string('bapak_wanita')->nullable();
|
||||
$table->string('ibu_wanita')->nullable();
|
||||
$table->string('instagram_wanita')->nullable();
|
||||
$table->string('facebook_wanita')->nullable();
|
||||
$table->string('twitter_wanita')->nullable();
|
||||
|
||||
// Cerita
|
||||
$table->text('cerita_kita')->nullable();
|
||||
|
||||
// Akad
|
||||
$table->date('hari_tanggal_akad')->nullable();
|
||||
$table->string('waktu_akad')->nullable();
|
||||
$table->text('alamat_akad')->nullable();
|
||||
$table->string('maps_akad')->nullable();
|
||||
|
||||
// Resepsi
|
||||
$table->date('hari_tanggal_resepsi')->nullable();
|
||||
$table->string('waktu_resepsi')->nullable();
|
||||
$table->text('alamat_resepsi')->nullable();
|
||||
$table->string('maps_resepsi')->nullable();
|
||||
|
||||
// Tambahan
|
||||
$table->string('no_rekening1')->nullable();
|
||||
$table->string('no_rekening2')->nullable();
|
||||
$table->string('link_musik')->nullable();
|
||||
$table->text('galeri')->nullable();
|
||||
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('pernikahans');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000005_create_ulang_tahuns_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('ulang_tahuns', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('nama_pemesan');
|
||||
$table->string('no_hp');
|
||||
$table->string('email');
|
||||
|
||||
// Data anak
|
||||
$table->string('nama_lengkap_anak');
|
||||
$table->string('nama_panggilan_anak');
|
||||
$table->string('bapak_anak');
|
||||
$table->string('ibu_anak');
|
||||
$table->string('umur_dirayakan');
|
||||
$table->string('anak_ke');
|
||||
|
||||
// Jadwal acara
|
||||
$table->date('hari_tanggal_acara');
|
||||
$table->string('waktu_acara');
|
||||
$table->text('alamat_acara');
|
||||
$table->string('maps_acara')->nullable();
|
||||
$table->string('link_musik')->nullable();
|
||||
$table->string('galeri')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('ulang_tahuns');
|
||||
}
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000006_create_khitans_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('khitans', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('nama_pemesan');
|
||||
$table->string('no_hp');
|
||||
$table->string('email');
|
||||
|
||||
// Data Anak
|
||||
$table->string('nama_lengkap_anak');
|
||||
$table->string('nama_panggilan_anak');
|
||||
$table->string('bapak_anak')->nullable();
|
||||
$table->string('ibu_anak')->nullable();
|
||||
|
||||
// Jadwal
|
||||
$table->date('hari_tanggal_acara')->nullable();
|
||||
$table->string('waktu_acara')->nullable();
|
||||
$table->text('alamat_acara')->nullable();
|
||||
$table->string('maps_acara')->nullable();
|
||||
|
||||
// Tambahan
|
||||
$table->string('no_rekening1')->nullable();
|
||||
$table->string('no_rekening2')->nullable();
|
||||
$table->string('link_musik')->nullable();
|
||||
$table->text('galeri')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('khitans');
|
||||
}
|
||||
};
|
||||
|
@ -2,16 +2,20 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use App\Models\Admin;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AdminSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
Admin::updateOrCreate(
|
||||
['email' => 'admin@example.com'],
|
||||
[
|
||||
'name' => 'Super Admin',
|
||||
'password' => Hash::make('password123'), // ganti setelah login
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,16 +7,10 @@ use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// \App\Models\User::factory(10)->create();
|
||||
|
||||
// \App\Models\User::factory()->create([
|
||||
// 'name' => 'Test User',
|
||||
// 'email' => 'test@example.com',
|
||||
// ]);
|
||||
}
|
||||
{
|
||||
$this->call([
|
||||
AdminSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
BIN
backend/public/images/logo.png
Normal file
BIN
backend/public/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
109
backend/resources/views/admin/auth/login.blade.php
Normal file
109
backend/resources/views/admin/auth/login.blade.php
Normal file
@ -0,0 +1,109 @@
|
||||
<!doctype html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Login Admin</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background: #ffffff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: #EFEFEF;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 25px rgb(0, 123, 255);
|
||||
width: 100%;
|
||||
max-width: 400px; /* batas lebar maksimum */
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
display: block;
|
||||
margin: 0 auto 15px;
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
background-color: #3bb9ff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.btn-login:hover {
|
||||
background-color: #1a8edb;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Responsive padding */
|
||||
@media (max-width: 576px) {
|
||||
.login-card {
|
||||
padding: 20px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login-card">
|
||||
<div class="text-center mb-3">
|
||||
<!-- Logo -->
|
||||
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="login-logo">
|
||||
</div>
|
||||
|
||||
<h4 class="text-center mb-2 fw-bold">SELAMAT DATANG</h4>
|
||||
<p class="text-center text-muted mb-4">Silakan masukkan email dan password anda.</p>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
{{ $errors->first() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('admin.login.post') }}" method="POST">
|
||||
@csrf
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nama</label>
|
||||
<input type="email" name="email" value="{{ old('email') }}" class="form-control"
|
||||
placeholder="Masukkan nama Anda" required autofocus>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Kata Sandi</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Masukkan kata sandi Anda" required>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="remember" id="remember">
|
||||
<label class="form-check-label" for="remember">Ingat saya</label>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none mt-2 mt-sm-0">Lupa kata sandi</a>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-login text-white w-100">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
54
backend/resources/views/admin/dashboard.blade.php
Normal file
54
backend/resources/views/admin/dashboard.blade.php
Normal file
@ -0,0 +1,54 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Halaman Dasbor')
|
||||
|
||||
@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">Halaman Dasbor</h3>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="bg-blue-100 text-blue-600 px-3 py-2 rounded-lg flex items-center gap-2 text-sm">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
{{ \Carbon\Carbon::now()->translatedFormat('l, d F Y') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="bg-white rounded-lg shadow-md p-4 flex justify-between items-center hover:shadow-lg transition-transform duration-300 transform hover:-translate-y-2">
|
||||
<div>
|
||||
<h5 class="text-gray-500 text-sm">Kategori</h5>
|
||||
<h3 class="font-bold text-xl">10</h3>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 text-blue-600 rounded-lg flex items-center justify-center text-xl">
|
||||
<i class="bi bi-diagram-3"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-4 flex justify-between items-center hover:shadow-lg transition-transform duration-300 transform hover:-translate-y-2">
|
||||
<div>
|
||||
<h5 class="text-gray-500 text-sm">Templat</h5>
|
||||
<h3 class="font-bold text-xl">20</h3>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 text-blue-600 rounded-lg flex items-center justify-center text-xl">
|
||||
<i class="bi bi-card-list"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-4 flex justify-between items-center hover:shadow-lg transition-transform duration-300 transform hover:-translate-y-2">
|
||||
<div>
|
||||
<h5 class="text-gray-500 text-sm">Pelanggan</h5>
|
||||
<h3 class="font-bold text-xl">24</h3>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 text-blue-600 rounded-lg flex items-center justify-center text-xl">
|
||||
<i class="bi bi-person"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pesan login -->
|
||||
<div class="bg-green-100 text-green-700 p-4 rounded-lg mt-4">
|
||||
Berhasil login sebagai <strong>{{ auth('admin')->user()->name }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
238
backend/resources/views/admin/fitur/index.blade.php
Normal file
238
backend/resources/views/admin/fitur/index.blade.php
Normal file
@ -0,0 +1,238 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Manajemen Fitur')
|
||||
|
||||
@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 Fitur</h3>
|
||||
<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 Fitur
|
||||
</button>
|
||||
</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 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>
|
||||
</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>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form action="{{ route('admin.fitur.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Tambah 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" 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" 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 ($fitur 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.fitur.update', $item->id) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="p-4 border-b">
|
||||
<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>
|
||||
@endforeach
|
||||
|
||||
<!-- Modal Hapus -->
|
||||
@foreach ($fitur as $item)
|
||||
<div id="modalDelete{{ $item->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50 closeDeleteOverlay" data-id="{{ $item->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 Fitur</h5>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<p>Apakah Anda yakin ingin menghapus fitur <strong>{{ $item->deskripsi }}</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="{{ $item->id }}">Batal</button>
|
||||
<form action="{{ route('admin.fitur.destroy', $item->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>
|
||||
// Modal Tambah
|
||||
const openTambahModal = document.getElementById('openTambahModal');
|
||||
const modalTambah = document.getElementById('modalTambah');
|
||||
const closeTambahBtn = document.getElementById('closeTambahBtn');
|
||||
const closeTambahOverlay = document.getElementById('closeTambahModal');
|
||||
|
||||
openTambahModal.addEventListener('click', () => {
|
||||
modalTambah.classList.remove('hidden');
|
||||
modalTambah.classList.add('flex');
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
// Modal Delete
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
285
backend/resources/views/admin/kategori/index.blade.php
Normal file
285
backend/resources/views/admin/kategori/index.blade.php
Normal file
@ -0,0 +1,285 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Manajemen Kategori')
|
||||
|
||||
@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 Kategori</h3>
|
||||
<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 Kategori
|
||||
</button>
|
||||
</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 Kategori -->
|
||||
<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">Nomor</th>
|
||||
<th class="p-2 border border-gray-300 w-[150px] text-center">Nama Kategori</th>
|
||||
<th class="p-2 border border-gray-300 w-[300px] text-center">Keterangan</th>
|
||||
<th class="p-2 border border-gray-300 w-[90px] text-center">Foto</th>
|
||||
<th class="p-2 border border-gray-300 w-[110px] text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($kategori as $key => $item)
|
||||
<tr>
|
||||
<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->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="{{ $item->foto ? asset('storage/' . $item->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 pr-4 openEditModalBtn"
|
||||
data-id="{{ $item->id }}">
|
||||
<i class="bi bi-pencil mr-1"></i> Ubah
|
||||
</button>
|
||||
<button class="text-red-600 hover:underline 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="5" class="p-2 text-center text-gray-500 border border-gray-300">
|
||||
Belum ada kategori
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</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>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form action="{{ route('admin.kategori.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="p-4 border-b">
|
||||
<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>
|
||||
<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>
|
||||
<input type="text" name="nama" value="{{ $item->nama }}"
|
||||
class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Deskripsi</label>
|
||||
<textarea name="deskripsi" class="w-full p-2 border rounded" rows="3">{{ $item->deskripsi }}</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>
|
||||
@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 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>
|
||||
@endforeach
|
||||
|
||||
<!-- Modal Hapus -->
|
||||
@foreach ($kategori as $item)
|
||||
<div id="modalDelete{{ $item->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50 closeDeleteOverlay" data-id="{{ $item->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 Kategori</h5>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<p>Apakah Anda yakin ingin menghapus kategori <strong>{{ $item->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="{{ $item->id }}">Batal</button>
|
||||
<form action="{{ route('admin.kategori.destroy', $item->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>
|
||||
// Modal Tambah
|
||||
const openTambahModal = document.getElementById('openTambahModal');
|
||||
const modalTambah = document.getElementById('modalTambah');
|
||||
const closeTambahBtn = document.getElementById('closeTambahBtn');
|
||||
const closeTambahOverlay = document.getElementById('closeTambahModal');
|
||||
|
||||
openTambahModal.addEventListener('click', () => {
|
||||
modalTambah.classList.remove('hidden');
|
||||
modalTambah.classList.add('flex');
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
// Modal Hapus
|
||||
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
|
141
backend/resources/views/admin/pelanggan/index.blade.php
Normal file
141
backend/resources/views/admin/pelanggan/index.blade.php
Normal file
@ -0,0 +1,141 @@
|
||||
@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 text-left border-collapse border border-gray-300">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="p-2 border border-gray-300 text-center w-16">Nomor</th>
|
||||
<th class="p-2 border border-gray-300 text-center">Nama</th>
|
||||
<th class="p-2 border border-gray-300 text-center">Nama template</th>
|
||||
<th class="p-2 border border-gray-300 text-center">Kategori</th>
|
||||
<th class="p-2 border border-gray-300 text-center">Email</th>
|
||||
<th class="p-2 border border-gray-300 text-center">No. Telepon</th>
|
||||
<th class="p-2 border border-gray-300 text-center">Harga</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 py-4 border border-gray-300 text-center">{{ $key + 1 }}</td>
|
||||
<td class="p-2 py-4 border border-gray-300 truncate">{{ $pelanggan->nama_pemesan }}</td>
|
||||
<td class="p-2 py-4 border border-gray-300 truncate">{{ $pelanggan->nama_template }}</td>
|
||||
<td class="p-2 py-4 border border-gray-300 truncate">{{ $pelanggan->kategori ?? '-' }}</td>
|
||||
<td class="p-2 py-4 border border-gray-300 truncate">{{ $pelanggan->email }}</td>
|
||||
<td class="p-2 py-4 border border-gray-300 truncate">{{ $pelanggan->no_tlpn ?? '-' }}</td>
|
||||
<td class="py-3 px-2 border border-gray-300 text-center">
|
||||
Rp {{ number_format($pelanggan->harga, 0, ',', '.') }}
|
||||
</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
|
119
backend/resources/views/admin/reviews/index.blade.php
Normal file
119
backend/resources/views/admin/reviews/index.blade.php
Normal file
@ -0,0 +1,119 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Manajemen Ulasan')
|
||||
|
||||
@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 Ulasan</h3>
|
||||
</div>
|
||||
|
||||
<!-- Alert sukses -->
|
||||
@if (session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4" role="alert">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Tabel Ulasan -->
|
||||
<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">Nomor</th>
|
||||
<th class="p-2 border border-gray-300 w-[150px] text-center">Nama</th>
|
||||
<th class="p-2 border border-gray-300 w-[120px] text-center">Kota</th>
|
||||
<th class="p-2 border border-gray-300 w-[120px] text-center">Penilaian</th>
|
||||
<th class="p-2 border border-gray-300 w-[350px] text-center">Pesan</th>
|
||||
<th class="p-2 border border-gray-300 w-[120px] text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($reviews as $key => $review)
|
||||
<tr>
|
||||
<td class="p-2 py-4 border border-gray-300 text-center truncate">{{ $key + 1 }}</td>
|
||||
<td class="p-2 py-4 border border-gray-300 truncate">{{ $review->name }}</td>
|
||||
<td class="p-2 py-4 border border-gray-300 truncate">{{ $review->city }}</td>
|
||||
<td class="p-2 py-4 border border-gray-300 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="p-2 border border-gray-300 truncate" title="{{ $review->message }}">
|
||||
{{ $review->message }}
|
||||
</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<button class="text-red-600 hover:underline flex items-center btn-delete"
|
||||
data-action="{{ route('admin.reviews.destroy', $review->id) }}"
|
||||
data-name="{{ $review->name }}">
|
||||
<i class="bi bi-trash mr-1"></i> Hapus
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="p-2 text-center text-gray-500 border border-gray-300">
|
||||
Belum ada ulasan
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Konfirmasi Hapus Tailwind -->
|
||||
<div id="modalDelete" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50" id="modalDeleteOverlay"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form id="deleteForm" method="POST">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Hapus Ulasan</h5>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
Apakah Anda yakin ingin menghapus ulasan dari <strong id="deleteName">—</strong>?
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" id="closeDeleteModal"
|
||||
class="bg-gray-300 text-black px-3 py-1 rounded">Batal</button>
|
||||
<button type="submit" class="bg-red-600 text-white px-3 py-1 rounded">Ya, Hapus</button>
|
||||
</div>
|
||||
</form>
|
||||
</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
|
295
backend/resources/views/admin/templates/index.blade.php
Normal file
295
backend/resources/views/admin/templates/index.blade.php
Normal file
@ -0,0 +1,295 @@
|
||||
@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">Nomor</th>
|
||||
<th class="p-2 border border-gray-300 w-[200px] text-center">Nama Template</th>
|
||||
<th class="p-2 border border-gray-300 w-[150px] text-center">Kategori</th>
|
||||
<th class="p-2 border border-gray-300 w-[150px] text-center">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-[100px] text-center">Harga</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-left font-semibold">
|
||||
Rp {{ number_format($template->harga, 0, ',', '.') }}
|
||||
</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="7" class="p-2 text-center text-gray-500 border border-gray-300">Belum ada
|
||||
template</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</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 }}">
|
||||
{{ \Illuminate\Support\Str::limit($fitur->deskripsi, 50) }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<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">
|
||||
</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">Harga</label>
|
||||
<input type="number" name="harga" value="{{ $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>
|
||||
</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>
|
||||
</div>
|
||||
@endsection
|
149
backend/resources/views/layouts/app.blade.php
Normal file
149
backend/resources/views/layouts/app.blade.php
Normal file
@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>@yield('title', 'Admin Panel')</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f1f5f9;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.submenu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.submenu.show {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- MENU -->
|
||||
<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 {{ request()->is('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="{{ route('admin.reviews.index') }}"
|
||||
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>
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<div class="flex-1 ml-[250px] p-6">
|
||||
@yield('content')
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Dropdown Templat
|
||||
const templatBtn = document.getElementById('templatBtn');
|
||||
const templatSubmenu = document.getElementById('templatSubmenu');
|
||||
const templatIcon = document.getElementById('templatIcon');
|
||||
|
||||
templatBtn.addEventListener('click', () => {
|
||||
templatSubmenu.classList.toggle('show');
|
||||
templatIcon.classList.toggle('rotate-180');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -2,18 +2,26 @@
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Api\ReviewController;
|
||||
use App\Http\Controllers\Api\KategoriApiController;
|
||||
use App\Http\Controllers\Api\PernikahanApiController;
|
||||
use App\Http\Controllers\Api\UlangTahunApiController;
|
||||
use App\Http\Controllers\Api\KhitanApiController;
|
||||
use App\Http\Controllers\Api\TemplateApiController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| API Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register API routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider and all of them will
|
||||
| be assigned to the "api" middleware group. Make something great!
|
||||
|
|
||||
*/
|
||||
// Form API (user)
|
||||
Route::post('form/pernikahan', [PernikahanApiController::class, 'store']);
|
||||
Route::post('form/ulang-tahun', [UlangTahunApiController::class, 'store']);
|
||||
Route::post('form/khitan', [KhitanApiController::class, 'store']);
|
||||
|
||||
// API Kategori hanya read-only
|
||||
Route::get('kategoris', [KategoriApiController::class, 'index']);
|
||||
Route::get('kategoris/{kategori}', [KategoriApiController::class, 'show']);
|
||||
|
||||
// API Reviews
|
||||
Route::apiResource('reviews', ReviewController::class);
|
||||
|
||||
// API Templates
|
||||
Route::get('templates', [TemplateApiController::class, 'index']);
|
||||
Route::get('templates/{template}', [TemplateApiController::class, 'show']);
|
||||
|
||||
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
});
|
||||
|
@ -1,18 +1,83 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\AdminAuthController;
|
||||
use App\Http\Controllers\KategoriController;
|
||||
use App\Http\Controllers\FiturController;
|
||||
use App\Http\Controllers\TemplateController;
|
||||
use App\Http\Controllers\PelangganController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Web Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register web routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider and all of them will
|
||||
| be assigned to the "web" middleware group. Make something great!
|
||||
|
|
||||
*/
|
||||
|
||||
//Login
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
return redirect()->route('admin.login');
|
||||
});
|
||||
Route::prefix('admin')->name('admin.')->group(function () {
|
||||
Route::middleware('guest:admin')->group(function () {
|
||||
Route::get('/login', [AdminAuthController::class, 'showLogin'])->name('login');
|
||||
Route::post('/login', [AdminAuthController::class, 'login'])->name('login.post');
|
||||
});
|
||||
|
||||
Route::middleware('auth:admin')->group(function () {
|
||||
Route::get('/dashboard', function () {
|
||||
return view('admin.dashboard');
|
||||
})->name('dashboard');
|
||||
|
||||
Route::post('/logout', [AdminAuthController::class, 'logout'])->name('logout');
|
||||
});
|
||||
});
|
||||
|
||||
//Kategori
|
||||
Route::prefix('admin')->name('admin.')->group(function () {
|
||||
Route::resource('kategori', KategoriController::class);
|
||||
});
|
||||
|
||||
// Route Admin Fitur
|
||||
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 Pelanggan
|
||||
Route::prefix('admin')->name('admin.')->group(function () {
|
||||
Route::resource('pelanggan', PelangganController::class)->only([
|
||||
'index',
|
||||
'show',
|
||||
'destroy'
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
use App\Http\Controllers\Api\ReviewController;
|
||||
use App\Models\Review;
|
||||
|
||||
Route::prefix('admin')->name('admin.')->middleware('auth:admin')->group(function () {
|
||||
// Halaman daftar ulasan
|
||||
Route::get('/ulasan', function () {
|
||||
$reviews = \App\Models\Review::latest()->get();
|
||||
return view('admin.reviews.index', compact('reviews'));
|
||||
})->name('reviews.index');
|
||||
|
||||
// Tambah ulasan
|
||||
Route::post('/ulasan', [ReviewController::class, 'store'])->name('reviews.store');
|
||||
|
||||
// Update ulasan
|
||||
Route::put('/ulasan/{review}', [ReviewController::class, 'update'])->name('reviews.update');
|
||||
|
||||
// Hapus ulasan
|
||||
Route::delete('/ulasan/{review}', function (Review $review) {
|
||||
$review->delete();
|
||||
return redirect()->route('admin.reviews.index')->with('success', 'Ulasan berhasil dihapus');
|
||||
})->name('reviews.destroy');
|
||||
});
|
||||
|
@ -2,4 +2,32 @@
|
||||
<div>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- <script setup>
|
||||
const props = defineProps({
|
||||
category: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
id_category: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
}) -->
|
||||
|
||||
// const templates = ref([])
|
||||
|
||||
// const fetchTemplates = async () => {
|
||||
// try {
|
||||
// templates.value = await $fetch(`http://localhost:8000/api/templates/category/${props.id_category}`)
|
||||
// } catch (error) {
|
||||
// console.error('Gagal ambil template:', error)
|
||||
// }
|
||||
// }
|
||||
|
||||
// onMounted(() => {
|
||||
// fetchTemplates()
|
||||
// })
|
||||
|
||||
// defineEmits(['back']);
|
@ -7,7 +7,7 @@
|
||||
<!-- Layout gambar + teks -->
|
||||
<div class="about-layout">
|
||||
<div class="about-image">
|
||||
<img src="/Rectangle.png" alt="Tentang Kami - Undangan Digital" />
|
||||
<img src="/logo1.png" alt="Tentang Kami - Undangan Digital" />
|
||||
</div>
|
||||
<div class="about-text">
|
||||
<p>
|
||||
|
@ -30,7 +30,7 @@
|
||||
</div>
|
||||
|
||||
<div class="see-more">
|
||||
<a href="#">Lihat Selengkapnya...</a>
|
||||
<NuxtLink to="/template">Lihat Selengkapnya...</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
@ -2,12 +2,13 @@
|
||||
<header class="main-header">
|
||||
<nav class="container">
|
||||
<div class="logo">
|
||||
<img src="/abbauflogo.png" alt="Abbauf Tech Logo" class="logo-icon">
|
||||
<span>ABBAUF TECH</span>
|
||||
<NuxtLink to="/" class="logo-link"> <img src="/ABBAUF.png" alt="Abbauf Tech Logo" class="logo-icon">
|
||||
<span>ABBAUF TECH</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li v-for="link in navLinks" :key="link.name">
|
||||
<a :href="link.path">{{ link.name }}</a>
|
||||
<NuxtLink :to="link.path">{{ link.name }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -18,12 +19,15 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const navLinks = ref([
|
||||
{ name: 'Beranda', path: '#beranda' },
|
||||
{ name: 'Tentang Kami', path: '#tentang-kami' },
|
||||
{ name: 'Templat', path: '#template' },
|
||||
{ name: 'Panduan', path: '#cara' },
|
||||
{ name: 'Keistimewaan', path: '#keistimewaan' },
|
||||
{ name: 'Testimoni', path: '#testimoni' },
|
||||
// Path untuk Beranda diubah menjadi / agar selalu kembali ke halaman utama
|
||||
{ name: 'Beranda', path: '/' },
|
||||
// Path ini agar berfungsi dari halaman lain (seperti /template)
|
||||
{ name: 'Tentang Kami', path: '/#tentang-kami' },
|
||||
// INI BAGIAN YANG PALING PENTING: Path diubah ke /template
|
||||
{ name: 'Templat', path: '/template' },
|
||||
{ name: 'Panduan', path: '/#cara' },
|
||||
{ name: 'Keistimewaan', path: '/#keistimewaan' },
|
||||
{ name: 'Testimoni', path: '/#testi' },
|
||||
]);
|
||||
</script>
|
||||
|
||||
@ -44,26 +48,17 @@ const navLinks = ref([
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
.logo a.logo-link { /* Style untuk logo link */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
color: #0d6efd;
|
||||
text-decoration: none; /* Hilangkan garis bawah default */
|
||||
}
|
||||
|
||||
|
||||
|
||||
.main-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.logo-icon {
|
||||
width: 55px;
|
||||
width: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<div class="w-full text-center lg:w-1/2">
|
||||
|
||||
<h1 class="text-5xl font-extrabold leading-tight text-gray-800 lg:text-6xl ml-14">
|
||||
<h1 class="text-5xl font-extrabold leading-tight text-gray-800 lg:text-6xl">
|
||||
Buat Undangan Digital Praktis Untuk
|
||||
|
||||
<div class="h-24 flex items-center justify-center">
|
||||
@ -16,10 +16,10 @@
|
||||
Tanpa Ribet
|
||||
</h1>
|
||||
|
||||
<p class="mt-4 mb-8 text-lg text-gray-600 ml-14">
|
||||
<p class="mt-4 mb-8 text-lg text-gray-600">
|
||||
Coba undangan digital PRAKTIS untuk berbagai acara. Pilih template praktis atau premium sesuai kebutuhanmu. Praktis, cepat, dan bisa langsung digunakan.
|
||||
</p>
|
||||
<div class="flex flex-col justify-center gap-4 sm:flex-row ml-14">
|
||||
<div class="flex flex-col justify-center gap-4 sm:flex-row">
|
||||
<a href="#" class="inline-flex items-center justify-center rounded-lg border-2 border-green-500 bg-white px-8 py-3 font-bold text-gray-800 shadow-sm transition-transform duration-300 hover:-translate-y-1 hover:shadow-md">
|
||||
<svg class="mr-2" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.2239 4.7761C17.1659 2.7181 14.6599 1.5 11.9999 1.5...Z" fill="#25D366"/>
|
||||
@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full lg:w-1/2">
|
||||
<img src="/iphone.png" alt="Tampilan Undangan Digital di Ponsel" class="mx-auto max-w-full">
|
||||
<img src="/logo2.png" alt="Tampilan Undangan Digital di Ponsel" class="mx-auto max-w-full">
|
||||
<div class="mt-6 text-center italic text-gray-500">
|
||||
<p>"Abadikan momen indahmu dengan undangan digital yang elegan."</p>
|
||||
<p>"Satu aplikasi untuk semua momen spesialmu."</p>
|
||||
|
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div>
|
||||
<LandingPageHeader />
|
||||
|
||||
<main class="container mx-auto px-4 py-16 md:py-20">
|
||||
|
||||
<div class="mb-8">
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="text-blue-600 hover:text-blue-800 font-semibold inline-flex items-center"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Kembali ke Beranda
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<h1 class="text-3xl md:text-4xl font-bold text-center text-gray-800">
|
||||
Pilih Template Favoritmu
|
||||
</h1>
|
||||
<p class="mt-2 text-center text-gray-500">
|
||||
"Tersedia berbagai desain undangan pernikahan, khitan, ulang tahun, dan lainnya."
|
||||
</p>
|
||||
|
||||
<div class="mt-12">
|
||||
<div v-if="isLoading" class="text-center text-gray-500">
|
||||
<p>Memuat kategori...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="text-center text-red-500 bg-red-100 p-4 rounded-lg">
|
||||
<p><strong>Oops, terjadi kesalahan:</strong></p>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div v-if="categories.length > 0" class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
@click="onCategoryClick(category.nama)"
|
||||
class="group cursor-pointer overflow-hidden rounded-lg shadow-lg hover:shadow-2xl transition-all duration-300"
|
||||
>
|
||||
<img :src="category.foto" :alt="category.nama" class="w-full h-48 object-cover group-hover:scale-110 transition-transform duration-300">
|
||||
<div class="p-6 bg-white">
|
||||
<h3 class="text-xl font-semibold text-gray-800">{{ category.nama }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-center text-gray-500">
|
||||
<p>Saat ini belum ada kategori yang tersedia.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LandingPageHeader from '~/components/landing-page/header.vue';
|
||||
import Footer from '~/components/landing-page/footer.vue';
|
||||
|
||||
const emit = defineEmits(['category-selected']);
|
||||
|
||||
const categories = ref([]);
|
||||
// State baru untuk loading dan error
|
||||
const isLoading = ref(true);
|
||||
const error = ref(null);
|
||||
|
||||
const fetchCategories = async () => {
|
||||
// Reset state sebelum fetch
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const data = await $fetch('http://localhost:8000/api/kategoris');
|
||||
categories.value = data;
|
||||
} catch (err) {
|
||||
console.error('Gagal ambil kategori:', err);
|
||||
// Set pesan error yang akan ditampilkan ke pengguna
|
||||
error.value = 'Tidak dapat terhubung ke server. Silakan coba lagi nanti.';
|
||||
} finally {
|
||||
// Set loading ke false setelah selesai (baik sukses maupun gagal)
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const onCategoryClick = (categoryName) => {
|
||||
emit('category-selected', categoryName);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchCategories();
|
||||
});
|
||||
// Cukup satu baris ini untuk mengambil data dan mengelola semua state!
|
||||
// const { data: categories, pending: isLoading, error } = await useFetch('http://localhost:8000/api/kategoris');
|
||||
</script>
|
102
proyek-frontend/app/components/template-page/TemplateGrid.vue
Normal file
102
proyek-frontend/app/components/template-page/TemplateGrid.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div>
|
||||
<LandingPageHeader />
|
||||
|
||||
<main class="container mx-auto px-4 py-16 md:py-20">
|
||||
<div class="flex items-center mb-8">
|
||||
<button
|
||||
@click="$emit('back')"
|
||||
class="text-blue-600 hover:text-blue-800 font-semibold inline-flex items-center mr-4"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 mr-1"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Kembali
|
||||
</button>
|
||||
<h1 class="text-3xl md:text-4xl font-bold text-gray-800">
|
||||
Template {{ category }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Loading -->
|
||||
<div v-if="isLoading" class="text-center py-10">
|
||||
<p>Memuat template...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-else-if="error" class="text-center py-10 text-red-600">
|
||||
<p>Gagal memuat template. Silakan coba lagi.</p>
|
||||
</div>
|
||||
|
||||
<!-- Data Ada -->
|
||||
<div
|
||||
v-else-if="templates && templates.length > 0"
|
||||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"
|
||||
>
|
||||
<div
|
||||
v-for="tpl in templates"
|
||||
:key="tpl.id"
|
||||
class="border rounded-lg overflow-hidden shadow hover:shadow-lg transition-shadow"
|
||||
>
|
||||
<img
|
||||
:src="`http://localhost:8000/storage/${tpl.foto}`"
|
||||
:alt="tpl.nama_template"
|
||||
class="bg-gray-200 h-48 w-full object-cover"
|
||||
/>
|
||||
<div class="p-4">
|
||||
<h4 class="font-semibold truncate">{{ tpl.nama_template }}</h4>
|
||||
<p class="text-gray-500">
|
||||
Rp {{ Number(tpl.harga ?? 0).toLocaleString('id-ID') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Kosong -->
|
||||
<div v-else class="text-center py-10 text-gray-500">
|
||||
<p>Belum ada template untuk kategori ini.</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LandingPageHeader from '~/components/landing-page/header.vue'
|
||||
import Footer from '~/components/landing-page/footer.vue'
|
||||
|
||||
const props = defineProps({
|
||||
category: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
id_category: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
defineEmits(['back'])
|
||||
|
||||
// Panggil API Laravel
|
||||
const {
|
||||
data: templates,
|
||||
pending: isLoading,
|
||||
error,
|
||||
} =
|
||||
useFetch(() => `/api/templates?kategori_id=${props.id_category}`, {
|
||||
baseURL: 'http://localhost:8000',
|
||||
key: () => `templates-${props.id_category}`,
|
||||
transform: (res) => res ?? []
|
||||
})
|
||||
</script>
|
37
proyek-frontend/app/pages/template.vue
Normal file
37
proyek-frontend/app/pages/template.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<main class="container mx-auto px-4 py-16">
|
||||
<TemplatePageCategorySelection
|
||||
v-if="!selectedCategory"
|
||||
@category-selected="handleCategorySelect"
|
||||
/>
|
||||
|
||||
<TemplatePageTemplateGrid
|
||||
v-else
|
||||
:id_category="selectedCategory.id"
|
||||
:category="selectedCategory.nama"
|
||||
@back="goBack"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
// 1. Impor komponen anak yang Anda gunakan
|
||||
import TemplatePageCategorySelection from '~/components/template-page/CategorySelection.vue'; // Pastikan path ini benar
|
||||
import TemplatePageTemplateGrid from '~/components/template-page/TemplateGrid.vue'; // Pastikan path ini benar
|
||||
|
||||
// 2. State untuk menyimpan SELURUH OBJEK kategori yang dipilih
|
||||
const selectedCategory = ref(null);
|
||||
|
||||
// Fungsi ini sekarang akan menerima seluruh objek { id, nama }
|
||||
const handleCategorySelect = (categoryObject) => {
|
||||
selectedCategory.value = categoryObject;
|
||||
};
|
||||
|
||||
// Fungsi ini akan menangkap event 'back' dari TemplateGrid
|
||||
const goBack = () => {
|
||||
selectedCategory.value = null;
|
||||
};
|
||||
</script>
|
Binary file not shown.
Before Width: | Height: | Size: 228 KiB |
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
Before Width: | Height: | Size: 233 KiB |
Loading…
Reference in New Issue
Block a user