Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 35 additions & 33 deletions app/Http/Controllers/Admin/AdminBookingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,118 +10,120 @@

class AdminBookingController extends Controller
{
/**
* Tampilkan daftar semua pesanan.
*/
const DP_PERCENTAGE = 0.5; // 50% dari total harga

public function index()
{
$bookings = Booking::latest()->paginate(15);
return view('admin.bookings.index', compact('bookings'));
}

/**
* Tampilkan detail pesanan.
*/
public function show(string $id)
{
$booking = Booking::findOrFail($id);
return view('admin.bookings.show', compact('booking'));
$expectedDpAmount = $booking->total_price * self::DP_PERCENTAGE;

return view('admin.bookings.show', compact('booking', 'expectedDpAmount'));
}

/**
* Konfirmasi DP: simpan bukti, ubah status ke 'booked', lalu arahkan ke WhatsApp.
*/
public function confirmDp(Request $request, $id)
{
$booking = Booking::findOrFail($id);
$expectedDpAmount = $booking->total_price * self::DP_PERCENTAGE;

// Validasi input
$request->validate([
'dp_amount' => 'required|numeric|min:0',
'dp_amount' => 'required|numeric|min:1',
'dp_proof' => 'required|image|max:2048',
], [
'dp_amount.required' => 'Nominal DP wajib diisi.',
'dp_amount.numeric' => 'Nominal DP harus berupa angka.',
'dp_amount.min' => 'Nominal DP harus lebih dari 0.',
'dp_proof.required' => 'Bukti transfer DP wajib diunggah.',
'dp_proof.image' => 'File bukti harus berupa gambar.',
'dp_proof.max' => 'Ukuran file maksimal 2MB.',
]);

// Simpan bukti DP
if ($request->dp_amount != $expectedDpAmount) {
return back()->withErrors([
'dp_amount' => "Nominal DP harus tepat Rp" . number_format($expectedDpAmount, 0, ',', '.') . " (50% dari total harga)."
])->withInput();
}

$path = $request->file('dp_proof')->store('bukti_dp', 'public');

// Update data booking
$booking->update([
'dp_amount' => $request->dp_amount,
'dp_proof' => $path,
'status' => 'booked',
]);

// Set pesan sukses
session()->flash('success', 'DP berhasil dikonfirmasi. Silakan kirim pesan ke pelanggan.');

// Buat pesan WhatsApp
$message = "Halo kak {$booking->contact_name},\n\n"
. "Terima kasih, DP kamu untuk sesi {$booking->session_name} pada tanggal "
. Carbon::parse($booking->booking_date)->translatedFormat('d F Y') . " pukul {$booking->booking_time} telah kami terima.\n"
. "Pesananmu sudah dikonfirmasi ✅ Sampai jumpa di hari H!";

// Format nomor WhatsApp (08xx → 628xx)
$whatsappNumber = preg_replace('/[^0-9]/', '', $booking->whatsapp_number);
if (str_starts_with($whatsappNumber, '0')) {
$whatsappNumber = '62' . substr($whatsappNumber, 1);
}

// ✅ URL BENAR: TANPA SPASI EKSTRA
$url = 'https://wa.me/' . $whatsappNumber . '?text=' . urlencode($message);

return redirect()->away($url);
}

/**
* Selesaikan pesanan: simpan bukti pelunasan, ubah status ke 'completed', lalu arahkan ke WhatsApp.
*/
public function completeBooking(Request $request, $id)
{
$booking = Booking::findOrFail($id);
$expectedFinalAmount = $booking->total_price - $booking->dp_amount;

// Validasi input
$request->validate([
'final_payment_amount' => 'required|numeric|min:0',
'final_payment_amount' => 'required|numeric|min:1',
'final_payment_proof' => 'required|image|max:2048',
], [
'final_payment_amount.required' => 'Nominal pelunasan wajib diisi.',
'final_payment_amount.numeric' => 'Nominal pelunasan harus berupa angka.',
'final_payment_amount.min' => 'Nominal pelunasan harus lebih dari 0.',
'final_payment_proof.required' => 'Bukti pelunasan wajib diunggah.',
'final_payment_proof.image' => 'File bukti harus berupa gambar.',
'final_payment_proof.max' => 'Ukuran file maksimal 2MB.',
]);

// Simpan bukti pelunasan
if ($request->final_payment_amount != $expectedFinalAmount) {
return back()->withErrors([
'final_payment_amount' => "Nominal pelunasan harus tepat Rp" . number_format($expectedFinalAmount, 0, ',', '.') . " (total - DP)."
])->withInput();
}

$path = $request->file('final_payment_proof')->store('bukti_pelunasan', 'public');

// Update data booking
$booking->update([
'final_payment_amount' => $request->final_payment_amount,
'final_payment_proof' => $path,
'status' => 'completed',
]);

// Set pesan sukses
session()->flash('success', 'Pelunasan berhasil dikonfirmasi.');

// Buat pesan WhatsApp
$message = "Halo kak {$booking->contact_name},\n\n"
. "Kami telah menerima pelunasan untuk sesi {$booking->session_name} pada tanggal "
. Carbon::parse($booking->booking_date)->translatedFormat('d F Y') . ".\n"
. "Terima kasih telah mempercayakan layanan kami. See you next time! ❤️";

// Format nomor WhatsApp
$whatsappNumber = preg_replace('/[^0-9]/', '', $booking->whatsapp_number);
if (str_starts_with($whatsappNumber, '0')) {
$whatsappNumber = '62' . substr($whatsappNumber, 1);
}

// ✅ URL BENAR: TANPA SPASI EKSTRA
$url = 'https://wa.me/' . $whatsappNumber . '?text=' . urlencode($message);

return redirect()->away($url);
}

/**
* Hapus pesanan dan file bukti pembayaran.
*/
public function destroy(Booking $booking)
{
// Hapus file bukti jika ada
if ($booking->dp_proof && Storage::disk('public')->exists($booking->dp_proof)) {
Storage::disk('public')->delete($booking->dp_proof);
}
Expand Down
3 changes: 3 additions & 0 deletions resources/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ select:focus[data-flux-control] {
@apply outline-hidden ring-2 ring-accent ring-offset-2 ring-offset-accent-foreground;
}




/* \[:where(&)\]:size-4 {
@apply size-4;
} */
16 changes: 16 additions & 0 deletions resources/views/admin/backgrounds/create.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,23 @@

{{-- Meng-include partial form --}}
@include('admin.backgrounds._form', ['background' => null])
<x-layouts.app :title="__('Background')">
<div class="container mx-auto px-4 py-8">
<h2 class="text-2xl font-bold text-gray-800 mb-6 font-playfair">Tambah Background Baru</h2>
<div class="bg-white shadow-lg rounded-xl p-6 border border-gray-200">
<form action="{{ route('backgrounds.store') }}" method="POST" enctype="multipart/form-data">
@csrf

@include('admin.backgrounds._form', ['background' => null])

<button type="submit"
class="bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-bold py-2 px-6 rounded-lg mt-6 transition-all duration-300 shadow-sm hover:shadow-md transform hover:scale-105">
Simpan Background
</button>
</form>
</div>
</div>
</x-layouts.app>
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg mt-4 transition duration-300">
Simpan Background
</button>
Expand Down
176 changes: 125 additions & 51 deletions resources/views/admin/backgrounds/index.blade.php
Original file line number Diff line number Diff line change
@@ -1,64 +1,138 @@
<x-layouts.app :title="__('Background')">

<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Manajemen Backgrounds</h2>
<a href="{{ route('backgrounds.create') }}" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg transition duration-300">
Tambah Background
</a>
</div>

@if(session('success'))
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6" role="alert">
{{ session('success') }}
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4">
<h2 class="text-2xl font-bold text-gray-800">Manajemen Backgrounds</h2>
<a href="{{ route('backgrounds.create') }}"
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-5 rounded-lg transition duration-300 shadow-sm hover:shadow-md">
+ Tambah Background
</a>
</div>
@endif

<div class="bg-white shadow-md rounded-lg overflow-hidden">
<table class="min-w-full leading-normal">
<thead>
<tr class="bg-gray-200 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
<th class="px-5 py-3 border-b-2 border-gray-200">No</th>
<th class="px-5 py-3 border-b-2 border-gray-200">Gambar</th>
<th class="px-5 py-3 border-b-2 border-gray-200">Nama</th>
<th class="px-5 py-3 border-b-2 border-gray-200">Kategori</th>
<th class="px-5 py-3 border-b-2 border-gray-200">Status</th>
<th class="px-5 py-3 border-b-2 border-gray-200">Aksi</th>
</tr>
</thead>
<tbody>
<!-- Alert: Sukses -->
@if(session('success'))
<div class="bg-green-100 border border-green-400 text-green-700 px-5 py-4 rounded-lg mb-6 text-sm flex items-center gap-2 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
{{ session('success') }}
</div>
@endif

<!-- Tabel (Desktop) / Cards (Mobile) -->
<div class="bg-white shadow-lg rounded-xl overflow-hidden border border-gray-200">
<!-- Desktop Table -->
<div class="hidden sm:block overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gradient-to-r from-gray-50 to-gray-100">
<tr class="text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
<th class="px-6 py-4">No</th>
<th class="px-6 py-4">Gambar</th>
<th class="px-6 py-4">Nama</th>
<th class="px-6 py-4">Kategori</th>
<th class="px-6 py-4">Status</th>
<th class="px-6 py-4">Aksi</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($backgrounds as $background)
<tr class="hover:bg-gray-50 transition duration-150">
<td class="px-6 py-5 text-sm text-gray-700">{{ $loop->iteration }}</td>
<td class="px-6 py-5">
<img src="{{ asset('storage/' . $background->image) }}"
alt="{{ $background->name }}"
class="w-16 h-16 object-cover rounded-lg shadow-sm border border-gray-200">
</td>
<td class="px-6 py-5 text-sm font-medium text-gray-800">{{ $background->name }}</td>
<td class="px-6 py-5 text-sm text-gray-600">
{{ Str::title(str_replace('-', ' ', $background->category)) }}
</td>
<td class="px-6 py-5 text-sm">
@if($background->is_active)
<span class="inline-flex items-center gap-1 bg-green-100 text-green-800 text-xs px-3 py-1 rounded-full font-medium border border-green-200">
<span class="w-2 h-2 bg-green-500 rounded-full"></span>
Aktif
</span>
@else
<span class="inline-flex items-center gap-1 bg-red-100 text-red-800 text-xs px-3 py-1 rounded-full font-medium border border-red-200">
<span class="w-2 h-2 bg-red-500 rounded-full"></span>
Non-Aktif
</span>
@endif
</td>
<td class="px-6 py-5 text-sm space-y-2">
<a href="{{ route('backgrounds.edit', $background) }}"
class="block w-full text-center bg-yellow-500 hover:bg-yellow-600 text-white text-sm font-semibold py-1.5 px-3 rounded-lg transition">
Edit
</a>
<form action="{{ route('backgrounds.destroy', $background) }}" method="POST" class="inline">
@csrf
@method('DELETE')
<button type="submit"
class="w-full text-center bg-red-500 hover:bg-red-600 text-white text-sm font-semibold py-1.5 px-3 rounded-lg transition"
onclick="return confirm('Apakah Anda yakin ingin menghapus background ini?');">
Hapus
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-8 text-center text-gray-500 text-sm italic">
Belum ada background yang ditambahkan.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>

<!-- Mobile Cards -->
<div class="sm:hidden space-y-4 p-4">
@forelse($backgrounds as $background)
<tr class="hover:bg-gray-100">
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">{{ $loop->iteration }}</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<img src="{{ asset('storage/' . $background->image) }}" alt="{{ $background->name }}" class="w-20 h-20 object-cover rounded">
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">{{ $background->name }}</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">{{ Str::title(str_replace('-', ' ', $background->category)) }}</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
@if($background->is_active)
<span class="bg-green-500 text-white text-xs font-bold px-2 py-1 rounded-full">Aktif</span>
@else
<span class="bg-red-500 text-white text-xs font-bold px-2 py-1 rounded-full">Non-Aktif</span>
@endif
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<a href="{{ route('backgrounds.edit', $background) }}" class="text-yellow-600 hover:text-yellow-800 text-sm font-semibold mr-2">Edit</a>
<form action="{{ route('backgrounds.destroy', $background) }}" method="POST" class="inline">
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-4 transition hover:shadow-md">
<div class="flex gap-4 mb-3">
<img src="{{ asset('storage/' . $background->image) }}"
alt="{{ $background->name }}"
class="w-16 h-16 object-cover rounded-lg border border-gray-200">
<div class="flex-1">
<h3 class="font-semibold text-gray-800 text-sm">{{ $background->name }}</h3>
<p class="text-xs text-gray-600 mt-1">
{{ Str::title(str_replace('-', ' ', $background->category)) }}
</p>
<div class="mt-2">
@if($background->is_active)
<span class="text-xs text-green-700 font-medium">🟢 Aktif</span>
@else
<span class="text-xs text-red-700 font-medium">🔴 Non-Aktif</span>
@endif
</div>
</div>
</div>
<div class="flex gap-2 pt-3 border-t border-gray-100">
<a href="{{ route('backgrounds.edit', $background) }}"
class="flex-1 text-center bg-yellow-500 hover:bg-yellow-600 text-white text-xs font-semibold py-2 px-3 rounded-lg text-sm transition">
Edit
</a>
<form action="{{ route('backgrounds.destroy', $background) }}" method="POST" class="flex-1">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800 text-sm font-semibold" onclick="return confirm('Apakah Anda yakin ingin menghapus background ini?');">Hapus</button>
<button type="submit"
class="w-full bg-red-500 hover:bg-red-600 text-white text-xs font-semibold py-2 px-3 rounded-lg text-sm transition"
onclick="return confirm('Apakah Anda yakin ingin menghapus background ini?');">
Hapus
</button>
</form>
</td>
</tr>
</div>
</div>
@empty
<tr>
<td colspan="6" class="px-5 py-5 border-b border-gray-200 bg-white text-center text-sm text-gray-500">Belum ada background yang ditambahkan.</td>
</tr>
<div class="text-center py-6 text-gray-500 text-sm">
Belum ada background yang ditambahkan.
</div>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>

</x-layouts.app>
Loading