434 lines
24 KiB
PHP
434 lines
24 KiB
PHP
@extends('layouts.admin.app')
|
|
|
|
@push('styles')
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.css" rel="stylesheet" />
|
|
<style>
|
|
.timeline-container {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
.timeline-item {
|
|
position: relative;
|
|
padding-left: 28px;
|
|
}
|
|
.timeline-item::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background-color: #e5e7eb;
|
|
}
|
|
.timeline-item::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: -4px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background-color: #8B0000;
|
|
}
|
|
.timeline-item.available::after {
|
|
background-color: #10B981;
|
|
}
|
|
.meja-image {
|
|
width: 100%;
|
|
height: 200px;
|
|
object-fit: cover;
|
|
border-radius: 8px;
|
|
}
|
|
.meja-image-preview {
|
|
max-width: 100%;
|
|
height: 150px;
|
|
object-fit: cover;
|
|
border-radius: 4px;
|
|
margin-top: 10px;
|
|
}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<div class="container mx-auto px-4 py-8">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h1 class="text-2xl font-bold text-gray-800">Manajemen Meja</h1>
|
|
<button type="button" onclick="openModal('addTableModal')" class="bg-[#8B0000] text-white px-4 py-2 rounded-lg hover:bg-red-800">
|
|
<i class="fas fa-plus mr-2"></i>Tambah Meja
|
|
</button>
|
|
</div>
|
|
|
|
@if(session('success'))
|
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
|
|
<span class="block sm:inline">{{ session('success') }}</span>
|
|
</div>
|
|
@endif
|
|
|
|
@if ($errors->any())
|
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4">
|
|
<ul>
|
|
@foreach ($errors->all() as $error)
|
|
<li>{{ $error }}</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
@foreach($tables as $table)
|
|
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
|
@if($table->gambar)
|
|
<img src="{{ asset('storage/' . $table->gambar) }}" alt="Meja {{ $table->nomor_meja }}" class="meja-image">
|
|
@else
|
|
<div class="bg-gray-200 h-48 flex items-center justify-center">
|
|
<span class="text-gray-500">Tidak ada gambar</span>
|
|
</div>
|
|
@endif
|
|
<div class="p-6">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<div>
|
|
<h2 class="text-xl font-semibold">Meja {{ $table->nomor_meja }}</h2>
|
|
<p class="text-gray-600">{{ ucfirst($table->kategori) }}</p>
|
|
</div>
|
|
<span class="px-3 py-1 rounded-full text-sm font-semibold
|
|
{{ $table->status === 'tersedia' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
|
|
{{ $table->status === 'tersedia' ? 'Tersedia' : 'Tidak Tersedia' }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<p class="text-gray-600">Kapasitas: {{ $table->kapasitas }} Orang</p>
|
|
@if($table->deskripsi)
|
|
<p class="text-gray-600 mt-2">{{ $table->deskripsi }}</p>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center">
|
|
<button onclick="checkTableSchedule({{ $table->id }})"
|
|
class="text-blue-600 hover:text-blue-800">
|
|
<i class="fas fa-clock mr-1"></i>Cek Jadwal
|
|
</button>
|
|
<div>
|
|
<button onclick="openEditModal({{ $table->id }}, {{ json_encode($table) }})" class="text-blue-600 hover:text-blue-900 mr-3">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<form action="{{ route('admin.tables.destroy', $table) }}" method="POST" class="inline">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="text-red-600 hover:text-red-900"
|
|
onclick="return confirm('Apakah Anda yakin ingin menghapus meja ini?')">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<!-- Modal Jadwal Meja -->
|
|
<div id="scheduleModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
|
<div class="relative w-full max-w-2xl max-h-full">
|
|
<!-- Modal content -->
|
|
<div class="relative bg-white rounded-lg shadow">
|
|
<!-- Modal header -->
|
|
<div class="flex items-start justify-between p-4 border-b rounded-t">
|
|
<h3 class="text-xl font-semibold text-gray-900">
|
|
Jadwal Meja <span id="tableNumber"></span>
|
|
</h3>
|
|
<button type="button" onclick="closeModal('scheduleModal')" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
|
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
</svg>
|
|
<span class="sr-only">Close modal</span>
|
|
</button>
|
|
</div>
|
|
<!-- Modal body -->
|
|
<div class="p-6 space-y-6">
|
|
<div class="mb-4">
|
|
<label class="block text-gray-700 text-sm font-bold mb-2">Pilih Tanggal</label>
|
|
<input type="date" id="scheduleDate"
|
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5"
|
|
onchange="updateSchedule()"
|
|
min="{{ date('Y-m-d') }}">
|
|
</div>
|
|
|
|
<div class="timeline-container">
|
|
<div id="scheduleTimeline" class="space-y-4">
|
|
<!-- Timeline items will be inserted here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Tambah Meja -->
|
|
<div id="addTableModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
|
<div class="relative w-full max-w-md max-h-full">
|
|
<div class="relative bg-white rounded-lg shadow">
|
|
<div class="flex items-start justify-between p-4 border-b rounded-t">
|
|
<h3 class="text-xl font-semibold text-gray-900">
|
|
Tambah Meja Baru
|
|
</h3>
|
|
<button type="button" onclick="closeModal('addTableModal')" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
|
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="p-6 space-y-6">
|
|
<form action="{{ route('admin.tables.store') }}" method="POST" enctype="multipart/form-data">
|
|
@csrf
|
|
<div class="grid gap-4 mb-4">
|
|
<div>
|
|
<label for="nomor_meja" class="block mb-2 text-sm font-medium text-gray-900">Nomor Meja</label>
|
|
<input type="text" name="nomor_meja" id="nomor_meja" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" required>
|
|
</div>
|
|
<div>
|
|
<label for="kapasitas" class="block mb-2 text-sm font-medium text-gray-900">Kapasitas</label>
|
|
<input type="number" name="kapasitas" id="kapasitas" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" required min="1">
|
|
</div>
|
|
<div>
|
|
<label for="kategori" class="block mb-2 text-sm font-medium text-gray-900">Kategori</label>
|
|
<select name="kategori" id="kategori" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" required>
|
|
<option value="outdoor">Outdoor</option>
|
|
<option value="vip-outdoor">VIP Outdoor</option>
|
|
<option value="vip-indoor">VIP Indoor</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="status" class="block mb-2 text-sm font-medium text-gray-900">Status</label>
|
|
<select name="status" id="status" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" required>
|
|
<option value="tersedia">Tersedia</option>
|
|
<option value="tidak_tersedia">Tidak Tersedia</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="deskripsi" class="block mb-2 text-sm font-medium text-gray-900">Deskripsi</label>
|
|
<textarea name="deskripsi" id="deskripsi" rows="3" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5"></textarea>
|
|
</div>
|
|
<div>
|
|
<label for="gambar" class="block mb-2 text-sm font-medium text-gray-900">Gambar Meja</label>
|
|
<input type="file" name="gambar" id="gambar" accept="image/*" onchange="previewImage(this, 'add-image-preview')" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5">
|
|
<img id="add-image-preview" class="meja-image-preview hidden" alt="Preview">
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center justify-end space-x-2">
|
|
<button type="button" onclick="closeModal('addTableModal')" class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10">Batal</button>
|
|
<button type="submit" class="text-white bg-[#8B0000] hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center">Simpan</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Edit Meja -->
|
|
<div id="editTableModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
|
<div class="relative w-full max-w-md max-h-full">
|
|
<div class="relative bg-white rounded-lg shadow">
|
|
<div class="flex items-start justify-between p-4 border-b rounded-t">
|
|
<h3 class="text-xl font-semibold text-gray-900">
|
|
Edit Meja
|
|
</h3>
|
|
<button type="button" onclick="closeModal('editTableModal')" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
|
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="p-6 space-y-6">
|
|
<form id="editTableForm" method="POST" enctype="multipart/form-data">
|
|
@csrf
|
|
@method('PUT')
|
|
<div class="grid gap-4 mb-4">
|
|
<div>
|
|
<label for="edit_nomor_meja" class="block mb-2 text-sm font-medium text-gray-900">Nomor Meja</label>
|
|
<input type="text" name="nomor_meja" id="edit_nomor_meja" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" required>
|
|
</div>
|
|
<div>
|
|
<label for="edit_kapasitas" class="block mb-2 text-sm font-medium text-gray-900">Kapasitas</label>
|
|
<input type="number" name="kapasitas" id="edit_kapasitas" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" required min="1">
|
|
</div>
|
|
<div>
|
|
<label for="edit_kategori" class="block mb-2 text-sm font-medium text-gray-900">Kategori</label>
|
|
<select name="kategori" id="edit_kategori" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" required>
|
|
<option value="outdoor">Outdoor</option>
|
|
<option value="vip-outdoor">VIP Outdoor</option>
|
|
<option value="vip-indoor">VIP Indoor</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="edit_status" class="block mb-2 text-sm font-medium text-gray-900">Status</label>
|
|
<select name="status" id="edit_status" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" required>
|
|
<option value="tersedia" {{ $table->status === 'tersedia' ? 'selected' : '' }}>Tersedia</option>
|
|
<option value="tidak_tersedia" {{ $table->status === 'tidak_tersedia' ? 'selected' : '' }}>Tidak Tersedia</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="edit_deskripsi" class="block mb-2 text-sm font-medium text-gray-900">Deskripsi</label>
|
|
<textarea name="deskripsi" id="edit_deskripsi" rows="3" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5"></textarea>
|
|
</div>
|
|
<div>
|
|
<label for="edit_gambar" class="block mb-2 text-sm font-medium text-gray-900">Gambar Meja</label>
|
|
<input type="file" name="gambar" id="edit_gambar" accept="image/*" onchange="previewImage(this, 'edit-image-preview')" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5">
|
|
<img id="edit-image-preview" class="meja-image-preview hidden" alt="Preview">
|
|
<div id="current-image" class="mt-2"></div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center justify-end space-x-2">
|
|
<button type="button" onclick="closeModal('editTableModal')" class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10">Batal</button>
|
|
<button type="submit" class="text-white bg-[#8B0000] hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center">Simpan Perubahan</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"></script>
|
|
<script>
|
|
let currentTableId = null;
|
|
|
|
function previewImage(input, previewId) {
|
|
const preview = document.getElementById(previewId);
|
|
if (input.files && input.files[0]) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
preview.src = e.target.result;
|
|
preview.classList.remove('hidden');
|
|
}
|
|
reader.readAsDataURL(input.files[0]);
|
|
}
|
|
}
|
|
|
|
function openEditModal(tableId, table) {
|
|
const form = document.getElementById('editTableForm');
|
|
form.action = `/admin/tables/${tableId}`;
|
|
|
|
document.getElementById('edit_nomor_meja').value = table.nomor_meja;
|
|
document.getElementById('edit_kapasitas').value = table.kapasitas;
|
|
document.getElementById('edit_kategori').value = table.kategori;
|
|
document.getElementById('edit_status').value = table.status;
|
|
document.getElementById('edit_deskripsi').value = table.deskripsi || '';
|
|
|
|
const currentImage = document.getElementById('current-image');
|
|
const imagePreview = document.getElementById('edit-image-preview');
|
|
|
|
if (table.gambar) {
|
|
currentImage.innerHTML = `<img src="{{ asset('storage') }}/${table.gambar}" class="meja-image-preview" alt="Current image">`;
|
|
imagePreview.classList.add('hidden');
|
|
} else {
|
|
currentImage.innerHTML = '<p class="text-gray-500">Tidak ada gambar</p>';
|
|
imagePreview.classList.add('hidden');
|
|
}
|
|
|
|
const statusSelect = document.getElementById('edit_status');
|
|
const options = statusSelect.options;
|
|
for (let i = 0; i < options.length; i++) {
|
|
if (options[i].value === table.status) {
|
|
options[i].selected = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
openModal('editTableModal');
|
|
}
|
|
|
|
function checkTableSchedule(tableId) {
|
|
currentTableId = tableId;
|
|
const table = @json($tables).find(t => t.id === tableId);
|
|
document.getElementById('tableNumber').textContent = table.nomor_meja;
|
|
document.getElementById('scheduleDate').value = new Date().toISOString().split('T')[0];
|
|
openModal('scheduleModal');
|
|
updateSchedule();
|
|
}
|
|
|
|
function updateSchedule() {
|
|
const date = document.getElementById('scheduleDate').value;
|
|
const timeline = document.getElementById('scheduleTimeline');
|
|
timeline.innerHTML = '<div class="text-center">Loading...</div>';
|
|
|
|
fetch(`/admin/tables/${currentTableId}/schedule?date=${date}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const timeSlots = generateTimeSlots(data.reservations);
|
|
timeline.innerHTML = '';
|
|
|
|
timeSlots.forEach(slot => {
|
|
const itemClass = slot.isReserved ? '' : 'available';
|
|
const statusClass = slot.isReserved ? 'text-red-600' : 'text-green-600';
|
|
const status = slot.isReserved ? 'Dipesan' : 'Tersedia';
|
|
const reservationInfo = slot.isReserved ? `
|
|
<div class="text-sm text-gray-600">
|
|
<p>Pemesan: ${slot.reservation.name}</p>
|
|
<p>Jumlah: ${slot.reservation.people} Orang</p>
|
|
<p>Status: ${slot.reservation.status}</p>
|
|
</div>
|
|
` : '';
|
|
|
|
timeline.innerHTML += `
|
|
<div class="timeline-item ${itemClass} p-4 mb-4 bg-gray-50 rounded-lg">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<span class="font-semibold">${slot.time}</span>
|
|
<span class="${statusClass} ml-2">${status}</span>
|
|
</div>
|
|
</div>
|
|
${reservationInfo}
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
timeline.innerHTML = '<div class="text-center text-red-600">Error loading schedule</div>';
|
|
});
|
|
}
|
|
|
|
function generateTimeSlots(reservations) {
|
|
const slots = [];
|
|
const startHour = 10; // 10:00
|
|
const endHour = 22; // 22:00
|
|
|
|
for (let hour = startHour; hour <= endHour; hour++) {
|
|
// Generate slots for both :00 and :30 of each hour
|
|
['00', '30'].forEach(minutes => {
|
|
const time = `${hour.toString().padStart(2, '0')}:${minutes}`;
|
|
const reservation = reservations.find(r => {
|
|
const startTime = r.start_time;
|
|
const endTime = r.end_time;
|
|
return time >= startTime && time < endTime;
|
|
});
|
|
|
|
slots.push({
|
|
time: time,
|
|
isReserved: !!reservation,
|
|
reservation: reservation
|
|
});
|
|
});
|
|
}
|
|
|
|
return slots;
|
|
}
|
|
|
|
// Update modal functions to use Flowbite's modal
|
|
function openModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
modal.classList.remove('hidden');
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
modal.classList.add('hidden');
|
|
}
|
|
</script>
|
|
@endpush
|
|
@endsection
|