290625
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Bundles; // Asumsikan ada model Bundle
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BundlesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Menampilkan daftar semua Bundles.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// Mengambil semua data bundle dari database
|
||||
$bundles = Bundles::all();
|
||||
// Mengembalikan view dengan data bundles
|
||||
return view('pages.back.bundles.index', compact('bundles'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan form untuk membuat Bundle baru.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
// Mengembalikan view form pembuatan bundle
|
||||
return view('pages.back.bundles.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Menyimpan Bundle baru ke database.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
// Validasi data input dari form
|
||||
$validatedData = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
// Tambahkan aturan validasi lain sesuai kolom tabel bundles Anda
|
||||
]);
|
||||
|
||||
// Membuat instance Bundle baru dan menyimpannya
|
||||
Bundle::create($validatedData);
|
||||
|
||||
// Redirect ke halaman index bundles dengan pesan sukses
|
||||
return redirect()->route('pages.back.bundles.index')->with('success', 'Bundle berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan detail satu Bundle tertentu.
|
||||
*/
|
||||
public function show(Bundle $bundle) // Route model binding
|
||||
{
|
||||
// Mengembalikan view detail bundle dengan data bundle yang ditemukan
|
||||
return view('pages.back.bundles.show', compact('bundle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan form untuk mengedit Bundle yang sudah ada.
|
||||
*/
|
||||
public function edit(Bundle $bundle) // Route model binding
|
||||
{
|
||||
// Mengembalikan view form edit bundle dengan data bundle yang ditemukan
|
||||
return view('pages.back.bundles.edit', compact('bundle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Memperbarui data Bundle di database.
|
||||
*/
|
||||
public function update(Request $request, Bundle $bundle) // Route model binding
|
||||
{
|
||||
// Validasi data input yang diperbarui
|
||||
$validatedData = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
// Tambahkan aturan validasi lain
|
||||
]);
|
||||
|
||||
// Memperbarui data bundle
|
||||
$bundle->update($validatedData);
|
||||
|
||||
// Redirect ke halaman index bundles dengan pesan sukses
|
||||
return redirect()->route('pages.back.bundles.index')->with('success', 'Bundle berhasil diperbarui!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Menghapus Bundle dari database.
|
||||
*/
|
||||
public function destroy(Bundle $bundle) // Route model binding
|
||||
{
|
||||
// Menghapus bundle
|
||||
$bundle->delete();
|
||||
|
||||
// Redirect ke halaman index bundles dengan pesan sukses
|
||||
return redirect()->route('pages.back.bundles.index')->with('success', 'Bundle berhasil dihapus!');
|
||||
}
|
||||
}
|
|
@ -2,93 +2,86 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Items; // Asumsikan ada model Item (untuk makanan/minuman)
|
||||
use App\Models\Items;
|
||||
use App\Models\TypeItems;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ItemsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Menampilkan daftar semua Items (makanan/minuman).
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$items = Items::all(); // Atau bisa juga Item::with('category', 'typeItem')->get();
|
||||
$items = Items::with('typeItem')->get();
|
||||
|
||||
return view('pages.back.items.index', compact('items'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan form untuk membuat Item baru.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
// Jika ada relasi ke kategori atau type, Anda mungkin perlu mengirim data itu juga
|
||||
// $categories = Category::all();
|
||||
// $typeItems = TypeItem::all();
|
||||
return view('pages.back.items.create'); // , compact('categories', 'typeItems')
|
||||
$typeItems = TypeItems::all();
|
||||
|
||||
return view('pages.back.items.create', compact('typeItems'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menyimpan Item baru ke database.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validatedData = $request->validate([
|
||||
$rules = [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'price' => 'required|numeric|min:0',
|
||||
'category_id' => 'nullable|exists:categories,id', // Jika ada relasi
|
||||
'type_item_id' => 'nullable|exists:type_items,id', // Jika ada relasi
|
||||
// ... kolom lain seperti image_url, is_available
|
||||
]);
|
||||
'image_url' => 'nullable|url|max:255',
|
||||
'is_available' => 'boolean',
|
||||
'type_item_id' => 'required|exists:type_items,id',
|
||||
];
|
||||
|
||||
$validatedData = $request->validate($rules);
|
||||
|
||||
Items::create($validatedData);
|
||||
|
||||
return redirect()->route('items.index')->with('success', 'Item berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan detail satu Item tertentu.
|
||||
*/
|
||||
public function show(Item $item)
|
||||
public function show(Items $item)
|
||||
{
|
||||
$item->load('typeItem');
|
||||
|
||||
return view('pages.back.items.show', compact('item'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan form untuk mengedit Item yang sudah ada.
|
||||
*/
|
||||
public function edit(Item $item)
|
||||
public function edit(Items $item)
|
||||
{
|
||||
// $categories = Category::all();
|
||||
// $typeItems = TypeItem::all();
|
||||
return view('pages.back.items.edit', compact('item')); // , compact('item', 'categories', 'typeItems')
|
||||
$typeItems = TypeItems::all();
|
||||
|
||||
return view('pages.back.items.edit', compact('item', 'typeItems'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Memperbarui data Item di database.
|
||||
*/
|
||||
public function update(Request $request, Item $item)
|
||||
public function update(Request $request, Items $item)
|
||||
{
|
||||
$validatedData = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
// Rule::unique('items')->ignore($item->id),
|
||||
],
|
||||
'description' => 'nullable|string',
|
||||
'price' => 'required|numeric|min:0',
|
||||
'category_id' => 'nullable|exists:categories,id',
|
||||
'type_item_id' => 'nullable|exists:type_items,id',
|
||||
// ...
|
||||
]);
|
||||
'image_url' => 'nullable|url|max:255',
|
||||
'is_available' => 'boolean',
|
||||
'type_item_id' => 'required|exists:type_items,id',
|
||||
];
|
||||
|
||||
$validatedData = $request->validate($rules);
|
||||
|
||||
$item->update($validatedData);
|
||||
|
||||
return redirect()->route('items.index')->with('success', 'Item berhasil diperbarui!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Menghapus Item dari database.
|
||||
*/
|
||||
public function destroy(Item $item)
|
||||
public function destroy(Items $item)
|
||||
{
|
||||
$item->delete();
|
||||
|
||||
return redirect()->route('items.index')->with('success', 'Item berhasil dihapus!');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,205 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Bundles; // Gunakan singular Bundle
|
||||
use App\Models\Items; // Gunakan singular Item
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class BundleTable extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $search = '';
|
||||
public $sortBy = 'name';
|
||||
public $sortDirection = 'asc';
|
||||
|
||||
// Properti untuk form Create/Edit Bundle
|
||||
public $bundleId;
|
||||
public $name;
|
||||
public $description;
|
||||
public $price;
|
||||
public $is_active = true;
|
||||
|
||||
// Untuk fitur mengelola item dalam bundle (penting untuk Many-to-Many)
|
||||
public $selectedItems = []; // Array ID item yang dipilih untuk bundle ini
|
||||
public $allItems = [];// Semua item yang tersedia untuk dipilih di form
|
||||
|
||||
public $isModalOpen = false;
|
||||
public $isDeleteModalOpen = false;
|
||||
public $bundleToDeleteId;
|
||||
|
||||
// Aturan validasi
|
||||
protected $rules = [
|
||||
'name' => 'required|string|max:255|unique:bundles,name',
|
||||
'description' => 'nullable|string',
|
||||
'price' => 'nullable|numeric|min:0',
|
||||
'is_active' => 'boolean',
|
||||
'selectedItems' => 'nullable|array', // selectedItems sekarang adalah array
|
||||
'selectedItems.*' => 'exists:items,id', // Setiap ID dalam array harus ada di tabel items
|
||||
];
|
||||
|
||||
// Mounted lifecycle hook untuk mengambil data dropdown
|
||||
public function mount()
|
||||
{
|
||||
$this->allItems = Items::all(['id', 'name']); // Ambil semua item untuk pilihan di form
|
||||
}
|
||||
|
||||
// Reset halaman pagination setiap kali properti pencarian berubah
|
||||
public function updatingSearch()
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
// Metode untuk mengubah kolom sorting
|
||||
public function sortBy($field)
|
||||
{
|
||||
if ($this->sortBy === $field) {
|
||||
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortBy = $field;
|
||||
$this->sortDirection = 'asc';
|
||||
}
|
||||
}
|
||||
|
||||
// Metode untuk membuka modal Create Bundle
|
||||
public function create()
|
||||
{
|
||||
$this->resetInputFields();
|
||||
$this->is_active = true;
|
||||
// Tidak perlu mengambil allItems lagi karena sudah di mount(),
|
||||
// tapi pastikan selectedItems direset
|
||||
$this->selectedItems = [];
|
||||
$this->isModalOpen = true;
|
||||
}
|
||||
|
||||
// Metode untuk mengisi form edit dan membuka modal
|
||||
public function edit($id)
|
||||
{
|
||||
$bundle = Bundles::findOrFail($id); // Gunakan Bundle
|
||||
$this->bundleId = $bundle->id;
|
||||
$this->name = $bundle->name;
|
||||
$this->description = $bundle->description;
|
||||
$this->price = $bundle->price;
|
||||
$this->is_active = $bundle->is_active;
|
||||
|
||||
// === PENTING: Ambil ID item yang sudah terkait dengan bundle ini ===
|
||||
$this->selectedItems = $bundle->items->pluck('id')->toArray();
|
||||
|
||||
$this->isModalOpen = true;
|
||||
}
|
||||
|
||||
// Metode untuk menyimpan atau memperbarui Bundle
|
||||
public function store()
|
||||
{
|
||||
$rules = $this->rules;
|
||||
if ($this->bundleId) {
|
||||
// Untuk update, 'name' harus unique kecuali jika itu adalah nama dari bundle yang sedang diedit
|
||||
$rules['name'] = 'required|string|max:255|unique:bundles,name,' . $this->bundleId;
|
||||
}
|
||||
$this->validate($rules);
|
||||
|
||||
$bundleData = [
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'price' => $this->price,
|
||||
'is_active' => $this->is_active,
|
||||
];
|
||||
|
||||
$bundle = null; // Inisialisasi variabel $bundle
|
||||
|
||||
if ($this->bundleId) {
|
||||
// Update Bundle
|
||||
$bundle = Bundles::find($this->bundleId); // Gunakan Bundle
|
||||
$bundle->update($bundleData);
|
||||
session()->flash('message', 'Bundle berhasil diperbarui!');
|
||||
} else {
|
||||
// Create Bundle
|
||||
$bundle = Bundles::create($bundleData); // Gunakan Bundle
|
||||
session()->flash('message', 'Bundle berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
// === PENTING: Sinkronkan relasi many-to-many setelah bundle dibuat/diperbarui ===
|
||||
if ($bundle && !empty($this->selectedItems)) {
|
||||
$bundle->items()->sync($this->selectedItems);
|
||||
} elseif ($bundle) {
|
||||
// Jika tidak ada item yang dipilih, detach semua relasi item yang ada
|
||||
$bundle->items()->detach();
|
||||
}
|
||||
|
||||
$this->closeModal();
|
||||
$this->resetInputFields();
|
||||
}
|
||||
|
||||
// Metode untuk membuka modal konfirmasi hapus
|
||||
public function confirmDelete($id)
|
||||
{
|
||||
$this->bundleToDeleteId = $id;
|
||||
$this->isDeleteModalOpen = true;
|
||||
}
|
||||
|
||||
// Metode untuk menghapus Bundle
|
||||
public function delete()
|
||||
{
|
||||
if ($this->bundleToDeleteId) {
|
||||
$bundle = Bundles::find($this->bundleToDeleteId); // Gunakan Bundle
|
||||
if ($bundle) {
|
||||
// Relasi many-to-many secara otomatis akan di-cascade oleh foreign key ON DELETE CASCADE
|
||||
// di tabel pivot (bundle_item) ketika bundle dihapus.
|
||||
// Oleh karena itu, $bundle->items()->detach(); sebenarnya tidak mutlak diperlukan di sini
|
||||
// jika DB constraint sudah diatur dengan benar.
|
||||
// Namun, secara eksplisit detach juga aman dilakukan.
|
||||
// $bundle->items()->detach();
|
||||
|
||||
$bundle->delete();
|
||||
session()->flash('message', 'Bundle berhasil dihapus!');
|
||||
}
|
||||
}
|
||||
$this->closeDeleteModal();
|
||||
}
|
||||
|
||||
// Tutup modal create/edit
|
||||
public function closeModal()
|
||||
{
|
||||
$this->isModalOpen = false;
|
||||
$this->resetValidation();
|
||||
}
|
||||
|
||||
// Tutup modal konfirmasi hapus
|
||||
public function closeDeleteModal()
|
||||
{
|
||||
$this->isDeleteModalOpen = false;
|
||||
$this->bundleToDeleteId = null;
|
||||
}
|
||||
|
||||
// Reset semua input form
|
||||
private function resetInputFields()
|
||||
{
|
||||
$this->bundleId = null;
|
||||
$this->name = '';
|
||||
$this->description = '';
|
||||
$this->price = '';
|
||||
$this->is_active = true;
|
||||
$this->selectedItems = []; // Reset selectedItems ke array kosong
|
||||
}
|
||||
|
||||
// Metode render komponen Livewire
|
||||
public function render()
|
||||
{
|
||||
$bundles = Bundles::query() // Gunakan Bundle
|
||||
->when($this->search, function ($query) {
|
||||
$query->where('name', 'like', '%' . $this->search . '%')
|
||||
->orWhere('description', 'like', '%' . $this->search . '%');
|
||||
})
|
||||
->orderBy($this->sortBy, $this->sortDirection)
|
||||
// === EAGER LOAD RELASI ITEMS UNTUK MENAMPILKANNYA DI VIEW ===
|
||||
->with('items') // Eager load relasi items untuk menampilkan di tabel
|
||||
->paginate(10);
|
||||
|
||||
return view('livewire.bundle-table', [
|
||||
'bundles' => $bundles,
|
||||
// allItems akan tersedia secara otomatis karena public property
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -11,8 +11,8 @@
|
|||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Mail\OrderReceipt;
|
||||
use Kreait\Firebase\Factory;
|
||||
use Kreait\Firebase\ServiceAccount;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class FoodOrder extends Component
|
||||
{
|
||||
|
@ -21,181 +21,137 @@ class FoodOrder extends Component
|
|||
public ?int $typeItemId = null;
|
||||
public bool $showEmailInput = false;
|
||||
public string $customerEmail = '';
|
||||
|
||||
public bool $isExistingCustomer = false; // Meja sudah dipesan
|
||||
public ?string $selectedTableId = null; // Untuk meja yang tersedia (table_activation_sensor_active = 0)
|
||||
public ?string $selectedOccupiedTableId = null; // Untuk meja yang sudah diduduki (table_activation_sensor_active = 1, dengan reserved_by)
|
||||
public string $inputCustomerName = ''; // Input nama pemesan
|
||||
public bool $isExistingCustomer = false;
|
||||
public ?string $selectedTableId = null;
|
||||
public ?string $selectedOccupiedTableId = null;
|
||||
public string $inputCustomerName = '';
|
||||
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 array $availableTables = [];
|
||||
public array $occupiedTables = [];
|
||||
public Collection $allTablesData;
|
||||
public bool $showMapModal = false;
|
||||
public ?string $tableIdFromUrl = null;
|
||||
|
||||
public ?string $tableIdFromUrl = null; // ID meja dari URL QR code
|
||||
private array $lastFirebaseDataHash = [];
|
||||
|
||||
protected array $rules = [
|
||||
'customerEmail' => 'required|email|max:255',
|
||||
'selectedTableId' => 'nullable|string',
|
||||
'selectedOccupiedTableId' => 'nullable|string',
|
||||
'inputCustomerName' => 'nullable|string|max:255', // Validasi bersyarat
|
||||
'inputCustomerName' => 'nullable|string|max:255',
|
||||
];
|
||||
|
||||
protected $listeners = [
|
||||
'paymentSuccess' => 'handlePaymentSuccess',
|
||||
'tableStatusUpdated' => 'syncTablesFromFirebase', // Refresh table data after status update
|
||||
'tableStatusUpdated' => 'syncTablesFromFirebase',
|
||||
];
|
||||
|
||||
public function mount($typeSlug = null)
|
||||
{
|
||||
$this->cart = session()->get('cart', []);
|
||||
$this->allTablesData = collect(); // Inisialisasi collection kosong
|
||||
$this->allTablesData = collect();
|
||||
$this->syncTablesFromFirebase();
|
||||
|
||||
if (request()->has('table_id')) {
|
||||
$this->tableIdFromUrl = request()->query('table_id');
|
||||
}
|
||||
$this->tableIdFromUrl = request()->query('table_id');
|
||||
$type = $typeSlug
|
||||
? TypeItems::whereRaw('LOWER(REPLACE(name, " ", "-")) = ?', [Str::lower($typeSlug)])->first()
|
||||
: TypeItems::whereRaw('LOWER(REPLACE(name, " ", "-")) = ?', ['makanan'])->first();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
$this->typeItemId = $type->id ?? null;
|
||||
}
|
||||
|
||||
private function getFirebaseDatabase()
|
||||
{
|
||||
try {
|
||||
$firebaseCredentialsPath = config('services.firebase.credentials');
|
||||
$path = config('services.firebase.credentials');
|
||||
$path = file_exists($path) ? $path : storage_path('app/' . $path);
|
||||
throw_unless(file_exists($path), new \Exception("Firebase credentials not found at: $path"));
|
||||
|
||||
// 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();
|
||||
return (new Factory)
|
||||
->withServiceAccount($path)
|
||||
->withDatabaseUri(config('services.firebase.database_url'))
|
||||
->createDatabase();
|
||||
} catch (\Exception $e) {
|
||||
logger()->error("Firebase initialization failed: " . $e->getMessage());
|
||||
$this->dispatch('notify', message: 'Gagal terhubung ke sistem meja: ' . $e->getMessage());
|
||||
logger()->error("Firebase init failed: " . $e->getMessage());
|
||||
$this->dispatch('notify', message: 'Gagal koneksi Firebase: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function syncTablesFromFirebase()
|
||||
public function syncTablesFromFirebase()
|
||||
{
|
||||
$database = $this->getFirebaseDatabase();
|
||||
if (!$database) {
|
||||
$this->availableTables = [];
|
||||
$this->occupiedTables = [];
|
||||
$this->allTablesData = collect();
|
||||
return;
|
||||
}
|
||||
$db = $this->getFirebaseDatabase();
|
||||
if (!$db) return $this->resetTables();
|
||||
|
||||
try {
|
||||
$firebaseTablesData = $database->getReference('/')->getValue();
|
||||
$processedAvailableTables = [];
|
||||
$processedOccupiedTables = [];
|
||||
$allProcessedTables = [];
|
||||
$data = $db->getReference('/')->getValue();
|
||||
if (!$data) return $this->resetTables();
|
||||
|
||||
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);
|
||||
$currentHash = md5(json_encode($data));
|
||||
if ($this->lastFirebaseDataHash['tables'] ?? null === $currentHash) {
|
||||
return; // Tidak berubah, jangan update properti
|
||||
}
|
||||
$this->lastFirebaseDataHash['tables'] = $currentHash;
|
||||
|
||||
$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
|
||||
];
|
||||
$available = $occupied = $all = [];
|
||||
|
||||
$allProcessedTables[$firebaseKey] = $tableDetail; // Gunakan ID sebagai kunci untuk pencarian cepat
|
||||
foreach ($data as $id => $d) {
|
||||
$isActive = $d['sensors']['table_activation_sensor_active'] ?? 0;
|
||||
$reservedBy = $d['reserved_by'] ?? null;
|
||||
$table = [
|
||||
'id' => $id,
|
||||
'device_id' => $d['device_id'] ?? $id,
|
||||
'table_activation_sensor_active' => $isActive,
|
||||
'reserved_by' => $reservedBy,
|
||||
'is_available' => !$isActive,
|
||||
'has_reserved_by' => !empty($reservedBy),
|
||||
'firebase_data' => $d
|
||||
];
|
||||
|
||||
if ($isAvailable) {
|
||||
$processedAvailableTables[] = $tableDetail;
|
||||
} elseif ($hasReservedBy) {
|
||||
$processedOccupiedTables[] = $tableDetail;
|
||||
}
|
||||
}
|
||||
$all[$id] = $table;
|
||||
$isActive ? ($reservedBy ? $occupied[] = $table : null) : $available[] = $table;
|
||||
}
|
||||
|
||||
$this->availableTables = array_values($processedAvailableTables);
|
||||
$this->occupiedTables = array_values($processedOccupiedTables);
|
||||
$this->allTablesData = collect($allProcessedTables); // Convert to Collection
|
||||
|
||||
// Panggil attemptAutoSelectTable setelah data meja siap
|
||||
$this->availableTables = $available;
|
||||
$this->occupiedTables = $occupied;
|
||||
$this->allTablesData = collect($all);
|
||||
$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();
|
||||
logger()->error("Sync Firebase failed: " . $e->getMessage());
|
||||
$this->dispatch('notify', message: 'Gagal sinkron Firebase: ' . $e->getMessage());
|
||||
$this->resetTables();
|
||||
}
|
||||
}
|
||||
|
||||
private function resetTables()
|
||||
{
|
||||
$this->availableTables = [];
|
||||
$this->occupiedTables = [];
|
||||
$this->allTablesData = collect();
|
||||
}
|
||||
|
||||
private function attemptAutoSelectTable()
|
||||
{
|
||||
if (!$this->tableIdFromUrl || $this->allTablesData->isEmpty()) return;
|
||||
|
||||
$table = $this->allTablesData->firstWhere('device_id', $this->tableIdFromUrl);
|
||||
if (!$table) return $this->dispatch('notify', message: 'Meja tidak ditemukan.');
|
||||
|
||||
$isActive = $table['firebase_data']['sensors']['table_activation_sensor_active'] ?? 0;
|
||||
$reserved = $table['reserved_by'] ?? null;
|
||||
|
||||
if (!$isActive) {
|
||||
$this->selectedTableId = $table['id'];
|
||||
$this->inputCustomerName = auth()->user()->name ?? 'Guest';
|
||||
$this->dispatch('notify', message: 'Meja berhasil dipilih otomatis.');
|
||||
} elseif ($reserved) {
|
||||
$this->selectedOccupiedTableId = $table['id'];
|
||||
$this->inputCustomerName = $reserved;
|
||||
$this->isExistingCustomer = true;
|
||||
$this->dispatch('notify', message: 'Meja sudah terisi oleh ' . $reserved);
|
||||
} else {
|
||||
$this->dispatch('notify', message: 'Meja tidak tersedia.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,18 +397,19 @@ private function updateTableAndResetState()
|
|||
$this->clearCartAndResetState();
|
||||
}
|
||||
|
||||
private function updateFirebaseTableStatus(string $firebaseTableId, array $updates)
|
||||
public function updateFirebaseTableStatus(string $firebaseTableId, array $updates, bool $refresh = true)
|
||||
{
|
||||
$database = $this->getFirebaseDatabase();
|
||||
if (!$database) {
|
||||
return;
|
||||
}
|
||||
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
|
||||
|
||||
if ($refresh) {
|
||||
$this->syncTablesFromFirebase();
|
||||
}
|
||||
} 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());
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
use App\Models\Items;
|
||||
use App\Models\TypeItems;
|
||||
use App\Models\Bundles;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use Livewire\WithFileUploads;
|
||||
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
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Intervention\Image\Laravel\Facades\Image;
|
||||
// use Intervention\Image\ImageManager;
|
||||
|
||||
class ItemTable extends Component
|
||||
{
|
||||
|
@ -19,25 +19,22 @@ class ItemTable extends Component
|
|||
public $search = '';
|
||||
public $sortBy = 'name';
|
||||
public $sortDirection = 'asc';
|
||||
|
||||
public $typeItemId = null;
|
||||
|
||||
public $itemId;
|
||||
public $name;
|
||||
public $description;
|
||||
public $price;
|
||||
public $image_url; // Ini akan menyimpan path relatif dari folder public, e.g., 'img/items/namafile.jpg'
|
||||
public $new_image; // Ini adalah objek TemporaryUploadedFile dari Livewire
|
||||
public $image_url;
|
||||
public $new_image;
|
||||
public $is_available = true;
|
||||
public $type_item_id;
|
||||
public $bundle_ids = [];
|
||||
|
||||
public $isModalOpen = false;
|
||||
public $isDeleteModalOpen = false;
|
||||
public $itemToDeleteId;
|
||||
|
||||
public $allTypeItems;
|
||||
public $allBundles;
|
||||
|
||||
protected $queryString = [
|
||||
'search' => ['except' => ''],
|
||||
|
@ -52,15 +49,12 @@ class ItemTable extends Component
|
|||
'price' => 'required|numeric|min:0',
|
||||
'is_available' => 'boolean',
|
||||
'type_item_id' => 'required|exists:type_items,id',
|
||||
'bundle_ids' => 'nullable|array',
|
||||
'bundle_ids.*' => 'exists:bundles,id',
|
||||
'new_image' => 'nullable|image|max:1024', // Validasi tetap sama untuk file upload
|
||||
'new_image' => 'nullable|image|max:1024',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->allTypeItems = TypeItems::all();
|
||||
$this->allBundles = Bundles::all();
|
||||
$this->typeItemId = request()->query('type_item_id');
|
||||
}
|
||||
|
||||
|
@ -99,11 +93,10 @@ public function edit($id)
|
|||
$this->name = $item->name;
|
||||
$this->description = $item->description;
|
||||
$this->price = $item->price;
|
||||
$this->image_url = $item->image_url; // Path yang tersimpan di database
|
||||
$this->image_url = $item->image_url;
|
||||
$this->is_available = $item->is_available;
|
||||
$this->type_item_id = $item->type_item_id;
|
||||
$this->bundle_ids = $item->bundles->pluck('id')->toArray();
|
||||
$this->new_image = null; // Reset new_image saat edit
|
||||
$this->new_image = null;
|
||||
$this->isModalOpen = true;
|
||||
}
|
||||
|
||||
|
@ -119,46 +112,35 @@ public function store()
|
|||
'type_item_id' => $this->type_item_id,
|
||||
];
|
||||
|
||||
// LOGIKA PENYIMPANAN GAMBAR DIUBAH DI SINI
|
||||
$targetPath = 'img/items/';
|
||||
if (!File::isDirectory(public_path($targetPath))) {
|
||||
File::makeDirectory(public_path($targetPath), 0755, true);
|
||||
}
|
||||
|
||||
if ($this->new_image) {
|
||||
// Hapus gambar lama jika ada dan file-nya masih ada di public
|
||||
if ($this->itemId && $this->image_url) {
|
||||
$oldImagePath = public_path($this->image_url);
|
||||
if (File::exists($oldImagePath)) {
|
||||
File::delete($oldImagePath);
|
||||
}
|
||||
if ($this->image_url && File::exists(public_path($this->image_url))) {
|
||||
File::delete(public_path($this->image_url));
|
||||
}
|
||||
|
||||
// Tentukan nama file baru (gunakan uniqid() atau timestamp untuk unik)
|
||||
$fileName = uniqid() . '.' . $this->new_image->extension();
|
||||
$targetPath = 'img/items/'; // Subfolder di dalam public
|
||||
$webpName = uniqid('item_') . '.webp';
|
||||
$image = Image::read($this->new_image->getRealPath())->toWebp(80);
|
||||
$image->save(public_path($targetPath . $webpName));
|
||||
|
||||
// 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;
|
||||
if ($this->itemId) {
|
||||
$itemData['image_url'] = $targetPath . $webpName;
|
||||
} elseif ($this->itemId && !$this->image_url) {
|
||||
$item = Items::find($this->itemId);
|
||||
$item->update($itemData);
|
||||
session()->flash('message', 'Item berhasil diperbarui!');
|
||||
} else {
|
||||
$item = Items::create($itemData);
|
||||
session()->flash('message', 'Item berhasil ditambahkan!');
|
||||
if ($item && $item->image_url && File::exists(public_path($item->image_url))) {
|
||||
File::delete(public_path($item->image_url));
|
||||
}
|
||||
$itemData['image_url'] = null;
|
||||
}
|
||||
|
||||
if ($item) {
|
||||
$item->bundles()->sync($this->bundle_ids);
|
||||
}
|
||||
$item = Items::updateOrCreate(
|
||||
['id' => $this->itemId],
|
||||
$itemData
|
||||
);
|
||||
|
||||
session()->flash('message', $this->itemId ? 'Item berhasil diperbarui!' : 'Item berhasil ditambahkan!');
|
||||
$this->closeModal();
|
||||
$this->resetInputFields();
|
||||
}
|
||||
|
@ -174,7 +156,6 @@ public function delete()
|
|||
if ($this->itemToDeleteId) {
|
||||
$item = Items::find($this->itemToDeleteId);
|
||||
if ($item) {
|
||||
// LOGIKA PENGHAPUSAN GAMBAR DIUBAH DI SINI
|
||||
if ($item->image_url) {
|
||||
$imagePath = public_path($item->image_url);
|
||||
if (File::exists($imagePath)) {
|
||||
|
@ -210,13 +191,6 @@ private function resetInputFields()
|
|||
$this->new_image = null;
|
||||
$this->is_available = true;
|
||||
$this->type_item_id = '';
|
||||
$this->bundle_ids = [];
|
||||
}
|
||||
|
||||
public function setTypeItem($id = null)
|
||||
{
|
||||
$this->typeItemId = $id;
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
@ -230,14 +204,12 @@ public function render()
|
|||
$query->where('type_item_id', $this->typeItemId)
|
||||
)
|
||||
->orderBy($this->sortBy, $this->sortDirection)
|
||||
->with(['typeItem', 'bundles'])
|
||||
->with(['typeItem'])
|
||||
->paginate(10);
|
||||
|
||||
return view('livewire.item-table', [
|
||||
'items' => $items,
|
||||
'allTypeItems' => $this->allTypeItems,
|
||||
'allBundles' => $this->allBundles,
|
||||
'typeItemId' => $this->typeItemId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Bundles extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'bundles';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'price',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
public function items(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Items::class, 'bundle_items', 'bundle_id', 'item_id');
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Items extends Model
|
||||
{
|
||||
|
@ -19,7 +18,6 @@ class Items extends Model
|
|||
'image_url', // Tambahkan ini
|
||||
'is_available',
|
||||
'type_item_id',
|
||||
'bundle_id',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -30,11 +28,4 @@ public function typeItem()
|
|||
return $this->belongsTo(TypeItems::class, 'type_item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Relasi Many-to-One: Item ini milik satu Bundle.
|
||||
*/
|
||||
public function items(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Items::class, 'bundle_items', 'bundle_id', 'item_id'); // Pastikan 'bundle_item'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,9 +24,5 @@ public function boot(): void
|
|||
View::composer('components.layouts.app.sidebar', function ($view) {
|
||||
$view->with('typeitems', TypeItems::orderBy('name')->get());
|
||||
});
|
||||
|
||||
View::composer('components.layouts.front', function ($view) {
|
||||
$view->with('typeitems', TypeItems::has('items')->orderBy('name')->get());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"require": {
|
||||
"php": "^8.2",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"intervention/image-laravel": "^1.5",
|
||||
"jeroennoten/laravel-adminlte": "^3.15",
|
||||
"kreait/laravel-firebase": "^6.1",
|
||||
"laravel/fortify": "^1.25",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e950c8c684cabe0d5943c715671e8406",
|
||||
"content-hash": "3eb9fc05a96175f48f2098ecb75289fb",
|
||||
"packages": [
|
||||
{
|
||||
"name": "almasaeed2010/adminlte",
|
||||
|
@ -1999,6 +1999,234 @@
|
|||
],
|
||||
"time": "2025-02-03T10:55:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/gif",
|
||||
"version": "4.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/gif.git",
|
||||
"reference": "5999eac6a39aa760fb803bc809e8909ee67b451a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/gif/zipball/5999eac6a39aa760fb803bc809e8909ee67b451a",
|
||||
"reference": "5999eac6a39aa760fb803bc809e8909ee67b451a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Gif\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "Native PHP GIF Encoder/Decoder",
|
||||
"homepage": "https://github.com/intervention/gif",
|
||||
"keywords": [
|
||||
"animation",
|
||||
"gd",
|
||||
"gif",
|
||||
"image"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/gif/issues",
|
||||
"source": "https://github.com/Intervention/gif/tree/4.2.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/interventionphp",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-29T07:46:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/image",
|
||||
"version": "3.11.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/image.git",
|
||||
"reference": "d0f097b8a3fa8fb758efc9440b513aa3833cda17"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/d0f097b8a3fa8fb758efc9440b513aa3833cda17",
|
||||
"reference": "d0f097b8a3fa8fb758efc9440b513aa3833cda17",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"intervention/gif": "^4.2",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-exif": "Recommended to be able to read EXIF data properly."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "PHP image manipulation",
|
||||
"homepage": "https://image.intervention.io/",
|
||||
"keywords": [
|
||||
"gd",
|
||||
"image",
|
||||
"imagick",
|
||||
"resize",
|
||||
"thumbnail",
|
||||
"watermark"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/image/issues",
|
||||
"source": "https://github.com/Intervention/image/tree/3.11.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/interventionphp",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-22T17:26:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/image-laravel",
|
||||
"version": "1.5.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/image-laravel.git",
|
||||
"reference": "056029431400a5cc56036172787a578f334683c4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image-laravel/zipball/056029431400a5cc56036172787a578f334683c4",
|
||||
"reference": "056029431400a5cc56036172787a578f334683c4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/http": "^8|^9|^10|^11|^12",
|
||||
"illuminate/routing": "^8|^9|^10|^11|^12",
|
||||
"illuminate/support": "^8|^9|^10|^11|^12",
|
||||
"intervention/image": "^3.11",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-fileinfo": "*",
|
||||
"orchestra/testbench": "^8.18 || ^9.9",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Image": "Intervention\\Image\\Laravel\\Facades\\Image"
|
||||
},
|
||||
"providers": [
|
||||
"Intervention\\Image\\Laravel\\ServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\Laravel\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Integration of Intervention Image",
|
||||
"homepage": "https://image.intervention.io/",
|
||||
"keywords": [
|
||||
"gd",
|
||||
"image",
|
||||
"imagick",
|
||||
"laravel",
|
||||
"resize",
|
||||
"thumbnail",
|
||||
"watermark"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/image-laravel/issues",
|
||||
"source": "https://github.com/Intervention/image-laravel/tree/1.5.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/interventionphp",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-04T15:09:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jeroennoten/laravel-adminlte",
|
||||
"version": "v3.15.0",
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Image Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Intervention Image supports “GD Library” and “Imagick” to process images
|
||||
| internally. Depending on your PHP setup, you can choose one of them.
|
||||
|
|
||||
| Included options:
|
||||
| - \Intervention\Image\Drivers\Gd\Driver::class
|
||||
| - \Intervention\Image\Drivers\Imagick\Driver::class
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => \Intervention\Image\Drivers\Gd\Driver::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Configuration Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options control the behavior of Intervention Image.
|
||||
|
|
||||
| - "autoOrientation" controls whether an imported image should be
|
||||
| automatically rotated according to any existing Exif data.
|
||||
|
|
||||
| - "decodeAnimation" decides whether a possibly animated image is
|
||||
| decoded as such or whether the animation is discarded.
|
||||
|
|
||||
| - "blendingColor" Defines the default blending color.
|
||||
|
|
||||
| - "strip" controls if meta data like exif tags should be removed when
|
||||
| encoding images.
|
||||
*/
|
||||
|
||||
'options' => [
|
||||
'autoOrientation' => true,
|
||||
'decodeAnimation' => true,
|
||||
'blendingColor' => 'ffffff',
|
||||
'strip' => false,
|
||||
]
|
||||
];
|
|
@ -41,10 +41,12 @@
|
|||
],
|
||||
|
||||
'midtrans' => [
|
||||
'server_key' => env('MIDTRANS_SERVER_KEY'),
|
||||
'client_key' => env('MIDTRANS_CLIENT_KEY'),
|
||||
'server_key' => env('MIDTRANS_SERVER_KEY'),
|
||||
'is_production' => env('MIDTRANS_IS_PRODUCTION', false),
|
||||
'snap_url' => env('MIDTRANS_SNAP_URL', 'https://app.sandbox.midtrans.com/snap/snap.js'),
|
||||
'snap_url' => env('MIDTRANS_IS_PRODUCTION')
|
||||
? 'https://app.midtrans.com/snap/snap.js'
|
||||
: 'https://app.sandbox.midtrans.com/snap/snap.js',
|
||||
],
|
||||
|
||||
];
|
||||
|
|
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
@ -31,8 +31,6 @@
|
|||
<flux:navlist.group :heading="__('Other')" class="grid">
|
||||
<flux:navlist.item icon="archive-box-arrow-down" :href="route('typeitems.index')"
|
||||
:current="request()->routeIs('typeitems.index')" wire:navigate>{{ __('Type Items') }}</flux:navlist.item>
|
||||
<flux:navlist.item icon="tag" :href="route('bundles.index')"
|
||||
:current="request()->routeIs('bundles.index')" wire:navigate>{{ __('Bundles') }}</flux:navlist.item>
|
||||
<flux:navlist.item icon="lock-closed" :href="route('tables.index')"
|
||||
:current="request()->routeIs('tables.index')" wire:navigate>{{ __('Tabels') }}</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
|
|
@ -115,31 +115,26 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Pindahkan setInterval untuk refresh CSRF di sini, di dalam livewire:initialized
|
||||
setInterval(() => {
|
||||
fetch('/refresh-csrf')
|
||||
fetch('/refresh-csrf', {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
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 => {
|
||||
if (component.canonical) {
|
||||
component.canonical.csrf = data.csrf_token;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('Livewire.findComponents not available yet.');
|
||||
}
|
||||
|
||||
// Tidak perlu akses internal Livewire jika ingin aman dari perubahan API internal
|
||||
console.log('CSRF token refreshed:', data.csrf_token);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing CSRF token:', error);
|
||||
});
|
||||
}, 1800000); // 30 menit (1800000 ms)
|
||||
}, 1800000); // 30 menit
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -94,7 +94,7 @@ class="mr-2 form-checkbox h-5 w-5 text-blue-600">
|
|||
<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"
|
||||
<select id="available-table-select" wire:model.live="selectedTableId" wire:key="select-available"
|
||||
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>
|
||||
|
@ -108,31 +108,25 @@ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 lead
|
|||
<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
|
||||
@elseif ($isExistingCustomer)
|
||||
<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" wire:key="select-occupied"
|
||||
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>
|
||||
@endif
|
||||
|
||||
{{-- Input Nama Pemesan (TERBARU: Hanya muncul jika showCustomerNameInput TRUE) --}}
|
||||
|
@ -247,8 +241,7 @@ class="bg-gray-600 hover:bg-gray-700 text-white py-2 rounded focus:outline-none
|
|||
|
||||
{{-- 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="fixed inset-0 z-20" wire:click="closeCart">
|
||||
<div class="absolute inset-0 blur-background"></div> {{-- Div blur palsu --}}
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
@ -40,6 +40,10 @@ class="px-6 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutra
|
|||
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">
|
||||
Deskripsi
|
||||
</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')">
|
||||
|
@ -96,8 +100,11 @@ class="h-10 w-10 object-cover rounded">
|
|||
<span class="text-neutral-400">Tidak ada gambar</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-neutral-500 dark:text-neutral-300 max-w-xs truncate">
|
||||
{{ $item->description ?? 'Tidak ada deskripsi' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
|
||||
Rp{{ number_format($item->price, 0, ',', '.') }}
|
||||
Rp. {{ number_format($item->price, 0, ',', '.') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-500 dark:text-neutral-300">
|
||||
{{ $item->typeItem->name ?? 'N/A' }}
|
||||
|
@ -124,7 +131,7 @@ class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-500
|
|||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6"
|
||||
<td colspan="7"
|
||||
class="px-6 py-4 text-center text-sm text-neutral-500 dark:text-neutral-300">
|
||||
Tidak ada data item yang ditemukan.
|
||||
</td>
|
||||
|
@ -210,7 +217,6 @@ class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Tipe It
|
|||
<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>
|
||||
{{-- --- UBAH: Gunakan $allTypeItems yang dilewatkan dari komponen Livewire --- --}}
|
||||
@foreach ($allTypeItems as $typeItem)
|
||||
<option value="{{ $typeItem->id }}">{{ $typeItem->name }}</option>
|
||||
@endforeach
|
||||
|
@ -220,29 +226,6 @@ class="mt-1 block w-full rounded-md border-neutral-300 dark:border-neutral-700 s
|
|||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- --- BARU: Bagian untuk pemilihan Bundles (multi-select) --- --}}
|
||||
<div class="mb-4">
|
||||
<label for="bundle_ids"
|
||||
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">Bundles</label>
|
||||
{{-- --- UBAH: Tambahkan 'multiple' dan 'wire:model.live' untuk mendukung multi-select --- --}}
|
||||
<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>
|
||||
{{-- --- UBAH: Gunakan $allBundles yang dilewatkan dari komponen Livewire --- --}}
|
||||
@foreach ($allBundles as $bundle)
|
||||
<option value="{{ $bundle->id }}">{{ $bundle->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
{{-- --- BARU: Error messages untuk bundle_ids dan bundle_ids.* --- --}}
|
||||
@error('bundle_ids')
|
||||
<span class="text-red-500 text-xs">{{ $message }}</span>
|
||||
@enderror
|
||||
@error('bundle_ids.*')
|
||||
<span class="text-red-500 text-xs">{{ $message }}</span>
|
||||
@enderror
|
||||
</div>
|
||||
{{-- --- AKHIR BARU: Bagian untuk pemilihan Bundles --- --}}
|
||||
|
||||
<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">
|
||||
|
|
|
@ -1,284 +0,0 @@
|
|||
<div>
|
||||
<style>
|
||||
/* General Reset and Base */
|
||||
* { box-sizing: border-box; }
|
||||
body, html {
|
||||
margin: 0; padding: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: #f1f3f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
header {
|
||||
background: #1d3557;
|
||||
color: #fff;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
header h1 { margin: 0; font-size: 1.75rem; }
|
||||
|
||||
button.cart-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
span.cart-count {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -10px;
|
||||
background: #e63946;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 0.75rem;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
padding: 3rem 1rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(to right, #f8c291, #f6e58d);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.hero h2 {
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Grid Layout for Items */
|
||||
.menu-grid {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto 4rem;
|
||||
padding: 0 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
article.card {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
transition: 0.3s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
article.card img {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.card-body {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.food-name {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.3rem;
|
||||
flex: 1;
|
||||
}
|
||||
.food-price {
|
||||
color: #2d6a4f;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
.quantity-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
input[type="number"] {
|
||||
width: 60px;
|
||||
padding: 0.3rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Cart Modal */
|
||||
.cart-popup {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
.cart-popup.show {
|
||||
display: flex;
|
||||
}
|
||||
.cart-content {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 480px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.cart-header, .cart-footer {
|
||||
padding: 1rem;
|
||||
background: #1d3557;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.cart-items {
|
||||
padding: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
.cart-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.checkout-btn {
|
||||
background-color: #1d3557;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.checkout-btn:hover {
|
||||
background-color: #457b9d;
|
||||
}
|
||||
.checkout-btn:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
@media(max-width: 600px) {
|
||||
header h1 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.hero h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<h1>MyFood IoT Ordering</h1>
|
||||
<button class="cart-btn" wire:click="openCart">
|
||||
🛒
|
||||
@if(array_sum($cart) > 0)
|
||||
<span class="cart-count">{{ array_sum($cart) }}</span>
|
||||
@endif
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Hero for Food -->
|
||||
<section class="hero">
|
||||
<h2>🍔 Daftar Makanan</h2>
|
||||
</section>
|
||||
|
||||
<!-- Food Items Grid -->
|
||||
<section class="menu-grid" aria-label="Makanan">
|
||||
@foreach($foodItems as $item)
|
||||
@if($item['type'] === 'food')
|
||||
<article class="card">
|
||||
<img src="{{ $item['image_url'] }}" alt="{{ $item['name'] }}">
|
||||
<div class="card-body">
|
||||
<div class="food-name">{{ $item['name'] }}</div>
|
||||
<div class="food-price">Rp {{ number_format($item['price'], 0, ',', '.') }}</div>
|
||||
<div class="quantity-wrapper">
|
||||
<label>Qty:</label>
|
||||
<input type="number" min="0" wire:model.live.debounce.300ms="cart.{{ $item['id'] }}">
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
@endforeach
|
||||
</section>
|
||||
|
||||
<!-- Hero for Drink -->
|
||||
<section class="hero" style="background: linear-gradient(to right, #74b9ff, #a29bfe);">
|
||||
<h2>🥤 Daftar Minuman</h2>
|
||||
</section>
|
||||
|
||||
<!-- Drink Items Grid -->
|
||||
<section class="menu-grid" aria-label="Minuman">
|
||||
@foreach($foodItems as $item)
|
||||
@if($item['type'] === 'beverage')
|
||||
<article class="card">
|
||||
<img src="{{ $item['image_url'] }}" alt="{{ $item['name'] }}">
|
||||
<div class="card-body">
|
||||
<div class="food-name">{{ $item['name'] }}</div>
|
||||
<div class="food-price">Rp {{ number_format($item['price'], 0, ',', '.') }}</div>
|
||||
<div class="quantity-wrapper">
|
||||
<label>Qty:</label>
|
||||
<input type="number" min="0" wire:model.live.debounce.300ms="cart.{{ $item['id'] }}">
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
@endforeach
|
||||
</section>
|
||||
|
||||
<!-- Cart Modal -->
|
||||
<div class="cart-popup @if($showCart) show @endif" wire:keydown.escape="closeCart" wire:click.self="closeCart">
|
||||
<div class="cart-content">
|
||||
<div class="cart-header">
|
||||
<h3>🛒 Keranjang</h3>
|
||||
<button wire:click="closeCart" style="font-size: 1.5rem;">×</button>
|
||||
</div>
|
||||
<div class="cart-items">
|
||||
@if(empty($cartItems))
|
||||
<p>Keranjang kosong.</p>
|
||||
@else
|
||||
@foreach($cartItems as $item)
|
||||
<div class="cart-item">
|
||||
<span>{{ $item['name'] }}</span>
|
||||
<span>x{{ $item['qty'] }}</span>
|
||||
<span>Rp {{ number_format($item['total_price'], 0, ',', '.') }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
<div class="cart-footer">
|
||||
<span>Total: Rp {{ number_format($cartTotal, 0, ',', '.') }}</span>
|
||||
<button class="checkout-btn" wire:click="checkout" @if(count($cartItems ?? []) === 0) disabled @endif>Checkout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Midtrans + Notify -->
|
||||
<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-key="{{ config('services.midtrans.client_key') }}"></script>
|
||||
<script>
|
||||
window.addEventListener('midtransSnapToken', event => {
|
||||
const token = event.detail.token;
|
||||
window.snap.pay(token, {
|
||||
onSuccess: () => window.livewire.dispatch('paymentSuccess'),
|
||||
onPending: () => window.livewire.dispatch('paymentSuccess'),
|
||||
onError: (result) => alert('Error: ' + result.status_message),
|
||||
onClose: () => {}
|
||||
});
|
||||
});
|
||||
window.addEventListener('notify', event => {
|
||||
alert(event.detail.message);
|
||||
});
|
||||
</script>
|
||||
</div>
|
|
@ -9,14 +9,18 @@
|
|||
use App\Http\Controllers\TypeItemsController;
|
||||
use App\Http\Controllers\ItemsController;
|
||||
use App\Http\Controllers\MejaController;
|
||||
use App\Http\Controllers\BundlesController;
|
||||
|
||||
Route::get('/refresh-csrf', function () {
|
||||
return response()->json(['csrf_token' => csrf_token()]);
|
||||
});
|
||||
|
||||
// Route::get('/', FoodOrder::class)->name('menu.all');
|
||||
Route::redirect('/', '/menu', 302);
|
||||
Route::get('/', function () {
|
||||
return redirect()->route('menu.byType', ['typeSlug' => 'makanan']);
|
||||
})->name('home');
|
||||
Route::get('/menu', function () {
|
||||
return redirect()->route('menu.byType', ['typeSlug' => 'makanan']);
|
||||
});
|
||||
Route::get('/menu/{typeSlug?}', FoodOrder::class)->name('menu.byType');
|
||||
Route::get('/pilih-meja', SelectTable::class)->name('select.table');
|
||||
|
||||
|
@ -30,7 +34,6 @@
|
|||
Route::resource('typeitems',TypeItemsController::class);
|
||||
Route::resource('items',ItemsController::class);
|
||||
Route::resource('tables',MejaController::class);
|
||||
Route::resource('bundles',BundlesController::class);
|
||||
|
||||
Route::get('settings/profile', Profile::class)->name('settings.profile');
|
||||
Route::get('settings/password', Password::class)->name('settings.password');
|
||||
|
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,19 @@
|
|||
<?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';
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ class="mr-2 form-checkbox h-5 w-5 text-blue-600">
|
|||
<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"
|
||||
<select id="available-table-select" wire:model.live="selectedTableId" wire:key="select-available"
|
||||
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>
|
||||
|
@ -116,39 +116,33 @@ class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 lead
|
|||
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']); ?>
|
||||
<?php elseif($isExistingCustomer): ?>
|
||||
<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" wire:key="select-occupied"
|
||||
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'];
|
||||
</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);
|
||||
<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]-->
|
||||
</div>
|
||||
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
|
||||
|
||||
|
||||
|
@ -277,8 +271,7 @@ class="bg-gray-600 hover:bg-gray-700 text-white py-2 rounded focus:outline-none
|
|||
|
||||
|
||||
<!--[if BLOCK]><![endif]--><?php if($showCart): ?>
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 z-20" wire:click="closeCart">
|
||||
|
||||
<div class="fixed inset-0 z-20" wire:click="closeCart">
|
||||
<div class="absolute inset-0 blur-background"></div>
|
||||
</div>
|
||||
<?php endif; ?><!--[if ENDBLOCK]><![endif]-->
|
||||
|
|
|
@ -181,25 +181,6 @@
|
|||
<?php endif; ?>
|
||||
<?php if (isset($component)) { $__componentOriginalda376aa217444bbd92367ba1444eb3b8 = $component; } ?>
|
||||
<?php if (isset($attributes)) { $__attributesOriginalda376aa217444bbd92367ba1444eb3b8 = $attributes; } ?>
|
||||
<?php $component = Illuminate\View\AnonymousComponent::resolve(['view' => 'e60dd9d2c3a62d619c9acb38f20d5aa5::navlist.item','data' => ['icon' => 'tag','href' => route('bundles.index'),'current' => request()->routeIs('bundles.index'),'wire:navigate' => true]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
|
||||
<?php $component->withName('flux::navlist.item'); ?>
|
||||
<?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(['icon' => 'tag','href' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(route('bundles.index')),'current' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(request()->routeIs('bundles.index')),'wire:navigate' => true]); ?><?php echo e(__('Bundles')); ?> <?php echo $__env->renderComponent(); ?>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($__attributesOriginalda376aa217444bbd92367ba1444eb3b8)): ?>
|
||||
<?php $attributes = $__attributesOriginalda376aa217444bbd92367ba1444eb3b8; ?>
|
||||
<?php unset($__attributesOriginalda376aa217444bbd92367ba1444eb3b8); ?>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($__componentOriginalda376aa217444bbd92367ba1444eb3b8)): ?>
|
||||
<?php $component = $__componentOriginalda376aa217444bbd92367ba1444eb3b8; ?>
|
||||
<?php unset($__componentOriginalda376aa217444bbd92367ba1444eb3b8); ?>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($component)) { $__componentOriginalda376aa217444bbd92367ba1444eb3b8 = $component; } ?>
|
||||
<?php if (isset($attributes)) { $__attributesOriginalda376aa217444bbd92367ba1444eb3b8 = $attributes; } ?>
|
||||
<?php $component = Illuminate\View\AnonymousComponent::resolve(['view' => 'e60dd9d2c3a62d619c9acb38f20d5aa5::navlist.item','data' => ['icon' => 'lock-closed','href' => route('tables.index'),'current' => request()->routeIs('tables.index'),'wire:navigate' => true]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
|
||||
<?php $component->withName('flux::navlist.item'); ?>
|
||||
<?php if ($component->shouldRender()): ?>
|
||||
|
|
|
@ -118,31 +118,26 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Pindahkan setInterval untuk refresh CSRF di sini, di dalam livewire:initialized
|
||||
setInterval(() => {
|
||||
fetch('/refresh-csrf')
|
||||
fetch('/refresh-csrf', {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
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 => {
|
||||
if (component.canonical) {
|
||||
component.canonical.csrf = data.csrf_token;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('Livewire.findComponents not available yet.');
|
||||
}
|
||||
|
||||
// Tidak perlu akses internal Livewire jika ingin aman dari perubahan API internal
|
||||
console.log('CSRF token refreshed:', data.csrf_token);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing CSRF token:', error);
|
||||
});
|
||||
}, 1800000); // 30 menit (1800000 ms)
|
||||
}, 1800000); // 30 menit
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
<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'): ?>
|
||||
↑
|
||||
<?php else: ?>
|
||||
↓
|
||||
<?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">
|
||||
Deskripsi
|
||||
</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'): ?>
|
||||
↑
|
||||
<?php else: ?>
|
||||
↓
|
||||
<?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'): ?>
|
||||
↑
|
||||
<?php else: ?>
|
||||
↓
|
||||
<?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'): ?>
|
||||
↑
|
||||
<?php else: ?>
|
||||
↓
|
||||
<?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(asset($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 text-sm text-neutral-500 dark:text-neutral-300 max-w-xs truncate">
|
||||
<?php echo e($item->description ?? 'Tidak ada deskripsi'); ?>
|
||||
|
||||
</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="7"
|
||||
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(asset($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="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**/ ?>
|