'required|email|max:255', 'selectedTableId' => 'nullable|string', 'selectedOccupiedTableId' => 'nullable|string', 'inputCustomerName' => 'nullable|string|max:255', // Validasi bersyarat ]; protected $listeners = [ 'paymentSuccess' => 'handlePaymentSuccess', 'tableStatusUpdated' => 'syncTablesFromFirebase', // Refresh table data after status update ]; public function mount($typeSlug = null) { $this->cart = session()->get('cart', []); $this->allTablesData = collect(); // Inisialisasi collection kosong $this->syncTablesFromFirebase(); if (request()->has('table_id')) { $this->tableIdFromUrl = request()->query('table_id'); } // Handle default typeSlug redirection only if not already on the correct path if (empty($typeSlug) && (request()->route()->getName() !== 'menu.byType' || request()->segment(2) !== 'makanan')) { return redirect()->route('menu.byType', ['typeSlug' => 'makanan']); } if (!empty($typeSlug)) { $foundType = TypeItems::whereRaw('LOWER(REPLACE(name, " ", "-")) = ?', [Str::lower($typeSlug)])->first(); $this->typeItemId = $foundType->id ?? null; if (!$this->typeItemId) { // If typeSlug is provided but not found, redirect to default 'makanan' return redirect()->route('menu.byType', ['typeSlug' => 'makanan']); } } else { // Default to 'makanan' if no typeSlug is provided $defaultType = TypeItems::whereRaw('LOWER(REPLACE(name, " ", "-")) = ?', ['makanan'])->first(); $this->typeItemId = $defaultType->id ?? null; } } private function attemptAutoSelectTable() { if ($this->tableIdFromUrl && $this->allTablesData->isNotEmpty()) { $foundTable = $this->allTablesData->firstWhere('device_id', $this->tableIdFromUrl); if ($foundTable) { $isActive = $foundTable['firebase_data']['sensors']['table_activation_sensor_active'] ?? 0; $hasReservedBy = isset($foundTable['reserved_by']) && !empty($foundTable['reserved_by']); if ($isActive === 0) { // Meja tersedia $this->selectedTableId = $foundTable['id']; $this->selectedOccupiedTableId = null; // Pastikan kosong $this->inputCustomerName = auth()->user()->name ?? 'Guest'; $this->isExistingCustomer = false; $this->dispatch('notify', message: 'Meja ' . $foundTable['device_id'] . ' berhasil dipilih secara otomatis.'); } elseif ($hasReservedBy) { // Meja tidak tersedia dan punya nama pemesan $this->selectedOccupiedTableId = $foundTable['id']; $this->selectedTableId = null; // Pastikan kosong $this->isExistingCustomer = true; $this->dispatch('notify', message: 'Meja ' . $foundTable['device_id'] . ' sudah terisi oleh ' . $foundTable['reserved_by'] . '. Harap konfirmasi nama Anda.'); } else { $this->dispatch('notify', message: 'Meja ' . $foundTable['device_id'] . ' saat ini tidak tersedia.'); $this->selectedTableId = null; $this->selectedOccupiedTableId = null; } } else { $this->dispatch('notify', message: 'Meja ' . $this->tableIdFromUrl . ' tidak ditemukan.'); $this->selectedTableId = null; $this->selectedOccupiedTableId = null; } } } private function getFirebaseDatabase() { try { $firebaseCredentialsPath = config('services.firebase.credentials'); // Periksa di lokasi default Laravel dan storage if (!file_exists($firebaseCredentialsPath)) { $firebaseCredentialsPath = storage_path('app/' . $firebaseCredentialsPath); } if (!file_exists($firebaseCredentialsPath)) { throw new \Exception("Firebase credentials file not found at: " . $firebaseCredentialsPath); } $factory = (new Factory) ->withServiceAccount($firebaseCredentialsPath) ->withDatabaseUri(config('services.firebase.database_url')); return $factory->createDatabase(); } catch (\Exception $e) { logger()->error("Firebase initialization failed: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal terhubung ke sistem meja: ' . $e->getMessage()); return null; } } public function syncTablesFromFirebase() { $database = $this->getFirebaseDatabase(); if (!$database) { $this->availableTables = []; $this->occupiedTables = []; $this->allTablesData = collect(); return; } try { $firebaseTablesData = $database->getReference('/')->getValue(); $processedAvailableTables = []; $processedOccupiedTables = []; $allProcessedTables = []; if ($firebaseTablesData) { foreach ($firebaseTablesData as $firebaseKey => $data) { $isActive = $data['sensors']['table_activation_sensor_active'] ?? 0; $isAvailable = ($isActive === 0); $reservedBy = $data['reserved_by'] ?? null; $hasReservedBy = !empty($reservedBy); $tableDetail = [ 'id' => $firebaseKey, 'device_id' => $data['device_id'] ?? $firebaseKey, 'table_activation_sensor_active' => $isActive, 'reserved_by' => $reservedBy, 'is_available' => $isAvailable, 'has_reserved_by' => $hasReservedBy, 'firebase_data' => $data // Simpan data mentah untuk referensi ]; $allProcessedTables[$firebaseKey] = $tableDetail; // Gunakan ID sebagai kunci untuk pencarian cepat if ($isAvailable) { $processedAvailableTables[] = $tableDetail; } elseif ($hasReservedBy) { $processedOccupiedTables[] = $tableDetail; } } } $this->availableTables = array_values($processedAvailableTables); $this->occupiedTables = array_values($processedOccupiedTables); $this->allTablesData = collect($allProcessedTables); // Convert to Collection // Panggil attemptAutoSelectTable setelah data meja siap $this->attemptAutoSelectTable(); logger("Tables synced from Firebase. Total available tables: " . count($this->availableTables)); logger("Total occupied tables (with name): " . count($this->occupiedTables)); logger("Total tables for dropdown and map: " . $this->allTablesData->count()); } catch (\Exception $e) { logger()->error("Failed to sync tables from Firebase: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal memperbarui status meja dari Firebase: ' . $e->getMessage()); $this->availableTables = []; $this->occupiedTables = []; $this->allTablesData = collect(); } } public function showMap() { $this->showMapModal = true; } public function closeMap() { $this->showMapModal = false; } public function updatedSelectedTableId($value) { // Reset state jika pilihan meja tersedia berubah $this->selectedOccupiedTableId = null; $this->inputCustomerName = ''; $this->showCustomerNameInput = false; $this->isCustomerNameInputDisabled = false; $this->resetErrorBag(['inputCustomerName']); if ($value) { $this->inputCustomerName = auth()->user()->name ?? 'Guest'; $this->showCustomerNameInput = true; // $this->isCustomerNameInputDisabled tetap false } logger("Meja kosong dipilih: " . $value); } public function updatedSelectedOccupiedTableId($value) { // Reset state jika pilihan meja terreservasi berubah $this->selectedTableId = null; $this->inputCustomerName = ''; $this->showCustomerNameInput = false; $this->isCustomerNameInputDisabled = false; $this->resetErrorBag(['inputCustomerName']); if ($value) { $selectedTable = $this->allTablesData->get($value); // Menggunakan get() dari Collection if ($selectedTable && $selectedTable['has_reserved_by']) { $this->inputCustomerName = $selectedTable['reserved_by']; $this->showCustomerNameInput = true; $this->isCustomerNameInputDisabled = true; $this->dispatch('notify', message: 'Nama pemesan otomatis terisi: ' . $selectedTable['reserved_by'] . '.'); } else { $this->inputCustomerName = ''; $this->showCustomerNameInput = true; $this->isCustomerNameInputDisabled = false; $this->dispatch('notify', message: 'Meja ini terisi, namun nama pemesan belum terdaftar. Mohon masukkan nama Anda.'); } } logger("Meja terisi dipilih: " . $value); } 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) { // Jika user TIDAK mencentang "Saya sudah memesan meja" (pemesanan baru) $this->inputCustomerName = auth()->user()->name ?? 'Guest'; $this->showCustomerNameInput = true; // $this->isCustomerNameInputDisabled tetap false } } private function validateTableSelection() { $this->resetErrorBag(['selectedTableId', 'selectedOccupiedTableId', 'inputCustomerName']); $selectedTableId = $this->isExistingCustomer ? $this->selectedOccupiedTableId : $this->selectedTableId; $errorProperty = $this->isExistingCustomer ? 'selectedOccupiedTableId' : 'selectedTableId'; if (empty($selectedTableId)) { $this->addError($errorProperty, 'Pilih meja Anda.'); return false; } $selectedTable = $this->allTablesData->get($selectedTableId); if (!$selectedTable) { $this->addError($errorProperty, 'Meja yang dipilih tidak valid.'); return false; } $isSensorActive = ($selectedTable['firebase_data']['sensors']['table_activation_sensor_active'] ?? 0) === 1; if ($this->isExistingCustomer) { // Mengisi meja yang sudah terisi if (!$isSensorActive) { // Jika meja dipilih sebagai "terisi" tapi sensornya 0 (tersedia) $this->addError($errorProperty, 'Meja yang dipilih sebagai "sudah terisi" ternyata masih tersedia.'); return false; } if (!$selectedTable['has_reserved_by']) { // Jika meja terisi tapi tidak ada nama pemesan $this->addError($errorProperty, 'Meja ini tidak tersedia untuk penambahan pesanan tanpa nama pemesan yang terdaftar. Pilih meja kosong atau meja terisi lain.'); return false; } // Validasi nama pemesan untuk meja terisi $this->validateOnly('inputCustomerName', ['inputCustomerName' => 'required|string|max:255'], [ 'inputCustomerName.required' => 'Masukkan nama pemesan yang sesuai dengan meja ini.', ]); if ($this->getErrorBag()->has('inputCustomerName')) { return false; } if (Str::lower($this->inputCustomerName) !== Str::lower($selectedTable['reserved_by'])) { $this->addError('inputCustomerName', 'Nama pemesan tidak cocok dengan data meja ini: "' . $selectedTable['reserved_by'] . '".'); return false; } } else { // Mengisi meja yang tersedia if ($isSensorActive) { // Jika meja dipilih sebagai "tersedia" tapi sensornya 1 (tidak tersedia) $this->addError($errorProperty, 'Meja yang dipilih tidak tersedia.'); return false; } // Pastikan nama pemesan tidak kosong saat memilih meja kosong $this->validateOnly('inputCustomerName', ['inputCustomerName' => 'required|string|max:255'], [ 'inputCustomerName.required' => 'Nama pemesan wajib diisi untuk meja baru.', ]); if ($this->getErrorBag()->has('inputCustomerName')) { return false; } } return true; } public function checkout() { if (!$this->validateTableSelection()) { return; } $this->validate(['customerEmail' => 'required|email']); logger("Checkout method called."); $cartItems = $this->getCartItemsProperty(); if (empty($cartItems)) { logger("Cart is empty"); $this->dispatch('notify', message: 'Keranjang kosong'); return; } try { Config::$serverKey = config('services.midtrans.server_key'); Config::$isProduction = config('services.midtrans.is_production', false); Config::$isSanitized = true; Config::$is3ds = true; $selectedTableData = $this->getSelectedTableForTransaction(); $params = [ 'transaction_details' => [ 'order_id' => 'ORDER-' . time() . '-' . uniqid(), 'gross_amount' => $this->getCartTotalProperty(), ], 'customer_details' => [ 'first_name' => $this->inputCustomerName ?: (auth()->user()->name ?? 'Guest'), 'email' => auth()->user()->email ?? $this->customerEmail ?? 'guest@example.com', ], 'item_details' => array_map(function($item) { return [ 'id' => $item['id'], 'price' => $item['price'], 'quantity' => $item['qty'], 'name' => $item['name'], ]; }, $cartItems), 'custom_field1' => 'Firebase Table ID: ' . ($selectedTableData['id'] ?? 'N/A'), 'custom_field2' => 'Nama Meja: ' . ($selectedTableData['device_id'] ?? 'N/A'), 'custom_field3' => 'Nama Pemesan: ' . $this->inputCustomerName, ]; $snapToken = Snap::getSnapToken($params); $this->dispatch('midtransSnapToken', token: $snapToken); } catch (\Exception $e) { logger("Midtrans error: " . $e->getMessage()); logger("Error trace: " . $e->getTraceAsString()); $this->dispatch('notify', message: 'Gagal memproses pembayaran: ' . $e->getMessage()); } } public function sendReceiptEmail() { if (!$this->validateTableSelection()) { return; } $this->validate(['customerEmail' => 'required|email|max:255']); $cartItems = $this->getCartItemsProperty(); $cartTotal = $this->getCartTotalProperty(); $mejaDetail = $this->getSelectedTableForTransaction(); try { Mail::to($this->customerEmail)->send(new OrderReceipt($cartItems, $cartTotal, $mejaDetail)); $this->dispatch('notify', message: 'Detail pesanan telah dikirim ke email Anda.'); $this->updateTableAndResetState(); // Menggunakan fungsi baru } catch (\Exception $e) { logger("Failed to send receipt email: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal mengirim email. Silakan coba lagi.'); } } public function skipPayment() { if (!$this->validateTableSelection()) { return; } $this->dispatch('notify', message: 'Pesanan Anda telah diproses tanpa pembayaran.'); $this->updateTableAndResetState(); // Menggunakan fungsi baru } public function handlePaymentSuccess() { $this->dispatch('notify', message: 'Pembayaran berhasil!'); $this->updateTableAndResetState(); // Menggunakan fungsi baru } // Fungsi Pembantu Baru private function getSelectedTableForTransaction(): ?array { $tableId = $this->selectedTableId ?? $this->selectedOccupiedTableId; return $this->allTablesData->get($tableId); } // Fungsi Pembantu Baru untuk mengupdate meja dan mereset state private function updateTableAndResetState() { $tableData = $this->getSelectedTableForTransaction(); if ($tableData) { $updates = [ 'sensors/table_activation_sensor_active' => 1, 'reserved_by' => $this->inputCustomerName ]; $this->updateFirebaseTableStatus($tableData['id'], $updates); } $this->clearCartAndResetState(); } private function updateFirebaseTableStatus(string $firebaseTableId, array $updates) { $database = $this->getFirebaseDatabase(); if (!$database) { return; } try { $database->getReference($firebaseTableId)->update($updates); logger("Firebase: Updated {$firebaseTableId} with " . json_encode($updates)); $this->dispatch('notify', message: "Status meja '{$firebaseTableId}' berhasil diperbarui di Firebase."); $this->syncTablesFromFirebase(); // Pertahankan ini untuk memastikan UI Livewire refresh } catch (\Exception $e) { logger()->error("Failed to update Firebase for table ID {$firebaseTableId}: " . $e->getMessage()); $this->dispatch('notify', message: 'Gagal memperbarui status meja di Firebase: ' . $e->getMessage()); } } private function clearCartAndResetState() { $this->cart = []; session()->forget('cart'); $this->showCart = false; $this->showEmailInput = false; $this->customerEmail = ''; $this->selectedTableId = null; $this->selectedOccupiedTableId = null; $this->inputCustomerName = ''; $this->resetValidation(); } 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(), // Kirim sebagai array ke view 'tableIdFromUrl' => $this->tableIdFromUrl, ])->layout('components.layouts.front'); } // --- Helper Properties --- public function getFoodItemsProperty() { return Items::all(); } public function getAllTypeItemsProperty() // Menggabungkan getTypeItemsProperty { 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.'); return; } $this->cart[$itemId] = ($this->cart[$itemId] ?? 0) + 1; session()->put('cart', $this->cart); $this->dispatch('notify', message: $item->name . ' ditambahkan ke keranjang.'); } public function removeFromCart($itemId) { if (isset($this->cart[$itemId])) { $item = Items::find($itemId); // Get item before potential unset if ($this->cart[$itemId] > 1) { $this->cart[$itemId]--; } else { unset($this->cart[$itemId]); } session()->put('cart', $this->cart); $this->dispatch('notify', message: $item->name . ' dihapus dari keranjang.'); if (empty($this->cart)) { $this->showCart = false; } } } public function openCart() { $this->showCart = true; } public function closeCart() { $this->showCart = false; } 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); } } }