220 lines
9.6 KiB
HTML
220 lines
9.6 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ title }}{% endblock %}
|
|
{% block content %}
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-6">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-danger text-white">
|
|
<h5 class="mb-0"><i class="bi bi-exclamation-triangle"></i> {{ title }}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="post" enctype="multipart/form-data">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
|
|
{# 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 — assigned_to hidden from customer role #}
|
|
<div class="mb-3">
|
|
{{ form.severity.label(class="form-label fw-semibold") }}
|
|
{{ form.severity(class="form-select") }}
|
|
{% for e in form.severity.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
{{ form.description.label(class="form-label fw-semibold") }}
|
|
<span class="text-danger">*</span>
|
|
{{ form.description(class="form-control", rows=4, required=True,
|
|
placeholder="Describe the issue in detail…") }}
|
|
{% for e in form.description.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
{{ form.photo.label(class="form-label fw-semibold") }}
|
|
{{ form.photo(class="form-control") }}
|
|
{% for e in form.photo.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
|
</div>
|
|
{% if current_user.role != 'customer' %}
|
|
<div class="mb-3">
|
|
{{ form.handler_type.label(class="form-label fw-semibold") }}
|
|
{{ form.handler_type(class="form-select", id="handler_type_select") }}
|
|
<div class="form-text" id="handler_desc"></div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold" id="assigned_to_label">Assign To</label>
|
|
{{ form.assigned_to(class="form-select") }}
|
|
<div class="form-text" id="assigned_to_help" style="display:none;">
|
|
The facility/vendor does the work; this is our follow-up owner.
|
|
</div>
|
|
{% for e in form.assigned_to.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
|
|
</div>
|
|
|
|
{# Janitorial-staff handler — shown when Handled By = Janitorial Staff #}
|
|
<div id="internal_handler_block" style="display:none;">
|
|
<div class="mb-2">
|
|
{{ form.internal_handler_name.label(class="form-label small fw-semibold mb-1") }}
|
|
{{ form.internal_handler_name(class="form-control form-control-sm",
|
|
placeholder="Name of the crew member who will handle it") }}
|
|
<div class="form-text">Optional — the janitorial staff member doing the work.</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
{{ form.internal_handler_contact.label(class="form-label small fw-semibold mb-1") }}
|
|
{{ form.internal_handler_contact(class="form-control form-control-sm",
|
|
placeholder="Phone or email") }}
|
|
</div>
|
|
</div>
|
|
|
|
{# Facility-staff handler — shown when Handled By = Facility Staff #}
|
|
<div id="facility_handler_block" style="display:none;">
|
|
<div class="mb-2">
|
|
{{ form.facility_handler_name.label(class="form-label small fw-semibold mb-1") }}
|
|
{{ form.facility_handler_name(class="form-control form-control-sm",
|
|
placeholder="Facility staff / point of contact") }}
|
|
</div>
|
|
<div class="mb-2">
|
|
{{ form.facility_handler_contact.label(class="form-label small fw-semibold mb-1") }}
|
|
{{ form.facility_handler_contact(class="form-control form-control-sm",
|
|
placeholder="Phone or email") }}
|
|
</div>
|
|
<div class="mb-3">
|
|
{{ form.facility_handler_notes.label(class="form-label small fw-semibold mb-1") }}
|
|
{{ form.facility_handler_notes(class="form-control form-control-sm", rows=2,
|
|
placeholder="What the facility staff are handling…") }}
|
|
</div>
|
|
</div>
|
|
|
|
{# External vendor — shown when Handled By = External Vendor #}
|
|
<div id="vendor_block" style="display:none;">
|
|
<div class="mb-2">
|
|
{{ form.vendor_name.label(class="form-label small fw-semibold mb-1") }}
|
|
{{ form.vendor_name(class="form-control form-control-sm",
|
|
placeholder="Contractor or vendor name") }}
|
|
</div>
|
|
<div class="mb-2">
|
|
{{ form.vendor_contact.label(class="form-label small fw-semibold mb-1") }}
|
|
{{ form.vendor_contact(class="form-control form-control-sm",
|
|
placeholder="Phone or email") }}
|
|
</div>
|
|
<div class="mb-3">
|
|
{{ form.vendor_notes.label(class="form-label small fw-semibold mb-1") }}
|
|
{{ form.vendor_notes(class="form-control form-control-sm", rows=2,
|
|
placeholder="Notes about what the contractor is handling…") }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-danger">Log Issue</button>
|
|
<a href="{{ url_for('issues.index') }}" class="btn btn-outline-secondary">Cancel</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% 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();
|
|
}
|
|
|
|
// ── "Handled By" — reveal the facility/vendor sub-block + relabel assignee ──
|
|
var handlerSelect = document.getElementById('handler_type_select');
|
|
var HANDLER_DESC = {
|
|
'internal': 'Our janitorial crew handles it.',
|
|
'facility': "The facility's own on-site staff handle it.",
|
|
'vendor': 'An outside contractor handles it.'
|
|
};
|
|
function syncHandlerUI() {
|
|
if (!handlerSelect) { return; }
|
|
var v = handlerSelect.value;
|
|
var intBlock = document.getElementById('internal_handler_block');
|
|
var facBlock = document.getElementById('facility_handler_block');
|
|
var venBlock = document.getElementById('vendor_block');
|
|
if (intBlock) { intBlock.style.display = (v === 'internal') ? '' : 'none'; }
|
|
if (facBlock) { facBlock.style.display = (v === 'facility') ? '' : 'none'; }
|
|
if (venBlock) { venBlock.style.display = (v === 'vendor') ? '' : 'none'; }
|
|
var desc = document.getElementById('handler_desc');
|
|
if (desc) { desc.textContent = HANDLER_DESC[v] || ''; }
|
|
var label = document.getElementById('assigned_to_label');
|
|
var help = document.getElementById('assigned_to_help');
|
|
if (label) {
|
|
label.textContent = (v === 'facility' || v === 'vendor') ? 'Follow-up Owner' : 'Assign To';
|
|
}
|
|
if (help) {
|
|
help.style.display = (v === 'facility' || v === 'vendor') ? '' : 'none';
|
|
}
|
|
}
|
|
if (handlerSelect) {
|
|
handlerSelect.addEventListener('change', syncHandlerUI);
|
|
syncHandlerUI();
|
|
}
|
|
}());
|
|
</script>
|
|
{% endblock %}
|