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
This commit is contained in:
Joe Banks
2026-10-02 16:28:11 +01:00
committed by GitHub
parent 08171767a0
commit a29384a314
124 changed files with 1538 additions and 4137 deletions

View File

@@ -3,7 +3,7 @@
<div class="card-header">Associated Organisations</div>
<ul class="list-group list-group-flush">
{% for organisation,count in object.organisations %}
<a class="list-group-item list-group-item-action" href="{% url 'organisation_detail' organisation.pk %}">{{ organisation.pk|stringformat:"05d" }} | {{ organisation.name }} <span class="badge badge-secondary" title="{{count}} events with {{object.name}} for {{organisation.name}}">{{count}}</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'organisation_detail' organisation.pk %}">{{ organisation.pk|stringformat:"05d" }} | {{ organisation.name }} <span class="badge text-bg-secondary" title="{{count}} events with {{object.name}} for {{organisation.name}}">{{count}}</span></a>
{% endfor %}
</ul>
</div>

View File

@@ -3,7 +3,7 @@
<div class="card-header">Associated People</div>
<div class="list-group list-group-flush">
{% for person,count in object.persons %}
<a class="list-group-item list-group-item-action" href="{% url 'person_detail' person.pk %}">{{ person.pk|stringformat:"05d" }} | {{ person.name }} <span class="badge badge-secondary" title="{{count}} events with {{person.name}} for {{object.name}}">{{count}}</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'person_detail' person.pk %}">{{ person.pk|stringformat:"05d" }} | {{ person.name }} <span class="badge text-bg-secondary" title="{{count}} events with {{person.name}} for {{object.name}}">{{count}}</span></a>
{% empty %}
<div class="card-body">None</div>
{% endfor %}

View File

@@ -28,7 +28,7 @@
<td>{% if crew.end_time %}
{{crew.end_time}}
{% else %}
<span class="text-success fas fa-clock" data-toggle="tooltip"
<span class="text-success fas fa-clock" data-bs-toggle="tooltip"
title="This person is currently checked into this event"></span>{% endif %}
</td>
<td>{% if crew.end_time %}

View File

@@ -1,4 +1,4 @@
<h5 class="py-3"><a class="btn btn-info" data-toggle="collapse" href="#values" aria-expanded="false" aria-controls="values">View Threshold Values</a></h5>
<h5 class="py-3"><a class="btn btn-info" data-bs-toggle="collapse" href="#values" aria-expanded="false" aria-controls="values">View Threshold Values</a></h5>
<div class="row collapse" id="values">
<div class="col-md-6 col-sm-12">
<table class="table table-bordered">

View File

@@ -11,7 +11,7 @@
{{ object.venue|namewithnotes:'venue_detail' }}
</a>
{% endif %}
{% if object.parking_and_access or object.riskassessment.parking_and_access %}<span class="badge badge-warning">Additional Access Requirements</span>{% endif %}
{% if object.parking_and_access or object.riskassessment.parking_and_access %}<span class="badge text-bg-warning">Additional Access Requirements</span>{% endif %}
</dd>
{% if object.venue %}
<dt class="col-sm-6">Venue Notes</dt>
@@ -85,7 +85,7 @@
{% else %}
<a href="{% url 'event_thread' object.pk %}" class="btn btn-primary" title="Create Forum Thread" target="_blank"><span
class="fas fa-plus"></span> <span
class="hidden-xs">Create Forum Thread</span></a>
class="d-none d-sm-inline">Create Forum Thread</span></a>
{% endif %}
</dl>
</div>

View File

@@ -5,7 +5,7 @@
<div class="card-header {{ header_bg }} {{ header_text }} py-3">
<div class="d-flex justify-content-between align-items-center">
<span class="d-flex align-items-center">
<h5 class="mb-0 mr-3">
<h5 class="mb-0 me-3">
<a href="{% url 'event_detail' event.pk %}"
class="{{ header_text }} text-decoration-underline fw-bold">
<strong>{{ event.display_id }}</strong> - {{ event.name }}
@@ -106,7 +106,7 @@
<div class="d-flex align-items-center mt-1">
{% if event.mic %}
<img src="{{ event.mic.profile_picture }}" alt="{{ event.mic.name }}"
class="rounded-circle mr-1" width="32" height="32">
class="rounded-circle me-1" width="32" height="32">
<span>
{% if perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">

View File

@@ -1,7 +1,7 @@
{% if object.event_size == 2 %}
<span class="badge badge-danger p-2 my-3">Large Event</span>
<span class="badge text-bg-danger p-2 my-3">Large Event</span>
{% elif object.event_size == 1 %}
<span class="badge badge-warning p-2 my-3">Medium Event</span>
<span class="badge text-bg-warning p-2 my-3">Medium Event</span>
{%else%}
<span class="badge badge-success p-2 my-3">Small Event</span>
<span class="badge text-bg-success p-2 my-3">Small Event</span>
{%endif%}

View File

@@ -1,51 +1,51 @@
<div id="event_status">
<span class="badge badge-{% if event.confirmed %}success{% elif event.cancelled %}dark{% else %}warning{% endif %}">Status: {{ event.get_status_display }}</span>
<span class="badge text-bg-{% if event.confirmed %}success{% elif event.cancelled %}dark{% else %}warning{% endif %}">Status: {{ event.get_status_display }}</span>
{% if event.is_rig %}
{% if event.sum_total > 0 %}
{% if event.purchase_order %}
<span class="badge badge-success">PO: Received</span>
<span class="badge text-bg-success">PO: Received</span>
{% elif event.authorised %}
<span class="badge badge-success">Authorisation: Complete <span class="fas fa-check"></span></span>
<span class="badge text-bg-success">Authorisation: Complete <span class="fas fa-check"></span></span>
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
<span class="badge badge-warning"> Authorisation: Issue <span class="fas fa-exclamation-circle"></span></span>
<span class="badge text-bg-warning"> Authorisation: Issue <span class="fas fa-exclamation-circle"></span></span>
{% elif event.auth_request_to %}
<span class="badge badge-info"> Authorisation: Sent <span class="fas fa-paper-plane"></span></span>
<span class="badge text-bg-info"> Authorisation: Sent <span class="fas fa-paper-plane"></span></span>
{% else %}
<span class="badge badge-danger">Authorisation: <span class="fas fa-times"></span></span>
<span class="badge text-bg-danger">Authorisation: <span class="fas fa-times"></span></span>
{% endif %}
{% endif %}
{% if not event.dry_hire %}
{% if event.riskassessment %}
<a href="{{ event.riskassessment.get_absolute_url }}"><span class="badge badge-success">RA: <span class="fas fa-check{% if event.riskassessment.reviewed_by %}-double{%endif%}"></span></a>
<a href="{{ event.riskassessment.get_absolute_url }}"><span class="badge text-bg-success">RA: <span class="fas fa-check{% if event.riskassessment.reviewed_by %}-double{%endif%}"></span></a>
{% else %}
<span class="badge badge-danger">RA: <span class="fas fa-times"></span></span>
<span class="badge text-bg-danger">RA: <span class="fas fa-times"></span></span>
{% endif %}
{% if event.has_checklist %}
<span class="badge badge-success">Checklist: <span class="fas fa-check"></span> {% if event.checklists.count > 1 %}({{event.checklists.count}}){% endif %}</span>
<span class="badge text-bg-success">Checklist: <span class="fas fa-check"></span> {% if event.checklists.count > 1 %}({{event.checklists.count}}){% endif %}</span>
{% else %}
<span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span>
<span class="badge text-bg-danger">Checklist: <span class="fas fa-times"></span></span>
{% endif %}
{% if event.has_power %}
<span class="badge badge-success">Power Record: <span class="fas fa-check"></span> {% if event.power_tests.count > 1 %}({{event.power_tests.count}}){% endif %}</span>
<span class="badge text-bg-success">Power Record: <span class="fas fa-check"></span> {% if event.power_tests.count > 1 %}({{event.power_tests.count}}){% endif %}</span>
{% else %}
<span class="badge badge-danger">Power Record: <span class="fas fa-times"></span></span>
<span class="badge text-bg-danger">Power Record: <span class="fas fa-times"></span></span>
{% endif %}
{% endif %}
{% if perms.RIGS.view_invoice %}
{% if event.invoice %}
{% if event.invoice.void %}
<span class="badge badge-secondary">Invoice: Void</span>
<span class="badge text-bg-secondary">Invoice: Void</span>
{% elif event.invoice.is_closed %}
<span class="badge badge-success">Invoice: Paid</span>
<span class="badge text-bg-success">Invoice: Paid</span>
{% else %}
<span class="badge badge-warning">Invoice: Outstanding</span>
<span class="badge text-bg-warning">Invoice: Outstanding</span>
{% endif %}
{% else %}
<span class="badge badge-info">Invoice: Not Generated</span>
<span class="badge text-bg-info">Invoice: Not Generated</span>
{% endif %}
{% endif %}
{% if event.parking_and_access %}
<span class="badge badge-warning">Addititional Access Requirements</span>
<span class="badge text-bg-warning">Addititional Access Requirements</span>
{% endif %}
{% endif %}
</div>

View File

@@ -14,7 +14,7 @@
<br/>
{% endfor %}
<a href="{% url 'event_ec' event.pk %}" class="btn btn-info mt-2"><span class="fas fa-paperclip"></span> <span
class="hidden-xs">Create</span></a>
class="d-none d-sm-inline">Create</span></a>
<hr>
<h5>Power Test Records:</h5>
{% for record in event.power_tests.all %}
@@ -22,6 +22,6 @@
<br/>
{% endfor %}
<a href="{% url 'event_pt' event.pk %}" class="btn btn-info mt-2"><span class="fas fa-paperclip"></span> <span
class="hidden-xs">Create</span></a>
class="d-none d-sm-inline">Create</span></a>
</div>
</div>

View File

@@ -7,5 +7,5 @@
{% include 'partials/review_status.html' %}
{% elif event != None %}
<a href="{% url create event.pk %}" class="btn btn-info"><span class="fas fa-paperclip"></span> <span
class="hidden-xs">Create</span></a>
class="d-none d-sm-inline">Create</span></a>
{% endif %}

View File

@@ -3,29 +3,27 @@
<div class="modal-content">
<div class="modal-header">
<h4>{{ object.name|default:"New Event" }}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="item-form">
<div class="modal-body">
<div class="form-group form-row">
<div class="mb-3 row g-2">
<label for="item_name" class="col-sm-2 col-form-label">Item Name</label>
<div class="col-sm-10">
<input type="text" placeholder="Item Name" class="form-control" required maxlength="255"
id="item_name"/>
</div>
</div>
<div class="form-group form-row" data-toggle="tooltip" title="A detailed description of the kit. MD enabled.">
<div class="mb-3 row g-2" data-bs-toggle="tooltip" title="A detailed description of the kit. MD enabled.">
<label for="item_description" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-10">
<textarea type="text" placeholder="Description" class="form-control md-enabled"
id="item_description" rows="8"></textarea>
</div>
</div>
<div class="form-row">
<div class="row g-2">
<div class="col-sm-6">
<div class="form-group form-row">
<div class="mb-3 row g-2">
<label for="item_quantity" class="col-sm-4 col-form-label">Quantity</label>
<div class="col-sm-8">
<input type="number" placeholder="Quantity" class="form-control" required
@@ -34,12 +32,12 @@
</div>
</div>
<div class="col-sm-6">
<div class="form-group form-row">
<div class="mb-3 row g-2">
<label for="item_cost" class="col-sm-4 col-form-label">Cost</label>
<div class="col-sm-8">
<div class="input-group">
<div class="input-group-prepend"><span class="input-group-text">£</span></div>
<span class="input-group-text">£</span>
<input type="number" placeholder="Cost" class="form-control" required
min="-99999999.99" max="99999999.99" step="0.01"
id="item_cost"/>

View File

@@ -14,11 +14,11 @@
<td>£<span class="sub-total" data-subtotal="{{item.total_cost}}">{{item.total_cost|floatformat:2}}</span></td>
{% endif %}
{% if edit %}
<td class="vert-align text-right">
<td class="vert-align text-end">
<div class="btn-group" role="group" aria-label="Action buttons">
<button type="button" class="item-edit btn btn-sm btn-warning"
data-pk="{{item.pk}}"
data-toggle="modal" data-target="#itemModal">
data-modal-target="#itemModal">
<span class="fas fa-edit"></span>
</button>
<button type="button" class="item-delete btn btn-sm btn-danger"

View File

@@ -11,10 +11,9 @@
<th scope="col">Sub-total</th>
{% endif %}
{% if edit %}
<th scope="col" class="text-right align-self-start">
<th scope="col" class="text-end align-self-start">
<button type="button" class="btn btn-success btn-sm item-add"
data-toggle="modal"
data-target="#itemModal">
data-modal-target="#itemModal">
<i class="fas fa-plus"></i> Add Item
</button>
</th>
@@ -65,11 +64,11 @@
<td class="quantity"></td>
<td>£<span class="sub-total"></span></td>
{% if edit %}
<td class="vert-align text-right">
<td class="vert-align text-end">
<div class="btn-group" role="group" aria-label="Action buttons">
<button type="button" class="item-edit btn btn-sm btn-warning"
data-pk="{{item.pk}}"
data-toggle="modal" data-target="#itemModal">
data-modal-target="#itemModal">
<span class="fas fa-edit"></span>
</button>
<button type="button" class="item-delete btn btn-sm btn-danger"

View File

@@ -94,7 +94,7 @@
}
</style>
<div id="event_table">
<div class="eventgrid grid-header font-weight-bold">
<div class="eventgrid grid-header fw-bold">
<div id="event_number">#</div>
<div id="event_dates">Dates & Times</div>
<div>Event Details</div>
@@ -117,29 +117,29 @@
table-warning
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
<!---Number-->
<div class="font-weight-bold c-none c-lg-block" id="event_number">{{ event.display_id }}</div>
<div class="fw-bold c-none c-lg-block" id="event_number">{{ event.display_id }}</div>
<!--Dates & Times-->
<div id="event_dates" style="min-width: 180px;">
<dl>
{% if not event.cancelled %}
{% if event.meet_at %}
<dt class="font-weight-normal">Meet:</dt>
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.meet_at|date:"D d/m/Y H:i" }}</dd>
<dt class="fw-normal">Meet:</dt>
<dd class="text-nowrap fw-bold text-lg-right">{{ event.meet_at|date:"D d/m/Y H:i" }}</dd>
{% endif %}
{% if event.access_at %}
<dt class="font-weight-normal">Access:</dt>
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.access_at|date:"D d/m/Y H:i" }}</dd>
<dt class="fw-normal">Access:</dt>
<dd class="text-nowrap fw-bold text-lg-right">{{ event.access_at|date:"D d/m/Y H:i" }}</dd>
{% endif %}
{% endif %}
<dt class="font-weight-normal">Start:</dt>
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.start_date|date:"D d/m/Y" }}
<dt class="fw-normal">Start:</dt>
<dd class="text-nowrap fw-bold text-lg-right">{{ event.start_date|date:"D d/m/Y" }}
{% if event.has_start_time %}
{{ event.start_time|date:"H:i" }}
{% endif %}
</dd>
{% if event.end_date %}
<dt class="font-weight-normal">End:</dt>
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.end_date|date:"D d/m/Y" }}
<dt class="fw-normal">End:</dt>
<dd class="text-nowrap fw-bold text-lg-right">{{ event.end_date|date:"D d/m/Y" }}
{% if event.has_end_time %}
{{ event.end_time|date:"H:i" }}
{% endif %}
@@ -154,7 +154,7 @@
<span class="c-inline c-lg-none">{{ event }}</span><span class="c-none c-lg-inline">{{ event.name }}</span>
</a>
{% if event.dry_hire %}
<span class="badge badge-secondary">Dry Hire</span>
<span class="badge text-bg-secondary">Dry Hire</span>
{% endif %}
<br class="c-none c-lg-inline">
{% if event.venue %}

View File

@@ -1,7 +1,7 @@
{% if object.reviewed_by %}
<span class="badge badge-success py-2">Reviewed by <a href="{% url 'profile_detail' object.reviewed_by.pk %}">{{object.reviewed_by}}</a> at {{object.reviewed_at}}</span>
<span class="badge text-bg-success py-2">Reviewed by <a href="{% url 'profile_detail' object.reviewed_by.pk %}">{{object.reviewed_by}}</a> at {{object.reviewed_at}}</span>
{% elif perm %}
<a class="btn btn-success my-2" href="{% url review object.pk %}">Mark Reviewed</a>
{% else %}
<span class="badge badge-danger py-2">Not reviewed</span>
<span class="badge text-bg-danger py-2">Not reviewed</span>
{% endif %}

View File

@@ -3,9 +3,9 @@
class="col-8 control-label">{{ formitem.help_text|safe }}</label>
<div class="col-4 pb-3" id="{{ formitem.0.id_for_label|slice:'-2' }}">
{% for radio in formitem %}
<div class="custom-control custom-radio">
<div class="form-check">
{{ radio.tag }}
<label class="custom-control-label" for="{{ radio.id_for_label }}">{{ radio.choice_label }}</label>
<label class="form-check-label" for="{{ radio.id_for_label }}">{{ radio.choice_label }}</label>
</div>
{% endfor %}
</div>