flask-base/templates/book_resource.html

236 lines
9.1 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="container">
<h2>Book {{ resource.name }}</h2>
{% if resource.image_url %}
<img src="{{ url_for('static', filename=resource.image_url) }}" alt="{{ resource.name }}" class="img-fluid mb-4 rounded" style="max-width: 100%; max-height: 400px; object-fit: cover; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
{% endif %}
<p class="text-muted">{{ resource.description }}</p>
<div class="row">
<div class="col-md-8">
<div id="calendar"></div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Create Booking</h5>
<form method="POST" id="bookingForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="mb-3">
<label for="start_time" class="form-label">Start Time</label>
<input type="datetime-local" class="form-control" id="start_time" name="start_time" required readonly>
</div>
<div class="mb-3">
<label for="end_time" class="form-label">End Time</label>
<input type="datetime-local" class="form-control" id="end_time" name="end_time" required readonly>
</div>
<div class="mb-3">
<label for="purpose" class="form-label">Purpose</label>
<textarea class="form-control" id="purpose" name="purpose" rows="3" placeholder="e.g., Client meeting, Team call, etc."></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">Book Now</button>
<button type="button" class="btn btn-secondary w-100 mt-2" id="clearSelection">Clear Selection</button>
</form>
<div class="mt-3">
<div class="alert alert-info" role="alert">
<strong>Booking Rules:</strong><br>
- Bookings can be made up to 6 months in advance<br>
- Only one booking per resource at a time (no overlapping slots)<br>
- Click on the calendar to select start time<br>
- Click again to select end time<br>
- Green events are your bookings<br>
- Red events are others' bookings
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css' rel='stylesheet' />
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js'></script>
<style>
#calendar {
max-width: 100%;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.fc-event {
cursor: pointer;
}
.fc-timegrid-slot {
cursor: pointer;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var startTimeInput = document.getElementById('start_time');
var endTimeInput = document.getElementById('end_time');
var purposeInput = document.getElementById('purpose');
var clearButton = document.getElementById('clearSelection');
var bookingForm = document.getElementById('bookingForm');
var selectedStart = null;
var selectedEnd = null;
var tempEvent = null;
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'timeGridWeek',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
weekends: false,
slotMinTime: '06:00:00',
slotMaxTime: '22:00:00',
allDaySlot: false,
selectable: true,
selectMirror: true,
nowIndicator: true,
slotDuration: '00:30:00',
height: 'auto',
minDate: new Date(), // Cannot book before today
maxDate: new Date(Date.now() + 182 * 24 * 60 * 60 * 1000), // Cannot book more than 6 months ahead
events: {
url: '/api/resource-bookings/{{ resource.id }}',
failure: function() {
alert('Failed to load bookings');
}
},
dateClick: function(info) {
// If we haven't selected start yet, or we're starting over
if (selectedStart === null) {
selectedStart = info.date;
selectedEnd = new Date(info.date.getTime() + 60 * 60 * 1000); // Default 1 hour
// Update inputs
startTimeInput.value = formatDateTimeLocal(selectedStart);
endTimeInput.value = formatDateTimeLocal(selectedEnd);
// Show temporary event
if (tempEvent) {
tempEvent.remove();
}
tempEvent = calendar.addEvent({
title: 'New Booking (select end time)',
start: selectedStart,
end: selectedEnd,
color: '#6c757d',
editable: false
});
} else {
// Selecting end time
if (info.date > selectedStart) {
selectedEnd = info.date;
endTimeInput.value = formatDateTimeLocal(selectedEnd);
// Update temporary event
if (tempEvent) {
tempEvent.remove();
}
tempEvent = calendar.addEvent({
title: 'New Booking (ready to submit)',
start: selectedStart,
end: selectedEnd,
color: '#0d6efd',
editable: false
});
// Focus on purpose field
purposeInput.focus();
} else {
alert('End time must be after start time. Please select a later time.');
}
}
},
select: function(info) {
selectedStart = info.start;
selectedEnd = info.end;
startTimeInput.value = formatDateTimeLocal(selectedStart);
endTimeInput.value = formatDateTimeLocal(selectedEnd);
if (tempEvent) {
tempEvent.remove();
}
tempEvent = calendar.addEvent({
title: 'New Booking (ready to submit)',
start: selectedStart,
end: selectedEnd,
color: '#0d6efd',
editable: false
});
// Set default purpose based on resource type if not already set
if (!purposeInput.value) {
purposeInput.value = getDefaultValue(resource.resource_type);
}
purposeInput.focus();
}
});
calendar.render();
// Clear selection button
clearButton.addEventListener('click', function() {
selectedStart = null;
selectedEnd = null;
startTimeInput.value = '';
endTimeInput.value = '';
purposeInput.value = '';
if (tempEvent) {
tempEvent.remove();
tempEvent = null;
}
});
// Set default purpose on form submit
bookingForm.addEventListener('submit', function(e) {
if (!selectedStart || !selectedEnd) {
e.preventDefault();
alert('Please select a time slot on the calendar');
return false;
}
// Set default purpose if empty
if (!purposeInput.value.trim()) {
purposeInput.value = getDefaultValue(resource.resource_type);
}
});
// Get default purpose based on resource type
function getDefaultValue(resourceType) {
const purposeMap = {
'Work Office': 'Office work',
'Meeting Room': 'Meeting',
'Phone Booth': 'Phone call',
'Focus Room': 'Focus work',
'Collaborative Space': 'Collaboration',
'Conference Room': 'Conference'
};
return purposeMap[resourceType] || '';
}
function formatDateTimeLocal(date) {
var year = date.getFullYear();
var month = String(date.getMonth() + 1).padStart(2, '0');
var day = String(date.getDate()).padStart(2, '0');
var hours = String(date.getHours()).padStart(2, '0');
var minutes = String(date.getMinutes()).padStart(2, '0');
return year + '-' + month + '-' + day + 'T' + hours + ':' + minutes;
}
});
</script>
{% endblock %}