Merge branch 'baru' of https://git.abbauf.com/Magang-2025/Undangan into baru
This commit is contained in:
commit
1ef83feac9
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\Review;
|
||||||
|
|
||||||
|
class ReviewApiController extends Controller
|
||||||
|
{
|
||||||
|
// Ambil semua ulasan (JSON)
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$reviews = Review::latest()->get();
|
||||||
|
|
||||||
|
return response()->json($reviews, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,35 +3,58 @@
|
|||||||
@section('title', 'Tambah Kategori')
|
@section('title', 'Tambah Kategori')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="container mt-4">
|
<div class="container mx-auto py-8">
|
||||||
<h2 class="mb-3">Tambah Kategori</h2>
|
<div class="max-w-2xl mx-auto bg-white shadow-md rounded-xl p-8 border border-blue-100">
|
||||||
|
<h2 class="text-2xl font-semibold text-black mb-6">Tambah Kategori</h2>
|
||||||
|
|
||||||
<form action="{{ route('admin.kategori.store') }}" method="POST" enctype="multipart/form-data">
|
<form action="{{ route('admin.kategori.store') }}" method="POST" enctype="multipart/form-data" class="space-y-5">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Nama Kategori -->
|
||||||
<label for="nama" class="form-label">Nama Kategori</label>
|
<div>
|
||||||
<input type="text" class="form-control @error('nama') is-invalid @enderror"
|
<label for="nama" class="block text-sm font-medium text-gray-700 mb-2">Nama Kategori</label>
|
||||||
id="nama" name="nama" value="{{ old('nama') }}" required>
|
<input type="text" id="nama" name="nama"
|
||||||
@error('nama') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
value="{{ old('nama') }}"
|
||||||
</div>
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none @error('nama') border-red-500 @enderror"
|
||||||
|
placeholder="Masukkan nama kategori" required>
|
||||||
|
@error('nama')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Deskripsi -->
|
||||||
<label for="deskripsi" class="form-label">Deskripsi</label>
|
<div>
|
||||||
<textarea class="form-control @error('deskripsi') is-invalid @enderror"
|
<label for="deskripsi" class="block text-sm font-medium text-gray-700 mb-2">Deskripsi</label>
|
||||||
id="deskripsi" name="deskripsi">{{ old('deskripsi') }}</textarea>
|
<textarea id="deskripsi" name="deskripsi" rows="4"
|
||||||
@error('deskripsi') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none @error('deskripsi') border-red-500 @enderror"
|
||||||
</div>
|
placeholder="Tuliskan deskripsi kategori">{{ old('deskripsi') }}</textarea>
|
||||||
|
@error('deskripsi')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Foto -->
|
||||||
<label for="foto" class="form-label">Foto</label>
|
<div>
|
||||||
<input type="file" class="form-control @error('foto') is-invalid @enderror"
|
<label for="foto" class="block text-sm font-medium text-gray-700 mb-2">Foto</label>
|
||||||
id="foto" name="foto">
|
<input type="file" id="foto" name="foto"
|
||||||
@error('foto') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 cursor-pointer focus:ring-2 focus:ring-blue-400 @error('foto') border-red-500 @enderror">
|
||||||
</div>
|
@error('foto')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success">Simpan</button>
|
<!-- Tombol -->
|
||||||
<a href="{{ route('admin.kategori.index') }}" class="btn btn-secondary">Kembali</a>
|
<div class="flex justify-end gap-3 pt-4">
|
||||||
</form>
|
<a href="{{ route('admin.kategori.index') }}"
|
||||||
|
class="bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-5 py-2 rounded-lg transition duration-200">
|
||||||
|
Kembali
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white font-medium px-6 py-2 rounded-lg shadow transition duration-200">
|
||||||
|
Simpan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@ -3,41 +3,64 @@
|
|||||||
@section('title', 'Edit Kategori')
|
@section('title', 'Edit Kategori')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="container mt-4">
|
<div class="container mx-auto py-8">
|
||||||
<h2 class="mb-3">Edit Kategori</h2>
|
<div class="max-w-2xl mx-auto bg-white shadow-md rounded-xl p-8 border border-blue-100">
|
||||||
|
<h2 class="text-2xl font-semibold text-black mb-6">Edit Kategori</h2>
|
||||||
|
|
||||||
<form action="{{ route('admin.kategori.update', $kategori->id) }}" method="POST" enctype="multipart/form-data">
|
<form action="{{ route('admin.kategori.update', $kategori->id) }}" method="POST" enctype="multipart/form-data" class="space-y-5">
|
||||||
@csrf
|
@csrf
|
||||||
@method('PUT')
|
@method('PUT')
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Nama Kategori -->
|
||||||
<label for="nama" class="form-label">Nama Kategori</label>
|
<div>
|
||||||
<input type="text" class="form-control @error('nama') is-invalid @enderror"
|
<label for="nama" class="block text-sm font-medium text-gray-700 mb-2">Nama Kategori</label>
|
||||||
id="nama" name="nama" value="{{ old('nama', $kategori->nama) }}" required>
|
<input type="text" id="nama" name="nama"
|
||||||
@error('nama') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
value="{{ old('nama', $kategori->nama) }}"
|
||||||
</div>
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none @error('nama') border-red-500 @enderror"
|
||||||
|
placeholder="Masukkan nama kategori" required>
|
||||||
|
@error('nama')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Deskripsi -->
|
||||||
<label for="deskripsi" class="form-label">Deskripsi</label>
|
<div>
|
||||||
<textarea class="form-control @error('deskripsi') is-invalid @enderror"
|
<label for="deskripsi" class="block text-sm font-medium text-gray-700 mb-2">Deskripsi</label>
|
||||||
id="deskripsi" name="deskripsi">{{ old('deskripsi', $kategori->deskripsi) }}</textarea>
|
<textarea id="deskripsi" name="deskripsi" rows="4"
|
||||||
@error('deskripsi') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none @error('deskripsi') border-red-500 @enderror"
|
||||||
</div>
|
placeholder="Tuliskan deskripsi kategori">{{ old('deskripsi', $kategori->deskripsi) }}</textarea>
|
||||||
|
@error('deskripsi')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Foto -->
|
||||||
<label for="foto" class="form-label">Foto</label>
|
<div>
|
||||||
@if($kategori->foto)
|
<label for="foto" class="block text-sm font-medium text-gray-700 mb-2">Foto</label>
|
||||||
<div class="mb-2">
|
@if($kategori->foto)
|
||||||
<img src="{{ asset('storage/'.$kategori->foto) }}" width="100" class="img-thumbnail">
|
<div class="mb-3">
|
||||||
</div>
|
<img src="{{ asset('storage/'.$kategori->foto) }}" alt="Foto Kategori" class="w-32 h-32 object-cover rounded-lg border border-gray-200">
|
||||||
@endif
|
</div>
|
||||||
<input type="file" class="form-control @error('foto') is-invalid @enderror"
|
@endif
|
||||||
id="foto" name="foto">
|
<input type="file" id="foto" name="foto"
|
||||||
@error('foto') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 cursor-pointer focus:ring-2 focus:ring-blue-400 @error('foto') border-red-500 @enderror">
|
||||||
</div>
|
@error('foto')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Update</button>
|
<!-- Tombol -->
|
||||||
<a href="{{ route('admin.kategori.index') }}" class="btn btn-secondary">Kembali</a>
|
<div class="flex justify-end gap-3 pt-4">
|
||||||
</form>
|
<a href="{{ route('admin.kategori.index') }}"
|
||||||
|
class="bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-5 py-2 rounded-lg transition duration-200">
|
||||||
|
Kembali
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white font-medium px-6 py-2 rounded-lg shadow transition duration-200">
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@ -3,44 +3,67 @@
|
|||||||
@section('title', 'Buat Pesanan')
|
@section('title', 'Buat Pesanan')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="container mt-4">
|
<div class="container mx-auto py-8">
|
||||||
<h2 class="mb-3">Buat Pesanan</h2>
|
<div class="max-w-2xl mx-auto bg-white shadow-md rounded-xl p-8 border border-blue-100">
|
||||||
|
<h2 class="text-2xl font-semibold text-black mb-6">Buat Pesanan</h2>
|
||||||
|
|
||||||
<form action="{{ route('pelanggans.store') }}" method="POST">
|
<form action="{{ route('pelanggans.store') }}" method="POST" class="space-y-5">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Nama Pemesan -->
|
||||||
<label>Nama Pemesan</label>
|
<div>
|
||||||
<input type="text" name="nama_pemesan" class="form-control" value="{{ old('nama_pemesan') }}" required>
|
<label class="block text-sm font-medium text-gray-700 mb-2">Nama Pemesan</label>
|
||||||
</div>
|
<input type="text" name="nama_pemesan"
|
||||||
|
value="{{ old('nama_pemesan') }}"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Email -->
|
||||||
<label>Email</label>
|
<div>
|
||||||
<input type="email" name="email" class="form-control" value="{{ old('email') }}" required>
|
<label class="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
||||||
</div>
|
<input type="email" name="email"
|
||||||
|
value="{{ old('email') }}"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- No. Telepon -->
|
||||||
<label>No. Telepon</label>
|
<div>
|
||||||
<input type="text" name="no_tlpn" class="form-control" value="{{ old('no_tlpn') }}" required>
|
<label class="block text-sm font-medium text-gray-700 mb-2">No. Telepon</label>
|
||||||
</div>
|
<input type="text" name="no_tlpn"
|
||||||
|
value="{{ old('no_tlpn') }}"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Template -->
|
||||||
<label>Template</label>
|
<div>
|
||||||
<select name="template_id" id="template_id" class="form-select" required>
|
<label class="block text-sm font-medium text-gray-700 mb-2">Template</label>
|
||||||
<option value="">-- Pilih Template --</option>
|
<select name="template_id" id="template_id"
|
||||||
@foreach($templates as $template)
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 focus:ring-2 focus:ring-blue-400 focus:outline-none"
|
||||||
<option value="{{ $template->id }}" data-form='@json($template->form)'>
|
required>
|
||||||
{{ $template->nama_template }} (Rp {{ number_format($template->harga,0,',','.') }})
|
<option value="">-- Pilih Template --</option>
|
||||||
</option>
|
@foreach($templates as $template)
|
||||||
@endforeach
|
<option value="{{ $template->id }}" data-form='@json($template->form)'>
|
||||||
</select>
|
{{ $template->nama_template }} (Rp {{ number_format($template->harga,0,',','.') }})
|
||||||
</div>
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- tempat field dinamis -->
|
<!-- Tempat field dinamis -->
|
||||||
<div id="dynamic-form"></div>
|
<div id="dynamic-form"></div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success">Kirim Pesanan</button>
|
<!-- Tombol -->
|
||||||
</form>
|
<div class="flex justify-end pt-4">
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white font-medium px-6 py-2 rounded-lg shadow transition duration-200">
|
||||||
|
Kirim Pesanan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -58,11 +81,11 @@ document.getElementById('template_id').addEventListener('change', function () {
|
|||||||
let input = '';
|
let input = '';
|
||||||
|
|
||||||
if (field.type === 'text') {
|
if (field.type === 'text') {
|
||||||
input = `<input type="text" name="form[${key}]" class="form-control" ${field.required ? 'required' : ''}>`;
|
input = `<input type="text" name="form[${key}]" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none" ${field.required ? 'required' : ''}>`;
|
||||||
} else if (field.type === 'textarea') {
|
} else if (field.type === 'textarea') {
|
||||||
input = `<textarea name="form[${key}]" class="form-control" ${field.required ? 'required' : ''}></textarea>`;
|
input = `<textarea name="form[${key}]" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none" ${field.required ? 'required' : ''}></textarea>`;
|
||||||
} else if (field.type === 'select') {
|
} else if (field.type === 'select') {
|
||||||
input = `<select name="form[${key}]" class="form-select">`;
|
input = `<select name="form[${key}]" class="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 focus:ring-2 focus:ring-blue-400 focus:outline-none">`;
|
||||||
field.options.forEach(opt => {
|
field.options.forEach(opt => {
|
||||||
input += `<option value="${opt}">${opt}</option>`;
|
input += `<option value="${opt}">${opt}</option>`;
|
||||||
});
|
});
|
||||||
@ -70,7 +93,7 @@ document.getElementById('template_id').addEventListener('change', function () {
|
|||||||
}
|
}
|
||||||
container.innerHTML += `
|
container.innerHTML += `
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label>${label}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">${label}</label>
|
||||||
${input}
|
${input}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -3,86 +3,101 @@
|
|||||||
@section('title', 'Edit Template')
|
@section('title', 'Edit Template')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="container mt-4">
|
<div class="container mx-auto py-8">
|
||||||
<h2>Edit Template</h2>
|
<div class="max-w-2xl mx-auto bg-white shadow-md rounded-xl p-8 border border-blue-100">
|
||||||
|
<h2 class="text-2xl font-semibold text-black mb-6">Edit Template</h2>
|
||||||
|
|
||||||
{{-- Alert sukses / error --}}
|
{{-- Alert sukses / error --}}
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
<div class="alert alert-success">{{ session('success') }}</div>
|
<div class="bg-green-100 text-green-700 px-4 py-2 rounded-lg mb-4 border border-green-200">
|
||||||
@endif
|
{{ session('success') }}
|
||||||
@if($errors->any())
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<ul class="mb-0">
|
|
||||||
@foreach($errors->all() as $err)
|
|
||||||
<li>{{ $err }}</li>
|
|
||||||
@endforeach
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<form action="{{ route('admin.template.update', $template->id) }}"
|
|
||||||
method="POST" enctype="multipart/form-data">
|
|
||||||
@csrf
|
|
||||||
@method('PUT')
|
|
||||||
|
|
||||||
{{-- Nama Template --}}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="nama_template" class="form-label">Nama Template</label>
|
|
||||||
<input type="text" name="nama_template" id="nama_template"
|
|
||||||
class="form-control @error('nama_template') is-invalid @enderror"
|
|
||||||
value="{{ old('nama_template', $template->nama_template) }}" required>
|
|
||||||
@error('nama_template')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Harga --}}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="harga" class="form-label">Harga</label>
|
|
||||||
<input type="number" name="harga" id="harga"
|
|
||||||
class="form-control @error('harga') is-invalid @enderror"
|
|
||||||
value="{{ old('harga', $template->harga) }}" required>
|
|
||||||
@error('harga')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Paket --}}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="paket" class="form-label">Paket</label>
|
|
||||||
<select name="paket" id="paket" class="form-select @error('paket') is-invalid @enderror" required>
|
|
||||||
<option value="starter" {{ old('paket', $template->paket) == 'starter' ? 'selected' : '' }}>Starter</option>
|
|
||||||
<option value="basic" {{ old('paket', $template->paket) == 'basic' ? 'selected' : '' }}>Basic</option>
|
|
||||||
<option value="premium" {{ old('paket', $template->paket) == 'premium' ? 'selected' : '' }}>Premium</option>
|
|
||||||
</select>
|
|
||||||
@error('paket')
|
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Foto / Gambar --}}
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="foto" class="form-label">Gambar Template</label>
|
|
||||||
<div class="mb-2">
|
|
||||||
@if($template->foto)
|
|
||||||
<img src="{{ asset('storage/' . $template->foto) }}"
|
|
||||||
alt="{{ $template->nama_template }}"
|
|
||||||
class="rounded border" style="max-height: 120px">
|
|
||||||
@else
|
|
||||||
<p class="text-muted">Belum ada gambar</p>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
<input type="file" name="foto" id="foto"
|
@endif
|
||||||
class="form-control @error('foto') is-invalid @enderror">
|
@if($errors->any())
|
||||||
<small class="text-muted">Kosongkan jika tidak ingin mengganti gambar.</small>
|
<div class="bg-red-100 text-red-700 px-4 py-2 rounded-lg mb-4 border border-red-200">
|
||||||
@error('foto')
|
<ul class="list-disc pl-5 mb-0">
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
@foreach($errors->all() as $err)
|
||||||
@enderror
|
<li>{{ $err }}</li>
|
||||||
</div>
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
{{-- Tombol --}}
|
<form action="{{ route('admin.template.update', $template->id) }}" method="POST" enctype="multipart/form-data" class="space-y-5">
|
||||||
<button type="submit" class="btn btn-primary">Simpan Perubahan</button>
|
@csrf
|
||||||
<a href="{{ route('admin.template.index') }}" class="btn btn-secondary">Batal</a>
|
@method('PUT')
|
||||||
</form>
|
|
||||||
|
{{-- Nama Template --}}
|
||||||
|
<div>
|
||||||
|
<label for="nama_template" class="block text-sm font-medium text-gray-700 mb-2">Nama Template</label>
|
||||||
|
<input type="text" id="nama_template" name="nama_template"
|
||||||
|
value="{{ old('nama_template', $template->nama_template) }}"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none @error('nama_template') border-red-500 @enderror"
|
||||||
|
required>
|
||||||
|
@error('nama_template')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Harga --}}
|
||||||
|
<div>
|
||||||
|
<label for="harga" class="block text-sm font-medium text-gray-700 mb-2">Harga</label>
|
||||||
|
<input type="number" id="harga" name="harga"
|
||||||
|
value="{{ old('harga', $template->harga) }}"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none @error('harga') border-red-500 @enderror"
|
||||||
|
required>
|
||||||
|
@error('harga')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Paket --}}
|
||||||
|
<div>
|
||||||
|
<label for="paket" class="block text-sm font-medium text-gray-700 mb-2">Paket</label>
|
||||||
|
<select id="paket" name="paket"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 focus:ring-2 focus:ring-blue-400 focus:outline-none @error('paket') border-red-500 @enderror"
|
||||||
|
required>
|
||||||
|
<option value="starter" {{ old('paket', $template->paket) == 'starter' ? 'selected' : '' }}>Starter</option>
|
||||||
|
<option value="basic" {{ old('paket', $template->paket) == 'basic' ? 'selected' : '' }}>Basic</option>
|
||||||
|
<option value="premium" {{ old('paket', $template->paket) == 'premium' ? 'selected' : '' }}>Premium</option>
|
||||||
|
</select>
|
||||||
|
@error('paket')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Foto / Gambar --}}
|
||||||
|
<div>
|
||||||
|
<label for="foto" class="block text-sm font-medium text-gray-700 mb-2">Gambar Template</label>
|
||||||
|
<div class="mb-3">
|
||||||
|
@if($template->foto)
|
||||||
|
<img src="{{ asset('storage/' . $template->foto) }}"
|
||||||
|
alt="{{ $template->nama_template }}"
|
||||||
|
class="w-32 h-32 object-cover rounded-lg border border-gray-200">
|
||||||
|
@else
|
||||||
|
<p class="text-gray-500 text-sm">Belum ada gambar</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<input type="file" id="foto" name="foto"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 cursor-pointer focus:ring-2 focus:ring-blue-400 @error('foto') border-red-500 @enderror">
|
||||||
|
<p class="text-gray-500 text-sm mt-1">Kosongkan jika tidak ingin mengganti gambar.</p>
|
||||||
|
@error('foto')
|
||||||
|
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Tombol --}}
|
||||||
|
<div class="flex justify-end gap-3 pt-4">
|
||||||
|
<a href="{{ route('admin.template.index') }}"
|
||||||
|
class="bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-5 py-2 rounded-lg transition duration-200">
|
||||||
|
Batal
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white font-medium px-6 py-2 rounded-lg shadow transition duration-200">
|
||||||
|
Simpan Perubahan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Route;
|
|||||||
use App\Http\Controllers\Api\TemplateApiController;
|
use App\Http\Controllers\Api\TemplateApiController;
|
||||||
use App\Http\Controllers\Api\KategoriApiController;
|
use App\Http\Controllers\Api\KategoriApiController;
|
||||||
use App\Http\Controllers\Api\PelangganApiController;
|
use App\Http\Controllers\Api\PelangganApiController;
|
||||||
|
use App\Http\Controllers\Api\ReviewApiController;
|
||||||
|
|
||||||
Route::get('kategoris', [KategoriApiController::class, 'index']);
|
Route::get('kategoris', [KategoriApiController::class, 'index']);
|
||||||
Route::get('kategoris/{kategori}', [KategoriApiController::class, 'show']);
|
Route::get('kategoris/{kategori}', [KategoriApiController::class, 'show']);
|
||||||
@ -12,7 +13,11 @@ Route::get('kategoris/{kategori}', [KategoriApiController::class, 'show']);
|
|||||||
Route::get('/templates', [TemplateApiController::class, 'index']);
|
Route::get('/templates', [TemplateApiController::class, 'index']);
|
||||||
Route::get('/templates/{template}', [TemplateApiController::class, 'show']);
|
Route::get('/templates/{template}', [TemplateApiController::class, 'show']);
|
||||||
|
|
||||||
|
|
||||||
Route::get('/templates/category/{id}', [TemplateApiController::class, 'getByCategory']);
|
Route::get('/templates/category/{id}', [TemplateApiController::class, 'getByCategory']);
|
||||||
|
|
||||||
Route::post('/pelanggans', [PelangganApiController::class, 'store']);
|
Route::post('/pelanggans', [PelangganApiController::class, 'store']);
|
||||||
|
|
||||||
|
Route::get('/reviews', [ReviewApiController::class, 'index']);
|
||||||
|
Route::post('/reviews', [ReviewApiController::class, 'store']);
|
||||||
|
Route::get('/reviews/{id}', [ReviewApiController::class, 'show']);
|
||||||
|
Route::delete('/reviews/{id}', [ReviewApiController::class, 'destroy']);
|
||||||
|
|||||||
@ -2,60 +2,16 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
// id template yang mau ditampilkan
|
// id template yang mau ditampilkan
|
||||||
const selectedIds = [1, 2, 3, 4, 5, 6]
|
const selectedIds = [1, 2, 3, 5, 6]
|
||||||
|
|
||||||
// state dropdown
|
// state dropdown (buat fitur paket aja sekarang)
|
||||||
const openDropdownId = ref(null)
|
const openDropdownId = ref(null)
|
||||||
const toggleDropdown = (templateId) => {
|
const toggleDropdown = (templateId) => {
|
||||||
openDropdownId.value = openDropdownId.value === templateId ? null : templateId
|
openDropdownId.value = openDropdownId.value === templateId ? null : templateId
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paket & fitur hardcode (tidak tergantung kategori backend)
|
// fetch API dari Laravel
|
||||||
const paketData = [
|
const { data: templatesData, error } = await useFetch('http://localhost:8000/api/templates')
|
||||||
{
|
|
||||||
paket: 'Starter',
|
|
||||||
fiturs: [
|
|
||||||
'1x Acara',
|
|
||||||
'Masa Aktif 3 Bulan',
|
|
||||||
'Nama Tamu Personal',
|
|
||||||
'Maks. 100 Tamu',
|
|
||||||
'Request Musik'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
paket: 'Basic',
|
|
||||||
fiturs: [
|
|
||||||
'1x Acara',
|
|
||||||
'6 Galeri Foto',
|
|
||||||
'Hitung Mundur Waktu Acara',
|
|
||||||
'Buku Tamu + Data Kehadiran',
|
|
||||||
'Masa Aktif 6 Bulan',
|
|
||||||
'Nama Tamu Personal',
|
|
||||||
'Maks. 200 Tamu',
|
|
||||||
'Request Musik'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
paket: 'Premium',
|
|
||||||
fiturs: [
|
|
||||||
'Maksimal 3x Acara (Akad, Resepsi, Syukuran)',
|
|
||||||
'Unlimited Galeri Foto',
|
|
||||||
'Timeline Story',
|
|
||||||
'Google Maps',
|
|
||||||
'Reminder Google Calendar',
|
|
||||||
'Link Instagram Live Streaming',
|
|
||||||
'Amplop Digital',
|
|
||||||
'Placement Video Cinematic',
|
|
||||||
'Bonus Undangan Image Post Story',
|
|
||||||
'Masa Aktif 12 Bulan',
|
|
||||||
'Nama Tamu Personal Unlimited Tamu',
|
|
||||||
'Request Musik'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// fetch API dari backend (hanya untuk nama template & harga)
|
|
||||||
const { data: templatesData } = await useFetch('http://localhost:8000/api/templates')
|
|
||||||
|
|
||||||
// mapping template: nama_template & harga dari backend, paket & fiturs hardcode
|
// mapping template: nama_template & harga dari backend, paket & fiturs hardcode
|
||||||
const templates = computed(() =>
|
const templates = computed(() =>
|
||||||
@ -84,10 +40,17 @@ const templates = computed(() =>
|
|||||||
|
|
||||||
<!-- Grid Template -->
|
<!-- Grid Template -->
|
||||||
<div v-if="templates.length" class="grid gap-8 max-w-[1100px] mx-auto grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
<div v-if="templates.length" class="grid gap-8 max-w-[1100px] mx-auto grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
<div v-for="t in templates" :key="t.id"
|
<div
|
||||||
class="bg-white border rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300">
|
v-for="t in templates"
|
||||||
|
:key="t.id"
|
||||||
|
class="bg-white border rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300"
|
||||||
|
>
|
||||||
<!-- Image -->
|
<!-- Image -->
|
||||||
<img :src="t.foto" :alt="t.nama_template" class="w-full h-48 object-cover" />
|
<img
|
||||||
|
:src="`http://localhost:8000${t.foto}`"
|
||||||
|
:alt="t.nama_template"
|
||||||
|
class="w-full h-48 object-cover"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="p-5 text-center">
|
<div class="p-5 text-center">
|
||||||
@ -95,45 +58,41 @@ const templates = computed(() =>
|
|||||||
<p class="text-green-600 font-semibold text-xl mb-1">
|
<p class="text-green-600 font-semibold text-xl mb-1">
|
||||||
Rp {{ Number(t.harga).toLocaleString('id-ID') }}
|
Rp {{ Number(t.harga).toLocaleString('id-ID') }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 mb-4 font-medium">Paket: {{ t.paket }}</p>
|
|
||||||
|
|
||||||
<!-- Dropdown fitur -->
|
<!-- Dropdown fitur -->
|
||||||
<div v-if="t.fiturs && t.fiturs.length > 0" class="relative mb-4">
|
<div v-if="t.fiturs && t.fiturs.length > 0" class="relative mb-4">
|
||||||
<button @click="toggleDropdown(t.id)"
|
<button
|
||||||
class="w-full bg-white border border-gray-300 rounded-md shadow-sm px-4 py-2 inline-flex justify-between items-start">
|
@click="toggleDropdown(t.id)"
|
||||||
|
class="w-full bg-white border border-gray-300 rounded-md shadow-sm px-4 py-2 inline-flex justify-between items-center"
|
||||||
|
>
|
||||||
<span class="mx-auto text-gray-700 font-semibold">FITUR YANG TERSEDIA</span>
|
<span class="mx-auto text-gray-700 font-semibold">FITUR YANG TERSEDIA</span>
|
||||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||||
fill="currentColor">
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
|
||||||
clip-rule="evenodd" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<transition name="fade">
|
<div v-if="openDropdownId === t.id">
|
||||||
<div v-if="openDropdownId === t.id" class="mt-4">
|
<ul class="mt-4 space-y-2 text-gray-600 text-left">
|
||||||
<ul
|
<li v-for="f in t.fiturs" :key="f.id" class="flex items-center">
|
||||||
class="space-y-2 text-gray-600 text-left max-h-60 overflow-y-auto px-3 py-2 border border-gray-200 rounded-md shadow-inner bg-gray-50">
|
<svg class="h-4 w-4 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<li v-for="f in t.fiturs" :key="f.id" class="flex items-center">
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||||
<svg class="h-4 w-4 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
</svg>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
{{ f.deskripsi }}
|
||||||
</svg>
|
</li>
|
||||||
{{ f.deskripsi }}
|
</ul>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Tombol -->
|
||||||
<div class="flex items-center gap-3 mt-6">
|
<div class="flex items-center gap-3 mt-6">
|
||||||
<button
|
<button
|
||||||
class="w-full bg-white border border-gray-300 text-gray-800 font-semibold py-2 px-4 rounded-lg hover:bg-gray-100 transition-colors">
|
class="w-full bg-white border border-gray-300 text-gray-800 font-semibold py-2 px-4 rounded-lg hover:bg-gray-100 transition-colors">
|
||||||
Preview
|
Preview
|
||||||
</button>
|
</button>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="`/form/${t.kategori ? t.kategori.toLowerCase().replace(/ /g, '-') : ''}` + `?template_id=${t.id}`"
|
:to="`/form/${t.kategori.nama.toLowerCase().replace(/ /g, '-')}` + `?template_id=${t.id}`"
|
||||||
class="w-full bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors text-center">
|
class="w-full bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors text-center"
|
||||||
|
>
|
||||||
Order
|
Order
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
@ -141,7 +100,7 @@ const templates = computed(() =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Jika tidak ada template -->
|
<!-- Jika error -->
|
||||||
<div v-else class="text-gray-500">Tidak ada template yang bisa ditampilkan</div>
|
<div v-else class="text-gray-500">Tidak ada template yang bisa ditampilkan</div>
|
||||||
|
|
||||||
<!-- See more -->
|
<!-- See more -->
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user