This commit is contained in:
DandhiAri 2025-06-28 23:09:25 +07:00
parent ef88610e1a
commit dc25732b43
68 changed files with 202851 additions and 1192 deletions

View File

@ -3,26 +3,573 @@
namespace App\Livewire; namespace App\Livewire;
use Livewire\Component; use Livewire\Component;
use App\Models\Items; // --- UBAH: Menggunakan model Items use App\Models\Items;
use App\Models\TypeItems; // --- BARU: Menambahkan model TypeItems use App\Models\TypeItems;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Midtrans\Config; use Midtrans\Config;
use Midtrans\Snap; 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 class FoodOrder extends Component
{ {
public $foodItems = []; // --- UBAH: Menghapus 'array' type hint agar lebih fleksibel
public array $cart = []; public array $cart = [];
public bool $showCart = false; public bool $showCart = false;
public ?int $typeItemId = null; // --- UBAH: Mengganti $foodType menjadi $typeItemId (sesuai ItemTable) public ?int $typeItemId = null;
public bool $showEmailInput = false;
public string $customerEmail = '';
// --- BARU: Properties untuk dropdown seperti di ItemTable public bool $isExistingCustomer = false; // Meja sudah dipesan
public $allTypeItems; 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 = [ protected $listeners = [
'paymentSuccess' => 'handlePaymentSuccess' '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() public function openCart()
{ {
$this->showCart = true; $this->showCart = true;
@ -33,239 +580,14 @@ public function closeCart()
$this->showCart = false; $this->showCart = false;
} }
// --- DIHAPUS: getGroupedFoodItemsProperty tidak lagi relevan dengan type_item_id public function filterByType($typeId)
// --- UBAH: Sesuaikan nama properti untuk filter
public function getFilteredFoodItemsProperty()
{ {
// --- UBAH: Menggunakan model Items $this->typeItemId = $typeId;
$query = Items::query(); $type = TypeItems::find($typeId);
if ($this->typeItemId) { // --- UBAH: Menggunakan $this->typeItemId
$query->where('type_item_id', $this->typeItemId);
}
// --- UBAH: Tambahkan kondisi is_available agar hanya menampilkan yang tersedia
$query->where('is_available', true);
// --- UBAH: eager load relasi typeItem
return $query->with('typeItem')->get(); // Menghapus ->toArray()
}
// --- UBAH: Sesuaikan nama method filter
public function filterByType($typeId = null) // --- UBAH: Parameter menjadi $typeId
{
$this->typeItemId = $typeId; // --- UBAH: Set $this->typeItemId
// Data foodItems akan diperbarui otomatis via render() dan getFilteredFoodItemsProperty()
// Jadi, tidak perlu secara eksplisit memuat ulang di sini.
// Dispatch event untuk update URL (jika diperlukan)
if ($typeId) {
// Asumsi route Anda mendukung parameter type_item_id
$typeItemName = TypeItems::find($typeId)->name ?? 'all'; // Ambil nama untuk URL
$this->dispatch('updateUrl', url: route('menu.byType', ['type' => Str::slug($typeItemName)]));
} else {
$this->dispatch('updateUrl', url: route('menu.all'));
}
}
public function mount($type = null) // --- UBAH: Parameter bisa berupa slug atau ID
{
// --- UBAH: Ambil semua TypeItems saat mount
$this->allTypeItems = TypeItems::all();
if ($type) { if ($type) {
// Coba temukan type_item berdasarkan slug atau ID $typeSlug = Str::slug($type->name);
$foundType = TypeItems::where('id', $type) $newUrl = route('menu.byType', ['typeSlug' => $typeSlug], false);
->orWhere('name', Str::title(str_replace('-', ' ', $type))) // Coba cari berdasarkan nama yang dislug $this->dispatch('updateUrl', url: $newUrl);
->first();
$this->typeItemId = $foundType->id ?? null;
}
// --- UBAH: Awalnya, foodItems diisi oleh getFilteredFoodItemsProperty() di render
// Jadi tidak perlu query langsung di mount, cukup set $typeItemId
// Namun, jika Anda ingin agar foodItems langsung terisi saat mount, Anda bisa panggil properti
$this->foodItems = $this->getFilteredFoodItemsProperty();
$this->cart = session()->get('cart', []);
}
public function addToCart($id)
{
logger("Adding to cart: $id");
// --- UBAH: Menggunakan model Items
$item = Items::find($id); // Langsung query dari DB untuk memastikan data terkini
if ($item) {
if (!isset($this->cart[$id])) {
$this->cart[$id] = 1;
} else {
$this->cart[$id]++;
}
$this->updateCart();
$this->dispatch('notify', message: $item->name . ' ditambahkan ke keranjang');
} else {
logger("Item with ID $id not found.");
$this->dispatch('notify', message: 'Item tidak ditemukan.');
} }
} }
public function removeFromCart($id)
{
if (isset($this->cart[$id])) {
$this->cart[$id]--;
if ($this->cart[$id] <= 0) {
unset($this->cart[$id]);
}
$this->updateCart();
}
}
public function updateCart()
{
session()->put('cart', $this->cart);
}
public function updated($name, $value)
{
if (Str::startsWith($name, 'cart.')) {
$foodId = (int)substr($name, 5);
if ($value <= 0 || $value === null) {
unset($this->cart[$foodId]);
} else {
$this->cart[$foodId] = (int)$value;
}
$this->updateCart();
}
}
public function getCartItemsProperty()
{
$items = [];
// --- UBAH: Query items dari database berdasarkan ID di keranjang
// Ini lebih aman daripada mengandalkan $this->foodItems lokal yang mungkin sudah difilter
$foodIdsInCart = array_keys($this->cart);
$foodModels = Items::whereIn('id', $foodIdsInCart)->get()->keyBy('id'); // Ambil model Item
foreach ($this->cart as $foodId => $qty) {
$food = $foodModels->get($foodId); // Ambil model langsung dari koleksi
if ($food && $qty > 0) {
$items[] = [
'id' => $foodId,
'name' => $food->name,
'price' => $food->price,
'image_url' => $food->image_url ?? null,
'qty' => $qty,
'total_price' => $food->price * $qty,
];
}
}
return $items;
}
public function getCartTotalProperty()
{
return array_sum(array_map(function($item) {
return $item['total_price'];
}, $this->getCartItemsProperty()));
}
public function clearCart()
{
$this->cart = [];
$this->updateCart();
$this->closeCart();
}
public function handlePaymentSuccess()
{
$this->clearCart();
$this->dispatch('notify', message: 'Pembayaran berhasil!');
}
public function checkout()
{
logger("Checkout method called");
$cartItems = $this->getCartItemsProperty();
logger("Cart items: " . json_encode($cartItems));
if (empty($cartItems)) {
logger("Cart is empty");
$this->dispatch('notify', message: 'Keranjang kosong');
return;
}
try {
logger("Setting up Midtrans config");
\Midtrans\Config::$serverKey = config('services.midtrans.server_key');
\Midtrans\Config::$isProduction = false;
\Midtrans\Config::$isSanitized = true;
\Midtrans\Config::$is3ds = true;
$params = [
'transaction_details' => [
'order_id' => 'ORDER-' . time(),
'gross_amount' => $this->getCartTotalProperty(),
],
'customer_details' => [
'first_name' => auth()->user()->name ?? 'Guest',
'email' => auth()->user()->email ?? 'guest@example.com',
],
// --- BARU: Menambahkan item_details untuk Midtrans
'item_details' => array_map(function($item) {
return [
'id' => $item['id'],
'price' => $item['price'],
'quantity' => $item['qty'],
'name' => $item['name'],
];
}, $cartItems),
];
logger("Midtrans params: " . json_encode($params));
$snapToken = \Midtrans\Snap::getSnapToken($params);
logger("Snap token generated: " . $snapToken);
$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());
}
}
// Method untuk mendapatkan title berdasarkan type_item_id
// --- UBAH: Menggunakan type_item_id dan mengambil nama dari TypeItems
public function getPageTitleProperty()
{
if ($this->typeItemId) {
$typeItem = TypeItems::find($this->typeItemId);
return $typeItem->name ?? 'Semua Menu';
}
return 'Semua Menu';
}
public function render()
{
return view('livewire.food-order', [
'foodItems' => $this->getFilteredFoodItemsProperty(), // --- UBAH: Gunakan properti terkomputasi
'cartItems' => $this->getCartItemsProperty(),
'cartTotal' => $this->getCartTotalProperty(),
'pageTitle' => $this->getPageTitleProperty(),
'typeItemId' => $this->typeItemId, // --- UBAH: Mengganti foodType menjadi typeItemId
'allTypeItems' => $this->allTypeItems, // --- BARU: Lewatkan allTypeItems ke view
])->layout('components.layouts.front');
}
} }

View File

@ -8,7 +8,8 @@
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\WithFileUploads; use Livewire\WithFileUploads;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\File; // Tambahkan ini untuk operasi file
use Illuminate\Support\Facades\Storage; // Masih dipakai untuk temporary URL, tapi tidak untuk simpan/hapus di public_path
class ItemTable extends Component class ItemTable extends Component
{ {
@ -25,8 +26,8 @@ class ItemTable extends Component
public $name; public $name;
public $description; public $description;
public $price; public $price;
public $image_url; public $image_url; // Ini akan menyimpan path relatif dari folder public, e.g., 'img/items/namafile.jpg'
public $new_image; public $new_image; // Ini adalah objek TemporaryUploadedFile dari Livewire
public $is_available = true; public $is_available = true;
public $type_item_id; public $type_item_id;
public $bundle_ids = []; public $bundle_ids = [];
@ -53,7 +54,7 @@ class ItemTable extends Component
'type_item_id' => 'required|exists:type_items,id', 'type_item_id' => 'required|exists:type_items,id',
'bundle_ids' => 'nullable|array', 'bundle_ids' => 'nullable|array',
'bundle_ids.*' => 'exists:bundles,id', 'bundle_ids.*' => 'exists:bundles,id',
'new_image' => 'nullable|image|max:1024', 'new_image' => 'nullable|image|max:1024', // Validasi tetap sama untuk file upload
]; ];
public function mount() public function mount()
@ -98,11 +99,11 @@ public function edit($id)
$this->name = $item->name; $this->name = $item->name;
$this->description = $item->description; $this->description = $item->description;
$this->price = $item->price; $this->price = $item->price;
$this->image_url = $item->image_url; $this->image_url = $item->image_url; // Path yang tersimpan di database
$this->is_available = $item->is_available; $this->is_available = $item->is_available;
$this->type_item_id = $item->type_item_id; $this->type_item_id = $item->type_item_id;
$this->bundle_ids = $item->bundles->pluck('id')->toArray(); $this->bundle_ids = $item->bundles->pluck('id')->toArray();
$this->new_image = null; $this->new_image = null; // Reset new_image saat edit
$this->isModalOpen = true; $this->isModalOpen = true;
} }
@ -118,13 +119,32 @@ public function store()
'type_item_id' => $this->type_item_id, 'type_item_id' => $this->type_item_id,
]; ];
// LOGIKA PENYIMPANAN GAMBAR DIUBAH DI SINI
if ($this->new_image) { if ($this->new_image) {
// Hapus gambar lama jika ada dan file-nya masih ada di public
if ($this->itemId && $this->image_url) { if ($this->itemId && $this->image_url) {
Storage::disk('public')->delete($this->image_url); $oldImagePath = public_path($this->image_url);
if (File::exists($oldImagePath)) {
File::delete($oldImagePath);
} }
$itemData['image_url'] = $this->new_image->store('items', 'public');
} }
// Tentukan nama file baru (gunakan uniqid() atau timestamp untuk unik)
$fileName = uniqid() . '.' . $this->new_image->extension();
$targetPath = 'img/items/'; // Subfolder di dalam public
// Pastikan folder target ada
if (!File::isDirectory(public_path($targetPath))) {
File::makeDirectory(public_path($targetPath), 0755, true, true);
}
// Pindahkan file dari temporary ke folder public yang diinginkan
$this->new_image->storeAs($targetPath, $fileName, 'public_path_disk'); // Menggunakan disk kustom 'public_path_disk'
$itemData['image_url'] = $targetPath . $fileName; // Simpan path relatif di database
}
$item = null; $item = null;
if ($this->itemId) { if ($this->itemId) {
$item = Items::find($this->itemId); $item = Items::find($this->itemId);
@ -154,8 +174,12 @@ public function delete()
if ($this->itemToDeleteId) { if ($this->itemToDeleteId) {
$item = Items::find($this->itemToDeleteId); $item = Items::find($this->itemToDeleteId);
if ($item) { if ($item) {
// LOGIKA PENGHAPUSAN GAMBAR DIUBAH DI SINI
if ($item->image_url) { if ($item->image_url) {
Storage::disk('public')->delete($item->image_url); $imagePath = public_path($item->image_url);
if (File::exists($imagePath)) {
File::delete($imagePath);
}
} }
$item->delete(); $item->delete();
session()->flash('message', 'Item berhasil dihapus!'); session()->flash('message', 'Item berhasil dihapus!');

View File

@ -0,0 +1,58 @@
<?php
namespace App\Livewire;
use Livewire\Component;
// Hapus use Kreait\Firebase\Factory, etc. jika tidak lagi digunakan di sini
class SelectTable extends Component
{
// Properti ini akan diisi dari parent (FoodOrder)
public array $availableTables = [];
public ?string $selectedTableId = null; // Ini mungkin tidak perlu jika Anda hanya menerima data ID
public bool $show = false; // Ini untuk kontrol visibilitas modal dari FoodOrder
// Listener 'refreshSelectTableModal' tidak lagi memanggil syncTablesFromFirebase di sini
// Melainkan, Anda bisa melewatkan availableTables dari FoodOrder ke modal.
// Jika Anda masih ingin modal me-refresh sendiri, maka syncTablesFromFirebase HARUS ADA.
// Untuk saat ini, kita asumsikan FoodOrder meneruskan data yang sudah di-sync.
protected $listeners = [
'closeThisModal' => 'closeModal',
// 'refreshSelectTableModal' => 'syncTablesFromFirebase', // Hapus ini jika syncTablesFromFirebase dihapus
];
// Hapus mount() jika tidak ada lagi logika yang perlu dijalankan saat mount
// public function mount()
// {
// // Tidak perlu panggil syncTablesFromFirebase di sini lagi jika parent yang menyediakan data
// }
// Hapus getFirebaseDatabase() dan syncTablesFromFirebase() dari sini
public function selectTable(string $tableId)
{
$foundTable = collect($this->availableTables)->firstWhere('id', $tableId);
if ($foundTable) {
// Dispatch event ke komponen FoodOrder (parent) dengan data meja
$this->dispatch('tableSelected', $tableId)->to(FoodOrder::class);
$this->closeModal(); // Tutup modal setelah pemilihan
} else {
// Jika meja tidak valid, beri notifikasi. FoodOrder akan me-refresh daftar mejanya.
$this->dispatch('notify', message: 'Meja tidak valid atau sudah tidak tersedia.')->to(FoodOrder::class);
// TIDAK ADA syncTablesFromFirebase() DI SINI
}
}
public function closeModal()
{
$this->show = false;
}
public function render()
{
// View ini akan menerima $availableTables dari parent
return view('livewire.select-table');
}
}

View File

@ -2,40 +2,43 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\TypeItems;
use Livewire\Component; use Livewire\Component;
use App\Models\TypeItems;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\WithFileUploads;
use Illuminate\Support\Facades\Storage; // Gunakan Storage facade
use Illuminate\Support\Str;
class TypeItemTable extends Component class TypeItemTable extends Component
{ {
use WithPagination; use WithPagination;
use WithFileUploads;
public $search = ''; public $search = '';
public $sortBy = 'name'; public $sortBy = 'name';
public $sortDirection = 'asc'; public $sortDirection = 'asc';
// Properti untuk form Create/Edit TypeItem
public $typeItemId; public $typeItemId;
public $name; public $name;
public $description; public $description;
public $image_url;
public $new_image;
public $isModalOpen = false; public $isModalOpen = false;
public $isDeleteModalOpen = false; public $isDeleteModalOpen = false; // Perbaikan typo, dari isDeleteModalOpen
public $typeItemToDeleteId; public $typeItemToDeleteId;
// Aturan validasi
protected $rules = [ protected $rules = [
'name' => 'required|string|max:255|unique:type_items,name', // Name harus unik 'name' => 'required|string|max:255|unique:type_items,name',
'description' => 'nullable|string', 'description' => 'nullable|string',
'new_image' => 'nullable|image|max:1024|mimes:jpeg,png,jpg,gif',
]; ];
// Reset halaman pagination setiap kali properti pencarian berubah
public function updatingSearch() public function updatingSearch()
{ {
$this->resetPage(); $this->resetPage();
} }
// Metode untuk mengubah kolom sorting
public function sortBy($field) public function sortBy($field)
{ {
if ($this->sortBy === $field) { if ($this->sortBy === $field) {
@ -46,31 +49,33 @@ public function sortBy($field)
} }
} }
// Metode untuk membuka modal Create TypeItem
public function create() public function create()
{ {
$this->resetInputFields(); $this->resetInputFields();
$this->isModalOpen = true; $this->isModalOpen = true;
} }
// Metode untuk mengisi form edit dan membuka modal
public function edit($id) public function edit($id)
{ {
$typeItem = TypeItems::findOrFail($id); $typeItem = TypeItems::findOrFail($id);
$this->typeItemId = $typeItem->id; $this->typeItemId = $typeItem->id;
$this->name = $typeItem->name; $this->name = $typeItem->name;
$this->description = $typeItem->description; $this->description = $typeItem->description;
$this->image_url = $typeItem->image_url;
$this->isModalOpen = true; $this->isModalOpen = true;
} }
// Metode untuk menyimpan atau memperbarui TypeItem
public function store() public function store()
{ {
$rules = $this->rules; $rules = $this->rules;
if ($this->typeItemId) { if ($this->typeItemId) {
// Untuk update, 'name' harus unique kecuali jika itu adalah nama dari type item yang sedang diedit
$rules['name'] = 'required|string|max:255|unique:type_items,name,' . $this->typeItemId; $rules['name'] = 'required|string|max:255|unique:type_items,name,' . $this->typeItemId;
} }
if (!$this->new_image && !$this->image_url) {
unset($rules['new_image']);
}
$this->validate($rules); $this->validate($rules);
$typeItemData = [ $typeItemData = [
@ -78,12 +83,41 @@ public function store()
'description' => $this->description, 'description' => $this->description,
]; ];
// Penanganan upload gambar menggunakan disk 'public_path_disk'
if ($this->new_image) {
// Hapus gambar lama jika ada saat update
if ($this->typeItemId && $this->image_url) {
// $this->image_url sudah menyimpan path relatif dari folder public (e.g., 'img/menu/nama_file.jpg')
Storage::disk('public_path_disk')->delete($this->image_url);
}
// Tentukan direktori tujuan di dalam public_path_disk (misal: 'img/menu/')
$uploadDirectory = 'img/menu'; // Relatif terhadap root 'public_path_disk' (yaitu public/)
// Buat direktori jika belum ada di dalam disk
if (!Storage::disk('public_path_disk')->exists($uploadDirectory)) {
Storage::disk('public_path_disk')->makeDirectory($uploadDirectory);
}
// Simpan gambar baru ke disk 'public_path_disk' dengan nama unik
$fileName = Str::random(20) . '.' . $this->new_image->extension();
$imagePath = $this->new_image->storeAs($uploadDirectory, $fileName, 'public_path_disk');
$typeItemData['image_url'] = $imagePath; // Simpan path relatif penuh (e.g., 'img/menu/nama_file.jpg')
} elseif ($this->typeItemId && $this->image_url !== null && !$this->new_image) {
// Jika pengguna menghapus gambar yang sudah ada (saat update)
// Hapus file fisik lama
Storage::disk('public_path_disk')->delete($this->image_url);
$typeItemData['image_url'] = null;
} elseif (!$this->typeItemId && !$this->new_image) {
// Jika membuat baru dan tidak ada gambar diupload
$typeItemData['image_url'] = null;
}
if ($this->typeItemId) { if ($this->typeItemId) {
// Update TypeItem
TypeItems::find($this->typeItemId)->update($typeItemData); TypeItems::find($this->typeItemId)->update($typeItemData);
session()->flash('message', 'Tipe item berhasil diperbarui!'); session()->flash('message', 'Tipe item berhasil diperbarui!');
} else { } else {
// Create TypeItem
TypeItems::create($typeItemData); TypeItems::create($typeItemData);
session()->flash('message', 'Tipe item berhasil ditambahkan!'); session()->flash('message', 'Tipe item berhasil ditambahkan!');
} }
@ -92,64 +126,69 @@ public function store()
$this->resetInputFields(); $this->resetInputFields();
} }
// Metode untuk membuka modal konfirmasi hapus
public function confirmDelete($id) public function confirmDelete($id)
{ {
$this->typeItemToDeleteId = $id; $this->typeItemToDeleteId = $id;
$this->isDeleteModalOpen = true; $this->isDeleteModalOpen = true; // Perbaikan typo
} }
// Metode untuk menghapus TypeItem
public function delete() public function delete()
{ {
if ($this->typeItemToDeleteId) { if ($this->typeItemToDeleteId) {
// Cek apakah ada item yang terkait dengan type item ini
// (Ini penting karena di database ada ON DELETE RESTRICT)
$typeItem = TypeItems::find($this->typeItemToDeleteId); $typeItem = TypeItems::find($this->typeItemToDeleteId);
if ($typeItem && $typeItem->items()->count() > 0) {
if ($typeItem) {
if ($typeItem->items()->count() > 0) {
session()->flash('error', 'Tidak dapat menghapus tipe item ini karena masih ada item yang terkait dengannya.'); session()->flash('error', 'Tidak dapat menghapus tipe item ini karena masih ada item yang terkait dengannya.');
$this->closeDeleteModal(); $this->closeDeleteModal();
return; return;
} }
TypeItems::destroy($this->typeItemToDeleteId); // Menggunakan destroy untuk menghapus // Hapus file gambar menggunakan disk 'public_path_disk'
if ($typeItem->image_url) {
Storage::disk('public_path_disk')->delete($typeItem->image_url);
}
TypeItems::destroy($this->typeItemToDeleteId);
session()->flash('message', 'Tipe item berhasil dihapus!'); session()->flash('message', 'Tipe item berhasil dihapus!');
} else {
session()->flash('error', 'Tipe item tidak ditemukan.');
}
} }
$this->closeDeleteModal(); $this->closeDeleteModal();
} }
// Tutup modal create/edit
public function closeModal() public function closeModal()
{ {
$this->isModalOpen = false; $this->isModalOpen = false;
$this->resetValidation();
} }
// Tutup modal konfirmasi hapus
public function closeDeleteModal() public function closeDeleteModal()
{ {
$this->isDeleteModalOpen = false; $this->isDeleteModalOpen = false; // Perbaikan typo
$this->typeItemToDeleteId = null; $this->typeItemToDeleteId = null;
} }
// Reset semua input form
private function resetInputFields() private function resetInputFields()
{ {
$this->typeItemId = null; $this->typeItemId = null;
$this->name = ''; $this->name = '';
$this->description = ''; $this->description = '';
$this->image_url = null;
$this->new_image = null;
} }
// Metode render komponen Livewire
public function render() public function render()
{ {
// Query untuk mengambil data type items
$typeItems = TypeItems::query() $typeItems = TypeItems::query()
->when($this->search, function ($query) { ->when($this->search, function ($query) {
$query->where('name', 'like', '%' . $this->search . '%') $query->where('name', 'like', '%' . $this->search . '%')
->orWhere('description', 'like', '%' . $this->search . '%'); ->orWhere('description', 'like', '%' . $this->search . '%');
}) })
->orderBy($this->sortBy, $this->sortDirection) ->orderBy($this->sortBy, $this->sortDirection)
->paginate(10); // Paginate 10 item per halaman ->paginate(10);
return view('livewire.type-item-table', [ return view('livewire.type-item-table', [
'typeItems' => $typeItems, 'typeItems' => $typeItems,
]); ]);

64
app/Mail/OrderReceipt.php Normal file
View File

@ -0,0 +1,64 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class OrderReceipt extends Mailable
{
use Queueable, SerializesModels;
public $cartItems;
public $cartTotal;
public ?array $mejaDetail = null; // Ini akan menerima array dari Firebase
/**
* Create a new message instance.
*/
public function __construct($cartItems, $cartTotal, ?array $mejaDetail = null)
{
$this->cartItems = $cartItems;
$this->cartTotal = $cartTotal;
$this->mejaDetail = $mejaDetail;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Detail Pesanan Anda di DFOOD',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.orders.receipt', // Ini menunjuk ke view Markdown
with: [
'cartItems' => $this->cartItems,
'cartTotal' => $this->cartTotal,
'meja' => $this->mejaDetail, // Kirim array detail meja ke view email
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -4,6 +4,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Bundles extends Model class Bundles extends Model
{ {
@ -18,8 +19,8 @@ class Bundles extends Model
'is_active', 'is_active',
]; ];
public function items() public function items(): BelongsToMany
{ {
return $this->hasMany(Items::class, 'bundle_id'); return $this->belongsToMany(Items::class, 'bundle_items', 'bundle_id', 'item_id');
} }
} }

View File

@ -4,6 +4,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Items extends Model class Items extends Model
{ {
@ -32,8 +33,8 @@ public function typeItem()
/** /**
* Relasi Many-to-One: Item ini milik satu Bundle. * Relasi Many-to-One: Item ini milik satu Bundle.
*/ */
public function bundles() // Nama relasi plural, mengikuti konvensi Laravel public function items(): BelongsToMany
{ {
return $this->belongsToMany(Bundles::class, 'bundle_items', 'item_id', 'bundle_id'); return $this->belongsToMany(Items::class, 'bundle_items', 'bundle_id', 'item_id'); // Pastikan 'bundle_item'
} }
} }

View File

@ -11,7 +11,11 @@ class TypeItems extends Model
protected $table = 'type_items'; protected $table = 'type_items';
protected $fillable = ['name', 'icon_class']; protected $fillable = [
'name',
'description',
'image_url',
];
public function items() public function items()
{ {

View File

@ -24,5 +24,9 @@ public function boot(): void
View::composer('components.layouts.app.sidebar', function ($view) { View::composer('components.layouts.app.sidebar', function ($view) {
$view->with('typeitems', TypeItems::orderBy('name')->get()); $view->with('typeitems', TypeItems::orderBy('name')->get());
}); });
View::composer('components.layouts.front', function ($view) {
$view->with('typeitems', TypeItems::has('items')->orderBy('name')->get());
});
} }
} }

View File

@ -12,6 +12,7 @@
"php": "^8.2", "php": "^8.2",
"guzzlehttp/guzzle": "^7.9", "guzzlehttp/guzzle": "^7.9",
"jeroennoten/laravel-adminlte": "^3.15", "jeroennoten/laravel-adminlte": "^3.15",
"kreait/laravel-firebase": "^6.1",
"laravel/fortify": "^1.25", "laravel/fortify": "^1.25",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1", "laravel/tinker": "^2.10.1",

1555
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
| |
*/ */
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'DFOOD'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -47,6 +47,13 @@
'report' => false, 'report' => false,
], ],
'public_path_disk' => [
'driver' => 'local',
'root' => public_path(), // ROOT DISK INI ADALAH FOLDER 'public'
'url' => env('APP_URL'), // Base URL Anda
'visibility' => 'public',
],
's3' => [ 's3' => [
'driver' => 's3', 'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'), 'key' => env('AWS_ACCESS_KEY_ID'),

View File

@ -35,9 +35,16 @@
], ],
], ],
'firebase' => [
'credentials' => env('FIREBASE_CREDENTIALS') ? storage_path(env('FIREBASE_CREDENTIALS')) : null,
'database_url' => env('FIREBASE_DATABASE_URL'),
],
'midtrans' => [ 'midtrans' => [
'server_key' => env('MIDTRANS_SERVER_KEY'), 'server_key' => env('MIDTRANS_SERVER_KEY'),
'client_key' => env('MIDTRANS_CLIENT_KEY'), 'client_key' => env('MIDTRANS_CLIENT_KEY'),
'is_production' => env('MIDTRANS_IS_PRODUCTION', false),
'snap_url' => env('MIDTRANS_SNAP_URL', 'https://app.sandbox.midtrans.com/snap/snap.js'),
], ],
]; ];

View File

@ -0,0 +1,17 @@
{
"meja_001": {
"device_id": "meja_001",
"last_update": 107,
"mac_address": "D8:BF:C0:E2:BA:AC",
"sensor4_active": 0,
"sensors": {
"s1": 3.23,
"s2": 0,
"s3": 0,
"s4": 3.536,
"table_activation_sensor_active": 0
},
"table_occupied": 0,
"time_remaining_minutes": 0
}
}

1130
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^1.7.4", "axios": "^1.7.4",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"firebase": "^11.9.1",
"laravel-vite-plugin": "^1.0", "laravel-vite-plugin": "^1.0",
"tailwindcss": "^4.0.7", "tailwindcss": "^4.0.7",
"vite": "^6.0" "vite": "^6.0"

BIN
public/img/denah-cafe.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -11,7 +11,7 @@
<span class="flex h-10 w-10 items-center justify-center rounded-md"> <span class="flex h-10 w-10 items-center justify-center rounded-md">
<x-app-logo-icon class="me-2 h-7 fill-current text-white" /> <x-app-logo-icon class="me-2 h-7 fill-current text-white" />
</span> </span>
{{ config('app.name', 'Laravel') }} {{ config('app.name', 'Login') }}
</a> </a>
@php @php
@ -32,7 +32,7 @@
<x-app-logo-icon class="size-9 fill-current text-black dark:text-white" /> <x-app-logo-icon class="size-9 fill-current text-black dark:text-white" />
</span> </span>
<span class="sr-only">{{ config('app.name', 'Laravel') }}</span> <span class="sr-only">{{ config('app.name', 'Login') }}</span>
</a> </a>
{{ $slot }} {{ $slot }}
</div> </div>

View File

@ -1,107 +1,146 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<title>DFOOD</title>
<!-- Tailwind CSS --> <title>{{ config('app.name', 'DFOOD') }}</title>
<script src="https://cdn.tailwindcss.com"></script>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<style> <script type="text/javascript" src="{{ config('services.midtrans.snap_url') }}"
@import url("https://fonts.googleapis.com/css2?family=Montserrat&display=swap");
body {
font-family: "Montserrat", sans-serif;
overflow: hidden;
height: 100dvh;
}
.sidebar-scroll {
overflow-y: auto;
height: 100dvh;
padding-bottom: 6em;
}
.sidebar-scroll::-webkit-scrollbar {
display: none;
}
.main-content-scroll {
overflow-y: auto;
height: 100dvh;
}
[x-cloak] {
display: none !important;
}
html {
scroll-behavior: smooth;
}
</style>
<!-- Midtrans -->
<script src="https://app.sandbox.midtrans.com/snap/snap.js"
data-client-key="{{ config('services.midtrans.client_key') }}"></script> data-client-key="{{ config('services.midtrans.client_key') }}"></script>
@livewireStyles @livewireStyles
</head> </head>
<body class="min-h-screen flex bg-gray-100"> <body class="font-sans antialiased">
<!-- Sidebar -->
<aside class="w-64 bg-white text-black min-h-screen px-4 py-6">
<div class="text-5xl text-center font-bold mb-6">DFOOD</div>
<div class="space-y-4 sidebar-scroll">
<a href="#food" class="block">
<div class="shadow rounded overflow-hidden">
<img src="img/makanan.jpg" class="w-full h-auto">
<div class="text-center py-2 border-t">Makanan</div>
</div>
</a>
<a href="#drink" class="block">
<div class="shadow rounded overflow-hidden">
<img src="img/minuman.jpg" class="w-full h-auto">
<div class="text-center py-2 border-t">Minuman</div>
</div>
</a>
</div>
</aside>
<!-- Main Content -->
{{ $slot }} {{ $slot }}
{{-- Alpine.js harus dimuat sebelum Livewire --}}
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
{{-- Livewire Scripts harus dimuat setelah Alpine.js --}}
@livewireScripts @livewireScripts
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script> {{-- Script kustom Anda harus dimuat setelah @livewireScripts --}}
<script> <script>
document.addEventListener('livewire:load', function() { document.addEventListener('livewire:initialized', function() {
Livewire.on('notify', message => { // Livewire Event Listener untuk notifikasi umum
console.log('Notification:', message); Livewire.on('notify', (data) => {
console.log('Notification:', data.message);
}); });
Livewire.hook('message.processed', (message, component) => { // Livewire Event Listener untuk menampilkan Midtrans Snap Pop-up
if (component.fingerprint.name === 'food-order') { Livewire.on('midtransSnapToken', (event) => {
console.log('Component updated:', component.serverMemo.data.cart); const snapToken = event.token;
console.log('Received Midtrans Snap Token:', snapToken);
if (typeof Snap !== 'undefined' && snapToken) {
try {
Snap.pay(snapToken, {
onSuccess: function(result) {
console.log('Payment success:', result);
Livewire.dispatch(
'paymentSuccess'); // Inform Livewire component
},
onPending: function(result) {
console.log('Payment pending:', result);
Livewire.dispatch('notify', {
message: 'Pembayaran menunggu konfirmasi Anda.'
});
},
onError: function(result) {
console.log('Payment error:', result);
let errorMessage = 'Terjadi kesalahan pembayaran.';
if (result.status_code === '400') {
errorMessage = 'Permintaan tidak valid. Mohon coba lagi.';
} else if (result.status_code === '401') {
errorMessage =
'Autentikasi Midtrans gagal. Hubungi administrator.';
}
Livewire.dispatch('notify', {
message: errorMessage
});
},
onClose: function() {
console.log('Payment closed by user');
Livewire.dispatch('notify', {
message: 'Pembayaran dibatalkan oleh pengguna.'
});
} }
}); });
} catch (e) {
console.error("Error calling Snap.pay:", e);
Livewire.dispatch('notify', {
message: 'Terjadi kesalahan saat memulai pembayaran.'
});
}
} else {
console.error(
'Midtrans Snap.js not loaded or Snap object is undefined, or snapToken is empty.'
);
Livewire.dispatch('notify', {
message: 'Sistem pembayaran tidak siap. Mohon refresh halaman.'
});
}
}); });
// Livewire Event Listener untuk update URL di browser history
Livewire.on('updateUrl', (data) => {
const newUrl = data.url;
if (window.history.pushState) {
window.history.pushState({
path: newUrl
}, '', newUrl);
}
});
// Livewire hook untuk logging proses (hapus di production jika tidak perlu)
Livewire.hook('message.processed', (message, component) => {
if (component.fingerprint.name === 'food-order') {
console.log('FoodOrder component updated. Current cart:', component.serverMemo.data
.cart);
}
});
// Livewire Event Listener untuk menyembunyikan progress bar navigasi
Livewire.on('start-navigation', () => {
const progressBar = document.querySelector('.livewire-progress-bar');
if (progressBar) {
progressBar.style.display = 'none';
}
});
// Pindahkan setInterval untuk refresh CSRF di sini, di dalam livewire:initialized
setInterval(() => { setInterval(() => {
fetch('/refresh-csrf') fetch('/refresh-csrf')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
document.querySelector('meta[name="csrf-token"]').setAttribute('content', data.csrf_token); const csrfMeta = document.querySelector('meta[name="csrf-token"]');
if (csrfMeta) {
csrfMeta.setAttribute('content', data.csrf_token);
}
// Pastikan Livewire sudah ada sebelum mencoba mengaksesnya
if (window.Livewire && typeof window.Livewire.findComponents === 'function') {
window.Livewire.findComponents().forEach(component => { window.Livewire.findComponents().forEach(component => {
component.canonical = component.canonical || {}; if (component.canonical) {
component.canonical.csrf = data.csrf_token; component.canonical.csrf = data.csrf_token;
}
}); });
} else {
console.warn('Livewire.findComponents not available yet.');
}
console.log('CSRF token refreshed:', data.csrf_token);
})
.catch(error => {
console.error('Error refreshing CSRF token:', error);
});
}, 1800000); // 30 menit (1800000 ms)
}); });
}, 1800000);
</script> </script>
</body> </body>

View File

@ -1,101 +1,294 @@
<div class="container mx-auto px-4 py-8 main-content-scroll"> <div class="flex h-screen bg-gray-100 font-sans">
<div class="flex flex-col sm:flex-row justify-between items-center mb-8 gap-4"> {{-- Kiri: Sidemenu Kategori --}}
<h1 class="text-3xl font-bold text-gray-800">{{ $pageTitle }}</h1> <div class="w-64 bg-white shadow-lg overflow-y-auto p-4 z-20">
<h1 class="text-5xl font-bold mb-6 text-center text-gray-800">DFOOD</h1>
<div class="w-full sm:w-auto"> <nav>
<label for="typeFilter" class="sr-only">Filter by Category</label> <ul class="space-y-4">
<select id="typeFilter" wire:model.live="typeItemId" wire:change="filterByType($event.target.value)"
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring focus:ring-green-500 focus:ring-opacity-50">
<option value="">Semua Menu</option>
@foreach ($allTypeItems as $type) @foreach ($allTypeItems as $type)
<option value="{{ $type->id }}">{{ $type->name }}</option> <li class="mb-2">
<a href="#" wire:click.prevent="filterByType({{ $type->id }})"
class="block rounded overflow-hidden shadow-md transition-all duration-200
{{ $typeItemId == $type->id ? 'ring-2 ring-green-600 ring-offset-2' : 'hover:scale-105' }}">
@php
$imageUrl = $type->image_url;
if (!filter_var($imageUrl, FILTER_VALIDATE_URL)) {
$imageUrl = asset($imageUrl);
}
@endphp
<img src="{{ $imageUrl }}" class="w-full h-32 object-cover" alt="{{ $type->name }}"
loading="lazy" decoding="async">
<div
class="text-center py-3 border-t border-gray-200 text-gray-800 font-medium
{{ $typeItemId == $type->id ? 'bg-green-600 text-white' : 'bg-gray-50' }}">
{{ $type->name }}
</div>
</a>
</li>
@endforeach @endforeach
</select> </ul>
</nav>
</div> </div>
</div> {{-- Tengah: Konten Utama (Daftar Makanan) --}}
<div class="flex-1 overflow-y-auto p-6">
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mb-8"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
@forelse ($foodItems as $item) {{-- UBAH: Gunakan $item sebagai objek model --}} @forelse ($foodItems as $item)
<div wire:click="addToCart({{ $item->id }})" {{-- UBAH: Akses ID sebagai properti objek --}} <div wire:click="addToCart({{ $item->id }})"
class="bg-white cursor-pointer shadow rounded-lg transition transform hover:scale-105 active:scale-95 active:ring-2 active:ring-green-400"> class="bg-white cursor-pointer shadow rounded-lg overflow-hidden
@if (isset($item->image_url) && $item->image_url) {{-- UBAH: Akses image_url sebagai properti objek --}} transition transform hover:scale-105 active:scale-95
<img src="{{ $item->image_url }}" alt="{{ $item->name }}" class="w-full h-40 object-cover" {{-- UBAH: Akses name sebagai properti objek --}} active:ring-2 active:ring-green-400 flex flex-col">
loading="lazy" decoding="async" /> @if (isset($item->image_url) && $item->image_url)
<img src="{{ asset($item->image_url) }}" alt="{{ $item->name }}"
class="w-full h-48 object-cover object-center" loading="lazy" decoding="async" />
@else @else
{{-- UBAH: Logika gambar default disesuaikan --}}
<img src="{{ asset('img/placeholder-food.jpg') }}" alt="{{ $item->name }}" <img src="{{ asset('img/placeholder-food.jpg') }}" alt="{{ $item->name }}"
class="w-full h-40 object-cover" loading="lazy" decoding="async" /> class="w-full h-48 object-cover object-center" loading="lazy" decoding="async" />
@endif @endif
<div class="p-4 flex flex-col flex-grow"> <div class="p-4 flex flex-col flex-grow">
<h2 class="text-lg font-semibold leading-tight">{{ $item->name }}</h2> {{-- UBAH: Akses name sebagai properti objek --}} <h2 class="text-xl font-semibold leading-tight text-gray-800 mb-2">{{ $item->name }}</h2>
<p class="text-gray-500 mb-3">Rp {{ number_format($item->price, 0, ',', '.') }}</p> {{-- UBAH: Akses price sebagai properti objek --}} <p class="text-gray-600 text-lg">Rp {{ number_format($item->price, 0, ',', '.') }}</p>
</div> </div>
</div> </div>
@empty @empty
<div class="col-span-full text-center py-10"> <div class="col-span-full text-center py-10 bg-white rounded-lg shadow">
<p class="text-gray-500 text-xl">Tidak ada menu tersedia untuk kategori ini.</p> <p class="text-gray-500 text-xl">Tidak ada menu tersedia untuk kategori ini.</p>
</div> </div>
@endforelse @endforelse
</div> </div>
</div>
<div class="fixed bottom-4 right-4 z-30"> {{-- Kanan: Sidebar Keranjang & Pembayaran --}}
<button wire:click="openCart" <div
class="bg-green-600 text-white rounded-full p-4 shadow-lg hover:bg-green-700 transition flex items-center gap-2"> class="fixed top-0 right-0 h-full w-96 bg-white shadow-xl z-30 flex flex-col transform
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" transition-transform duration-300 ease-in-out
stroke="currentColor"> {{ $showCart ? 'translate-x-0' : 'translate-x-full' }}">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <div class="p-6 border-b border-gray-200 flex justify-between items-center">
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /> <h2 id="cart-title" class="text-2xl font-bold text-gray-800">Keranjang Anda</h2>
<button wire:click="closeCart" class="text-gray-500 hover:text-gray-700 focus:outline-none"
aria-label="Close cart" type="button">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
<span class="font-medium">{{ array_sum($cart) }}</span>
</button> </button>
</div> </div>
@if ($showCart) <div class="flex-1 overflow-y-auto p-6">
<div class="fixed inset-0 bg-black bg-opacity-50 z-40 flex items-center justify-center" role="dialog"
aria-modal="true" aria-labelledby="cart-title">
<div class="bg-white w-full max-w-md rounded shadow-lg p-6 z-50 relative">
<h2 id="cart-title" class="text-xl font-bold mb-4">Keranjang</h2>
@if (empty($cartItems)) @if (empty($cartItems))
<p class="text-gray-500">Keranjang kosong.</p> <p class="text-gray-500 text-center py-10">Keranjang kosong.</p>
@else @else
<ul class="divide-y divide-gray-200 mb-4 max-h-72 overflow-y-auto" tabindex="0"> {{-- Di dalam modal keranjang, di bagian 'Pilih Meja Anda' --}}
<div class="p-4 bg-white rounded-lg shadow-md mb-6">
<h3 class="text-xl font-semibold mb-4 text-gray-800">Pilih Meja Anda</h3>
{{-- Checkbox untuk memilih jenis pemesanan meja --}}
<div class="mb-4 flex items-center">
<input type="checkbox" id="is-existing-customer" wire:model.live="isExistingCustomer"
class="mr-2 form-checkbox h-5 w-5 text-blue-600">
<label for="is-existing-customer" class="text-gray-700 text-sm font-bold">Saya sudah memesan
meja.</label>
</div>
{{-- Kondisional untuk menampilkan dropdown meja tersedia atau meja terisi --}}
@if (!$isExistingCustomer)
{{-- Dropdown untuk Meja Tersedia --}}
<div class="mb-4">
<label for="available-table-select" class="block text-gray-700 text-sm font-bold mb-2">Meja
Tersedia:</label>
<select id="available-table-select" wire:model.live="selectedTableId"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
{{ $tableIdFromUrl && $selectedOccupiedTableId === null ? 'disabled' : '' }}>
<option value="">-- Pilih Meja Tersedia --</option>
@foreach ($availableTables as $table)
<option value="{{ $table['id'] }}">
{{ $table['device_id'] }} || TERSEDIA
</option>
@endforeach
</select>
@error('selectedTableId')
<span class="text-red-500 text-sm mt-1">{{ $message }}</span>
@enderror
</div>
@else
{{-- Dropdown untuk Meja Sudah Terisi (dengan Nama Pemesan) --}}
@if (!empty($occupiedTables))
<div class="mb-4">
<label for="occupied-table-select"
class="block text-gray-700 text-sm font-bold mb-2">Meja Sudah Terisi (Konfirmasi
Nama):</label>
<select id="occupied-table-select" wire:model.live="selectedOccupiedTableId"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
{{ $tableIdFromUrl && $selectedTableId === null ? 'disabled' : '' }}>
<option value="">-- Pilih Meja Terisi --</option>
@foreach ($occupiedTables as $table)
<option value="{{ $table['id'] }}">
{{ $table['device_id'] }} || Oleh: {{ $table['reserved_by'] }}
</option>
@endforeach
</select>
@error('selectedOccupiedTableId')
<span class="text-red-500 text-sm mt-1">{{ $message }}</span>
@enderror
</div>
@else
<p class="text-gray-600 text-sm">Tidak ada meja yang saat ini terisi dengan nama pemesan.
</p>
@endif
@endif
{{-- Input Nama Pemesan (TERBARU: Hanya muncul jika showCustomerNameInput TRUE) --}}
@if ($showCustomerNameInput)
<div class="mb-4">
<label for="customer-name-input" class="block text-gray-700 text-sm font-bold mb-2">Nama
Pemesan:</label>
<input type="text" id="customer-name-input" wire:model.live="inputCustomerName"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="Masukkan nama Anda" {{ $isCustomerNameInputDisabled ? 'disabled' : '' }}>
@error('inputCustomerName')
<span class="text-red-500 text-sm mt-1">{{ $message }}</span>
@enderror
</div>
@endif
{{-- Tombol Lihat Denah Meja --}}
<div class="mb-4">
<button wire:click="showMap"
class="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Lihat Denah Meja
</button>
</div>
</div>
{{-- Daftar Item Keranjang --}}
<ul class="divide-y divide-gray-200 mb-4" tabindex="0">
@foreach ($cartItems as $item) @foreach ($cartItems as $item)
<li class="py-2 flex justify-between items-center"> <li class="py-3 flex justify-between items-center">
<div> <div>
<p class="font-medium">{{ $item['name'] }}</p> <p class="font-medium text-gray-800">{{ $item['name'] }}</p>
<p class="text-sm text-gray-500">Rp <p class="text-sm text-gray-600">Rp
{{ number_format($item['price'], 0, ',', '.') }}</p> {{ number_format($item['price'], 0, ',', '.') }}</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button wire:click="removeFromCart({{ $item['id'] }})" type="button" <button wire:click="removeFromCart({{ $item['id'] }})" type="button"
class="px-2 py-1 bg-red-100 text-red-700 rounded"> class="px-2 py-1 bg-red-100 text-red-700 rounded hover:bg-red-200">
- -
</button> </button>
<span class="mx-2">{{ $item['qty'] }}</span> <span class="mx-1 text-gray-800 font-semibold">{{ $item['qty'] }}</span>
<button wire:click="addToCart({{ $item['id'] }})" type="button" <button wire:click="addToCart({{ $item['id'] }})" type="button"
class="px-2 py-1 bg-green-100 text-green-700 rounded"> class="px-2 py-1 bg-green-100 text-green-700 rounded hover:bg-green-200">
+ +
</button> </button>
</div> </div>
</li> </li>
@endforeach @endforeach
</ul> </ul>
<div class="font-bold mb-4 text-right text-lg">Total: Rp @endif
</div>
{{-- Footer Keranjang (Total dan Tombol Pembayaran) --}}
<div class="p-6 border-t border-gray-200 bg-white">
<div class="mb-4 text-right text-xl text-gray-900">Total: Rp
{{ number_format($cartTotal, 0, ',', '.') }}</div> {{ number_format($cartTotal, 0, ',', '.') }}</div>
{{-- Input Email (jika showEmailInput true) --}}
@if ($showEmailInput)
<div class="mt-4 mb-4">
<label for="customerEmail" class="block text-gray-700 text-sm font-bold mb-2">Email Anda:</label>
<input type="email" id="customerEmail" wire:model.defer="customerEmail"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="nama@contoh.com">
@error('customerEmail')
<span class="text-red-500 text-sm mt-1">{{ $message }}</span>
@enderror
</div>
@endif
{{-- Tombol Pembayaran --}}
<div class="flex flex-col space-y-3 mt-4">
<button wire:click="checkout" <button wire:click="checkout"
class="w-full bg-green-600 hover:bg-green-700 text-white py-2 rounded mb-2 focus:outline-none focus:ring-2 focus:ring-green-500" class="bg-green-600 hover:bg-green-700 text-white py-2 rounded focus:outline-none focus:ring-2 focus:ring-green-500 font-semibold text-lg"
type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50"> type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50">
<span wire:loading.remove>Checkout</span> <span wire:loading.remove wire:target="checkout">Bayar Sekarang (Midtrans)</span>
<span wire:loading>Processing...</span> <span wire:loading wire:target="checkout">Processing...</span>
</button>
{{-- Menggunakan $showEmailInput untuk toggle form, bukan tombol terpisah --}}
@if (!$showEmailInput && empty(auth()->user()->email))
<button wire:click="$toggle('showEmailInput')"
class="bg-blue-600 hover:bg-blue-700 text-white py-2 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 font-semibold text-lg"
type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50">
<span wire:loading.remove wire:target="$toggle('showEmailInput')">Bayar Nanti (Kirim Email
Receipt)</span>
<span wire:loading wire:target="$toggle('showEmailInput')">Processing...</span>
</button>
@elseif($showEmailInput)
<button wire:click="sendReceiptEmail"
class="bg-indigo-500 hover:bg-indigo-600 text-white font-bold py-2 px-4 rounded-lg"
type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50">
<span wire:loading.remove wire:target="sendReceiptEmail">Kirim Email & Pesan</span>
<span wire:loading wire:target="sendReceiptEmail">Mengirim...</span>
</button>
<button wire:click="$toggle('showEmailInput')"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded-lg"
type="button">
Batal
</button> </button>
@endif @endif
<button wire:click="closeCart"
<button wire:click="skipPayment"
class="bg-gray-600 hover:bg-gray-700 text-white py-2 rounded focus:outline-none focus:ring-2 focus:ring-gray-500 font-semibold text-lg"
type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50">
<span wire:loading.remove wire:target="skipPayment">Lewati Pembayaran</span>
<span wire:loading wire:target="skipPayment">Processing...</span>
</button>
</div>
</div>
</div>
{{-- Overlay untuk sidebar keranjang --}}
@if ($showCart)
<div class="fixed inset-0 bg-black bg-opacity-50 z-20" wire:click="closeCart">
{{-- Ini adalah div overlay --}}
<div class="absolute inset-0 blur-background"></div> {{-- Div blur palsu --}}
</div>
@endif
{{-- Tombol Keranjang Mengambang --}}
<div class="fixed bottom-4 right-4 z-10"> {{-- Z-index diatur lebih rendah dari sidebar dan modal --}}
<button wire:click="openCart"
class="bg-green-600 text-white rounded-full py-3 px-6 shadow-lg hover:bg-green-700 transition flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
<span class="font-semibold text-xl">Keranjang ({{ array_sum($cart) }})</span>
</button>
</div>
{{-- Modal Denah Meja --}}
@if ($showMapModal)
<div class="fixed inset-0 bg-black bg-opacity-75 z-[60] flex items-center justify-center" role="dialog"
aria-modal="true" aria-labelledby="map-title">
<div class="bg-white w-full max-w-2xl lg:max-w-4xl xl:max-w-5xl rounded shadow-lg p-6 relative">
<h2 id="map-title" class="text-xl font-bold mb-4">Denah Meja Cafe</h2>
<div class="relative w-full overflow-hidden border border-gray-300 rounded-lg"
style="max-height: 80vh;">
<img src="{{ asset('img/denah_cafe.jpg') }}" alt="Denah Cafe"
class="w-full h-auto object-contain">
</div>
<div class="mt-6 text-right">
<button wire:click="closeMap" type="button"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded-lg">
Tutup
</button>
</div>
<button wire:click="closeMap"
class="absolute top-2 right-2 text-gray-500 hover:text-gray-700 focus:outline-none" class="absolute top-2 right-2 text-gray-500 hover:text-gray-700 focus:outline-none"
aria-label="Close cart" type="button"> aria-label="Close map" type="button">
</button> </button>
</div> </div>
@ -111,56 +304,8 @@ class="absolute top-2 right-2 text-gray-500 hover:text-gray-700 focus:outline-no
x-transition:leave="transition ease-in duration-200" x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 transform translate-y-0" x-transition:leave-start="opacity-100 transform translate-y-0"
x-transition:leave-end="opacity-0 transform translate-y-2" x-transition:leave-end="opacity-0 transform translate-y-2"
class="fixed top-8 right-8 bg-green-500 text-white px-4 py-2 rounded shadow-lg z-50"> {{-- Tambahkan z-50 --}} class="fixed bottom-8 left-1/2 -translate-x-1/2 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-50">
<p x-text="message"></p> <p x-text="message"></p>
</div> </div>
</div> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
Livewire.on('midtransSnapToken', (data) => {
console.log('Snap token received:', data.token);
if (typeof snap !== 'undefined') {
snap.pay(data.token, {
onSuccess: function(result) {
console.log('Payment success:', result);
Livewire.dispatch('paymentSuccess');
},
onPending: function(result) {
console.log('Payment pending:', result);
Livewire.dispatch('notify', {
message: 'Pembayaran belum selesai'
});
},
onError: function(result) {
console.log('Payment error:', result);
Livewire.dispatch('notify', {
message: 'Terjadi kesalahan pembayaran'
});
},
onClose: function() {
console.log('Payment closed');
Livewire.dispatch('notify', {
message: 'Pembayaran dibatalkan'
});
}
});
} else {
console.error("Midtrans Snap is not loaded. Please check your Midtrans script tag.");
Livewire.dispatch('notify', {
message: 'Payment gateway tidak tersedia'
});
}
});
// Handle URL updates from Livewire component
Livewire.on('updateUrl', (data) => {
const newUrl = data.url;
if (window.history.pushState) {
window.history.pushState({ path: newUrl }, '', newUrl);
}
});
});
</script>
</div> </div>

View File

@ -90,7 +90,7 @@ class="px-6 py-4 whitespace-nowrap text-sm font-medium text-neutral-900 dark:tex
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300"> <td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
@if ($item->image_url) @if ($item->image_url)
<img src="{{ Storage::url($item->image_url) }}" alt="{{ $item->name }}" <img src="{{ asset($item->image_url) }}" alt="{{ $item->name }}"
class="h-10 w-10 object-cover rounded"> class="h-10 w-10 object-cover rounded">
@else @else
<span class="text-neutral-400">Tidak ada gambar</span> <span class="text-neutral-400">Tidak ada gambar</span>
@ -190,7 +190,7 @@ class="mt-1 block w-full text-sm text-neutral-700 dark:text-neutral-200 file:mr-
<img src="{{ $new_image->temporaryUrl() }}" class="mt-2 h-20 w-20 object-cover rounded"> <img src="{{ $new_image->temporaryUrl() }}" class="mt-2 h-20 w-20 object-cover rounded">
@elseif ($image_url) @elseif ($image_url)
<div class="mt-2 text-sm text-neutral-500 dark:text-neutral-300">Gambar Saat Ini:</div> <div class="mt-2 text-sm text-neutral-500 dark:text-neutral-300">Gambar Saat Ini:</div>
<img src="{{ Storage::url($image_url) }}" class="mt-2 h-20 w-20 object-cover rounded"> <img src="{{ asset($image_url) }}" class="mt-2 h-20 w-20 object-cover rounded">
@endif @endif
</div> </div>

View File

@ -0,0 +1,38 @@
<div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-4">Pilih Meja Anda</h1>
@if (session()->has('message'))
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert">
<span class="block sm:inline">{{ session('message') }}</span>
</div>
@endif
{{-- Contoh denah, ganti dengan gambar denah Anda --}}
<div class="relative mb-8">
<img src="{{ asset('images/denah-contoh.png') }}" alt="Denah Restoran" class="w-full h-auto rounded-lg shadow-lg">
{{-- Anda bisa menempatkan tombol meja di atas gambar ini menggunakan posisi absolut --}}
{{-- Contoh penempatan meja (ini hanya placeholder, sesuaikan koordinatnya) --}}
@foreach ($availableTables as $table)
<button wire:click="selectTable('{{ $table['id'] }}')"
class="absolute bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-full shadow-lg"
style="top: {{ rand(10, 80) }}%; left: {{ rand(10, 80) }}%;"> {{-- Ini random, harus diganti dengan koordinat tetap --}}
{{ $table['name'] }}
</button>
@endforeach
</div>
<h2 class="text-xl font-semibold mb-3">Daftar Meja Tersedia:</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@forelse ($availableTables as $table)
<div class="bg-white p-4 rounded-lg shadow-md flex justify-between items-center">
<span class="text-lg font-medium">{{ $table['name'] }}</span>
<button wire:click="selectTable('{{ $table['id'] }}')"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Pilih
</button>
</div>
@empty
<p class="text-gray-500 col-span-full">Tidak ada meja yang tersedia saat ini.</p>
@endforelse
</div>
</div>

View File

@ -33,6 +33,10 @@ class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:t
<table class="min-w-full divide-y divide-neutral-200 dark:divide-neutral-700"> <table class="min-w-full divide-y divide-neutral-200 dark:divide-neutral-700">
<thead class="bg-neutral-50 dark:bg-neutral-800"> <thead class="bg-neutral-50 dark:bg-neutral-800">
<tr> <tr>
{{-- BARU: Kolom Gambar --}}
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider">
Gambar
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider cursor-pointer" <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider cursor-pointer"
wire:click="sortBy('name')"> wire:click="sortBy('name')">
Nama Tipe Nama Tipe
@ -63,11 +67,20 @@ class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:t
<tbody class="bg-white dark:bg-neutral-900 divide-y divide-neutral-200 dark:divide-neutral-700"> <tbody class="bg-white dark:bg-neutral-900 divide-y divide-neutral-200 dark:divide-neutral-700">
@forelse ($typeItems as $typeItem) @forelse ($typeItems as $typeItem)
<tr> <tr>
{{-- BARU: Data Kolom Gambar --}}
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
@if ($typeItem->image_url)
{{-- UBAH INI: Gunakan asset() --}}
<img src="{{ asset($typeItem->image_url) }}" alt="{{ $typeItem->name }}" class="h-10 w-10 object-cover rounded">
@else
<span class="text-neutral-400">Tidak ada gambar</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-neutral-900 dark:text-neutral-100"> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-neutral-900 dark:text-neutral-100">
{{ $typeItem->name }} {{ $typeItem->name }}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300"> <td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
{{ $typeItem->description ?? '-' }} {{-- Tampilkan '-' jika deskripsi kosong --}} {{ $typeItem->description ?? '-' }}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button wire:click="edit({{ $typeItem->id }})" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-500 mr-2">Edit</button> <button wire:click="edit({{ $typeItem->id }})" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-500 mr-2">Edit</button>
@ -76,7 +89,7 @@ class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:t
</tr> </tr>
@empty @empty
<tr> <tr>
<td colspan="3" class="px-6 py-4 text-center text-sm text-neutral-500 dark:text-neutral-300"> <td colspan="4" class="px-6 py-4 text-center text-sm text-neutral-500 dark:text-neutral-300"> {{-- UBAH colspan menjadi 4 --}}
Tidak ada data tipe item yang ditemukan. Tidak ada data tipe item yang ditemukan.
</td> </td>
</tr> </tr>
@ -111,6 +124,28 @@ class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 s
class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100"></textarea> class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100"></textarea>
@error('description') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror @error('description') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div> </div>
{{-- BARU: Input Gambar --}}
<div class="mb-4">
<label for="new_image" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Gambar Tipe Item</label>
{{-- Gunakan wire:model="new_image" untuk file upload --}}
<input type="file" id="new_image" wire:model="new_image"
class="mt-1 block w-full text-sm text-neutral-700 dark:text-neutral-200 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 dark:file:bg-neutral-700 dark:file:text-neutral-200 dark:hover:file:bg-neutral-600">
@error('new_image') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
{{-- Pratinjau gambar baru yang akan diupload --}}
@if ($new_image)
<div class="mt-2 text-sm text-neutral-500 dark:text-neutral-300">Pratinjau Gambar Baru:</div>
<img src="{{ $new_image->temporaryUrl() }}" class="mt-2 h-20 w-20 object-cover rounded">
{{-- Pratinjau gambar lama jika sedang mode edit dan belum ada gambar baru --}}
@elseif ($image_url)
<div class="mt-2 text-sm text-neutral-500 dark:text-neutral-300">Gambar Saat Ini:</div>
{{-- UBAH INI: Gunakan asset() --}}
<img src="{{ asset($image_url) }}" class="mt-2 h-20 w-20 object-cover rounded">
@endif
</div>
{{-- END BARU: Input Gambar --}}
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button type="button" wire:click="closeModal()" <button type="button" wire:click="closeModal()"
class="inline-flex justify-center rounded-md border border-neutral-300 dark:border-neutral-700 shadow-sm px-4 py-2 bg-white dark:bg-neutral-700 text-base font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> class="inline-flex justify-center rounded-md border border-neutral-300 dark:border-neutral-700 shadow-sm px-4 py-2 bg-white dark:bg-neutral-700 text-base font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">

View File

@ -0,0 +1,25 @@
@component('mail::message')
# Pesanan Anda Telah Diterima!
Terima kasih telah berbelanja di DFOOD. Berikut adalah detail pesanan Anda:
@if($meja)
{{-- Mengakses sebagai array. Hanya device_id yang tersedia. --}}
**Meja yang dipilih:** {{ $meja['device_id'] ?? $meja['id'] ?? 'N/A' }}
@endif
@component('mail::table')
| Item | Qty | Harga Satuan | Total Harga |
| :------------- | :-- | :----------- | :---------- |
@foreach($cartItems as $item)
| {{ $item['name'] }} | {{ $item['qty'] }} | Rp {{ number_format($item['price'], 0, ',', '.') }} | Rp {{ number_format($item['total_price'], 0, ',', '.') }} |
@endforeach
@endcomponent
### Total Pembayaran: Rp {{ number_format($cartTotal, 0, ',', '.') }}
Jika Anda memiliki pertanyaan, jangan ragu untuk menghubungi kami.
Terima kasih,<br>
{{ config('app.name') }}
@endcomponent

View File

@ -4,45 +4,23 @@
use App\Livewire\Settings\Password; use App\Livewire\Settings\Password;
use App\Livewire\Settings\Profile; use App\Livewire\Settings\Profile;
use App\Livewire\FoodOrder; use App\Livewire\FoodOrder;
use App\Livewire\SelectTable;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TypeItemsController; use App\Http\Controllers\TypeItemsController;
use App\Http\Controllers\ItemsController; use App\Http\Controllers\ItemsController;
use App\Http\Controllers\MejaController; use App\Http\Controllers\MejaController;
use App\Http\Controllers\BundlesController; use App\Http\Controllers\BundlesController;
// Route::get('/', function () {
// return view('welcome');
// })->name('home');
Route::get('/', FoodOrder::class)->name('food.order');
// Route::get('/menu/makanan', function() {
// return app(FoodOrder::class, ['type' => 'makanan']);
// })->name('menu.makanan');
// Route::get('/menu/minuman', function() {
// return app(FoodOrder::class, ['type' => 'minuman']);
// })->name('menu.minuman');
// Route::get('/menu/snack', function() {
// return app(FoodOrder::class, ['type' => 'snack']);
// })->name('menu.snack');
// Route::get('/menu/dessert', function() {
// return app(FoodOrder::class, ['type' => 'dessert']);
// })->name('menu.dessert');
Route::get('/refresh-csrf', function () { Route::get('/refresh-csrf', function () {
return response()->json(['csrf_token' => csrf_token()]); return response()->json(['csrf_token' => csrf_token()]);
}); });
// Route::get('/', FoodOrder::class)->name('menu.all');
Route::redirect('/', '/menu', 302);
Route::get('/menu/{typeSlug?}', FoodOrder::class)->name('menu.byType');
Route::get('/pilih-meja', SelectTable::class)->name('select.table');
Route::view('dashboard', 'dashboard')->middleware(['auth', 'verified'])->name('dashboard');
Route::view('dashboard', 'dashboard')
->middleware(['auth', 'verified'])
->name('dashboard');
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::redirect('settings', 'settings/profile'); Route::redirect('settings', 'settings/profile');
@ -59,5 +37,4 @@
Route::get('settings/appearance', Appearance::class)->name('settings.appearance'); Route::get('settings/appearance', Appearance::class)->name('settings.appearance');
}); });
require __DIR__.'/auth.php'; require __DIR__.'/auth.php';

View File

@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "meja-iotv0",
"private_key_id": "5eeb8a374164a1dd206e77961a344295d3e9f09c",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQJ5NLDmqcKdVm\nGBaIEalPyEIHE2CXPCxnWCAncBcBHAmcfMFS09p6EDdkrevI6qo2fysla/J9lTJR\ny5XcjAOYCSQGVwrq0ImYd+bPzSJMwfXuiAIqF/f6kOQ0Uv5DKewLRAxdvOaQ+ZZH\n+HTYUeZXcGph7cDxA0J+3JCzSqnezQsjCCB9paC8us3/kbwo9OHJCxIVGQUNFVl1\nTl3eJv3AZOzr9saP3YYDgV2E652c9OWLaLqc2adS4YzIo0kkHXumSo2/oGLbF5kA\nG+NYUY6tFWb7wSUEW27p+zImXLBmCMnYVp2A0IU6Sst1PKSVWVYZa4iUw5xJjPFG\nigEFDZB/AgMBAAECggEAW0dvhPNudsq1hM69Wq/8Ajt8EORDcC53/l2bxA+YVuk3\nocLnd+3lGa/wypqmEfYt7DX58rASp1V8une/YPaZQNuJS6px5CTMoeq6cZewGd7Y\nfOBxqonWOa++Et/uFf/VPd96WMTUivCYITln69CGZQOr8wOynz3htAX/AlCUi1mt\nrKQbirmX8o68GB0oA921u8KdFVLoIx+TzEGuXxiurwILmJtsJhjuTYzd7TAidZTO\na0Ns3p5Al6lsnd017pff1njHxYTuNRNfnM3PEEQvFxmjATVQQWyMjjjGx6u/La42\nNBG19jlRVS7RWXzctKpkZUEpUzjhLYG78CZ2KWSPWQKBgQD/n1kJg/IACBAKzWya\ngqv/4W1DhkTU/DaBIy+a1ik03GkIQcb45q+HatxHg6+8fxhlx4FtXyS7T+7vdL8b\n4+KECOfqyjsIJ3ML1Ni5hJw214SoDDwgVh5J2HFAoCvGuhM8UuYCNyx/H9wo7KRS\nzqa5sbjs0SGlZvwN7px0Mg7SMwKBgQDQdkebUcV9oCoKI3sLrIXHiJpsXkgb87Dz\n9EE/QHmWMLxQLPPSGegXf4FzYBwgWK+PODamnLc8bnbUdIou+xJ+1yD3ZsSAH4dB\nsSatdMtFVI6LcPba8zstJl8CD9jKKULYsPkL4dzlGHZTko+WjqGkIZj8Dbm+mjWE\nah93ZLg0hQKBgHnnbjjBeelV7wCX0tEkvA39XXQ0ViMUXJHdS9iVhXU2H8jWRaNx\nsAfuiDp6rJZCLkpriGe2VfgWOKZtkK4mUWISNTedVeWfmpfr26ctfKoSwaq6mvn4\n19zzoZ7mQ7o0BvRfvF0WHtxJP18s63iqIIyyGrSlT1xn7pBeWLJ5JORNAoGBAIgw\nvhd7FMs+GDkHdRvrWntBg6HI9tNMIg8/RGFOfiDhNiYrOB/LTRYM90WpRLbdOdUX\npJy1RgD1Ki0i9iQ7Y5n6ck8vlLEf5/3C/sHTy/k/hOEUrDHfNooIx4lRURDCZCmt\ndQeOSAJkOOnglm6GQJQ6XvhM/0zG8q/PpT8bTj0BAoGBAKAMiVU2fHRzi7Kw2Mmi\n+e5J+4kgoMyA8mY5hguhxiSFumS+sENtcOX5T4KLFntRrjXyZ070dvBwfLzyPXd/\nImH9lhvfpPSLhGvreUeftwMWMm0GjvQQFBueTzoaHA21cVD4MXQSBbknfEuuRtVr\nxSceo6sepT8jRDzII0A015H4\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@meja-iotv0.iam.gserviceaccount.com",
"client_id": "112854400819780721785",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40meja-iotv0.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,19 +0,0 @@
<?php
namespace Facades\Livewire\Features\SupportFileUploads;
use Illuminate\Support\Facades\Facade;
/**
* @mixin \Livewire\Features\SupportFileUploads\GenerateSignedUploadUrl
*/
class GenerateSignedUploadUrl extends Facade
{
/**
* Get the registered name of the component.
*/
protected static function getFacadeAccessor(): string
{
return 'Livewire\Features\SupportFileUploads\GenerateSignedUploadUrl';
}
}

View File

@ -1,5 +0,0 @@
<?php $__env->startSection('title', __('Forbidden')); ?>
<?php $__env->startSection('code', '403'); ?>
<?php $__env->startSection('message', __($exception->getMessage() ?: 'Forbidden')); ?>
<?php echo $__env->make('errors::minimal', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH E:\!PROJECT\dfood-website\vendor\laravel\framework\src\Illuminate\Foundation\Exceptions/views/403.blade.php ENDPATH**/ ?>

View File

@ -1,101 +1,324 @@
<div class="container mx-auto px-4 py-8 main-content-scroll"> <div class="flex h-screen bg-gray-100 font-sans">
<div class="flex flex-col sm:flex-row justify-between items-center mb-8 gap-4">
<h1 class="text-3xl font-bold text-gray-800"><?php echo e($pageTitle); ?></h1>
<div class="w-full sm:w-auto"> <div class="w-64 bg-white shadow-lg overflow-y-auto p-4 z-20">
<label for="typeFilter" class="sr-only">Filter by Category</label> <h1 class="text-5xl font-bold mb-6 text-center text-gray-800">DFOOD</h1>
<select id="typeFilter" wire:model.live="typeItemId" wire:change="filterByType($event.target.value)" <nav>
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring focus:ring-green-500 focus:ring-opacity-50"> <ul class="space-y-4">
<option value="">Semua Menu</option>
<!--[if BLOCK]><![endif]--><?php $__currentLoopData = $allTypeItems; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $type): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> <!--[if BLOCK]><![endif]--><?php $__currentLoopData = $allTypeItems; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $type): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<option value="<?php echo e($type->id); ?>"><?php echo e($type->name); ?></option> <li class="mb-2">
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]--> <a href="#" wire:click.prevent="filterByType(<?php echo e($type->id); ?>)"
</select> class="block rounded overflow-hidden shadow-md transition-all duration-200
<?php echo e($typeItemId == $type->id ? 'ring-2 ring-green-600 ring-offset-2' : 'hover:scale-105'); ?>">
<?php
$imageUrl = $type->image_url;
if (!filter_var($imageUrl, FILTER_VALIDATE_URL)) {
$imageUrl = asset($imageUrl);
}
?>
<img src="<?php echo e($imageUrl); ?>" class="w-full h-32 object-cover" alt="<?php echo e($type->name); ?>"
loading="lazy" decoding="async">
<div
class="text-center py-3 border-t border-gray-200 text-gray-800 font-medium
<?php echo e($typeItemId == $type->id ? 'bg-green-600 text-white' : 'bg-gray-50'); ?>">
<?php echo e($type->name); ?>
</div> </div>
</a>
</li>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]-->
</ul>
</nav>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mb-8"> <div class="flex-1 overflow-y-auto p-6">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<!--[if BLOCK]><![endif]--><?php $__empty_1 = true; $__currentLoopData = $foodItems; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?> <!--[if BLOCK]><![endif]--><?php $__empty_1 = true; $__currentLoopData = $foodItems; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<div wire:click="addToCart(<?php echo e($item->id); ?>)" <div wire:click="addToCart(<?php echo e($item->id); ?>)"
class="bg-white cursor-pointer shadow rounded-lg transition transform hover:scale-105 active:scale-95 active:ring-2 active:ring-green-400"> class="bg-white cursor-pointer shadow rounded-lg overflow-hidden
transition transform hover:scale-105 active:scale-95
active:ring-2 active:ring-green-400 flex flex-col">
<!--[if BLOCK]><![endif]--><?php if(isset($item->image_url) && $item->image_url): ?> <!--[if BLOCK]><![endif]--><?php if(isset($item->image_url) && $item->image_url): ?>
<img src="<?php echo e($item->image_url); ?>" alt="<?php echo e($item->name); ?>" class="w-full h-40 object-cover" <img src="<?php echo e(asset($item->image_url)); ?>" alt="<?php echo e($item->name); ?>"
loading="lazy" decoding="async" /> class="w-full h-48 object-cover object-center" loading="lazy" decoding="async" />
<?php else: ?> <?php else: ?>
<img src="<?php echo e(asset('img/placeholder-food.jpg')); ?>" alt="<?php echo e($item->name); ?>" <img src="<?php echo e(asset('img/placeholder-food.jpg')); ?>" alt="<?php echo e($item->name); ?>"
class="w-full h-40 object-cover" loading="lazy" decoding="async" /> class="w-full h-48 object-cover object-center" loading="lazy" decoding="async" />
<?php endif; ?><!--[if ENDBLOCK]><![endif]--> <?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<div class="p-4 flex flex-col flex-grow"> <div class="p-4 flex flex-col flex-grow">
<h2 class="text-lg font-semibold leading-tight"><?php echo e($item->name); ?></h2> <h2 class="text-xl font-semibold leading-tight text-gray-800 mb-2"><?php echo e($item->name); ?></h2>
<p class="text-gray-500 mb-3">Rp <?php echo e(number_format($item->price, 0, ',', '.')); ?></p> <p class="text-gray-600 text-lg">Rp <?php echo e(number_format($item->price, 0, ',', '.')); ?></p>
</div> </div>
</div> </div>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?> <?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<div class="col-span-full text-center py-10"> <div class="col-span-full text-center py-10 bg-white rounded-lg shadow">
<p class="text-gray-500 text-xl">Tidak ada menu tersedia untuk kategori ini.</p> <p class="text-gray-500 text-xl">Tidak ada menu tersedia untuk kategori ini.</p>
</div> </div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]--> <?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</div> </div>
</div>
<div class="fixed bottom-4 right-4 z-30">
<button wire:click="openCart" <div
class="bg-green-600 text-white rounded-full p-4 shadow-lg hover:bg-green-700 transition flex items-center gap-2"> class="fixed top-0 right-0 h-full w-96 bg-white shadow-xl z-30 flex flex-col transform
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" transition-transform duration-300 ease-in-out
stroke="currentColor"> <?php echo e($showCart ? 'translate-x-0' : 'translate-x-full'); ?>">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <div class="p-6 border-b border-gray-200 flex justify-between items-center">
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /> <h2 id="cart-title" class="text-2xl font-bold text-gray-800">Keranjang Anda</h2>
<button wire:click="closeCart" class="text-gray-500 hover:text-gray-700 focus:outline-none"
aria-label="Close cart" type="button">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
<span class="font-medium"><?php echo e(array_sum($cart)); ?></span>
</button> </button>
</div> </div>
<!--[if BLOCK]><![endif]--><?php if($showCart): ?> <div class="flex-1 overflow-y-auto p-6">
<div class="fixed inset-0 bg-black bg-opacity-50 z-40 flex items-center justify-center" role="dialog"
aria-modal="true" aria-labelledby="cart-title">
<div class="bg-white w-full max-w-md rounded shadow-lg p-6 z-50 relative">
<h2 id="cart-title" class="text-xl font-bold mb-4">Keranjang</h2>
<!--[if BLOCK]><![endif]--><?php if(empty($cartItems)): ?> <!--[if BLOCK]><![endif]--><?php if(empty($cartItems)): ?>
<p class="text-gray-500">Keranjang kosong.</p> <p class="text-gray-500 text-center py-10">Keranjang kosong.</p>
<?php else: ?> <?php else: ?>
<ul class="divide-y divide-gray-200 mb-4 max-h-72 overflow-y-auto" tabindex="0">
<div class="p-4 bg-white rounded-lg shadow-md mb-6">
<h3 class="text-xl font-semibold mb-4 text-gray-800">Pilih Meja Anda</h3>
<div class="mb-4 flex items-center">
<input type="checkbox" id="is-existing-customer" wire:model.live="isExistingCustomer"
class="mr-2 form-checkbox h-5 w-5 text-blue-600">
<label for="is-existing-customer" class="text-gray-700 text-sm font-bold">Saya sudah memesan
meja.</label>
</div>
<!--[if BLOCK]><![endif]--><?php if(!$isExistingCustomer): ?>
<div class="mb-4">
<label for="available-table-select" class="block text-gray-700 text-sm font-bold mb-2">Meja
Tersedia:</label>
<select id="available-table-select" wire:model.live="selectedTableId"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
<?php echo e($tableIdFromUrl && $selectedOccupiedTableId === null ? 'disabled' : ''); ?>>
<option value="">-- Pilih Meja Tersedia --</option>
<!--[if BLOCK]><![endif]--><?php $__currentLoopData = $availableTables; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $table): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<option value="<?php echo e($table['id']); ?>">
<?php echo e($table['device_id']); ?> || TERSEDIA
</option>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]-->
</select>
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['selectedTableId'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-sm mt-1"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<?php else: ?>
<!--[if BLOCK]><![endif]--><?php if(!empty($occupiedTables)): ?>
<div class="mb-4">
<label for="occupied-table-select"
class="block text-gray-700 text-sm font-bold mb-2">Meja Sudah Terisi (Konfirmasi
Nama):</label>
<select id="occupied-table-select" wire:model.live="selectedOccupiedTableId"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
<?php echo e($tableIdFromUrl && $selectedTableId === null ? 'disabled' : ''); ?>>
<option value="">-- Pilih Meja Terisi --</option>
<!--[if BLOCK]><![endif]--><?php $__currentLoopData = $occupiedTables; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $table): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<option value="<?php echo e($table['id']); ?>">
<?php echo e($table['device_id']); ?> || Oleh: <?php echo e($table['reserved_by']); ?>
</option>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]-->
</select>
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['selectedOccupiedTableId'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-sm mt-1"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<?php else: ?>
<p class="text-gray-600 text-sm">Tidak ada meja yang saat ini terisi dengan nama pemesan.
</p>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<!--[if BLOCK]><![endif]--><?php if($showCustomerNameInput): ?>
<div class="mb-4">
<label for="customer-name-input" class="block text-gray-700 text-sm font-bold mb-2">Nama
Pemesan:</label>
<input type="text" id="customer-name-input" wire:model.live="inputCustomerName"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="Masukkan nama Anda" <?php echo e($isCustomerNameInputDisabled ? 'disabled' : ''); ?>>
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['inputCustomerName'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-sm mt-1"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<div class="mb-4">
<button wire:click="showMap"
class="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Lihat Denah Meja
</button>
</div>
</div>
<ul class="divide-y divide-gray-200 mb-4" tabindex="0">
<!--[if BLOCK]><![endif]--><?php $__currentLoopData = $cartItems; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?> <!--[if BLOCK]><![endif]--><?php $__currentLoopData = $cartItems; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<li class="py-2 flex justify-between items-center"> <li class="py-3 flex justify-between items-center">
<div> <div>
<p class="font-medium"><?php echo e($item['name']); ?></p> <p class="font-medium text-gray-800"><?php echo e($item['name']); ?></p>
<p class="text-sm text-gray-500">Rp <p class="text-sm text-gray-600">Rp
<?php echo e(number_format($item['price'], 0, ',', '.')); ?></p> <?php echo e(number_format($item['price'], 0, ',', '.')); ?></p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button wire:click="removeFromCart(<?php echo e($item['id']); ?>)" type="button" <button wire:click="removeFromCart(<?php echo e($item['id']); ?>)" type="button"
class="px-2 py-1 bg-red-100 text-red-700 rounded"> class="px-2 py-1 bg-red-100 text-red-700 rounded hover:bg-red-200">
- -
</button> </button>
<span class="mx-2"><?php echo e($item['qty']); ?></span> <span class="mx-1 text-gray-800 font-semibold"><?php echo e($item['qty']); ?></span>
<button wire:click="addToCart(<?php echo e($item['id']); ?>)" type="button" <button wire:click="addToCart(<?php echo e($item['id']); ?>)" type="button"
class="px-2 py-1 bg-green-100 text-green-700 rounded"> class="px-2 py-1 bg-green-100 text-green-700 rounded hover:bg-green-200">
+ +
</button> </button>
</div> </div>
</li> </li>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]--> <?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]-->
</ul> </ul>
<div class="font-bold mb-4 text-right text-lg">Total: Rp <?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="p-6 border-t border-gray-200 bg-white">
<div class="mb-4 text-right text-xl text-gray-900">Total: Rp
<?php echo e(number_format($cartTotal, 0, ',', '.')); ?></div> <?php echo e(number_format($cartTotal, 0, ',', '.')); ?></div>
<!--[if BLOCK]><![endif]--><?php if($showEmailInput): ?>
<div class="mt-4 mb-4">
<label for="customerEmail" class="block text-gray-700 text-sm font-bold mb-2">Email Anda:</label>
<input type="email" id="customerEmail" wire:model.defer="customerEmail"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="nama@contoh.com">
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['customerEmail'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-sm mt-1"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<div class="flex flex-col space-y-3 mt-4">
<button wire:click="checkout" <button wire:click="checkout"
class="w-full bg-green-600 hover:bg-green-700 text-white py-2 rounded mb-2 focus:outline-none focus:ring-2 focus:ring-green-500" class="bg-green-600 hover:bg-green-700 text-white py-2 rounded focus:outline-none focus:ring-2 focus:ring-green-500 font-semibold text-lg"
type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50"> type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50">
<span wire:loading.remove>Checkout</span> <span wire:loading.remove wire:target="checkout">Bayar Sekarang (Midtrans)</span>
<span wire:loading>Processing...</span> <span wire:loading wire:target="checkout">Processing...</span>
</button>
<!--[if BLOCK]><![endif]--><?php if(!$showEmailInput && empty(auth()->user()->email)): ?>
<button wire:click="$toggle('showEmailInput')"
class="bg-blue-600 hover:bg-blue-700 text-white py-2 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 font-semibold text-lg"
type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50">
<span wire:loading.remove wire:target="$toggle('showEmailInput')">Bayar Nanti (Kirim Email
Receipt)</span>
<span wire:loading wire:target="$toggle('showEmailInput')">Processing...</span>
</button>
<?php elseif($showEmailInput): ?>
<button wire:click="sendReceiptEmail"
class="bg-indigo-500 hover:bg-indigo-600 text-white font-bold py-2 px-4 rounded-lg"
type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50">
<span wire:loading.remove wire:target="sendReceiptEmail">Kirim Email & Pesan</span>
<span wire:loading wire:target="sendReceiptEmail">Mengirim...</span>
</button>
<button wire:click="$toggle('showEmailInput')"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded-lg"
type="button">
Batal
</button> </button>
<?php endif; ?><!--[if ENDBLOCK]><![endif]--> <?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<button wire:click="closeCart"
<button wire:click="skipPayment"
class="bg-gray-600 hover:bg-gray-700 text-white py-2 rounded focus:outline-none focus:ring-2 focus:ring-gray-500 font-semibold text-lg"
type="button" wire:loading.attr="disabled" wire:loading.class="opacity-50">
<span wire:loading.remove wire:target="skipPayment">Lewati Pembayaran</span>
<span wire:loading wire:target="skipPayment">Processing...</span>
</button>
</div>
</div>
</div>
<!--[if BLOCK]><![endif]--><?php if($showCart): ?>
<div class="fixed inset-0 bg-black bg-opacity-50 z-20" wire:click="closeCart">
<div class="absolute inset-0 blur-background"></div>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<div class="fixed bottom-4 right-4 z-10">
<button wire:click="openCart"
class="bg-green-600 text-white rounded-full py-3 px-6 shadow-lg hover:bg-green-700 transition flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
<span class="font-semibold text-xl">Keranjang (<?php echo e(array_sum($cart)); ?>)</span>
</button>
</div>
<!--[if BLOCK]><![endif]--><?php if($showMapModal): ?>
<div class="fixed inset-0 bg-black bg-opacity-75 z-[60] flex items-center justify-center" role="dialog"
aria-modal="true" aria-labelledby="map-title">
<div class="bg-white w-full max-w-2xl lg:max-w-4xl xl:max-w-5xl rounded shadow-lg p-6 relative">
<h2 id="map-title" class="text-xl font-bold mb-4">Denah Meja Cafe</h2>
<div class="relative w-full overflow-hidden border border-gray-300 rounded-lg"
style="max-height: 80vh;">
<img src="<?php echo e(asset('img/denah_cafe.jpg')); ?>" alt="Denah Cafe"
class="w-full h-auto object-contain">
</div>
<div class="mt-6 text-right">
<button wire:click="closeMap" type="button"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded-lg">
Tutup
</button>
</div>
<button wire:click="closeMap"
class="absolute top-2 right-2 text-gray-500 hover:text-gray-700 focus:outline-none" class="absolute top-2 right-2 text-gray-500 hover:text-gray-700 focus:outline-none"
aria-label="Close cart" type="button"> aria-label="Close map" type="button">
</button> </button>
</div> </div>
@ -111,57 +334,9 @@ class="absolute top-2 right-2 text-gray-500 hover:text-gray-700 focus:outline-no
x-transition:leave="transition ease-in duration-200" x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 transform translate-y-0" x-transition:leave-start="opacity-100 transform translate-y-0"
x-transition:leave-end="opacity-0 transform translate-y-2" x-transition:leave-end="opacity-0 transform translate-y-2"
class="fixed top-8 right-8 bg-green-500 text-white px-4 py-2 rounded shadow-lg z-50"> class="fixed bottom-8 left-1/2 -translate-x-1/2 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-50">
<p x-text="message"></p> <p x-text="message"></p>
</div> </div>
</div> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
Livewire.on('midtransSnapToken', (data) => {
console.log('Snap token received:', data.token);
if (typeof snap !== 'undefined') {
snap.pay(data.token, {
onSuccess: function(result) {
console.log('Payment success:', result);
Livewire.dispatch('paymentSuccess');
},
onPending: function(result) {
console.log('Payment pending:', result);
Livewire.dispatch('notify', {
message: 'Pembayaran belum selesai'
});
},
onError: function(result) {
console.log('Payment error:', result);
Livewire.dispatch('notify', {
message: 'Terjadi kesalahan pembayaran'
});
},
onClose: function() {
console.log('Payment closed');
Livewire.dispatch('notify', {
message: 'Pembayaran dibatalkan'
});
}
});
} else {
console.error("Midtrans Snap is not loaded. Please check your Midtrans script tag.");
Livewire.dispatch('notify', {
message: 'Payment gateway tidak tersedia'
});
}
});
// Handle URL updates from Livewire component
Livewire.on('updateUrl', (data) => {
const newUrl = data.url;
if (window.history.pushState) {
window.history.pushState({ path: newUrl }, '', newUrl);
}
});
});
</script>
</div> </div>
<?php /**PATH E:\!PROJECT\dfood-website\resources\views/livewire/food-order.blade.php ENDPATH**/ ?> <?php /**PATH E:\!PROJECT\dfood-website\resources\views/livewire/food-order.blade.php ENDPATH**/ ?>

View File

@ -0,0 +1,216 @@
<div class="flex h-full w-full flex-1 flex-col gap-4 rounded-xl">
<!--[if BLOCK]><![endif]--><?php if(session()->has('message')): ?>
<div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 3000)"
class="bg-green-500 text-white p-3 rounded-lg shadow-md mb-4">
<?php echo e(session('message')); ?>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<?php if(session()->has('error')): ?>
<div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 5000)"
class="bg-red-500 text-white p-3 rounded-lg shadow-md mb-4">
<?php echo e(session('error')); ?>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<div class="relative h-full flex-1 overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700 p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-neutral-800 dark:text-neutral-100">Daftar Tipe Item</h2>
<button wire:click="create()" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg">
Tambah Tipe Item
</button>
</div>
<div class="mb-4">
<input type="text" wire:model.live.debounce.300ms="search" placeholder="Cari tipe item..."
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-neutral-300 dark:border-neutral-700 rounded-md dark:bg-neutral-800 dark:text-neutral-200">
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-neutral-200 dark:divide-neutral-700">
<thead class="bg-neutral-50 dark:bg-neutral-800">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider">
Gambar
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider cursor-pointer"
wire:click="sortBy('name')">
Nama Tipe
<!--[if BLOCK]><![endif]--><?php if($sortBy == 'name'): ?>
<!--[if BLOCK]><![endif]--><?php if($sortDirection == 'asc'): ?>
&uarr;
<?php else: ?>
&darr;
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider cursor-pointer"
wire:click="sortBy('description')">
Deskripsi
<!--[if BLOCK]><![endif]--><?php if($sortBy == 'description'): ?>
<!--[if BLOCK]><![endif]--><?php if($sortDirection == 'asc'): ?>
&uarr;
<?php else: ?>
&darr;
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Aksi</span>
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-neutral-900 divide-y divide-neutral-200 dark:divide-neutral-700">
<!--[if BLOCK]><![endif]--><?php $__empty_1 = true; $__currentLoopData = $typeItems; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $typeItem): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
<!--[if BLOCK]><![endif]--><?php if($typeItem->image_url): ?>
<img src="<?php echo e(asset($typeItem->image_url)); ?>" alt="<?php echo e($typeItem->name); ?>" class="h-10 w-10 object-cover rounded">
<?php else: ?>
<span class="text-neutral-400">Tidak ada gambar</span>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-neutral-900 dark:text-neutral-100">
<?php echo e($typeItem->name); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
<?php echo e($typeItem->description ?? '-'); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button wire:click="edit(<?php echo e($typeItem->id); ?>)" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-500 mr-2">Edit</button>
<button wire:click="confirmDelete(<?php echo e($typeItem->id); ?>)" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-500">Hapus</button>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<tr>
<td colspan="4" class="px-6 py-4 text-center text-sm text-neutral-500 dark:text-neutral-300">
Tidak ada data tipe item yang ditemukan.
</td>
</tr>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</tbody>
</table>
</div>
<div class="mt-4">
<?php echo e($typeItems->links()); ?>
</div>
</div>
<!--[if BLOCK]><![endif]--><?php if($isModalOpen): ?>
<div class="fixed inset-0 bg-gray-600 bg-opacity-75 flex items-center justify-center z-50">
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-xl p-6 w-full max-w-lg mx-auto">
<h3 class="text-lg leading-6 font-medium text-neutral-900 dark:text-neutral-100 mb-4">
<?php echo e($typeItemId ? 'Edit Tipe Item' : 'Tambah Tipe Item Baru'); ?>
</h3>
<form wire:submit.prevent="store">
<div class="mb-4">
<label for="name" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Nama Tipe</label>
<input type="text" id="name" wire:model.lazy="name"
class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100">
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['name'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?> <span class="text-red-500 text-xs"><?php echo e($message); ?></span> <?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="mb-4">
<label for="description" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Deskripsi</label>
<textarea id="description" wire:model.lazy="description" rows="3"
class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100"></textarea>
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['description'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?> <span class="text-red-500 text-xs"><?php echo e($message); ?></span> <?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="mb-4">
<label for="new_image" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Gambar Tipe Item</label>
<input type="file" id="new_image" wire:model="new_image"
class="mt-1 block w-full text-sm text-neutral-700 dark:text-neutral-200 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 dark:file:bg-neutral-700 dark:file:text-neutral-200 dark:hover:file:bg-neutral-600">
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['new_image'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?> <span class="text-red-500 text-xs"><?php echo e($message); ?></span> <?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
<!--[if BLOCK]><![endif]--><?php if($new_image): ?>
<div class="mt-2 text-sm text-neutral-500 dark:text-neutral-300">Pratinjau Gambar Baru:</div>
<img src="<?php echo e($new_image->temporaryUrl()); ?>" class="mt-2 h-20 w-20 object-cover rounded">
<?php elseif($image_url): ?>
<div class="mt-2 text-sm text-neutral-500 dark:text-neutral-300">Gambar Saat Ini:</div>
<img src="<?php echo e(asset($image_url)); ?>" class="mt-2 h-20 w-20 object-cover rounded">
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="flex justify-end gap-2">
<button type="button" wire:click="closeModal()"
class="inline-flex justify-center rounded-md border border-neutral-300 dark:border-neutral-700 shadow-sm px-4 py-2 bg-white dark:bg-neutral-700 text-base font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Batal
</button>
<button type="submit"
class="inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm">
Simpan
</button>
</div>
</form>
</div>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<!--[if BLOCK]><![endif]--><?php if($isDeleteModalOpen): ?>
<div class="fixed inset-0 bg-gray-600 bg-opacity-75 flex items-center justify-center z-50">
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-xl p-6 w-full max-w-sm mx-auto">
<h3 class="text-lg leading-6 font-medium text-neutral-900 dark:text-neutral-100 mb-4">
Konfirmasi Hapus
</h3>
<p class="text-neutral-600 dark:text-neutral-300 mb-6">
Apakah Anda yakin ingin menghapus tipe item ini?
<br><span class="font-bold text-red-500">Peringatan: Tidak dapat dihapus jika masih ada item yang terkait!</span>
</p>
<div class="flex justify-end gap-2">
<button type="button" wire:click="closeDeleteModal()"
class="inline-flex justify-center rounded-md border border-neutral-300 dark:border-neutral-700 shadow-sm px-4 py-2 bg-white dark:bg-neutral-700 text-base font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Batal
</button>
<button type="button" wire:click="delete()"
class="inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
Hapus
</button>
</div>
</div>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</div>
<?php /**PATH E:\!PROJECT\dfood-website\resources\views/livewire/type-item-table.blade.php ENDPATH**/ ?>

View File

@ -30,7 +30,7 @@
<?php unset($__componentOriginal159d6670770cb479b1921cea6416c26c); ?> <?php unset($__componentOriginal159d6670770cb479b1921cea6416c26c); ?>
<?php endif; ?> <?php endif; ?>
</span> </span>
<?php echo e(config('app.name', 'Laravel')); ?> <?php echo e(config('app.name', 'Login')); ?>
</a> </a>
@ -107,7 +107,7 @@
<?php endif; ?> <?php endif; ?>
</span> </span>
<span class="sr-only"><?php echo e(config('app.name', 'Laravel')); ?></span> <span class="sr-only"><?php echo e(config('app.name', 'Login')); ?></span>
</a> </a>
<?php echo e($slot); ?> <?php echo e($slot); ?>

View File

@ -0,0 +1,40 @@
<div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-4">Pilih Meja Anda</h1>
<!--[if BLOCK]><![endif]--><?php if(session()->has('message')): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert">
<span class="block sm:inline"><?php echo e(session('message')); ?></span>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<div class="relative mb-8">
<img src="<?php echo e(asset('images/denah-contoh.png')); ?>" alt="Denah Restoran" class="w-full h-auto rounded-lg shadow-lg">
<!--[if BLOCK]><![endif]--><?php $__currentLoopData = $availableTables; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $table): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<button wire:click="selectTable('<?php echo e($table['id']); ?>')"
class="absolute bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-full shadow-lg"
style="top: <?php echo e(rand(10, 80)); ?>%; left: <?php echo e(rand(10, 80)); ?>%;">
<?php echo e($table['name']); ?>
</button>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<h2 class="text-xl font-semibold mb-3">Daftar Meja Tersedia:</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!--[if BLOCK]><![endif]--><?php $__empty_1 = true; $__currentLoopData = $availableTables; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $table): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<div class="bg-white p-4 rounded-lg shadow-md flex justify-between items-center">
<span class="text-lg font-medium"><?php echo e($table['name']); ?></span>
<button wire:click="selectTable('<?php echo e($table['id']); ?>')"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Pilih
</button>
</div>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<p class="text-gray-500 col-span-full">Tidak ada meja yang tersedia saat ini.</p>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</div>
</div>
<?php /**PATH E:\!PROJECT\dfood-website\resources\views/livewire/select-table.blade.php ENDPATH**/ ?>

View File

@ -0,0 +1,76 @@
<?php $attributes ??= new \Illuminate\View\ComponentAttributeBag;
$__newAttributes = [];
$__propNames = \Illuminate\View\ComponentAttributeBag::extractPropNames(([
'variant' => 'outline',
]));
foreach ($attributes->all() as $__key => $__value) {
if (in_array($__key, $__propNames)) {
$$__key = $$__key ?? $__value;
} else {
$__newAttributes[$__key] = $__value;
}
}
$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
unset($__propNames);
unset($__newAttributes);
foreach (array_filter(([
'variant' => 'outline',
]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
$$__key = $$__key ?? $__value;
}
$__defined_vars = get_defined_vars();
foreach ($attributes->all() as $__key => $__value) {
if (array_key_exists($__key, $__defined_vars)) unset($$__key);
}
unset($__defined_vars); ?>
<?php
$classes = Flux::classes('shrink-0')
->add(match($variant) {
'outline' => '[:where(&)]:size-6',
'solid' => '[:where(&)]:size-6',
'mini' => '[:where(&)]:size-5',
'micro' => '[:where(&)]:size-4',
});
?>
<?php switch ($variant): case ('outline'): ?>
<svg <?php echo e($attributes->class($classes)); ?> data-flux-icon xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"/>
</svg>
<?php break; ?>
<?php case ('solid'): ?>
<svg <?php echo e($attributes->class($classes)); ?> data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon">
<path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd"/>
</svg>
<?php break; ?>
<?php case ('mini'): ?>
<svg <?php echo e($attributes->class($classes)); ?> data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon">
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
</svg>
<?php break; ?>
<?php case ('micro'): ?>
<svg <?php echo e($attributes->class($classes)); ?> data-flux-icon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon">
<path fill-rule="evenodd" d="M6.701 2.25c.577-1 2.02-1 2.598 0l5.196 9a1.5 1.5 0 0 1-1.299 2.25H2.804a1.5 1.5 0 0 1-1.3-2.25l5.197-9ZM8 4a.75.75 0 0 1 .75.75v3a.75.75 0 1 1-1.5 0v-3A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
</svg>
<?php break; ?>
<?php endswitch; ?>
<?php /**PATH E:\!PROJECT\dfood-website\vendor\livewire\flux\src/../stubs/resources/views/flux/icon/exclamation-triangle.blade.php ENDPATH**/ ?>

View File

@ -1,110 +1,149 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="<?php echo e(str_replace('_', '-', app()->getLocale())); ?>">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="<?php echo e(csrf_token()); ?>"> <meta name="csrf-token" content="<?php echo e(csrf_token()); ?>">
<title>DFOOD</title>
<!-- Tailwind CSS --> <title><?php echo e(config('app.name', 'DFOOD')); ?></title>
<script src="https://cdn.tailwindcss.com"></script>
<?php echo app('Illuminate\Foundation\Vite')(['resources/css/app.css', 'resources/js/app.js']); ?>
<style> <script type="text/javascript" src="<?php echo e(config('services.midtrans.snap_url')); ?>"
@import url("https://fonts.googleapis.com/css2?family=Montserrat&display=swap");
body {
font-family: "Montserrat", sans-serif;
overflow: hidden;
height: 100dvh;
}
.sidebar-scroll {
overflow-y: auto;
height: 100dvh;
padding-bottom: 6em;
}
.sidebar-scroll::-webkit-scrollbar {
display: none;
}
.main-content-scroll {
overflow-y: auto;
height: 100dvh;
}
[x-cloak] {
display: none !important;
}
html {
scroll-behavior: smooth;
}
</style>
<!-- Midtrans -->
<script src="https://app.sandbox.midtrans.com/snap/snap.js"
data-client-key="<?php echo e(config('services.midtrans.client_key')); ?>"></script> data-client-key="<?php echo e(config('services.midtrans.client_key')); ?>"></script>
<?php echo \Livewire\Mechanisms\FrontendAssets\FrontendAssets::styles(); ?> <?php echo \Livewire\Mechanisms\FrontendAssets\FrontendAssets::styles(); ?>
</head> </head>
<body class="min-h-screen flex bg-gray-100"> <body class="font-sans antialiased">
<!-- Sidebar -->
<aside class="w-64 bg-white text-black min-h-screen px-4 py-6">
<div class="text-5xl text-center font-bold mb-6">DFOOD</div>
<div class="space-y-4 sidebar-scroll">
<a href="#food" class="block">
<div class="shadow rounded overflow-hidden">
<img src="img/makanan.jpg" class="w-full h-auto">
<div class="text-center py-2 border-t">Makanan</div>
</div>
</a>
<a href="#drink" class="block">
<div class="shadow rounded overflow-hidden">
<img src="img/minuman.jpg" class="w-full h-auto">
<div class="text-center py-2 border-t">Minuman</div>
</div>
</a>
</div>
</aside>
<!-- Main Content -->
<?php echo e($slot); ?> <?php echo e($slot); ?>
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<?php echo \Livewire\Mechanisms\FrontendAssets\FrontendAssets::scripts(); ?> <?php echo \Livewire\Mechanisms\FrontendAssets\FrontendAssets::scripts(); ?>
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script> <script>
document.addEventListener('livewire:load', function() { document.addEventListener('livewire:initialized', function() {
Livewire.on('notify', message => { // Livewire Event Listener untuk notifikasi umum
console.log('Notification:', message); Livewire.on('notify', (data) => {
console.log('Notification:', data.message);
}); });
Livewire.hook('message.processed', (message, component) => { // Livewire Event Listener untuk menampilkan Midtrans Snap Pop-up
if (component.fingerprint.name === 'food-order') { Livewire.on('midtransSnapToken', (event) => {
console.log('Component updated:', component.serverMemo.data.cart); const snapToken = event.token;
console.log('Received Midtrans Snap Token:', snapToken);
if (typeof Snap !== 'undefined' && snapToken) {
try {
Snap.pay(snapToken, {
onSuccess: function(result) {
console.log('Payment success:', result);
Livewire.dispatch(
'paymentSuccess'); // Inform Livewire component
},
onPending: function(result) {
console.log('Payment pending:', result);
Livewire.dispatch('notify', {
message: 'Pembayaran menunggu konfirmasi Anda.'
});
},
onError: function(result) {
console.log('Payment error:', result);
let errorMessage = 'Terjadi kesalahan pembayaran.';
if (result.status_code === '400') {
errorMessage = 'Permintaan tidak valid. Mohon coba lagi.';
} else if (result.status_code === '401') {
errorMessage =
'Autentikasi Midtrans gagal. Hubungi administrator.';
}
Livewire.dispatch('notify', {
message: errorMessage
});
},
onClose: function() {
console.log('Payment closed by user');
Livewire.dispatch('notify', {
message: 'Pembayaran dibatalkan oleh pengguna.'
});
} }
}); });
} catch (e) {
console.error("Error calling Snap.pay:", e);
Livewire.dispatch('notify', {
message: 'Terjadi kesalahan saat memulai pembayaran.'
});
}
} else {
console.error(
'Midtrans Snap.js not loaded or Snap object is undefined, or snapToken is empty.'
);
Livewire.dispatch('notify', {
message: 'Sistem pembayaran tidak siap. Mohon refresh halaman.'
});
}
}); });
// Livewire Event Listener untuk update URL di browser history
Livewire.on('updateUrl', (data) => {
const newUrl = data.url;
if (window.history.pushState) {
window.history.pushState({
path: newUrl
}, '', newUrl);
}
});
// Livewire hook untuk logging proses (hapus di production jika tidak perlu)
Livewire.hook('message.processed', (message, component) => {
if (component.fingerprint.name === 'food-order') {
console.log('FoodOrder component updated. Current cart:', component.serverMemo.data
.cart);
}
});
// Livewire Event Listener untuk menyembunyikan progress bar navigasi
Livewire.on('start-navigation', () => {
const progressBar = document.querySelector('.livewire-progress-bar');
if (progressBar) {
progressBar.style.display = 'none';
}
});
// Pindahkan setInterval untuk refresh CSRF di sini, di dalam livewire:initialized
setInterval(() => { setInterval(() => {
fetch('/refresh-csrf') fetch('/refresh-csrf')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
document.querySelector('meta[name="csrf-token"]').setAttribute('content', data.csrf_token); const csrfMeta = document.querySelector('meta[name="csrf-token"]');
if (csrfMeta) {
csrfMeta.setAttribute('content', data.csrf_token);
}
// Pastikan Livewire sudah ada sebelum mencoba mengaksesnya
if (window.Livewire && typeof window.Livewire.findComponents === 'function') {
window.Livewire.findComponents().forEach(component => { window.Livewire.findComponents().forEach(component => {
component.canonical = component.canonical || {}; if (component.canonical) {
component.canonical.csrf = data.csrf_token; component.canonical.csrf = data.csrf_token;
}
}); });
} else {
console.warn('Livewire.findComponents not available yet.');
}
console.log('CSRF token refreshed:', data.csrf_token);
})
.catch(error => {
console.error('Error refreshing CSRF token:', error);
});
}, 1800000); // 30 menit (1800000 ms)
}); });
}, 1800000);
</script> </script>
</body> </body>

View File

@ -1,345 +0,0 @@
<div class="flex h-full w-full flex-1 flex-col gap-4 rounded-xl">
<!--[if BLOCK]><![endif]--><?php if(session()->has('message')): ?>
<div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 3000)"
class="bg-green-500 text-white p-3 rounded-lg shadow-md mb-4">
<?php echo e(session('message')); ?>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<div class="relative h-full flex-1 overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700 p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-neutral-800 dark:text-neutral-100">Daftar Item</h2>
<button wire:click="create()"
class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg">
Tambah Item
</button>
</div>
<div class="mb-4">
<input type="text" wire:model.live.debounce.300ms="search" placeholder="Cari item..."
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-neutral-300 dark:border-neutral-700 rounded-md dark:bg-neutral-800 dark:text-neutral-200">
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-neutral-200 dark:divide-neutral-700">
<thead class="bg-neutral-50 dark:bg-neutral-800">
<tr>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider cursor-pointer"
wire:click="sortBy('name')">
Nama Item
<!--[if BLOCK]><![endif]--><?php if($sortBy == 'name'): ?>
<!--[if BLOCK]><![endif]--><?php if($sortDirection == 'asc'): ?>
&uarr;
<?php else: ?>
&darr;
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider">
Gambar
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider cursor-pointer"
wire:click="sortBy('price')">
Harga
<!--[if BLOCK]><![endif]--><?php if($sortBy == 'price'): ?>
<!--[if BLOCK]><![endif]--><?php if($sortDirection == 'asc'): ?>
&uarr;
<?php else: ?>
&darr;
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider cursor-pointer"
wire:click="sortBy('type_item_id')">
Tipe Item
<!--[if BLOCK]><![endif]--><?php if($sortBy == 'type_item_id'): ?>
<!--[if BLOCK]><![endif]--><?php if($sortDirection == 'asc'): ?>
&uarr;
<?php else: ?>
&darr;
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</th>
<th scope="col"
class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-300 uppercase tracking-wider cursor-pointer"
wire:click="sortBy('is_available')">
Tersedia
<!--[if BLOCK]><![endif]--><?php if($sortBy == 'is_available'): ?>
<!--[if BLOCK]><![endif]--><?php if($sortDirection == 'asc'): ?>
&uarr;
<?php else: ?>
&darr;
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Aksi</span>
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-neutral-900 divide-y divide-neutral-200 dark:divide-neutral-700">
<!--[if BLOCK]><![endif]--><?php $__empty_1 = true; $__currentLoopData = $items; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<tr>
<td
class="px-6 py-4 whitespace-nowrap text-sm font-medium text-neutral-900 dark:text-neutral-100">
<?php echo e($item->name); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
<!--[if BLOCK]><![endif]--><?php if($item->image_url): ?>
<img src="<?php echo e(Storage::url($item->image_url)); ?>" alt="<?php echo e($item->name); ?>"
class="h-10 w-10 object-cover rounded">
<?php else: ?>
<span class="text-neutral-400">Tidak ada gambar</span>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
Rp<?php echo e(number_format($item->price, 0, ',', '.')); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
<?php echo e($item->typeItem->name ?? 'N/A'); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<!--[if BLOCK]><![endif]--><?php if($item->is_available): ?>
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100">
Ya
</span>
<?php else: ?>
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100">
Tidak
</span>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button wire:click="edit(<?php echo e($item->id); ?>)"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-500 mr-2">Edit</button>
<button wire:click="confirmDelete(<?php echo e($item->id); ?>)"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-500">Hapus</button>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<tr>
<td colspan="6"
class="px-6 py-4 text-center text-sm text-neutral-500 dark:text-neutral-300">
Tidak ada data item yang ditemukan.
</td>
</tr>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</tbody>
</table>
</div>
<div class="mt-4">
<?php echo e($items->links()); ?>
</div>
</div>
<!--[if BLOCK]><![endif]--><?php if($isModalOpen): ?>
<div class="fixed inset-0 bg-gray-600 bg-opacity-75 flex items-center justify-center z-50">
<div
class="bg-white dark:bg-neutral-800 rounded-lg shadow-xl p-6 w-full max-w-lg mx-auto overflow-y-auto max-h-[90vh]">
<h3 class="text-lg leading-6 font-medium text-neutral-900 dark:text-neutral-100 mb-4">
<?php echo e($itemId ? 'Edit Item' : 'Tambah Item Baru'); ?>
</h3>
<form wire:submit.prevent="store">
<div class="mb-4">
<label for="name"
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Nama Item</label>
<input type="text" id="name" wire:model.lazy="name"
class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100">
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['name'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-xs"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="mb-4">
<label for="description"
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Deskripsi</label>
<textarea id="description" wire:model.lazy="description" rows="3"
class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100"></textarea>
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['description'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-xs"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="mb-4">
<label for="price"
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Harga</label>
<input type="number" step="0.01" id="price" wire:model.lazy="price"
class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100">
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['price'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-xs"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="mb-4">
<label for="new_image"
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Gambar Item</label>
<input type="file" id="new_image" wire:model="new_image"
class="mt-1 block w-full text-sm text-neutral-700 dark:text-neutral-200 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 dark:file:bg-neutral-700 dark:file:text-neutral-200 dark:hover:file:bg-neutral-600">
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['new_image'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-xs"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
<!--[if BLOCK]><![endif]--><?php if($new_image): ?>
<div class="mt-2 text-sm text-neutral-500 dark:text-neutral-300">Pratinjau Gambar Baru:
</div>
<img src="<?php echo e($new_image->temporaryUrl()); ?>" class="mt-2 h-20 w-20 object-cover rounded">
<?php elseif($image_url): ?>
<div class="mt-2 text-sm text-neutral-500 dark:text-neutral-300">Gambar Saat Ini:</div>
<img src="<?php echo e(Storage::url($image_url)); ?>" class="mt-2 h-20 w-20 object-cover rounded">
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="mb-4">
<label for="is_available"
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Tersedia?</label>
<input type="checkbox" id="is_available" wire:model="is_available"
class="mt-1 rounded border-neutral-300 dark:border-neutral-700 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['is_available'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-xs"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="mb-4">
<label for="type_item_id"
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Tipe Item</label>
<select id="type_item_id" wire:model.lazy="type_item_id"
class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100">
<option value="">-- Pilih Tipe Item --</option>
<!--[if BLOCK]><![endif]--><?php $__currentLoopData = $allTypeItems; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $typeItem): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<option value="<?php echo e($typeItem->id); ?>"><?php echo e($typeItem->name); ?></option>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]-->
</select>
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['type_item_id'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-xs"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="mb-4">
<label for="bundle_ids"
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Bundles</label>
<select id="bundle_ids" wire:model.live="bundle_ids" multiple
class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-neutral-700 dark:text-neutral-100 h-48">
<option value="">-- Pilih Bundles (Opsional) --</option>
<!--[if BLOCK]><![endif]--><?php $__currentLoopData = $allBundles; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $bundle): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<option value="<?php echo e($bundle->id); ?>"><?php echo e($bundle->name); ?></option>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?><!--[if ENDBLOCK]><![endif]-->
</select>
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['bundle_ids'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-xs"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
<!--[if BLOCK]><![endif]--><?php $__errorArgs = ['bundle_ids.*'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="text-red-500 text-xs"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?><!--[if ENDBLOCK]><![endif]-->
</div>
<div class="flex justify-end gap-2">
<button type="button" wire:click="closeModal()"
class="inline-flex justify-center rounded-md border border-neutral-300 dark:border-neutral-700 shadow-sm px-4 py-2 bg-white dark:bg-neutral-700 text-base font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Batal
</button>
<button type="submit"
class="inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm">
Simpan
</button>
</div>
</form>
</div>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
<!--[if BLOCK]><![endif]--><?php if($isDeleteModalOpen): ?>
<div class="fixed inset-0 bg-gray-600 bg-opacity-75 flex items-center justify-center z-50">
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-xl p-6 w-full max-w-sm mx-auto">
<h3 class="text-lg leading-6 font-medium text-neutral-900 dark:text-neutral-100 mb-4">
Konfirmasi Hapus
</h3>
<p class="text-neutral-600 dark:text-neutral-300 mb-6">
Apakah Anda yakin ingin menghapus item ini? Tindakan ini tidak dapat dibatalkan.
</p>
<div class="flex justify-end gap-2">
<button type="button" wire:click="closeDeleteModal()"
class="inline-flex justify-center rounded-md border border-neutral-300 dark:border-neutral-700 shadow-sm px-4 py-2 bg-white dark:bg-neutral-700 text-base font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Batal
</button>
<button type="button" wire:click="delete()"
class="inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
Hapus
</button>
</div>
</div>
</div>
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
</div>
<?php /**PATH E:\!PROJECT\dfood-website\resources\views/livewire/item-table.blade.php ENDPATH**/ ?>

View File

@ -0,0 +1,38 @@
<?php if (isset($component)) { $__componentOriginal5863877a5171c196453bfa0bd807e410 = $component; } ?>
<?php if (isset($attributes)) { $__attributesOriginal5863877a5171c196453bfa0bd807e410 = $attributes; } ?>
<?php $component = Illuminate\View\AnonymousComponent::resolve(['view' => 'components.layouts.app','data' => ['title' => __('Dashboard')]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
<?php $component->withName('layouts.app'); ?>
<?php if ($component->shouldRender()): ?>
<?php $__env->startComponent($component->resolveView(), $component->data()); ?>
<?php if (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag): ?>
<?php $attributes = $attributes->except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
<?php endif; ?>
<?php $component->withAttributes(['title' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(__('Dashboard'))]); ?>
<?php
$__split = function ($name, $params = []) {
return [$name, $params];
};
[$__name, $__params] = $__split('type-item-table');
$__html = app('livewire')->mount($__name, $__params, 'lw-164052697-0', $__slots ?? [], get_defined_vars());
echo $__html;
unset($__html);
unset($__name);
unset($__params);
unset($__split);
if (isset($__slots)) unset($__slots);
?>
<?php echo $__env->renderComponent(); ?>
<?php endif; ?>
<?php if (isset($__attributesOriginal5863877a5171c196453bfa0bd807e410)): ?>
<?php $attributes = $__attributesOriginal5863877a5171c196453bfa0bd807e410; ?>
<?php unset($__attributesOriginal5863877a5171c196453bfa0bd807e410); ?>
<?php endif; ?>
<?php if (isset($__componentOriginal5863877a5171c196453bfa0bd807e410)): ?>
<?php $component = $__componentOriginal5863877a5171c196453bfa0bd807e410; ?>
<?php unset($__componentOriginal5863877a5171c196453bfa0bd807e410); ?>
<?php endif; ?>
<?php /**PATH E:\!PROJECT\dfood-website\resources\views/pages/back/typeitems/index.blade.php ENDPATH**/ ?>

File diff suppressed because it is too large Load Diff