TKK_E32222868/app/Livewire/FoodOrder.php

594 lines
23 KiB
PHP

<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Items;
use App\Models\TypeItems;
use Illuminate\Support\Str;
use Midtrans\Config;
use Midtrans\Snap;
use Illuminate\Support\Facades\Mail;
use App\Mail\OrderReceipt;
use Kreait\Firebase\Factory;
use Kreait\Firebase\ServiceAccount;
use Illuminate\Support\Collection;
class FoodOrder extends Component
{
public array $cart = [];
public bool $showCart = false;
public ?int $typeItemId = null;
public bool $showEmailInput = false;
public string $customerEmail = '';
public bool $isExistingCustomer = false; // Meja sudah dipesan
public ?string $selectedTableId = null; // Untuk meja yang tersedia (table_activation_sensor_active = 0)
public ?string $selectedOccupiedTableId = null; // Untuk meja yang sudah diduduki (table_activation_sensor_active = 1, dengan reserved_by)
public string $inputCustomerName = ''; // Input nama pemesan
public bool $showCustomerNameInput = false;
public bool $isCustomerNameInputDisabled = false;
public array $availableTables = []; // Meja yang table_activation_sensor_active = 0
public array $occupiedTables = []; // Meja yang table_activation_sensor_active = 1 dan punya reserved_by
public Collection $allTablesData; // Menggunakan Collection untuk pencarian lebih cepat
public bool $showMapModal = false;
public ?string $tableIdFromUrl = null; // ID meja dari URL QR code
protected array $rules = [
'customerEmail' => '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);
}
}
}