MIF_E31222882/resources/views/pages/reschedule.blade.php

239 lines
12 KiB
PHP

@extends('layouts.main')
@section('content')
<div class="min-h-96 mx-4 md:w-3/4 md:mx-auto py-8">
<div id="notification-container" class="fixed top-4 right-4 z-50 space-y-2"></div>
<h1 class="text-2xl font-bold mb-6">Reschedule Booking</h1>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-lg font-semibold mb-4">Detail Booking Saat Ini</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<p class="text-sm text-gray-500">Venue</p>
<p class="font-medium">{{ $booking->table->venue->name }}</p>
</div>
<div>
<p class="text-sm text-gray-500">Meja</p>
<p class="font-medium">{{ $booking->table->name }} ({{ $booking->table->brand }})</p>
</div>
<div>
<p class="text-sm text-gray-500">Tanggal & Waktu</p>
<p class="font-medium">
{{ \Carbon\Carbon::parse($booking->start_time)->format('d M Y') }},
{{ \Carbon\Carbon::parse($booking->start_time)->format('H:i') }} -
{{ \Carbon\Carbon::parse($booking->end_time)->format('H:i') }}
</p>
</div>
<div>
<p class="text-sm text-gray-500">Durasi</p>
<p class="font-medium">{{ $duration }} Jam</p>
</div>
</div>
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<div class="flex items-start">
<div class="mr-3 text-yellow-500"><i class="fa-solid fa-exclamation-circle text-xl"></i></div>
<div>
<h3 class="font-semibold text-yellow-700">Perhatian</h3>
<p class="text-yellow-700 text-sm">
Reschedule dapat dilakukan selama minimal 1 jam sebelum jadwal booking<br>
Setiap booking hanya dapat di-reschedule maksimal 1 kali<br>
Durasi booking akan tetap sama ({{ $duration }} jam)<br>
Setelah reschedule, jadwal lama akan digantikan dengan jadwal baru
</p>
</div>
</div>
</div>
</div>
<div x-data="rescheduleForm" class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-lg font-semibold mb-4">Pilih Jadwal Baru</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="date-picker" class="block text-sm font-medium text-gray-700 mb-1">Tanggal Operasional:</label>
<input type="date" id="date-picker" x-model="selectedDate" class="w-full border p-2 rounded-lg bg-gray-100 cursor-not-allowed" disabled>
</div>
<div>
<label for="table-picker" class="block text-sm font-medium text-gray-700 mb-1">Pilih Meja:</label>
<select id="table-picker" x-model="selectedTableId" class="w-full border p-2 rounded-lg" @change="fetchSchedules">
<option value="">-- Pilih Meja --</option>
<template x-for="table in tables" :key="table.id">
<option :value="table.id" x-text="table.name + ' (' + table.brand + ')'"></option>
</template>
</select>
</div>
</div>
<div class="mt-6" x-show="selectedDate && selectedTableId">
<label class="block text-sm font-medium text-gray-700 mb-1">Pilih Jam Mulai:</label>
<div x-show="!isLoadingSchedules" class="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2">
<template x-for="hour in availableHours" :key="hour">
<button class="py-2 px-1 rounded-lg text-sm font-medium transition duration-150"
:class="getSlotClass(hour)" :disabled="!isSlotAvailable(hour)" @click="selectedStartHour = hour"
x-text="hour + ':00'"></button>
</template>
</div>
<div x-show="isLoadingSchedules" class="text-center text-gray-500 py-4">
Memeriksa jadwal...
</div>
</div>
<div class="mt-8 flex justify-end">
<a href="{{ route('booking.history') }}" class="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg mr-3 hover:bg-gray-300">Batal</a>
<button @click="submitReschedule" :disabled="!canSubmit || isSubmitting"
class="text-white px-4 py-2 rounded-lg flex items-center justify-center" :class="canSubmit ? 'bg-green-600 hover:bg-green-700' : 'bg-green-300 cursor-not-allowed'">
<span x-show="!isSubmitting">Konfirmasi Reschedule</span>
<span x-show="isSubmitting">
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div> Memproses...
</span>
</button>
</div>
</div>
</div>
<script>
function showNotification(message, type = 'info') { /* ... fungsi notifikasi tidak berubah ... */ }
document.addEventListener('alpine:init', () => {
Alpine.data('rescheduleForm', () => ({
// PERUBAHAN 2: Inisialisasi data yang bersih & benar
venue: @json($venue),
bookingId: {{ $booking->id }},
bookingDuration: {{ $duration }},
tables: @json($venue->tables),
selectedDate: '{{ $operational_date_string }}',
selectedTableId: {{ $booking->table_id }},
selectedStartHour: null,
bookedSchedules: [],
isLoadingSchedules: true,
isSubmitting: false,
// PERUBAHAN 3: Fungsi init() disederhanakan
init() {
this.fetchSchedules();
},
// Semua fungsi di bawah ini sudah benar & tidak perlu diubah lagi
get openHour() { return parseInt(this.venue.open_time.split(':')[0]); },
get closeHour() { return parseInt(this.venue.close_time.split(':')[0]); },
get isOvernight() { return this.venue.is_overnight; },
get availableHours() {
let hours = [];
if (this.isOvernight) {
for (let i = this.openHour; i < 24; i++) hours.push(i.toString().padStart(2, '0'));
for (let i = 0; i <= this.closeHour; i++) hours.push(i.toString().padStart(2, '0'));
} else {
for (let i = this.openHour; i <= this.closeHour; i++) hours.push(i.toString().padStart(2, '0'));
}
return hours;
},
get canSubmit() {
return this.selectedStartHour !== null && !this.isLoadingSchedules;
},
async fetchSchedules() {
if (!this.selectedDate || !this.selectedTableId) return;
this.isLoadingSchedules = true;
this.bookedSchedules = [];
this.selectedStartHour = null;
try {
const response = await fetch(`/booking/reschedule/check-availability?table_id=${this.selectedTableId}&date=${this.selectedDate}&booking_id=${this.bookingId}`);
if (!response.ok) throw new Error('Failed to fetch schedules');
this.bookedSchedules = await response.json();
} catch (error) {
console.error(error);
} finally {
this.isLoadingSchedules = false;
}
},
isSlotAvailable(hour) {
const startHourInt = parseInt(hour);
const endHourInt = startHourInt + this.bookingDuration;
if (this.isOvernight) {
if (startHourInt >= this.openHour && endHourInt > (24 + this.closeHour)) return false;
if (startHourInt < this.openHour && endHourInt > this.closeHour) return false;
} else {
if (endHourInt > this.closeHour) return false;
}
const now = new Date();
const selectedDateTime = new Date(`${this.selectedDate}T${hour}:00:00`);
if (this.isOvernight && startHourInt < this.openHour) {
selectedDateTime.setDate(selectedDateTime.getDate() + 1);
}
if (selectedDateTime <= now) {
return false;
}
for (const schedule of this.bookedSchedules) {
const scheduleStart = parseInt(schedule.start.split(':')[0]);
const scheduleEnd = parseInt(schedule.end.split(':')[0]);
const isOvernightBooking = scheduleEnd < scheduleStart;
if (isOvernightBooking) {
if ((startHourInt >= scheduleStart || startHourInt < scheduleEnd) && (endHourInt > scheduleStart || endHourInt <= scheduleEnd)) return false;
} else {
if (startHourInt < scheduleEnd && endHourInt > scheduleStart) return false;
}
}
return true;
},
getSlotClass(hour) {
if (!this.isSlotAvailable(hour)) {
return 'bg-gray-200 text-gray-400 cursor-not-allowed';
}
if (this.selectedStartHour === hour) {
return 'bg-blue-600 text-white shadow-md';
}
return 'bg-gray-100 hover:bg-gray-200 text-gray-800';
},
async submitReschedule() {
if (!this.canSubmit) return;
this.isSubmitting = true;
const startDateTime = new Date(`${this.selectedDate}T${this.selectedStartHour}:00:00`);
if (this.isOvernight && parseInt(this.selectedStartHour) < this.openHour) {
startDateTime.setDate(startDateTime.getDate() + 1);
}
const endDateTime = new Date(startDateTime.getTime());
endDateTime.setHours(endDateTime.getHours() + this.bookingDuration);
const formatForServer = (date) => {
const pad = (num) => num.toString().padStart(2, '0');
const year = date.getFullYear();
const month = pad(date.getMonth() + 1);
const day = pad(date.getDate());
const hours = pad(date.getHours());
const minutes = pad(date.getMinutes());
const seconds = pad(date.getSeconds());
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
try {
const response = await fetch(`/booking/${this.bookingId}/reschedule`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content },
body: JSON.stringify({
table_id: this.selectedTableId,
start_time: formatForServer(startDateTime),
end_time: formatForServer(endDateTime),
}),
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Booking berhasil di-reschedule!', 'success');
setTimeout(() => window.location.href = result.redirect, 1500);
} else { throw new Error(result.message || 'Gagal reschedule.'); }
} catch (error) {
showNotification(error.message, 'error');
} finally {
this.isSubmitting = false;
}
}
}));
});
</script>
@endsection