05/26 Add contract information to issues related pages

This commit is contained in:
2026-05-26 11:39:30 -04:00
parent b065f131c0
commit 175f514033
4 changed files with 103 additions and 2 deletions
+12 -1
View File
@@ -449,13 +449,23 @@ def unfollow(issue_id):
@login_required @login_required
@supervisor_required @supervisor_required
def create(): def create():
from app.models.project import Project
form = IssueForm() form = IssueForm()
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all() facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
projects = Project.query.filter_by(active=True).order_by(Project.name).all()
staff = User.query.filter(User.role.in_(['admin', 'director', 'inspector'])).order_by(User.username).all() staff = User.query.filter(User.role.in_(['admin', 'director', 'inspector'])).order_by(User.username).all()
form.facility_id.choices = [(f.id, f.name) for f in facilities] form.facility_id.choices = [(f.id, f.name) for f in facilities]
form.assigned_to.choices = [(0, '— Unassigned —')] + [(u.id, u.username) for u in staff] form.assigned_to.choices = [(0, '— Unassigned —')] + [(u.id, u.username) for u in staff]
# On POST validation error: identify which contract the submitted facility
# belongs to so the contract selector can be restored on re-render.
selected_project_id = None
if form.facility_id.data:
_fac = db.session.get(Facility, form.facility_id.data)
if _fac:
selected_project_id = _fac.project_id
if form.validate_on_submit(): if form.validate_on_submit():
from app.routes.inspections import _save_photo from app.routes.inspections import _save_photo
photo_path = _save_photo(form.photo.data, subfolder='issue_photos') photo_path = _save_photo(form.photo.data, subfolder='issue_photos')
@@ -520,7 +530,8 @@ def create():
flash('Issue created.', 'success') flash('Issue created.', 'success')
return redirect(url_for('issues.index')) return redirect(url_for('issues.index'))
return render_template('issues/form.html', form=form, title='Log New Issue') return render_template('issues/form.html', form=form, title='Log New Issue',
projects=projects, selected_project_id=selected_project_id)
# ── Supervisor verify resolved issue ───────────────────────────────────────── # ── Supervisor verify resolved issue ─────────────────────────────────────────
+79 -1
View File
@@ -10,13 +10,34 @@
<div class="card-body"> <div class="card-body">
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% for field in [form.facility_id, form.severity, form.description, form.photo, form.assigned_to] %}
{# Contract selector — UI only; narrows the facility list via AJAX #}
<div class="mb-3">
<label class="form-label fw-semibold" for="contract_select">Contract</label>
<select id="contract_select" class="form-select">
<option value="">— Select Contract —</option>
{% for p in projects %}
<option value="{{ p.id }}">{{ p.name }}</option>
{% endfor %}
</select>
</div>
{# Facility — populated by JS once a contract is chosen #}
<div class="mb-3">
{{ form.facility_id.label(class="form-label fw-semibold") }}
{{ form.facility_id(class="form-select", id="facility_id") }}
{% for e in form.facility_id.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
</div>
{# Remaining fields #}
{% for field in [form.severity, form.description, form.photo, form.assigned_to] %}
<div class="mb-3"> <div class="mb-3">
{{ field.label(class="form-label fw-semibold") }} {{ field.label(class="form-label fw-semibold") }}
{{ field(class="form-select" if field.type == 'SelectField' else "form-control", rows=4 if field.type == 'TextAreaField' else none) }} {{ field(class="form-select" if field.type == 'SelectField' else "form-control", rows=4 if field.type == 'TextAreaField' else none) }}
{% for e in field.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %} {% for e in field.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
</div> </div>
{% endfor %} {% endfor %}
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="submit" class="btn btn-danger">Log Issue</button> <button type="submit" class="btn btn-danger">Log Issue</button>
<a href="{{ url_for('issues.index') }}" class="btn btn-outline-secondary">Cancel</a> <a href="{{ url_for('issues.index') }}" class="btn btn-outline-secondary">Cancel</a>
@@ -27,3 +48,60 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %}
<script>
(function () {
'use strict';
var contractSel = document.getElementById('contract_select');
var facilitySel = document.getElementById('facility_id');
var FACILITIES_URL = '{{ url_for("inspections.facilities_for_project", project_id=0) }}'.replace('/0', '/');
// Values injected from the route (non-null only after a POST validation error)
var preProjectId = {{ selected_project_id | tojson }};
var preFacilityId = {{ form.facility_id.data | tojson }};
function setPlaceholder() {
facilitySel.innerHTML = '<option value="">— Select a Contract first —</option>';
facilitySel.disabled = true;
}
function loadFacilities(projectId, restoreFacilityId) {
facilitySel.disabled = true;
facilitySel.innerHTML = '<option value="">Loading…</option>';
fetch(FACILITIES_URL + projectId)
.then(function (r) { return r.json(); })
.then(function (data) {
facilitySel.innerHTML = '<option value="">— Select Facility —</option>';
data.forEach(function (f) {
var opt = document.createElement('option');
opt.value = f.id;
opt.textContent = f.name;
if (restoreFacilityId && f.id === restoreFacilityId) { opt.selected = true; }
facilitySel.appendChild(opt);
});
facilitySel.disabled = false;
})
.catch(function () {
facilitySel.innerHTML = '<option value="">Could not load facilities</option>';
});
}
contractSel.addEventListener('change', function () {
if (this.value) {
loadFacilities(this.value, null);
} else {
setPlaceholder();
}
});
// Restore state after a POST validation error
if (preProjectId) {
contractSel.value = String(preProjectId);
loadFacilities(preProjectId, preFacilityId);
} else {
setPlaceholder();
}
}());
</script>
{% endblock %}
+5
View File
@@ -66,6 +66,7 @@
<tr> <tr>
<th>Reported</th> <th>Reported</th>
<th>Severity</th> <th>Severity</th>
<th>Contract</th>
<th>Facility / Area</th> <th>Facility / Area</th>
<th>Description</th> <th>Description</th>
<th>Status</th> <th>Status</th>
@@ -85,6 +86,10 @@
{{ issue.severity|title }} {{ issue.severity|title }}
</span> </span>
</td> </td>
<td>
{% set _c = issue.resolved_facility.project if issue.resolved_facility else none %}
<small>{{ _c.name if _c else '—' }}</small>
</td>
<td> <td>
{{ issue.resolved_facility.name if issue.resolved_facility else '—' }}<br> {{ issue.resolved_facility.name if issue.resolved_facility else '—' }}<br>
<small class="text-muted">{{ issue.area.name if issue.area else '—' }}</small> <small class="text-muted">{{ issue.area.name if issue.area else '—' }}</small>
+7
View File
@@ -17,6 +17,13 @@
<dt class="col-sm-3">Reported</dt> <dt class="col-sm-3">Reported</dt>
<dd class="col-sm-9">{{ issue.reported_at.strftime('%Y-%m-%d %H:%M') }}</dd> <dd class="col-sm-9">{{ issue.reported_at.strftime('%Y-%m-%d %H:%M') }}</dd>
<dt class="col-sm-3">Contract</dt>
<dd class="col-sm-9">
{% if issue.resolved_facility and issue.resolved_facility.project %}
{{ issue.resolved_facility.project.name }}
{% else %}—{% endif %}
</dd>
<dt class="col-sm-3">Facility</dt> <dt class="col-sm-3">Facility</dt>
<dd class="col-sm-9">{{ issue.resolved_facility.name if issue.resolved_facility else '—' }}</dd> <dd class="col-sm-9">{{ issue.resolved_facility.name if issue.resolved_facility else '—' }}</dd>