Files
PyRIGS/RIGS/templates/calendar.html
Joe Banks a29384a314 Bootstrap 5 & remove jQuery (#661)
* build: move frontend assets to Bootstrap 5.3 and Tom Select

* feat: migrate templates and scripts to Bootstrap 5, dropping jQuery

Templates
- Rename Bootstrap 4 classes and data attributes (data-bs-*, ms-/me-/ps-/pe-,
  text-start/end, form-group -> mb-3, input-group wrappers removed, badges,
  custom controls -> form-check/form-select, media -> d-flex, btn-close, ...)
- Dark mode now uses Bootstrap 5.3 colour modes (data-bs-theme on <html>),
  set from <head> to avoid a flash of the light theme
- Remove the Bootstrap 4 /bootstrap/ demo page
- Contextual table classes (table-success etc.) still work as plain
  backgrounds for the rigboard key

JavaScript
- Remove jQuery: src.js/interaction.js and every inline template script are
  rewritten in plain JS on top of small helpers (helpers.js, bundled with
  Bootstrap into jpop.js so inline scripts can use them)
- Replace bootstrap-select/ajax-bootstrap-select with Tom Select. The remote
  search now sends the `q` parameter and reads `text`, which is what the
  API actually uses (the old picker sent `term` and read `label`)
- HTML fragments are fetched with an X-Requested-With header so views keep
  rendering their modal variants, and fetched scripts are re-executed
- Remove the Bootstrap 4 plugin script includes

* fix: stop rigboard header buttons stretching to full width under Bootstrap 5

* fix: restore dark-mode brand colours and contextual table colours under Bootstrap 5

* fix: brighten dark-mode links and make coloured card outlines visible

* fix: calendar layout and grid borders, navbar search button width, guard missing modal links

* fix: readable text in dark-mode Tom Select inputs and placeholders, restore form-control column widths

* build: split the Dockerfile into cached stages with prod and dev targets

* deploy: make compose.yml development-only with live reload, and document it

* fix: keep badge text colour for links inside badges

* fix: restore table-bordered grid lines in dark mode

* fix: include Bootstrap's progress styles and keep progress labels readable
2026-10-02 16:28:11 +01:00

176 lines
7.9 KiB
HTML

{% extends 'base_rigs.html' %}
{% load static %}
{% block title %}Calendar{% endblock %}
{% block css %}
<link href="{% static 'css/main.css' %}" rel='stylesheet' />
{% endblock %}
{% block js %}
<script src="{% static 'js/moment.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
<script>
viewToUrl = {
'timeGridWeek':'week',
'timeGridDay':'day',
'dayGridMonth':'month'
}
viewFromUrl = {
'week':'timeGridWeek',
'day':'timeGridDay',
'month':'dayGridMonth'
}
var calendar; //Need to access it from jquery ready
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
calendar = new FullCalendar.Calendar(calendarEl, {
firstDay: 1,
themeSystem: 'standard',
aspectRatio: 1.5,
eventTimeFormat: {
'hour': '2-digit',
'minute': '2-digit',
'hour12': false
},
headerToolbar: false,
editable: false,
dayMaxEventRows: true, // allow "more" link when too many events
events: function(fetchInfo, successCallback, failureCallback) {
var params = new URLSearchParams({
start: moment(fetchInfo.startStr).format("YYYY-MM-DD[T]HH:mm:ss"),
end: moment(fetchInfo.endStr).format("YYYY-MM-DD[T]HH:mm:ss")
});
fetch('/api/event?' + params, {credentials: 'same-origin'})
.then(function (response) { return response.json(); })
.then(function (doc) {
var events = [];
colours = {
'Provisional': '#FFE89B',
'Confirmed': '#3AB54A' ,
'Booked': '#3AB54A' ,
'Cancelled': 'grey' ,
'non-rig': '#25AAE2'
};
doc.forEach(function (item) {
end = item.latest
allDay = false
if(end.indexOf("T") < 0){ //If latest does not contain a time
end = moment(end + " 23:59").format("YYYY-MM-DD[T]HH:mm:ss")
allDay = true
}
thisEvent = {
'start': item.earliest,
'end': end,
'className': 'modal-href',
'title': item.title,
'url': item.url,
'allDay': allDay
}
if(item.is_rig===true || item.status === "Cancelled"){
thisEvent['color'] = colours[item.status];
}else{
thisEvent['color'] = colours['non-rig'];
}
events.push(thisEvent);
});
successCallback(events);
})
.catch(failureCallback);
},
datesSet: function(info) {
var view = info.view;
// Set the title of the view
document.getElementById('calendar-header').textContent = view.title;
// Enable/Disable "Today" button as required
//Today is within the current view
document.getElementById('today-button').disabled = moment().isBetween(view.currentStart, view.currentEnd);
// Set active view select button
var buttons = {
dayGridMonth: document.getElementById('month-button'),
timeGridWeek: document.getElementById('week-button'),
timeGridDay: document.getElementById('day-button')
};
Object.keys(buttons).forEach(function (type) {
buttons[type].classList.toggle('active', type === view.type);
});
history.replaceState(null,null,"{% url 'web_calendar' %}"+viewToUrl[view.type]+'/'+moment(view.currentStart).format('YYYY-MM-DD')+'/');
}
});
calendar.render();
});
ready(function() {
// set some button listeners
var click = function (id, fn) { document.getElementById(id).addEventListener('click', fn); };
click('next-button', function(){ calendar.next(); });
click('prev-button', function(){ calendar.prev(); });
click('today-button', function(){ calendar.today(); });
click('month-button', function(){ calendar.changeView('dayGridMonth'); });
click('week-button', function(){ calendar.changeView('timeGridWeek'); });
click('day-button', function(){ calendar.changeView('timeGridDay'); });
var dateInput = document.getElementById('go-to-date-input');
var dateButton = document.getElementById('go-to-date-button');
dateInput.addEventListener('change', function(){
dateButton.disabled = !moment(dateInput.value).isValid();
});
dateButton.addEventListener('click', function(){
day = moment(dateInput.value);
if(day.isValid()){
calendar.gotoDate(day.format("YYYY-MM-DD"));
} else{
alert('Invalid Date');
}
});
{% if view and date %}
// Go to the initial settings, if they're valid
view = viewFromUrl['{{view}}'];
calendar.changeView(view);
day = moment('{{date}}');
if(day.isValid()){
calendar.gotoDate(day.format("YYYY-MM-DD"));
} else{
console.log('Supplied date is invalid - using default')
}
{% endif %}
});
</script>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12 d-flex flex-wrap justify-content-between align-items-center">
<div>
<span id="calendar-header" class="h2"></span>
</div>
<div class="d-flex align-items-center flex-wrap gap-2 btn-page my-3">
<div class="input-group w-auto">
<input type="date" class="form-control" id="go-to-date-input" placeholder="Go to date...">
<button class="btn btn-success" id="go-to-date-button" type="button" disabled>Go!</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" id="today-button">Today</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-secondary" id="prev-button"><span class="fas fa-chevron-left"></span></button>
<button type="button" class="btn btn-secondary" id="next-button"><span class="fas fa-chevron-right"></span></button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-light" id="month-button">Month</button>
<button type="button" class="btn btn-light" id="week-button">Week</button>
<button type="button" class="btn btn-light" id="day-button">Day</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div id='calendar'></div>
</div>
</div>
{% endblock %}