TKK_E32222868/app/Livewire/FoodOrder.php

959 lines
38 KiB
PHP

<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use App\Models\Items;
use App\Models\TypeItems;
use App\Models\Order;
use App\Models\OrderItem;
use Midtrans\Config;
use Midtrans\Snap;
use App\Mail\OrderReceiptMail;
use Carbon\Carbon;
use Kreait\Firebase\Factory;
class FoodOrder extends Component
{
public array $cart = [];
public bool $showCart = false;
public ?int $typeItemId = null;
public bool $showEmailInput = true;
public string $customerEmail = '';
public $isExistingCustomer = '0';
public ?string $selectedTableId = null;
public ?string $selectedOccupiedTableId = null;
public string $inputCustomerName = '';
public bool $showCustomerNameInput = false;
public bool $isCustomerNameInputDisabled = false;
public array $availableTables = [];
public array $occupiedTables = [];
public Collection $allTablesData;
public bool $showMapModal = false;
public ?string $tableIdFromUrl = null;
public $showQrisPayment = false;
public $qrisImageUrl = 'img/qr-code.png';
public bool $paymentInProgress = false;
public bool $showCheckoutDetailsModal = false;
public ?string $selectedPaymentMethod = null;
private array $lastFirebaseDataHash = [];
protected function rules()
{
return [
'inputCustomerName' => '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');
}
}
}