Reservasi-Cafe/resources/views/admin/reservations.blade.php

732 lines
32 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>
.modal-container {
height: 90vh;
margin: 5vh auto;
display: flex;
flex-direction: column;
background: white;
border-radius: 0.5rem;
max-width: 800px;
width: 95%;
}
.modal-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.modal-body {
padding: 1.5rem;
overflow-y: auto;
flex: 1;
}
.timeline-container {
overflow-y: auto;
}
.time-slot {
display: flex;
align-items: center;
padding: 0.75rem;
margin-bottom: 0.5rem;
border-radius: 0.5rem;
background-color: #f9fafb;
border-left: 4px solid transparent;
}
.time-slot.available {
border-left-color: #10B981;
}
.time-slot.reserved {
border-left-color: #EF4444;
}
.time-slot .time {
min-width: 100px;
font-weight: 600;
}
.time-slot .status {
margin-left: 1rem;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
}
.time-slot .status.available {
background-color: #D1FAE5;
color: #065F46;
}
.time-slot .status.reserved {
background-color: #FEE2E2;
color: #991B1B;
}
.time-slot .details {
margin-left: 1.5rem;
font-size: 0.875rem;
color: #6B7280;
}
/* Custom scrollbar */
.modal-body::-webkit-scrollbar {
width: 6px;
}
.modal-body::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.modal-body::-webkit-scrollbar-thumb {
background: #8B0000;
border-radius: 10px;
}
.modal-body::-webkit-scrollbar-thumb:hover {
background: #6d0000;
}
</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 Reservasi</h1>
{{-- <button type="button" onclick="openModal('addReservationModal')" class="bg-[#8B0000] text-white px-4 py-2 rounded-lg hover:bg-red-800">
<i class="fas fa-plus mr-2"></i>Tambah Reservasi
</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="bg-white rounded-lg shadow overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tanggal & Waktu</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pelanggan</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Harga</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($reservations as $reservation)
<tr>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">{{ $reservation->date->format('d M Y') }}</div>
<div class="text-sm text-gray-500">
{{ Carbon\Carbon::parse($reservation->start_time)->format('H:i') }} -
{{ Carbon\Carbon::parse($reservation->end_time)->format('H:i') }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">{{ $reservation->name }}</div>
<div class="text-sm text-gray-500">{{ $reservation->user->email }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
Rp {{ number_format($reservation->total_harga, 0, ',', '.') }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full
{{ $reservation->status === 'confirmed' ? 'bg-green-100 text-green-800' :
($reservation->status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
($reservation->status === 'completed' ? 'bg-blue-100 text-blue-800' : 'bg-red-100 text-red-800')) }}">
{{ ucfirst($reservation->status) }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button onclick="openEditModal('{{ $reservation->id }}')" class="text-blue-600 hover:text-blue-900 mr-3">
<i class="fas fa-edit"></i>
</button>
<button onclick="openDetailModal('{{ $reservation->id }}')" class="text-green-600 hover:text-green-900 mr-3">
<i class="fas fa-eye"></i>
</button>
<form action="{{ route('admin.reservations.destroy', $reservation) }}" 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 reservasi ini?')">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<!-- Modal Tambah Reservasi -->
<div id="addReservationModal" class="modal fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-40">
<div class="bg-white rounded-lg p-8 max-w-2xl w-full m-4" onclick="event.stopPropagation();">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">Tambah Reservasi Baru</h2>
<button type="button" onclick="closeModal('addReservationModal')" class="text-gray-600 hover:text-gray-800">
<i class="fas fa-times"></i>
</button>
</div>
<form action="{{ route('admin.reservations.store') }}" method="POST">
@csrf
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Pelanggan</label>
<select name="user_id" class="w-full border rounded px-3 py-2" required>
<option value="">Pilih Pelanggan</option>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->name }} ({{ $user->email }})</option>
@endforeach
</select>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Tanggal</label>
<div class="relative">
<input type="date" name="date" id="reservation_date"
class="w-full border rounded px-3 py-2"
required min="{{ date('Y-m-d') }}">
</div>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Meja</label>
<select name="meja_id" id="meja_id" class="w-full border rounded px-3 py-2" required>
<option value="">Pilih Meja</option>
@foreach($tables as $table)
<option value="{{ $table->id }}">{{ $table->nomor_meja }} ({{ ucfirst($table->kategori) }} - {{ $table->kapasitas }} Orang)</option>
@endforeach
</select>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Waktu</label>
<div class="flex space-x-2">
<div class="w-1/2">
<input type="text" name="start_time" id="start_time" required value="{{ old('start_time') }}" class="hidden">
<button type="button"
onclick="showTimeModal('start')"
class="w-full px-4 py-2 bg-gray-50 border border-gray-300 rounded-lg text-left"
id="start_time_button">
Waktu Mulai
</button>
</div>
<div class="w-1/2">
<input type="text" name="end_time" id="end_time" required value="{{ old('end_time') }}" class="hidden">
<button type="button"
onclick="showTimeModal('end')"
class="w-full px-4 py-2 bg-gray-50 border border-gray-300 rounded-lg text-left"
id="end_time_button">
Waktu Selesai
</button>
</div>
</div>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Nama Pemesan</label>
<input type="text" name="name" class="w-full border rounded px-3 py-2" required>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Nomor Telepon</label>
<input type="text" name="phone" class="w-full border rounded px-3 py-2" required>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Jumlah Orang</label>
<input type="number" name="people" class="w-full border rounded px-3 py-2" required min="1">
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Total Harga</label>
<input type="number" name="total_harga" class="w-full border rounded px-3 py-2" required min="0" step="1000">
</div>
<div class="col-span-2">
<label class="block text-gray-700 text-sm font-bold mb-2">Catatan</label>
<textarea name="notes" class="w-full border rounded px-3 py-2" rows="3"></textarea>
</div>
</div>
<div class="mt-6 flex justify-end">
<button type="button" onclick="closeModal('addReservationModal')" class="bg-gray-300 text-gray-700 px-4 py-2 rounded mr-2">Batal</button>
<button type="submit" class="bg-[#8B0000] text-white px-4 py-2 rounded">Simpan</button>
</div>
</form>
</div>
</div>
<!-- Modal Edit Status -->
<div id="editStatusModal" class="modal fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg p-8 max-w-md w-full m-4" onclick="event.stopPropagation();">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">Update Reservasi</h2>
<button type="button" onclick="closeModal('editStatusModal')" class="text-gray-600 hover:text-gray-800">
<i class="fas fa-times"></i>
</button>
</div>
<form id="editStatusForm" method="POST">
@csrf
@method('PUT')
<div class="space-y-4">
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Status</label>
<select name="status" id="edit_status" class="w-full border rounded px-3 py-2" required>
<option value="pending">Pending</option>
<option value="confirmed">Confirmed</option>
{{-- <option value="completed">Completed</option>
<option value="cancelled">Cancelled</option> --}}
</select>
</div>
<div>
<label class="block text-gray-700 text-sm font-bold mb-2">Meja</label>
<select name="meja_id" id="edit_meja_id" class="w-full border rounded px-3 py-2" required>
@foreach($tables as $table)
<option value="{{ $table->id }}">
Meja {{ $table->nomor_meja }} ({{ ucfirst($table->kategori) }} - {{ $table->kapasitas }} Orang)
</option>
@endforeach
</select>
</div>
</div>
<div class="mt-6 flex justify-end">
<button type="button" onclick="closeModal('editStatusModal')" class="bg-gray-300 text-gray-700 px-4 py-2 rounded mr-2">Batal</button>
<button type="submit" class="bg-[#8B0000] text-white px-4 py-2 rounded">Simpan Perubahan</button>
</div>
</form>
</div>
</div>
<!-- Modal Detail Reservasi -->
<div id="detailModal" class="modal fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg p-8 max-w-lg w-full m-4" onclick="event.stopPropagation();">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">Detail Reservasi</h2>
<button type="button" onclick="closeModal('detailModal')" class="text-gray-600 hover:text-gray-800">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4" id="reservationDetails">
<div class="grid grid-cols-2 gap-4">
<div>
<p class="text-sm font-semibold text-gray-600">Tanggal</p>
<p class="text-base" id="detail_date"></p>
</div>
<div>
<p class="text-sm font-semibold text-gray-600">Waktu</p>
<p class="text-base" id="detail_time"></p>
</div>
<div>
<p class="text-sm font-semibold text-gray-600">Nama Pemesan</p>
<p class="text-base" id="detail_name"></p>
</div>
<div>
<p class="text-sm font-semibold text-gray-600">Email</p>
<p class="text-base" id="detail_email"></p>
</div>
<div>
<p class="text-sm font-semibold text-gray-600">Nomor Meja</p>
<p class="text-base" id="detail_table"></p>
</div>
<div>
<p class="text-sm font-semibold text-gray-600">Total Harga</p>
<p class="text-base" id="detail_price"></p>
</div>
<div>
<p class="text-sm font-semibold text-gray-600">Status</p>
<p class="text-base"><span id="detail_status" class="px-2 py-1 text-xs rounded-full"></span></p>
</div>
</div>
</div>
<div class="mt-6 flex justify-end">
<button type="button" onclick="closeModal('detailModal')" class="bg-gray-300 text-gray-700 px-4 py-2 rounded">Tutup</button>
</div>
</div>
</div>
<!-- Modal Jadwal -->
<div id="scheduleModal" class="modal fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 max-w-lg w-full m-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold" id="scheduleModalTitle">Jadwal Meja</h2>
<button type="button" onclick="closeScheduleModal()" class="text-gray-600 hover:text-gray-800">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mb-4 space-y-4">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-600" id="selectedDateDisplay"></div>
<select id="schedule_filter" class="border rounded-lg px-3 py-1 text-sm" onchange="updateScheduleView()">
<option value="all">Semua</option>
<option value="available">Tersedia</option>
<option value="reserved">Dipesan</option>
</select>
</div>
</div>
<div id="loadingIndicator" class="hidden">
<div class="flex items-center justify-center p-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-[#8B0000]"></div>
<span class="ml-2">Memuat jadwal...</span>
</div>
</div>
<div class="space-y-2 max-h-[400px] overflow-y-auto" id="scheduleTimeSlots">
<!-- Time slots will be populated here -->
</div>
</div>
</div>
</div>
@push('scripts')
<script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"></script>
<script>
// Make reservations data available globally for the modals
window.reservations = @json($reservations);
document.addEventListener('DOMContentLoaded', function() {
const dateInput = document.getElementById('reservation_date');
const mejaSelect = document.getElementById('meja_id');
const startTimeInput = document.getElementById('start_time');
const endTimeInput = document.getElementById('end_time');
const startTimeButton = document.getElementById('start_time_button');
const endTimeButton = document.getElementById('end_time_button');
const loadingIndicator = document.getElementById('loadingIndicator');
let availableTimeSlots = [];
let currentTimeType = null;
// Add openDetailModal function
window.openDetailModal = function(reservationId) {
const reservation = window.reservations.find(r => r.id == reservationId);
if (!reservation) {
console.error('Reservation not found');
return;
}
// Format date
const date = new Date(reservation.date);
const formattedDate = date.toLocaleDateString('id-ID', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
// Format time
const startTime = reservation.start_time ? reservation.start_time.substring(0, 5) : '';
// Format price
const formattedPrice = new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(reservation.total_harga);
// Update modal content
document.getElementById('detail_date').textContent = formattedDate;
document.getElementById('detail_time').textContent = startTime;
document.getElementById('detail_name').textContent = reservation.name;
document.getElementById('detail_email').textContent = reservation.user.email;
document.getElementById('detail_table').textContent = `Meja ${reservation.meja.nomor_meja} (${reservation.meja.kategori})`;
document.getElementById('detail_price').textContent = formattedPrice;
// Update status with appropriate styling
const statusElement = document.getElementById('detail_status');
statusElement.textContent = reservation.status.charAt(0).toUpperCase() + reservation.status.slice(1);
// Apply status styling
statusElement.className = 'px-2 py-1 text-xs rounded-full ' +
(reservation.status === 'confirmed' ? 'bg-green-100 text-green-800' :
(reservation.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
(reservation.status === 'completed' ? 'bg-blue-100 text-blue-800' : 'bg-red-100 text-red-800')));
// Open the modal
openModal('detailModal');
};
// Update openEditModal function
window.openEditModal = function(reservationId) {
const reservation = window.reservations.find(r => r.id == reservationId);
if (!reservation) {
console.error('Reservation not found');
return;
}
// Set up the form action URL
const form = document.getElementById('editStatusForm');
form.action = `{{ url('/admin/reservations') }}/${reservationId}`;
// Set current status in select
document.getElementById('edit_status').value = reservation.status;
// Set current meja in select
document.getElementById('edit_meja_id').value = reservation.meja_id;
// Open the modal
openModal('editStatusModal');
};
function updateAvailableTimeSlots() {
const date = dateInput.value;
const mejaId = mejaSelect.value;
if (!date || !mejaId) {
return;
}
loadingIndicator.classList.remove('hidden');
document.getElementById('scheduleTimeSlots').innerHTML = '';
fetch(`/admin/reservations/time-slots?date=${date}&meja_id=${mejaId}`)
.then(response => response.json())
.then(data => {
loadingIndicator.classList.add('hidden');
console.log('Server response:', data); // Debug log
if (data.success) {
availableTimeSlots = data.data || [];
displaySchedule();
} else {
throw new Error(data.message || 'Terjadi kesalahan saat memuat jadwal');
}
})
.catch(error => {
console.error('Error:', error);
loadingIndicator.classList.add('hidden');
document.getElementById('scheduleTimeSlots').innerHTML = `
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50">
<p>Error: ${error.message || 'Terjadi kesalahan saat memuat jadwal'}</p>
<p class="mt-2">Silakan coba lagi atau hubungi administrator.</p>
</div>`;
});
}
window.showTimeModal = function(type) {
const date = document.getElementById('reservation_date').value;
const mejaId = document.getElementById('meja_id').value;
if (!date || !mejaId) {
alert('Pilih tanggal dan meja terlebih dahulu');
return;
}
if (type === 'end' && !startTimeInput.value) {
alert('Pilih waktu mulai terlebih dahulu');
return;
}
currentTimeType = type;
document.getElementById('scheduleModalTitle').textContent =
type === 'start' ? 'Pilih Waktu Mulai' : 'Pilih Waktu Selesai';
document.getElementById('selectedDateDisplay').textContent =
new Date(date).toLocaleDateString('id-ID', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
updateAvailableTimeSlots();
openModal('scheduleModal');
}
function displaySchedule() {
const container = document.getElementById('scheduleTimeSlots');
container.innerHTML = '';
const filter = document.getElementById('schedule_filter').value;
console.log('Displaying schedule with filter:', filter); // Debug log
console.log('Available time slots:', availableTimeSlots); // Debug log
let hasDisplayedSlots = false;
// Generate time slots from 10:00 to 22:00
for (let hour = 10; hour < 22; hour++) {
for (const minutes of ['00', '30']) {
const time = `${hour.toString().padStart(2, '0')}:${minutes}`;
// Find the slot in our available slots data
const slot = availableTimeSlots.find(s => s.start_time === time);
if (!slot) continue;
const isAvailable = slot.is_available;
// Skip based on filter
if ((filter === 'available' && !isAvailable) ||
(filter === 'reserved' && isAvailable)) {
continue;
}
// For end time selection, skip times before or equal to start time
if (currentTimeType === 'end' && time <= startTimeInput.value) {
continue;
}
const timeSlot = createTimeSlot(time, isAvailable, slot.end_time_options);
container.appendChild(timeSlot);
hasDisplayedSlots = true;
}
}
if (!hasDisplayedSlots) {
container.innerHTML = `
<div class="p-4 text-sm text-gray-700 bg-gray-100 rounded-lg">
${filter === 'all' ?
'Tidak ada slot waktu yang tersedia untuk tanggal ini.' :
`Tidak ada slot waktu yang ${filter === 'available' ? 'tersedia' : 'dipesan'} untuk tanggal ini.`
}
</div>
`;
}
}
function createTimeSlot(time, isAvailable, endTimeOptions = []) {
const slot = document.createElement('div');
slot.className = `flex items-center justify-between p-3 rounded-lg ${
isAvailable ? 'bg-green-50' : 'bg-red-50'
}`;
const timeInfo = document.createElement('div');
timeInfo.className = 'flex items-center space-x-4';
const timeText = document.createElement('span');
timeText.className = 'text-lg font-medium';
timeText.textContent = time;
const status = document.createElement('span');
status.className = `px-2 py-1 rounded-full text-sm ${
isAvailable ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`;
status.textContent = isAvailable ? 'Tersedia' : 'Dipesan';
timeInfo.appendChild(timeText);
timeInfo.appendChild(status);
slot.appendChild(timeInfo);
if (isAvailable) {
const button = document.createElement('button');
button.className = 'px-4 py-2 bg-[#8B0000] text-white rounded-lg hover:bg-red-800 transition-colors';
button.textContent = 'Pilih';
button.onclick = () => selectTime(time, endTimeOptions);
slot.appendChild(button);
}
return slot;
}
function selectTime(time, endTimeOptions = []) {
if (currentTimeType === 'start') {
startTimeInput.value = time;
startTimeButton.textContent = time;
endTimeInput.value = '';
endTimeButton.textContent = 'Waktu Selesai';
currentEndTimeOptions = endTimeOptions;
} else {
endTimeInput.value = time;
endTimeButton.textContent = time;
}
closeModal('scheduleModal');
}
window.updateScheduleView = function() {
displaySchedule();
}
window.closeScheduleModal = function() {
closeModal('scheduleModal');
}
// Modal handling functions
window.openModal = function(modalId) {
const modal = document.getElementById(modalId);
modal.classList.remove('hidden');
modal.classList.add('flex');
}
window.closeModal = function(modalId) {
const modal = document.getElementById(modalId);
modal.classList.add('hidden');
modal.classList.remove('flex');
}
// Form validation
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
const startTime = startTimeInput.value;
const endTime = endTimeInput.value;
if (!startTime || !endTime) {
alert('Mohon pilih waktu mulai dan selesai');
return;
}
if (endTime <= startTime) {
alert('Waktu selesai harus setelah waktu mulai');
return;
}
this.submit();
});
// Initialize time inputs if there are existing values
if (startTimeInput.value) {
startTimeButton.textContent = startTimeInput.value;
}
if (endTimeInput.value) {
endTimeButton.textContent = endTimeInput.value;
}
// Add event listeners for date and table selection
dateInput?.addEventListener('change', () => {
// Reset time selections when date changes
startTimeInput.value = '';
endTimeInput.value = '';
startTimeButton.textContent = 'Waktu Mulai';
endTimeButton.textContent = 'Waktu Selesai';
});
mejaSelect?.addEventListener('change', () => {
// Reset time selections when table changes
startTimeInput.value = '';
endTimeInput.value = '';
startTimeButton.textContent = 'Waktu Mulai';
endTimeButton.textContent = 'Waktu Selesai';
});
});
</script>
@endpush
@endsection