Pending Booking dan Booking History
This commit is contained in:
parent
49d4a07895
commit
d4305a4f61
|
@ -5,11 +5,13 @@
|
|||
|
||||
use App\Models\Booking;
|
||||
use App\Models\Table;
|
||||
use App\Models\PendingBooking;
|
||||
use App\Services\MidtransService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class BookingController extends Controller
|
||||
{
|
||||
|
@ -20,7 +22,7 @@ public function __construct(MidtransService $midtransService)
|
|||
$this->midtransService = $midtransService;
|
||||
}
|
||||
|
||||
public function store(Request $request) {
|
||||
public function createPaymentIntent(Request $request) {
|
||||
try {
|
||||
$request->validate([
|
||||
'table_id' => 'required|exists:tables,id',
|
||||
|
@ -28,7 +30,7 @@ public function store(Request $request) {
|
|||
'end_time' => 'required|date|after:start_time',
|
||||
]);
|
||||
|
||||
// Cek apakah meja sedang dibooking pada waktu tersebut
|
||||
// Cek apakah meja sedang dibooking pada waktu tersebut (hanya yang sudah paid)
|
||||
$conflict = Booking::where('table_id', $request->table_id)
|
||||
->where(function($query) use ($request) {
|
||||
$query->whereBetween('start_time', [$request->start_time, $request->end_time])
|
||||
|
@ -37,8 +39,7 @@ public function store(Request $request) {
|
|||
->where('end_time', '>', $request->start_time);
|
||||
});
|
||||
})
|
||||
->where('status', '!=', 'cancelled')
|
||||
->where('status', '!=', 'expired')
|
||||
->where('status', 'paid') // Hanya cek yang sudah paid
|
||||
->exists();
|
||||
|
||||
if ($conflict) {
|
||||
|
@ -52,61 +53,157 @@ public function store(Request $request) {
|
|||
$duration = $endTime->diffInHours($startTime);
|
||||
$totalAmount = $duration * $table->price_per_hour;
|
||||
|
||||
// Buat booking dengan status pending
|
||||
$booking = Booking::create([
|
||||
// Simpan data booking sementara di session untuk digunakan setelah pembayaran
|
||||
Session::put('temp_booking', [
|
||||
'table_id' => $request->table_id,
|
||||
'user_id' => Auth::id(),
|
||||
'start_time' => $request->start_time,
|
||||
'end_time' => $request->end_time,
|
||||
'status' => 'pending',
|
||||
'total_amount' => $totalAmount,
|
||||
'payment_expired_at' => now()->addHours(24), // Expired dalam 24 jam
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
// Dapatkan snap token dari Midtrans
|
||||
$snapToken = $this->midtransService->createTransaction($booking);
|
||||
// Generate unique order ID
|
||||
$tempOrderId = 'TEMP-' . Auth::id() . '-' . time();
|
||||
Session::put('temp_order_id', $tempOrderId);
|
||||
|
||||
// Simpan booking sementara ke database untuk bisa dilanjutkan nanti
|
||||
PendingBooking::updateOrCreate(
|
||||
[
|
||||
'user_id' => Auth::id(),
|
||||
'table_id' => $request->table_id,
|
||||
'start_time' => $request->start_time
|
||||
],
|
||||
[
|
||||
'end_time' => $request->end_time,
|
||||
'total_amount' => $totalAmount,
|
||||
'order_id' => $tempOrderId,
|
||||
'expired_at' => now()->addHours(24), // Kadaluarsa dalam 24 jam
|
||||
]
|
||||
);
|
||||
|
||||
// Dapatkan snap token dari Midtrans tanpa menyimpan booking
|
||||
$snapToken = $this->midtransService->createTemporaryTransaction($table, $totalAmount, $tempOrderId, Auth::user());
|
||||
|
||||
if (!$snapToken) {
|
||||
throw new \Exception('Failed to get snap token from Midtrans');
|
||||
}
|
||||
|
||||
\Log::info('Booking created successfully:', [
|
||||
'booking_id' => $booking->id,
|
||||
\Log::info('Payment intent created successfully:', [
|
||||
'order_id' => $tempOrderId,
|
||||
'snap_token' => $snapToken
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Booking berhasil dibuat, silahkan lakukan pembayaran',
|
||||
'booking_id' => $booking->id,
|
||||
'message' => 'Payment intent created, proceed to payment',
|
||||
'total_amount' => $totalAmount,
|
||||
'snap_token' => $snapToken
|
||||
'snap_token' => $snapToken,
|
||||
'order_id' => $tempOrderId
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Booking error:', [
|
||||
\Log::error('Payment intent error:', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
if (isset($booking)) {
|
||||
$booking->delete(); // Hapus booking jika gagal membuat transaksi
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Gagal membuat transaksi: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request) {
|
||||
try {
|
||||
$request->validate([
|
||||
'order_id' => 'required|string',
|
||||
'transaction_id' => 'required|string',
|
||||
'payment_method' => 'required|string',
|
||||
'transaction_status' => 'required|string',
|
||||
]);
|
||||
|
||||
// Retrieve booking data from session
|
||||
$tempBooking = Session::get('temp_booking');
|
||||
$tempOrderId = Session::get('temp_order_id');
|
||||
|
||||
// If not in session, try from pending bookings
|
||||
if (!$tempBooking || $tempOrderId != $request->order_id) {
|
||||
$pendingBooking = PendingBooking::where('order_id', $request->order_id)
|
||||
->where('user_id', Auth::id())
|
||||
->first();
|
||||
|
||||
if (!$pendingBooking) {
|
||||
throw new \Exception('Invalid or expired booking session');
|
||||
}
|
||||
|
||||
$tempBooking = [
|
||||
'table_id' => $pendingBooking->table_id,
|
||||
'user_id' => $pendingBooking->user_id,
|
||||
'start_time' => $pendingBooking->start_time,
|
||||
'end_time' => $pendingBooking->end_time,
|
||||
'total_amount' => $pendingBooking->total_amount,
|
||||
];
|
||||
$tempOrderId = $pendingBooking->order_id;
|
||||
}
|
||||
|
||||
// Process based on transaction status
|
||||
if ($request->transaction_status == 'settlement' || $request->transaction_status == 'capture') {
|
||||
// Create the actual booking record
|
||||
$booking = Booking::create([
|
||||
'table_id' => $tempBooking['table_id'],
|
||||
'user_id' => $tempBooking['user_id'],
|
||||
'start_time' => $tempBooking['start_time'],
|
||||
'end_time' => $tempBooking['end_time'],
|
||||
'status' => 'paid',
|
||||
'total_amount' => $tempBooking['total_amount'],
|
||||
'payment_id' => $request->transaction_id,
|
||||
'payment_method' => $request->payment_method,
|
||||
'order_id' => $request->order_id,
|
||||
]);
|
||||
|
||||
// Update table status to booked
|
||||
$table = Table::findOrFail($tempBooking['table_id']);
|
||||
$table->update(['status' => 'Booked']);
|
||||
|
||||
// Delete pending booking if exists
|
||||
PendingBooking::where('order_id', $request->order_id)->delete();
|
||||
|
||||
// Clear session data
|
||||
Session::forget('temp_booking');
|
||||
Session::forget('temp_order_id');
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Booking created successfully',
|
||||
'booking_id' => $booking->id
|
||||
]);
|
||||
} else {
|
||||
// For pending, deny, cancel, etc. - don't create booking
|
||||
return response()->json([
|
||||
'message' => 'Payment ' . $request->transaction_status . ', booking not created',
|
||||
'status' => $request->transaction_status
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Booking store error:', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Failed to create booking: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function getBookedSchedules(Request $request) {
|
||||
$request->validate([
|
||||
'table_id' => 'required|exists:tables,id',
|
||||
'date' => 'required|date',
|
||||
]);
|
||||
|
||||
// Only get bookings with paid status
|
||||
$bookings = Booking::where('table_id', $request->table_id)
|
||||
->whereDate('start_time', $request->date)
|
||||
->where('status', '!=', 'cancelled')
|
||||
->where('status', '!=', 'expired')
|
||||
->where('status', 'paid') // Only include paid bookings
|
||||
->select('start_time', 'end_time')
|
||||
->get()
|
||||
->map(function ($booking) {
|
||||
|
@ -127,9 +224,22 @@ public function handleNotification(Request $request)
|
|||
|
||||
$transactionStatus = $notification['transaction_status'];
|
||||
$orderId = $notification['order_id'];
|
||||
$fraudStatus = $notification['fraud_status'];
|
||||
$fraudStatus = $notification['fraud_status'] ?? null;
|
||||
$transactionId = $notification['transaction_id'];
|
||||
$paymentType = $notification['payment_type'];
|
||||
|
||||
// Get booking from order_id
|
||||
// Check if this is a temporary order (from our new flow)
|
||||
if (strpos($orderId, 'TEMP-') === 0) {
|
||||
// This is a notification for a transaction that started with our new flow
|
||||
// We don't need to do anything here as the frontend will handle creating the booking
|
||||
// after successful payment via the store method
|
||||
Log::info('Received notification for temp order, will be handled by frontend', [
|
||||
'order_id' => $orderId
|
||||
]);
|
||||
return response()->json(['message' => 'Notification received for temp order']);
|
||||
}
|
||||
|
||||
// Handle notifications for existing bookings (from old flow or admin-created bookings)
|
||||
$booking = Booking::where('order_id', $orderId)->first();
|
||||
if (!$booking) {
|
||||
Log::error('Booking not found for order_id: ' . $orderId);
|
||||
|
@ -163,7 +273,10 @@ public function handleNotification(Request $request)
|
|||
$booking->status = 'pending';
|
||||
}
|
||||
|
||||
$booking->payment_id = $transactionId;
|
||||
$booking->payment_method = $paymentType;
|
||||
$booking->save();
|
||||
|
||||
Log::info('Booking status updated:', ['booking_id' => $booking->id, 'status' => $booking->status]);
|
||||
|
||||
return response()->json(['message' => 'Notification processed successfully']);
|
||||
|
@ -172,4 +285,114 @@ public function handleNotification(Request $request)
|
|||
return response()->json(['message' => 'Error processing notification'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPendingBookings()
|
||||
{
|
||||
$pendingBookings = PendingBooking::where('user_id', Auth::id())
|
||||
->where('expired_at', '>', now())
|
||||
->with(['table.venue'])
|
||||
->get();
|
||||
|
||||
return response()->json($pendingBookings);
|
||||
}
|
||||
|
||||
public function resumeBooking($id)
|
||||
{
|
||||
try {
|
||||
$pendingBooking = PendingBooking::where('id', $id)
|
||||
->where('user_id', Auth::id())
|
||||
->where('expired_at', '>', now())
|
||||
->firstOrFail();
|
||||
|
||||
// Cek apakah meja masih available di waktu tersebut
|
||||
$conflict = Booking::where('table_id', $pendingBooking->table_id)
|
||||
->where(function($query) use ($pendingBooking) {
|
||||
$query->whereBetween('start_time', [$pendingBooking->start_time, $pendingBooking->end_time])
|
||||
->orWhere(function($query) use ($pendingBooking) {
|
||||
$query->where('start_time', '<', $pendingBooking->start_time)
|
||||
->where('end_time', '>', $pendingBooking->start_time);
|
||||
});
|
||||
})
|
||||
->where('status', 'paid')
|
||||
->exists();
|
||||
|
||||
if ($conflict) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Meja ini sudah tidak tersedia pada waktu yang Anda pilih'
|
||||
], 409);
|
||||
}
|
||||
|
||||
// Simpan ke session
|
||||
Session::put('temp_booking', [
|
||||
'table_id' => $pendingBooking->table_id,
|
||||
'user_id' => Auth::id(),
|
||||
'start_time' => $pendingBooking->start_time,
|
||||
'end_time' => $pendingBooking->end_time,
|
||||
'total_amount' => $pendingBooking->total_amount,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
Session::put('temp_order_id', $pendingBooking->order_id);
|
||||
|
||||
// Dapatkan table data
|
||||
$table = Table::findOrFail($pendingBooking->table_id);
|
||||
|
||||
// Dapatkan snap token baru dari Midtrans
|
||||
$snapToken = $this->midtransService->createTemporaryTransaction(
|
||||
$table,
|
||||
$pendingBooking->total_amount,
|
||||
$pendingBooking->order_id,
|
||||
Auth::user()
|
||||
);
|
||||
|
||||
if (!$snapToken) {
|
||||
throw new \Exception('Failed to get snap token from Midtrans');
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Booking dapat dilanjutkan',
|
||||
'snap_token' => $snapToken,
|
||||
'order_id' => $pendingBooking->order_id,
|
||||
'venue_id' => $pendingBooking->table->venue_id,
|
||||
'table_id' => $pendingBooking->table_id,
|
||||
'table_name' => $pendingBooking->table->name,
|
||||
'start_time' => Carbon::parse($pendingBooking->start_time)->format('H:i'),
|
||||
'duration' => Carbon::parse($pendingBooking->start_time)->diffInHours($pendingBooking->end_time),
|
||||
'total_amount' => $pendingBooking->total_amount
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Resume booking error:', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Gagal memproses pembayaran: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function deletePendingBooking($id)
|
||||
{
|
||||
try {
|
||||
$pendingBooking = PendingBooking::where('id', $id)
|
||||
->where('user_id', Auth::id())
|
||||
->firstOrFail();
|
||||
|
||||
$pendingBooking->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Booking berhasil dihapus'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Gagal menghapus booking: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\pages;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Booking;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class BookingHistoryController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$bookings = Booking::where('user_id', Auth::id())
|
||||
->with(['table.venue'])
|
||||
->orderBy('start_time', 'desc')
|
||||
->paginate(10);
|
||||
|
||||
return view('pages.booking-history', compact('bookings'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PendingBooking extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'table_id',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'total_amount',
|
||||
'order_id',
|
||||
'expired_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_time' => 'datetime',
|
||||
'end_time' => 'datetime',
|
||||
'expired_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function table()
|
||||
{
|
||||
return $this->belongsTo(Table::class);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
namespace App\Services;
|
||||
|
||||
use App\Models\Booking;
|
||||
use App\Models\Table;
|
||||
use App\Models\User;
|
||||
use Midtrans\Config;
|
||||
use Midtrans\Snap;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
@ -42,6 +44,67 @@ public function __construct()
|
|||
]);
|
||||
}
|
||||
|
||||
// Method baru untuk membuat transaksi sementara tanpa booking record
|
||||
public function createTemporaryTransaction(Table $table, int $amount, string $orderId, User $user)
|
||||
{
|
||||
try {
|
||||
if ($amount <= 0) {
|
||||
throw new \Exception('Invalid booking amount');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'transaction_details' => [
|
||||
'order_id' => $orderId,
|
||||
'gross_amount' => (int) $amount,
|
||||
],
|
||||
'customer_details' => [
|
||||
'first_name' => $user->name,
|
||||
'email' => $user->email,
|
||||
],
|
||||
'item_details' => [
|
||||
[
|
||||
'id' => $table->id,
|
||||
'price' => (int) $amount,
|
||||
'quantity' => 1,
|
||||
'name' => 'Booking Meja ' . $table->name,
|
||||
],
|
||||
],
|
||||
'expiry' => [
|
||||
'start_time' => now()->format('Y-m-d H:i:s O'),
|
||||
'unit' => 'hour',
|
||||
'duration' => 24,
|
||||
],
|
||||
];
|
||||
|
||||
Log::info('Creating Midtrans temporary transaction:', [
|
||||
'order_id' => $orderId,
|
||||
'amount' => $amount,
|
||||
'params' => $params
|
||||
]);
|
||||
|
||||
$snapToken = Snap::getSnapToken($params);
|
||||
|
||||
if (empty($snapToken)) {
|
||||
throw new \Exception('Empty snap token received from Midtrans');
|
||||
}
|
||||
|
||||
Log::info('Midtrans temporary transaction created successfully:', [
|
||||
'order_id' => $orderId,
|
||||
'snap_token' => $snapToken
|
||||
]);
|
||||
|
||||
return $snapToken;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Midtrans temporary transaction failed:', [
|
||||
'order_id' => $orderId,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
throw new \Exception('Failed to create Midtrans transaction: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Metode original untuk backward compatibility
|
||||
public function createTransaction(Booking $booking)
|
||||
{
|
||||
try {
|
||||
|
@ -120,6 +183,15 @@ public function handleNotification($notification)
|
|||
'fraud_status' => $fraud
|
||||
]);
|
||||
|
||||
// Check if this is a temporary order
|
||||
if (strpos($orderId, 'TEMP-') === 0) {
|
||||
Log::info('Notification for temporary order received, will be handled separately', [
|
||||
'order_id' => $orderId
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle existing bookings
|
||||
// Extract booking ID from order ID (format: BOOK-{id})
|
||||
$bookingId = explode('-', $orderId)[1];
|
||||
$booking = Booking::findOrFail($bookingId);
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('pending_bookings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('table_id')->constrained()->onDelete('cascade');
|
||||
$table->dateTime('start_time');
|
||||
$table->dateTime('end_time');
|
||||
$table->decimal('total_amount', 10, 2);
|
||||
$table->string('order_id')->unique();
|
||||
$table->dateTime('expired_at');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pending_bookings');
|
||||
}
|
||||
};
|
|
@ -39,6 +39,9 @@ class="block lg:hidden border-l pl-4 border-gray-300 focus:outline-none">
|
|||
<!-- Desktop buttons -->
|
||||
<div class="hidden lg:flex items-center space-x-4">
|
||||
@auth
|
||||
<a href="{{ route('booking.history') }}"
|
||||
class="text-sm font-medium text-gray-700 hover:text-primary transition">Riwayat Booking</a>
|
||||
|
||||
<div x-data="{ open: false }" class="relative">
|
||||
<button @click="open = !open"
|
||||
class="flex items-center space-x-2 text-sm font-medium text-gray-700 hover:text-primary focus:outline-none">
|
||||
|
@ -51,6 +54,10 @@ class="flex items-center space-x-2 text-sm font-medium text-gray-700 hover:text-
|
|||
|
||||
<div x-show="open" @click.away="open = false" x-transition
|
||||
class="absolute right-0 mt-2 w-40 bg-white rounded-lg shadow-lg py-2 z-50">
|
||||
<a href="{{ route('booking.history') }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
Riwayat Booking
|
||||
</a>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
|
@ -69,8 +76,13 @@ class="bg-primary hover:bg-primary-dark text-white px-4 py-2 rounded text-sm fon
|
|||
</div>
|
||||
|
||||
<!-- Mobile menu -->
|
||||
<div x-show="isMobileMenuOpen" ...>
|
||||
<div x-show="isMobileMenuOpen"
|
||||
class="absolute top-full left-0 right-0 bg-white shadow-md mt-1 p-4 z-50">
|
||||
@auth
|
||||
<a href="{{ route('booking.history') }}"
|
||||
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
Riwayat Booking
|
||||
</a>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
|
@ -79,8 +91,10 @@ class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|||
</button>
|
||||
</form>
|
||||
@else
|
||||
<button @click="showModal = true; modalType = 'login'" ...>Masuk</button>
|
||||
<button @click="showModal = true; modalType = 'register'" ...>Daftar</button>
|
||||
<button @click="showModal = true; modalType = 'login'"
|
||||
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Masuk</button>
|
||||
<button @click="showModal = true; modalType = 'register'"
|
||||
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 mt-2">Daftar</button>
|
||||
@endauth
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -151,21 +165,21 @@ class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded">Daftar</but
|
|||
<main class="pt-20">
|
||||
@if (session('success') || session('error'))
|
||||
<div id="floating-alert" style="
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: {{ session('success') ? '#d1e7dd' : '#f8d7da' }};
|
||||
color: {{ session('success') ? '#0f5132' : '#842029' }};
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.15);
|
||||
z-index: 9999;
|
||||
max-width: 300px;
|
||||
text-align: center;
|
||||
">
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: {{ session('success') ? '#d1e7dd' : '#f8d7da' }};
|
||||
color: {{ session('success') ? '#0f5132' : '#842029' }};
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.15);
|
||||
z-index: 9999;
|
||||
max-width: 300px;
|
||||
text-align: center;
|
||||
">
|
||||
{{ session('success') ?? session('error') }}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
@extends('layouts.main')
|
||||
@section('content')
|
||||
<div class="min-h-96 mx-4 md:w-3/4 md:mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">Riwayat Booking</h1>
|
||||
|
||||
@if($bookings->isEmpty())
|
||||
<div class="bg-white rounded-lg shadow-md p-6 text-center">
|
||||
<p class="text-gray-500">Anda belum memiliki riwayat booking.</p>
|
||||
<a href="{{ route('venues.index') }}" class="mt-4 inline-block bg-blue-500 text-white px-4 py-2 rounded-lg">Cari
|
||||
Venue</a>
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-4">
|
||||
@foreach($bookings as $booking)
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div class="p-4 border-b {{ $booking->start_time > now() ? 'bg-green-50' : 'bg-gray-50' }}">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="font-semibold text-lg">{{ $booking->table->venue->name }}</h3>
|
||||
<span
|
||||
class="px-3 py-1 rounded-full text-sm {{ $booking->start_time > now() ? 'bg-green-100 text-green-800' : 'bg-gray-200 text-gray-800' }}">
|
||||
{{ $booking->start_time > now() ? 'Upcoming' : 'Completed' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Meja</p>
|
||||
<p class="font-medium">{{ $booking->table->name }} ({{ $booking->table->brand }})</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Tanggal & Waktu</p>
|
||||
<p class="font-medium">{{ \Carbon\Carbon::parse($booking->start_time)->format('d M Y') }},
|
||||
{{ \Carbon\Carbon::parse($booking->start_time)->format('H:i') }} -
|
||||
{{ \Carbon\Carbon::parse($booking->end_time)->format('H:i') }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Durasi</p>
|
||||
<p class="font-medium">
|
||||
{{ \Carbon\Carbon::parse($booking->start_time)->diffInHours($booking->end_time) }} Jam
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Total Bayar</p>
|
||||
<p class="font-medium">Rp {{ number_format($booking->total_amount, 0, ',', '.') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Status Pembayaran</p>
|
||||
<p class="font-medium capitalize">{{ $booking->status }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Metode Pembayaran</p>
|
||||
<p class="font-medium capitalize">{{ $booking->payment_method ?? '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($booking->start_time > now() && $booking->status == 'paid')
|
||||
<div class="mt-4 flex justify-end">
|
||||
<a href="{{ route('venue', $booking->table->venue->name) }}"
|
||||
class="text-blue-500 hover:underline">Lihat Venue</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
{{ $bookings->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
|
@ -16,6 +16,63 @@ class="flex items-center bg-[url('/public/images/map.jpg')] bg-cover bg-center p
|
|||
<i class="fa-solid fa-map-pin text-red-800 text-3xl"></i>
|
||||
</div>
|
||||
</a>
|
||||
@auth
|
||||
<!-- Pending Bookings Section -->
|
||||
<div x-data="pendingBookingsComponent" class="mt-6">
|
||||
<template x-if="pendingBookings.length > 0">
|
||||
<div class="bg-orange-50 border border-orange-200 rounded-lg p-4 mb-6">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h2 class="font-semibold text-orange-700">
|
||||
<i class="fa-solid fa-clock"></i> Booking yang Belum Diselesaikan
|
||||
</h2>
|
||||
<button @click="showPendingBookings = !showPendingBookings"
|
||||
class="text-orange-700 hover:text-orange-900">
|
||||
<span x-show="!showPendingBookings">▼ Lihat</span>
|
||||
<span x-show="showPendingBookings">▲ Tutup</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div x-show="showPendingBookings" x-transition class="mt-3">
|
||||
<p class="text-sm text-orange-700 mb-2">Anda memiliki booking yang belum diselesaikan:</p>
|
||||
<template x-for="booking in pendingBookings" :key="booking . id">
|
||||
<div class="bg-white rounded-md p-3 mb-2 shadow-sm border border-orange-200">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<p class="font-medium" x-text="booking.table.name"></p>
|
||||
<p class="text-sm text-gray-600"
|
||||
x-text="formatDateTime(booking.start_time) + ' - ' + formatTime(booking.end_time)">
|
||||
</p>
|
||||
<p class="text-sm font-medium text-gray-800 mt-1">
|
||||
<span>Rp </span>
|
||||
<span x-text="formatPrice(booking.total_amount)"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button @click="resumeBooking(booking.id)"
|
||||
class="bg-blue-500 text-white text-sm px-3 py-1 rounded-md hover:bg-blue-600"
|
||||
:disabled="isLoadingPending">
|
||||
<template x-if="isLoadingPending">
|
||||
<span>Loading...</span>
|
||||
</template>
|
||||
<template x-if="!isLoadingPending">
|
||||
<span>Lanjutkan</span>
|
||||
</template>
|
||||
</button>
|
||||
<button @click="deletePendingBooking(booking.id)"
|
||||
class="bg-gray-200 text-gray-700 text-sm px-3 py-1 rounded-md hover:bg-gray-300"
|
||||
:disabled="isLoadingPending">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@endauth
|
||||
|
||||
<div class="mt-6">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
|
@ -26,8 +83,10 @@ class="flex items-center bg-[url('/public/images/map.jpg')] bg-cover bg-center p
|
|||
</div>
|
||||
</div>
|
||||
@foreach ($venue['tables'] as $table)
|
||||
<div x-data="booking(@json(auth()->check()), '{{ $table['id'] }}')" class="border rounded-lg shadow-md p-4 mb-4">
|
||||
<div class="flex items-center justify-between cursor-pointer" @click="open = !open; if(open) checkBookedSchedules()">
|
||||
<div x-data="booking(@json(auth()->check()), '{{ $table['id'] }}')"
|
||||
class="border rounded-lg shadow-md p-4 mb-4">
|
||||
<div class="flex items-center justify-between cursor-pointer"
|
||||
@click="open = !open; if(open) checkBookedSchedules()">
|
||||
<div class="flex items-center">
|
||||
<img src="{{ asset('images/meja.jpg') }}" class="w-24">
|
||||
<div class="ml-4">
|
||||
|
@ -49,10 +108,9 @@ class="flex items-center bg-[url('/public/images/map.jpg')] bg-cover bg-center p
|
|||
<h4 class="font-semibold mb-2">Pilih Jam Booking:</h4>
|
||||
<select class="w-full border p-2 rounded-lg" x-model="selectedTime">
|
||||
<option value="">-- Pilih Jam --</option>
|
||||
<template x-for="hour in getHoursInRange(9, 22)" :key="hour">
|
||||
<option :value="hour + ':00'"
|
||||
:disabled="isTimeBooked(hour + ':00')"
|
||||
x-text="hour + ':00' + (isTimeBooked(hour + ':00') ? ' (Booked)' : '')">
|
||||
<template x-for="hour in getHoursInRange(9, 24)" :key="hour">
|
||||
<option :value="hour + ':00'" :disabled="isTimeBooked(hour + ':00')"
|
||||
x-text="hour + ':00' + (isTimeBooked(hour + ':00') ? ' (Booked)' : '')">
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
|
@ -66,7 +124,7 @@ class="flex items-center bg-[url('/public/images/map.jpg')] bg-cover bg-center p
|
|||
</select>
|
||||
|
||||
<button class="mt-3 px-4 py-2 bg-green-500 text-white rounded-lg w-full" :disabled="!selectedTime || !selectedDuration || isLoading"
|
||||
@click="submitBooking('{{ $table['id'] }}', '{{ addslashes($table['name']) }}')">
|
||||
@click="initiateBooking('{{ $table['id'] }}', '{{ addslashes($table['name']) }}')">
|
||||
<template x-if="isLoading">
|
||||
<span>Loading...</span>
|
||||
</template>
|
||||
|
@ -81,7 +139,8 @@ class="flex items-center bg-[url('/public/images/map.jpg')] bg-cover bg-center p
|
|||
</div>
|
||||
|
||||
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-key="{{ config('midtrans.client_key') }}"></script>
|
||||
<script src="https://app.sandbox.midtrans.com/snap/snap.js"
|
||||
data-client-key="{{ config('midtrans.client_key') }}"></script>
|
||||
<script>
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
|
@ -92,7 +151,175 @@ function updateClock() {
|
|||
setInterval(updateClock, 1000);
|
||||
updateClock();
|
||||
|
||||
// Format functions for pending bookings
|
||||
function formatDateTime(dateTimeStr) {
|
||||
const date = new Date(dateTimeStr);
|
||||
// Explicitly use Jakarta timezone for display
|
||||
return new Intl.DateTimeFormat('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
timeZone: 'Asia/Jakarta'
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
function formatTime(timeStr) {
|
||||
const date = new Date(timeStr);
|
||||
// Explicitly use Jakarta timezone for display
|
||||
return new Intl.DateTimeFormat('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
timeZone: 'Asia/Jakarta'
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
function formatPrice(price) {
|
||||
return new Intl.NumberFormat('id-ID').format(price);
|
||||
}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
// Pending bookings component
|
||||
Alpine.data('pendingBookingsComponent', () => ({
|
||||
pendingBookings: [],
|
||||
showPendingBookings: false,
|
||||
isLoadingPending: false,
|
||||
|
||||
init() {
|
||||
this.fetchPendingBookings();
|
||||
},
|
||||
|
||||
fetchPendingBookings() {
|
||||
fetch('/booking/pending')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Filter bookings untuk venue saat ini jika diperlukan
|
||||
const currentVenueId = {{ $venue['id'] ?? 'null' }};
|
||||
if (currentVenueId) {
|
||||
this.pendingBookings = data.filter(booking =>
|
||||
booking.table && booking.table.venue_id === currentVenueId
|
||||
);
|
||||
} else {
|
||||
this.pendingBookings = data;
|
||||
}
|
||||
|
||||
// Log jumlah pending bookings yang ditemukan
|
||||
console.log("Found", this.pendingBookings.length, "pending bookings");
|
||||
})
|
||||
.catch(error => console.error('Error fetching pending bookings:', error));
|
||||
},
|
||||
|
||||
resumeBooking(bookingId) {
|
||||
this.isLoadingPending = true;
|
||||
fetch(`/booking/pending/${bookingId}/resume`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
console.log("Opening payment with snap token:", data.snap_token);
|
||||
// Open Snap payment
|
||||
window.snap.pay(data.snap_token, {
|
||||
onSuccess: (result) => {
|
||||
this.createBookingAfterPayment(data.order_id, result);
|
||||
},
|
||||
onPending: (result) => {
|
||||
alert('Pembayaran pending, silahkan selesaikan pembayaran');
|
||||
this.isLoadingPending = false;
|
||||
},
|
||||
onError: (result) => {
|
||||
alert('Pembayaran gagal');
|
||||
this.isLoadingPending = false;
|
||||
},
|
||||
onClose: () => {
|
||||
alert('Anda menutup popup tanpa menyelesaikan pembayaran');
|
||||
this.isLoadingPending = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert(data.message);
|
||||
this.isLoadingPending = false;
|
||||
// Refresh pending bookings list
|
||||
this.fetchPendingBookings();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error resuming booking:', error);
|
||||
alert('Gagal melanjutkan booking');
|
||||
this.isLoadingPending = false;
|
||||
});
|
||||
},
|
||||
|
||||
deletePendingBooking(bookingId) {
|
||||
if (confirm('Apakah Anda yakin ingin menghapus booking ini?')) {
|
||||
this.isLoadingPending = true;
|
||||
fetch(`/booking/pending/${bookingId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Booking berhasil dihapus');
|
||||
this.fetchPendingBookings();
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
this.isLoadingPending = false;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error deleting booking:', error);
|
||||
alert('Gagal menghapus booking');
|
||||
this.isLoadingPending = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
createBookingAfterPayment(orderId, paymentResult) {
|
||||
fetch('/booking', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order_id: orderId,
|
||||
transaction_id: paymentResult.transaction_id,
|
||||
payment_method: paymentResult.payment_type,
|
||||
transaction_status: paymentResult.transaction_status
|
||||
}),
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
return res.json().then(err => {
|
||||
throw new Error(err.message || 'Gagal menyimpan booking');
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
alert('Pembayaran dan booking berhasil!');
|
||||
this.isLoadingPending = false;
|
||||
|
||||
// Refresh pending bookings list
|
||||
this.fetchPendingBookings();
|
||||
|
||||
// Redirect to booking history
|
||||
window.location.href = '/booking/history';
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Booking error:', err);
|
||||
alert('Pembayaran berhasil tetapi gagal menyimpan booking: ' + err.message);
|
||||
this.isLoadingPending = false;
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// Regular booking component (existing)
|
||||
Alpine.data('booking', (isLoggedIn, tableId) => ({
|
||||
isLoggedIn,
|
||||
tableId,
|
||||
|
@ -127,7 +354,7 @@ function updateClock() {
|
|||
}
|
||||
},
|
||||
|
||||
submitBooking(tableId, tableName) {
|
||||
initiateBooking(tableId, tableName) {
|
||||
if (!this.isLoggedIn) {
|
||||
alert('Silahkan login terlebih dahulu untuk melakukan booking.');
|
||||
return;
|
||||
|
@ -146,10 +373,11 @@ function updateClock() {
|
|||
const [selectedHour, selectedMinute] = selectedTime.split(':').map(Number);
|
||||
selectedDateTime.setHours(selectedHour, selectedMinute, 0, 0);
|
||||
|
||||
if (selectedDateTime <= now) {
|
||||
alert('Jam yang dipilih sudah lewat. Silakan pilih jam yang masih tersedia.');
|
||||
return;
|
||||
}
|
||||
// Uncomment this for production to prevent booking past times
|
||||
// if (selectedDateTime <= now) {
|
||||
// alert('Jam yang dipilih sudah lewat. Silakan pilih jam yang masih tersedia.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
|
@ -164,8 +392,8 @@ function updateClock() {
|
|||
const start_time = `${today} ${selectedTime}`;
|
||||
const end_time = `${today} ${endTimeFormatted}`;
|
||||
|
||||
// Kirim ke backend
|
||||
fetch('/booking', {
|
||||
// Kirim ke backend untuk membuat payment intent (tanpa membuat booking dulu)
|
||||
fetch('/booking/payment-intent', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -177,45 +405,89 @@ function updateClock() {
|
|||
end_time: end_time,
|
||||
}),
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
return res.json().then(err => {
|
||||
throw new Error(err.message || 'Gagal membuat booking');
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (!data.snap_token) {
|
||||
throw new Error('Snap token tidak ditemukan');
|
||||
}
|
||||
|
||||
// Buka Snap Midtrans
|
||||
window.snap.pay(data.snap_token, {
|
||||
onSuccess: function(result) {
|
||||
alert('Pembayaran berhasil!');
|
||||
location.reload();
|
||||
},
|
||||
onPending: function(result) {
|
||||
alert('Pembayaran pending, silahkan selesaikan pembayaran');
|
||||
},
|
||||
onError: function(result) {
|
||||
alert('Pembayaran gagal');
|
||||
},
|
||||
onClose: function() {
|
||||
alert('Anda menutup popup tanpa menyelesaikan pembayaran');
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
return res.json().then(err => {
|
||||
throw new Error(err.message || 'Gagal membuat payment intent');
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (!data.snap_token) {
|
||||
throw new Error('Snap token tidak ditemukan');
|
||||
}
|
||||
|
||||
// Buka Snap Midtrans
|
||||
window.snap.pay(data.snap_token, {
|
||||
onSuccess: (result) => {
|
||||
this.createBookingAfterPayment(data.order_id, result);
|
||||
},
|
||||
onPending: (result) => {
|
||||
alert('Pembayaran pending, silahkan selesaikan pembayaran');
|
||||
this.isLoading = false;
|
||||
},
|
||||
onError: (result) => {
|
||||
alert('Pembayaran gagal');
|
||||
this.isLoading = false;
|
||||
},
|
||||
onClose: () => {
|
||||
alert('Anda menutup popup tanpa menyelesaikan pembayaran');
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Payment intent error:', err);
|
||||
alert('Gagal membuat payment: ' + err.message);
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
// Fungsi untuk menyimpan booking setelah pembayaran berhasil
|
||||
createBookingAfterPayment(orderId, paymentResult) {
|
||||
fetch('/booking', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order_id: orderId,
|
||||
transaction_id: paymentResult.transaction_id,
|
||||
payment_method: paymentResult.payment_type,
|
||||
transaction_status: paymentResult.transaction_status
|
||||
}),
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Booking error:', err);
|
||||
alert('Gagal booking: ' + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
return res.json().then(err => {
|
||||
throw new Error(err.message || 'Gagal menyimpan booking');
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
alert('Pembayaran dan booking berhasil!');
|
||||
|
||||
// Refresh component data
|
||||
document.dispatchEvent(new CustomEvent('booking-completed'));
|
||||
|
||||
// Redirect to booking history page
|
||||
window.location.href = '/booking/history';
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Booking error:', err);
|
||||
alert('Pembayaran berhasil tetapi gagal menyimpan booking: ' + err.message);
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
// Method to refresh booked schedules without reloading the page
|
||||
async refreshBookedSchedules() {
|
||||
await this.checkBookedSchedules();
|
||||
}
|
||||
}))
|
||||
})
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -3,6 +3,7 @@
|
|||
use App\Http\Controllers\pages\HomeController;
|
||||
use App\Http\Controllers\pages\VenueController;
|
||||
use App\Http\Controllers\pages\BookingController;
|
||||
use App\Http\Controllers\pages\BookingHistoryController;
|
||||
use App\Http\Controllers\admin\BookingsController;
|
||||
use App\Http\Controllers\admin\TableController;
|
||||
use App\Http\Controllers\admin\AdminController;
|
||||
|
@ -12,11 +13,24 @@
|
|||
Auth::routes();
|
||||
Route::get('/', [HomeController::class, "index"])->name('index');
|
||||
Route::get('/venue/{venueName}', [VenueController::class, "venue"])->name('venue');
|
||||
|
||||
// Changed routes for the new booking flow
|
||||
Route::post('/booking/payment-intent', [BookingController::class, 'createPaymentIntent'])->name('booking.payment-intent');
|
||||
Route::post('/booking', [BookingController::class, 'store'])->name('booking.store');
|
||||
Route::get('/booking/schedules', [BookingController::class, 'getBookedSchedules'])->name('booking.schedules');
|
||||
Route::post('/booking/payment', [BookingController::class, 'processPayment'])->name('booking.payment');
|
||||
Route::get('/booking/payment/{bookingId}', [BookingController::class, 'checkPaymentStatus'])->name('booking.payment.status');
|
||||
Route::post('/payment/notification', [BookingController::class, 'handleNotification'])->name('payment.notification');
|
||||
|
||||
// Booking history routes (authenticated only)
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/booking/history', [BookingHistoryController::class, 'index'])->name('booking.history');
|
||||
|
||||
// Pending bookings routes
|
||||
Route::get('/booking/pending', [BookingController::class, 'getPendingBookings'])->name('booking.pending');
|
||||
Route::get('/booking/pending/{id}/resume', [BookingController::class, 'resumeBooking'])->name('booking.resume');
|
||||
Route::delete('/booking/pending/{id}', [BookingController::class, 'deletePendingBooking'])->name('booking.pending.delete');
|
||||
});
|
||||
|
||||
// Admin routes
|
||||
Route::middleware(['auth', 'is_admin'])->prefix('admin')->group(function () {
|
||||
Route::get('/', [AdminController::class, 'index'])->name('admin.dashboard');
|
||||
Route::get('/bookings', [BookingsController::class, 'index'])->name('admin.bookings.index');
|
||||
|
@ -24,5 +38,4 @@
|
|||
|
||||
Route::get('/tables/{id}/edit', [TableController::class, 'editTable'])->name('admin.tables.edit');
|
||||
Route::put('/tables/{id}', [TableController::class, 'updateTable'])->name('admin.tables.update');
|
||||
|
||||
});
|
Loading…
Reference in New Issue