'required|string|min:3', 'customerEmail' => 'nullable|email|max:255', 'selectedTableId' => 'required_without:selectedOccupiedTableId', 'selectedOccupiedTableId' => 'required_without:selectedTableId', 'selectedPaymentMethod' => 'required|in:midtrans,cash,qris', ]; } protected function messages() { return [ 'inputCustomerName.required' => 'Nama pemesan wajib diisi.', 'inputCustomerName.min' => 'Nama pemesan minimal 3 karakter.', 'selectedTableId.required_without' => 'Mohon pilih meja untuk pesanan.', 'selectedOccupiedTableId.required_without' => 'Mohon pilih meja untuk pesanan.', 'customerEmail.email' => 'Format email tidak valid.', 'selectedPaymentMethod.required' => 'Mohon pilih metode pembayaran.', 'selectedPaymentMethod.in' => 'Metode pembayaran tidak valid.', ]; } protected $listeners = [ 'paymentSuccess' => 'handlePaymentSuccess', // misalnya untuk pembayaran tunai/manual 'midtransPaymentSuccess' => 'handleMidtransSuccess', // khusus untuk callback Snap Midtrans 'tableStatusUpdated' => 'syncTablesFromFirebase', // untuk update status meja via Firebase 'resetCartAfterOrder' => 'clearCartAndResetState' ]; public function mount($typeSlug = null) { $this->cart = session()->get('cart', []); $this->allTablesData = collect(); $this->syncTablesFromFirebase(); $this->tableIdFromUrl = request()->query('table_id'); $type = $typeSlug ? TypeItems::whereRaw('LOWER(REPLACE(name, " ", "-")) = ?', [Str::lower($typeSlug)])->first() : TypeItems::whereRaw('LOWER(REPLACE(name, " ", "-")) = ?', ['makanan'])->first(); $this->typeItemId = $type->id ?? null; if (empty($this->customerEmail)) { $this->customerEmail = 'guest@gmail.com'; } } public function getIsExistingCustomerBoolProperty(): bool { return $this->isExistingCustomer === '1'; } private function getFirebaseDatabase() { try { $path = config('services.firebase.credentials'); $path = file_exists($path) ? $path : storage_path('app/' . $path); throw_unless(file_exists($path), new \Exception("Firebase credentials not found at: $path")); return (new Factory) ->withServiceAccount($path) ->withDatabaseUri(config('services.firebase.database_url')) ->createDatabase(); } catch (\Exception $e) { Log::error("Firebase init failed: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal koneksi Firebase: ' . $e->getMessage(), type: 'error'); $this->dispatch('logToConsole', message: 'ERROR: Firebase init failed: ' . $e->getMessage()); return null; } } public function syncTablesFromFirebase() { $db = $this->getFirebaseDatabase(); if (!$db) { return $this->resetTables(); } try { $data = $db->getReference('/')->getValue(); if (empty($data)) { $this->dispatch('logToConsole', message: 'Firebase data is empty, resetting tables.'); return $this->resetTables(); } $currentHash = md5(json_encode($data)); if (($this->lastFirebaseDataHash['tables'] ?? null) === $currentHash) { return; } // Only process if data changed significantly $this->processFirebaseData($data); $this->lastFirebaseDataHash['tables'] = $currentHash; } catch (\Exception $e) { Log::error("Sync Firebase failed: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal sinkron Firebase: ' . $e->getMessage(), type: 'error'); $this->resetTables(); } } private function processFirebaseData($data) { $available = $occupied = $all = []; foreach ($data as $id => $d) { $isActive = (bool)($d['sensors']['table_activation_sensor_active'] ?? false); $reservedBy = $d['reserved_by'] ?? null; $reservedBy = ($reservedBy === 'N/A' || empty($reservedBy)) ? null : $reservedBy; $table = [ 'id' => $id, 'device_id' => $d['device_id'] ?? $id, 'table_activation_sensor_active' => $isActive, 'reserved_by' => $reservedBy, 'is_available' => !$isActive && empty($reservedBy), 'is_occupied_by_someone' => $isActive && !empty($reservedBy), 'is_occupied_but_empty_reserved' => $isActive && empty($reservedBy), 'firebase_data' => $d ]; $all[$id] = $table; if ($table['is_available']) { $available[] = $table; } elseif ($table['is_occupied_by_someone']) { $occupied[] = $table; } } $this->availableTables = $available; $this->occupiedTables = $occupied; $this->allTablesData = collect($all); $this->attemptAutoSelectTable(); } private function resetTables() { $this->availableTables = []; $this->occupiedTables = []; $this->allTablesData = collect(); $this->selectedTableId = null; $this->selectedOccupiedTableId = null; $this->inputCustomerName = ''; $this->isExistingCustomer = false; $this->showCustomerNameInput = false; $this->isCustomerNameInputDisabled = false; $this->tableIdFromUrl = null; $this->dispatch('logToConsole', message: 'Tables reset.'); } private function attemptAutoSelectTable() { if (!$this->tableIdFromUrl || $this->allTablesData->isEmpty()) { $this->dispatch('logToConsole', message: 'No table_id from URL or tables data empty. Skipping auto-select.'); return; } $table = $this->allTablesData->firstWhere('device_id', $this->tableIdFromUrl); if (!$table) { $this->dispatch('notify', message: 'Meja dengan ID "' . $this->tableIdFromUrl . '" tidak ditemukan.', type: 'warning'); $this->tableIdFromUrl = null; $this->dispatch('logToConsole', message: 'Auto-select aborted: Table ' . $this->tableIdFromUrl . ' not found.'); return; } $this->dispatch('logToConsole', message: 'Attempting to auto-select table: ' . $this->tableIdFromUrl . ' with data: ' . json_encode($table)); if ($table['is_available']) { $this->selectedTableId = $table['id']; $this->selectedOccupiedTableId = null; $this->isExistingCustomer = false; $this->inputCustomerName = ''; $this->showCustomerNameInput = true; $this->isCustomerNameInputDisabled = false; $this->dispatch('notify', message: 'Meja ' . $table['device_id'] . ' berhasil dipilih otomatis sebagai meja baru.', type: 'success'); $this->dispatch('logToConsole', message: 'Auto-selected available table: ' . $table['device_id']); } elseif ($table['is_occupied_by_someone']) { $this->selectedOccupiedTableId = $table['id']; $this->selectedTableId = null; $this->isExistingCustomer = true; $this->inputCustomerName = $table['reserved_by']; $this->isCustomerNameInputDisabled = true; $this->dispatch('notify', message: 'Meja ' . $table['device_id'] . ' sudah terisi oleh ' . $table['reserved_by'] . '.', type: 'info'); $this->dispatch('logToConsole', message: 'Auto-selected occupied table with reservation: ' . $table['device_id'] . ' by ' . $table['reserved_by']); } elseif ($table['is_occupied_but_empty_reserved']) { $this->selectedOccupiedTableId = $table['id']; $this->selectedTableId = null; $this->isExistingCustomer = true; $this->inputCustomerName = ''; $this->showCustomerNameInput = true; $this->isCustomerNameInputDisabled = false; $this->dispatch('notify', message: 'Meja ' . $table['device_id'] . ' terisi tetapi nama pemesan belum terdaftar. Mohon masukkan nama Anda.', type: 'warning'); $this->dispatch('logToConsole', message: 'Auto-selected occupied table without reservation: ' . $table['device_id'] . '. Customer needs to input name.'); } else { $this->dispatch('notify', message: 'Meja ' . $table['device_id'] . ' tidak tersedia atau tidak dikenal.', type: 'warning'); $this->dispatch('logToConsole', message: 'Auto-select failed for table: ' . $table['device_id'] . '. Not available or unknown state.'); } $this->tableIdFromUrl = null; } public function showMap() { $this->showMapModal = true; $this->dispatch('logToConsole', message: 'Showing map modal.'); } public function closeMap() { $this->showMapModal = false; $this->dispatch('logToConsole', message: 'Closing map modal.'); } public function updatedSelectedTableId($value) { $this->resetErrorBag(['selectedTableId', 'selectedOccupiedTableId', 'inputCustomerName']); $this->selectedOccupiedTableId = null; $this->isExistingCustomer = false; if ($value) { $this->showCustomerNameInput = true; $this->isCustomerNameInputDisabled = false; $this->inputCustomerName = ''; $this->dispatch('logToConsole', message: "Meja kosong dipilih: " . $value . ". Nama default: " . ($this->inputCustomerName ?: 'Kosong')); } else { $this->showCustomerNameInput = false; $this->inputCustomerName = ''; $this->dispatch('logToConsole', message: "Meja kosong tidak dipilih."); } $this->showEmailInput = true; } public function updatedSelectedOccupiedTableId($value) { $this->resetErrorBag(['selectedTableId', 'selectedOccupiedTableId', 'inputCustomerName']); $this->selectedTableId = null; $this->isExistingCustomer = true; if ($value) { $selectedTable = $this->allTablesData->get($value); if ($selectedTable) { $this->showCustomerNameInput = true; if ($selectedTable['is_occupied_by_someone']) { $this->inputCustomerName = $selectedTable['reserved_by']; $this->isCustomerNameInputDisabled = true; $this->dispatch('notify', message: 'Nama pemesan otomatis terisi: ' . $selectedTable['reserved_by'] . '.', type: 'info'); $this->dispatch('logToConsole', message: "Meja terisi dipilih dengan reservasi: " . $value . ". Nama: " . $this->inputCustomerName); } elseif ($selectedTable['is_occupied_but_empty_reserved']) { $this->inputCustomerName = ''; $this->isCustomerNameInputDisabled = false; $this->dispatch('notify', message: 'Meja ini terisi, namun nama pemesan belum terdaftar. Mohon masukkan nama Anda.', type: 'warning'); $this->dispatch('logToConsole', message: "Meja terisi dipilih tanpa reservasi: " . $value . ". Customer needs to input name."); } else { $this->inputCustomerName = ''; $this->showCustomerNameInput = false; $this->isExistingCustomer = false; $this->dispatch('notify', message: 'Kesalahan: Meja yang dipilih tidak valid sebagai meja terisi. Silakan pilih ulang.', type: 'error'); $this->selectedOccupiedTableId = null; $this->dispatch('logToConsole', message: "Invalid occupied table selected: " . $value . ". Resetting selection."); } } else { $this->dispatch('notify', message: 'Meja yang dipilih tidak ditemukan.', type: 'error'); $this->selectedOccupiedTableId = null; $this->inputCustomerName = ''; $this->showCustomerNameInput = false; $this->isExistingCustomer = false; $this->dispatch('logToConsole', message: "Selected occupied table not found: " . $value . ". Resetting selection."); } } else { $this->showCustomerNameInput = false; $this->inputCustomerName = ''; $this->isExistingCustomer = false; $this->dispatch('logToConsole', message: "Meja terisi tidak dipilih."); } $this->showEmailInput = true; } public function updatedIsExistingCustomer($value) { $this->selectedTableId = null; $this->selectedOccupiedTableId = null; $this->inputCustomerName = ''; $this->showCustomerNameInput = false; $this->isCustomerNameInputDisabled = false; $this->resetErrorBag(['selectedTableId', 'selectedOccupiedTableId', 'inputCustomerName']); if (!$value) { $this->showCustomerNameInput = true; $this->isCustomerNameInputDisabled = false; $this->inputCustomerName = ''; $this->dispatch('logToConsole', message: "isExistingCustomer changed to NEW. Input name displayed."); } else { $this->showCustomerNameInput = false; $this->inputCustomerName = ''; $this->dispatch('logToConsole', message: "isExistingCustomer changed to EXISTING. Input name hidden until table selected."); } $this->showEmailInput = true; } public function goToCheckoutDetails() { if (empty($this->cart)) { $this->dispatch('notify', message: 'Keranjang belanja Anda kosong. Silakan tambahkan item.'); return; } $this->showCheckoutDetailsModal = true; $this->dispatch('logToConsole', message: 'Proceeding to checkout details.'); } public function closeCheckoutDetailsModal() { $this->showCheckoutDetailsModal = false; $this->dispatch('logToConsole', message: 'Checkout details modal closed.'); } public function processOrderConfirmation() { $this->dispatch('logToConsole', message: 'Memulai proses konfirmasi order...'); $cartItems = $this->getCartItemsProperty(); if (empty($cartItems) && !$this->paymentInProgress) return; try { $this->validate(); } catch (ValidationException $e) { foreach ($e->errors() as $field => $messages) { foreach ($messages as $message) { $this->dispatch('notify', message: $message, type: 'error'); } } return; } $selectedTableData = $this->getSelectedTableForTransaction(); if (!$selectedTableData) { $this->dispatch('notify', message: 'Detail meja tidak ditemukan.', type: 'error'); return; } $isSensorActive = (bool)($selectedTableData['firebase_data']['sensors']['table_activation_sensor_active'] ?? false); $reservedBy = $selectedTableData['reserved_by']; if ($this->isExistingCustomer) { if (!$isSensorActive) { $this->dispatch('notify', message: 'Meja ini belum terisi.', type: 'warning'); return; } if (!empty($reservedBy) && Str::lower($this->inputCustomerName) !== Str::lower($reservedBy)) { $this->dispatch('notify', message: 'Nama tidak cocok dengan reservasi: ' . $reservedBy, type: 'warning'); return; } } else { if ($isSensorActive) { $this->dispatch('notify', message: 'Meja sudah terisi, pilih meja lain.', type: 'warning'); return; } } if (empty($this->customerEmail)) { $this->customerEmail = 'guest@gmail.com'; } $totalAmount = $this->getCartTotalProperty(); if ($this->selectedPaymentMethod === 'midtrans' && $totalAmount < 10000) { $this->dispatch('notify', message: 'Minimum Midtrans Rp 10.000', type: 'error'); return; } try { $waktuWIB = now()->timezone('Asia/Jakarta'); if ($this->selectedPaymentMethod === 'midtrans'){ $midtransTransactionId = 'MIDTRANS-' . now()->timestamp . '-' . rand(1000, 9999); } // Buat order terlebih dahulu (baik untuk midtrans maupun metode lain) $order = Order::create([ 'customer_name' => $this->inputCustomerName, 'customer_email' => $this->customerEmail, 'table_id' => $selectedTableData['id'], 'total_amount' => $totalAmount, 'transaction_status' => 'pending', 'payment_method' => $this->selectedPaymentMethod, 'midtrans_transaction_id' => $midtransTransactionId ?? null, 'created_at' => $waktuWIB, 'updated_at' => $waktuWIB, ]); foreach ($cartItems as $item) { OrderItem::create([ 'order_id' => $order->id, 'item_id' => $item['id'], 'item_name' => $item['name'], 'item_price' => $item['price'], 'quantity' => $item['qty'], 'total_price' => $item['total_price'], 'created_at' => $waktuWIB, 'updated_at' => $waktuWIB, ]); } if ($this->selectedPaymentMethod === 'midtrans') { $this->paymentInProgress = true; $this->clearCartAndResetState(); return redirect()->route('midtrans.payment', [ 'midtrans_transaction_id' => $midtransTransactionId, ]); } elseif ($this->selectedPaymentMethod === 'qris') { $this->processQrisPayment($order->id); } else { $this->processManualPaymentInternal($order->id); } } catch (\Exception $e) { Log::error("Gagal membuat order: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]); $this->dispatch('notify', message: 'Gagal memproses pesanan: ' . $e->getMessage(), type: 'error'); } } public function updateFirebaseTableStatus(string $firebaseTableId, array $updates, bool $refresh = true) { $database = $this->getFirebaseDatabase(); if (!$database) return; try { $database->getReference($firebaseTableId)->update($updates); $this->dispatch('logToConsole', message: "Firebase: Updated {$firebaseTableId} with " . json_encode($updates)); $this->dispatch('notify', message: "Status meja '{$firebaseTableId}' berhasil diperbarui di Firebase.", type: 'success'); if ($refresh) { $this->syncTablesFromFirebase(); } } catch (\Exception $e) { Log::error("Failed to update Firebase for table ID {$firebaseTableId}: " . $e->getMessage()); $this->dispatch('logToConsole', message: 'ERROR: Failed to update Firebase for table ID ' . $firebaseTableId . ': ' . $e->getMessage()); $this->dispatch('notify', message: 'Gagal memperbarui status meja di Firebase: ' . $e->getMessage(), type: 'error'); } } protected function initiateMidtransPayment($midtransTransactionId, $totalAmount, array $selectedTableData, array $cartItems): Redirector|null { $this->dispatch('logToConsole', message: 'Memulai pembayaran Midtrans...'); try { if (empty($midtransTransactionId)) { throw new \Exception('ID transaksi Midtrans tidak boleh kosong.'); } if ($totalAmount < 10000) { throw new \Exception('Minimum pembayaran Midtrans adalah Rp 10.000'); } if (empty($cartItems)) { throw new \Exception('Keranjang belanja kosong'); } // Data customer $customerDetails = [ 'first_name' => substr($this->inputCustomerName ?: 'Customer', 0, 50), 'email' => $this->customerEmail ?: 'guest@gmail.com', 'phone' => '08123456789', ]; // Item $itemDetails = array_map(fn($item) => [ 'id' => $item['id'], 'price' => $item['price'], 'quantity' => $item['qty'], 'name' => substr($item['name'], 0, 50), ], $cartItems); $params = [ 'transaction_details' => [ 'order_id' => (string) $midtransTransactionId, 'gross_amount' => $totalAmount, ], 'customer_details' => $customerDetails, 'item_details' => $itemDetails, 'enabled_payments' => [ 'credit_card', 'bca_va', 'bni_va', 'bri_va' ], 'callbacks' => [ 'finish' => url('/midtrans/finish'), 'error' => url('/midtrans/error'), 'pending' => url('/midtrans/pending'), ] ]; session([ 'order_data' => [ 'customer_name' => $this->inputCustomerName, 'customer_email' => $this->customerEmail, 'table_id' => $selectedTableData['id'], 'midtrans_transaction_id' => $midtransTransactionId, 'total_amount' => $totalAmount, 'payment_method' => 'midtrans', 'cart_items' => $cartItems, ] ]); // Konfigurasi Midtrans Config::$serverKey = config('services.midtrans.server_key'); Config::$isProduction = config('services.midtrans.is_production', false); Config::$isSanitized = true; Config::$is3ds = true; // Snap Token $snapToken = Snap::getSnapToken($params); if (empty($snapToken)) { throw new \Exception('Gagal mendapatkan token pembayaran dari Midtrans'); } // Redirect ke halaman Snap Midtrans return $this->redirectRoute('midtrans.payment.page', ['token' => $snapToken]); } catch (\Exception $e) { Log::error("Error Midtrans: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]); $this->dispatch('notify', message: 'Gagal memproses pembayaran: ' . $e->getMessage(), type: 'error'); return null; } } protected function updateTableStatusToReserved(array $orderData): void { $firebase = $this->getFirebaseDatabase(); if (!$firebase) { return; } try { $firebase->getReference($orderData['table_device_id'])->update([ 'reserved_by' => $orderData['customer_name'], 'sensors/table_activation_sensor_active' => 1, 'table_occupied' => 1, ]); \Log::info("Firebase: Meja {$orderData['table_device_id']} diupdate sebagai reserved oleh {$orderData['customer_name']}."); } catch (\Exception $e) { \Log::error("Gagal update status meja di Firebase: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal update status meja di Firebase.', type: 'error'); } } public function handleMidtransSuccess($result) { $selectedTableData = $this->getSelectedTableForTransaction(); $totalAmount = $this->getCartTotalProperty(); $cartItems = $this->getCartItemsProperty(); // Simpan ke database $order = Order::create([ 'customer_name' => $this->inputCustomerName, 'customer_email' => $this->customerEmail, 'table_id' => $selectedTableData['id'], 'total_amount' => $totalAmount, 'payment_method' => 'midtrans', 'midtrans_transaction_id' => $result['transaction_id'], 'status' => 'confirmed', 'created_at' => now(), 'updated_at' => now(), ]); foreach ($cartItems as $item) { OrderItem::create([ 'order_id' => $order->id, 'item_id' => $item['id'], 'item_name' => $item['name'], 'item_price' => $item['price'], 'quantity' => $item['qty'], 'total_price' => $item['total_price'], 'created_at' => now(), 'updated_at' => now(), ]); } // ✅ Tambahkan update ke Firebase di sini try { $firebase = (new \Kreait\Firebase\Factory) ->withServiceAccount(storage_path('app/firebase/.firebase_credentials.json')) ->createDatabase(); $firebase->getReference($selectedTableData['device_id'])->update([ 'sensors/table_activation_sensor_active' => 1, 'reserved_by' => $this->inputCustomerName, ]); } catch (\Exception $e) { // Logging atau tindakan jika gagal update ke Firebase \Log::error('Firebase update failed: ' . $e->getMessage()); } // Bersihkan keranjang $this->resetCart(); // Notifikasi $this->dispatch('notify', [ 'type' => 'success', 'message' => 'Pembayaran berhasil!' ]); } protected function processManualPaymentInternal($orderDbId) { $paymentTypes = [ 'cash' => 'Pembayaran tunai berhasil. Silakan tunggu konfirmasi dari kasir.', 'transfer' => 'Transfer berhasil. Mohon tunjukkan bukti ke kasir.', 'qris' => 'QRIS berhasil. Mohon tunjukkan bukti ke kasir.', ]; $message = $paymentTypes[$this->selectedPaymentMethod] ?? 'Pembayaran berhasil. Mohon tunggu konfirmasi dari kasir.'; try { // Aktifkan flag agar tidak muncul "Keranjang kosong" $this->paymentInProgress = true; // Tutup modal checkout $this->closeCheckoutDetailsModal(); // Tampilkan notifikasi sukses $this->dispatch('notify', message: $message, type: 'success' ); // Reset tombol "Processing" $this->dispatch('resetProcessingButton'); // Kosongkan keranjang setelah notifikasi $this->clearCartAndResetState(); $order = \App\Models\Order::find($orderDbId); if ($order && $order->customer_email) { Mail::to($order->customer_email)->send(new OrderReceiptMail($order)); } } catch (\Exception $e) { // Tangani error jika terjadi $this->dispatch('notify', [ 'message' => 'Terjadi kesalahan saat memproses pembayaran.', 'type' => 'error', ]); $this->dispatch('resetProcessingButton'); } finally { // Matikan flag walaupun error $this->paymentInProgress = false; } } protected function processQrisPayment($orderId) { $this->dispatch('logToConsole', message: 'Memulai pembayaran QRIS...'); try { // Generate or get your static QRIS image URL // This could be from your payment gateway or a static image $this->qrisImageUrl = url('img/qr-code.png'); // Replace with actual QRIS image // Show the QRIS payment modal $this->showQrisPayment = true; $this->dispatch('notify', message: 'Silakan scan QRIS untuk melakukan pembayaran', type: 'info' ); } catch (\Exception $e) { Log::error("Error QRIS: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]); $this->dispatch('notify', message: 'Gagal memproses pembayaran QRIS: ' . $e->getMessage(), type: 'error' ); } } public function confirmQrisPayment() { try { // Here you would typically verify the payment with your payment gateway // For now we'll just simulate successful payment $this->showQrisPayment = false; $this->clearCartAndResetState(); $this->dispatch('notify', message: 'Pembayaran QRIS berhasil dikonfirmasi!', type: 'success' ); } catch (\Exception $e) { Log::error("QRIS confirmation error: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal mengkonfirmasi pembayaran QRIS: ' . $e->getMessage(), type: 'error' ); } } private function getSelectedTableData() { $tableId = $this->selectedTableId ?? $this->selectedOccupiedTableId; return $this->allTablesData->firstWhere('id', $tableId)?->toArray() ?? [ 'id' => null, 'device_id' => null, ]; } public function handlePaymentSuccess() { $this->clearCartAndResetState(); $this->dispatch('notify', message: 'Pembayaran berhasil! Menunggu konfirmasi tolong konfirmasi ke kasir...', type: 'success'); } protected function sendReceiptEmail($order) { try { if ($order->customer_email && filter_var($order->customer_email, FILTER_VALIDATE_EMAIL)) { Mail::to($order->customer_email)->send(new OrderReceipt($order)); $this->dispatch('logToConsole', message: 'Order receipt email sent to ' . $order->customer_email); } else { $this->dispatch('logToConsole', message: 'No valid email provided for receipt.'); } } catch (\Exception $e) { Log::error("Failed to send order receipt email: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal mengirim struk ke email.', type: 'error'); } } private function getSelectedTableForTransaction(): ?array { $tableId = $this->selectedTableId ?? $this->selectedOccupiedTableId; if (empty($tableId)) { $this->dispatch('logToConsole', message: 'No table ID selected for transaction.'); return null; } $table = $this->allTablesData->get($tableId); if (!$table) { $this->dispatch('logToConsole', message: 'Table data not found for ID: ' . $tableId); } else { $this->dispatch('logToConsole', message: 'Selected table data for transaction: ' . json_encode($table)); } return $table; } private function clearCartAndResetState() { $this->cart = []; session()->forget('cart'); $this->showCart = false; $this->showEmailInput = true; $this->customerEmail = ''; $this->selectedTableId = null; $this->selectedOccupiedTableId = null; $this->inputCustomerName = ''; $this->isExistingCustomer = false; $this->showCustomerNameInput = false; $this->isCustomerNameInputDisabled = false; $this->resetValidation(); $this->selectedPaymentMethod = null; $this->dispatch('logToConsole', message: 'Cart cleared and state reset.'); } public function render() { return view('livewire.food-order', [ 'foodItems' => $this->getFilteredFoodItemsProperty(), 'cartItems' => $this->getCartItemsProperty(), 'cartTotal' => $this->getCartTotalProperty(), 'pageTitle' => $this->getPageTitleProperty(), 'typeItemId' => $this->typeItemId, 'allTypeItems' => $this->getAllTypeItemsProperty(), 'availableTables' => $this->availableTables, 'occupiedTables' => $this->occupiedTables, 'allTablesData' => $this->allTablesData->toArray(), 'tableIdFromUrl' => $this->tableIdFromUrl, ])->layout('components.layouts.front'); } public function getFoodItemsProperty() { return Cache::remember('food_items_'.$this->typeItemId, now()->addHours(1), function() { if ($this->typeItemId) { return Items::where('type_item_id', $this->typeItemId)->get(); } return Items::all(); }); } public function getAllTypeItemsProperty() { return Cache::remember('all_type_items', now()->addHours(6), function() { return TypeItems::all(); }); } public function getFilteredFoodItemsProperty() { if ($this->typeItemId) { return Items::where('type_item_id', $this->typeItemId)->get(); } return Items::all(); } public function getCartItemsProperty() { $items = []; foreach ($this->cart as $itemId => $qty) { $item = Items::find($itemId); if ($item) { $items[] = [ 'id' => $item->id, 'name' => $item->name, 'price' => $item->price, 'qty' => $qty, 'total_price' => $item->price * $qty, ]; } } return $items; } public function getCartTotalProperty() { return collect($this->getCartItemsProperty())->sum('total_price'); } public function getPageTitleProperty() { if ($this->typeItemId) { $type = TypeItems::find($this->typeItemId); return $type ? $type->name : 'Semua Menu'; } return 'Semua Menu'; } public function addToCart($itemId) { $item = Items::find($itemId); if (!$item) { $this->dispatch('notify', message: 'Item tidak ditemukan.', type: 'error'); $this->dispatch('logToConsole', message: 'Failed to add to cart: Item ' . $itemId . ' not found.'); return; } $this->cart[$itemId] = ($this->cart[$itemId] ?? 0) + 1; session()->put('cart', $this->cart); $this->dispatch('notify', message: $item->name . ' ditambahkan ke keranjang.', type: 'success'); $this->dispatch('logToConsole', message: 'Added ' . $item->name . ' to cart. Current cart: ' . json_encode($this->cart)); } public function removeFromCart($itemId) { if (isset($this->cart[$itemId])) { $item = Items::find($itemId); if ($this->cart[$itemId] > 1) { $this->cart[$itemId]--; $message = ($item->name ?? 'Item') . ' dikurangi dari keranjang.'; } else { unset($this->cart[$itemId]); $message = ($item->name ?? 'Item') . ' dihapus dari keranjang.'; } session()->put('cart', $this->cart); $this->dispatch('notify', message: $message, type: 'info'); $this->dispatch('logToConsole', message: $message . '. Current cart: ' . json_encode($this->cart)); if (empty($this->cart)) { $this->showCart = false; $this->dispatch('logToConsole', message: 'Cart is now empty. Closing cart view.'); } } else { $this->dispatch('logToConsole', message: 'Attempted to remove non-existent item ' . $itemId . ' from cart.'); $this->dispatch('notify', message: 'Item tidak ditemukan di keranjang.', type: 'warning'); } } public function openCart() { $this->showCart = true; $this->dispatch('logToConsole', message: 'Cart opened.'); } public function closeCart() { $this->showCart = false; $this->dispatch('logToConsole', message: 'Cart closed.'); } public function filterByType($typeId) { $this->typeItemId = $typeId; $type = TypeItems::find($typeId); if ($type) { $typeSlug = Str::slug($type->name); $newUrl = route('menu.byType', ['typeSlug' => $typeSlug], false); $this->dispatch('updateUrl', url: $newUrl); $this->dispatch('logToConsole', message: 'Filtered by type: ' . $type->name . ' (ID: ' . $typeId . '). URL updated.'); } else { $this->dispatch('logToConsole', message: 'Attempted to filter by non-existent type ID: ' . $typeId); $this->dispatch('notify', message: 'Tipe menu tidak ditemukan.', type: 'warning'); } } }