Aug 19 - Update code to catch up with ST
This commit is contained in:
@@ -564,7 +564,7 @@
|
||||
<option value="0">— Unassigned —</option>
|
||||
{% set staff = staff_for_flag_issue %}
|
||||
{% if staff %}{% for u in staff %}
|
||||
<option value="{{ u.id }}">{{ u.display_name }}{{ ' (External)' if u.is_external_inspector }}</option>
|
||||
<option value="{{ u.id }}">{{ u.display_name }}{{ ' (Customer)' if u.is_external_inspector }}</option>
|
||||
{% endfor %}{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-clipboard-data"></i> Inspections</h2>
|
||||
{% if current_user.role != 'customer' %}
|
||||
{# Customer Directors schedule inspections for their own facilities, so the
|
||||
Scheduled link is theirs too — but starting an ad-hoc inspection is not. #}
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ url_for('inspection_schedules.index') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-calendar-check"></i> Scheduled
|
||||
</a>
|
||||
{% if current_user.role != 'customer' %}
|
||||
<a href="{{ url_for('inspections.start') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> New Inspection
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Filters #}
|
||||
@@ -106,10 +108,15 @@
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
{% if inspections.items %}
|
||||
{% include 'partials/bulk_inspections_toolbar.html' %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:34px;">
|
||||
<input type="checkbox" class="form-check-input bulk-check-all"
|
||||
title="Select all on this page" aria-label="Select all">
|
||||
</th>
|
||||
<th>#</th><th>Date</th><th>Contract</th><th>Facility</th><th>Area</th>
|
||||
<th>Template</th><th>Inspector</th><th>Score</th>
|
||||
<th>Status</th><th></th>
|
||||
@@ -118,6 +125,11 @@
|
||||
<tbody>
|
||||
{% for ins in inspections.items %}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" class="form-check-input bulk-check"
|
||||
form="inspectionsBulkForm" name="inspection_ids" value="{{ ins.id }}"
|
||||
aria-label="Select inspection #{{ ins.id }}">
|
||||
</td>
|
||||
<td><small class="text-muted">#{{ ins.id }}</small></td>
|
||||
<td>{{ ins.inspection_date.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td><small>{{ ins.facility.project.name if ins.facility and ins.facility.project else '—' }}</small></td>
|
||||
@@ -152,9 +164,9 @@
|
||||
</td>
|
||||
<td class="text-nowrap">
|
||||
{% if ins.status == 'in_progress' or ins.status == 'flagged' %}
|
||||
<a href="{{ url_for('inspections.execute', inspection_id=ins.id) }}" class="btn btn-sm btn-outline-primary insp-list-link">Continue</a>
|
||||
<a href="{{ url_for('inspections.execute', inspection_id=ins.id, next=current_url()) }}" class="btn btn-sm btn-outline-primary insp-list-link">Continue</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('inspections.view', inspection_id=ins.id) }}" class="btn btn-sm btn-outline-secondary insp-list-link">View</a>
|
||||
<a href="{{ url_for('inspections.view', inspection_id=ins.id, next=current_url()) }}" class="btn btn-sm btn-outline-secondary insp-list-link">View</a>
|
||||
{% endif %}
|
||||
{% if current_user.role in ['admin', 'director'] %}
|
||||
<button type="button"
|
||||
@@ -216,6 +228,7 @@
|
||||
</button>
|
||||
<form id="deleteInspectionForm" method="POST" action="" class="d-inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="next" value="{{ current_url() }}">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash3-fill"></i> Delete Permanently
|
||||
</button>
|
||||
@@ -228,6 +241,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% include 'partials/bulk_select_js.html' %}
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
@@ -14,8 +14,14 @@
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.template_id.label(class="form-label fw-semibold") }}
|
||||
{{ form.template_id(class="form-select" + (" is-invalid" if form.template_id.errors else "")) }}
|
||||
{{ form.template_id(class="form-select" + (" is-invalid" if form.template_id.errors else ""), id="templateSelect") }}
|
||||
{% for e in form.template_id.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
|
||||
{# phase52 — the list shows shared forms plus the ones attached to
|
||||
the selected contract, refreshed by JS when the contract changes. #}
|
||||
<div class="form-text">Shows forms available on the selected contract.</div>
|
||||
<div id="templateEmpty" class="form-text text-danger d-none">
|
||||
No forms are available on this contract yet.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -55,6 +61,8 @@
|
||||
<script>
|
||||
(function () {
|
||||
const projectSel = document.getElementById('projectSelect');
|
||||
const templateSel = document.getElementById('templateSelect');
|
||||
const templateEmpty = document.getElementById('templateEmpty');
|
||||
const facilitySel = document.getElementById('facilitySelect');
|
||||
const spinner = document.getElementById('facilitySpinner');
|
||||
const emptyMsg = document.getElementById('facilityEmpty');
|
||||
@@ -62,6 +70,7 @@
|
||||
const areaSel = document.getElementById('areaSelect');
|
||||
|
||||
const FACILITIES_URL = `{{ url_for('inspections.facilities_for_project', project_id=0) }}`.replace('/0', '/');
|
||||
const TEMPLATES_URL = `{{ url_for('inspections.templates_for_project', project_id=0) }}`.replace('/0', '/');
|
||||
const AREAS_URL = `{{ url_for('inspections.areas_for_facility', facility_id=0) }}`.replace('/0', '/');
|
||||
|
||||
function loadAreas(facilityId, selectedAreaId) {
|
||||
@@ -93,6 +102,28 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Forms are per-contract (phase52): a customer's bespoke form must not be
|
||||
// offered on another customer's facilities. Keeps the currently selected
|
||||
// form if it is still valid on the new contract.
|
||||
function loadTemplates(projectId) {
|
||||
if (!projectId || !templateSel) return;
|
||||
const keep = templateSel.value;
|
||||
fetch(TEMPLATES_URL + projectId)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
templateSel.innerHTML = '';
|
||||
data.forEach(t => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = t.id;
|
||||
opt.textContent = t.name;
|
||||
if (String(t.id) === keep) opt.selected = true;
|
||||
templateSel.appendChild(opt);
|
||||
});
|
||||
templateEmpty.classList.toggle('d-none', data.length > 0);
|
||||
})
|
||||
.catch(() => {}); // leave the server-rendered list in place
|
||||
}
|
||||
|
||||
function loadFacilities(projectId, selectedFacilityId, selectedAreaId) {
|
||||
if (!projectId) return;
|
||||
spinner.classList.remove('d-none');
|
||||
@@ -129,6 +160,7 @@
|
||||
|
||||
projectSel.addEventListener('change', function () {
|
||||
loadFacilities(this.value, null, null);
|
||||
loadTemplates(this.value);
|
||||
});
|
||||
|
||||
facilitySel.addEventListener('change', function () {
|
||||
|
||||
@@ -338,7 +338,11 @@
|
||||
|
||||
{# Action bar #}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<a id="backToInspectionsBtn" href="{{ url_for('inspections.index') }}" class="btn btn-sm btn-outline-secondary">
|
||||
{# `next` carries the filtered list URL from the list page; the
|
||||
sessionStorage fallback below still covers links opened before
|
||||
this page started sending one. #}
|
||||
{% set back_url = request.args.get('next') or url_for('inspections.index') %}
|
||||
<a id="backToInspectionsBtn" href="{{ back_url }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Inspections
|
||||
</a>
|
||||
<div class="d-flex gap-2">
|
||||
@@ -378,6 +382,7 @@
|
||||
action="{{ url_for('inspections.clear_followup', inspection_id=inspection.id) }}"
|
||||
class="d-inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="next" value="{{ back_url }}">
|
||||
<button class="btn btn-sm btn-warning">
|
||||
<i class="bi bi-flag-fill"></i> Clear Follow-up
|
||||
</button>
|
||||
@@ -386,6 +391,7 @@
|
||||
<form method="post" action="{{ url_for('inspections.delete', inspection_id=inspection.id) }}"
|
||||
onsubmit="return confirm('Delete this inspection permanently?')">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="next" value="{{ back_url }}">
|
||||
<button class="btn btn-sm btn-outline-danger"><i class="bi bi-trash3"></i> Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
@@ -892,7 +898,11 @@
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
(function () {
|
||||
var backUrl = sessionStorage.getItem('insp_list_back_url');
|
||||
// A server-provided `next` is authoritative — it reflects the list this
|
||||
// page was actually opened from. Only fall back to sessionStorage when
|
||||
// there is none (e.g. a link created before `next` was threaded in).
|
||||
var hasNext = {{ 'true' if request.args.get('next') else 'false' }};
|
||||
var backUrl = hasNext ? null : sessionStorage.getItem('insp_list_back_url');
|
||||
if (backUrl) {
|
||||
var btn = document.getElementById('backToInspectionsBtn');
|
||||
if (btn) btn.href = backUrl;
|
||||
@@ -918,6 +928,7 @@ document.addEventListener('keydown', e => { if (e.key === 'Escape') closeMedia()
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" action="{{ url_for('inspections.flag_followup', inspection_id=inspection.id) }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="next" value="{{ back_url }}">
|
||||
<div class="modal-content">
|
||||
{% set is_cust = current_user.role == 'customer' %}
|
||||
<div class="modal-header">
|
||||
|
||||
Reference in New Issue
Block a user