163 lines
7.1 KiB
HTML
163 lines
7.1 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Execute Inspection{% endblock %}
|
||
{% block extra_css %}
|
||
<style>
|
||
.item-card { border-left: 4px solid #dee2e6; transition: border-color .2s; }
|
||
.item-card.answered { border-left-color: #198754; }
|
||
.item-card.flagged { border-left-color: #dc3545; }
|
||
.pass-fail-group .btn-check:checked + .btn-outline-success { background:#198754; color:#fff; }
|
||
.pass-fail-group .btn-check:checked + .btn-outline-danger { background:#dc3545; color:#fff; }
|
||
.progress-bar-label { font-size:.75rem; font-weight:600; }
|
||
.sticky-toolbar { position:sticky; top:56px; z-index:20; background:#fff; border-bottom:1px solid #dee2e6; padding:.6rem 1rem; }
|
||
</style>
|
||
{% endblock %}
|
||
{% block content %}
|
||
<form method="post" enctype="multipart/form-data" id="inspectionForm">
|
||
{{ csrf_token() | safe }}
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||
|
||
{# Sticky toolbar #}
|
||
<div class="sticky-toolbar d-flex align-items-center gap-3 mb-4">
|
||
<div class="flex-grow-1">
|
||
<h5 class="mb-0">{{ inspection.template.name }}</h5>
|
||
<small class="text-muted">{{ inspection.facility.name }}{% if inspection.area %} · {{ inspection.area.name }}{% endif %}</small>
|
||
</div>
|
||
<div class="text-center" style="min-width:120px;">
|
||
<div class="progress" style="height:8px;">
|
||
<div class="progress-bar bg-success" style="width:{{ (answered / results|length * 100)|int if results else 0 }}%"></div>
|
||
</div>
|
||
<span class="progress-bar-label text-muted">{{ answered }}/{{ results|length }} answered</span>
|
||
</div>
|
||
<a href="{{ url_for('inspections.flag_issue', inspection_id=inspection.id) }}"
|
||
class="btn btn-sm btn-outline-danger">
|
||
<i class="bi bi-exclamation-triangle"></i> Flag Issue
|
||
</a>
|
||
<button type="submit" name="action" value="save" class="btn btn-sm btn-outline-secondary">
|
||
<i class="bi bi-floppy"></i> Save
|
||
</button>
|
||
<button type="submit" name="action" value="complete" class="btn btn-success"
|
||
onclick="return confirm('Mark this inspection as complete?')">
|
||
<i class="bi bi-check-circle"></i> Complete
|
||
</button>
|
||
</div>
|
||
|
||
{% if inspection.notes %}
|
||
<div class="alert alert-info py-2"><i class="bi bi-sticky"></i> <strong>Notes:</strong> {{ inspection.notes }}</div>
|
||
{% endif %}
|
||
|
||
{% set ns = namespace(current_cat='') %}
|
||
{% for result in results %}
|
||
{% set item = result.checklist_item %}
|
||
{% if item.category != ns.current_cat %}
|
||
{% set ns.current_cat = item.category %}
|
||
<h5 class="text-primary border-bottom pb-1 mt-4 mb-3">
|
||
<i class="bi bi-folder2"></i> {{ item.category or 'General' }}
|
||
</h5>
|
||
{% endif %}
|
||
|
||
{% set is_answered = result.passed is not none or result.score is not none %}
|
||
<div class="card shadow-sm mb-3 item-card {{ 'answered' if is_answered }}">
|
||
<div class="card-body">
|
||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||
<div>
|
||
<span class="fw-semibold">{{ item.item_description }}</span>
|
||
{% if item.requires_photo %}
|
||
<span class="badge bg-warning text-dark ms-1"><i class="bi bi-camera"></i> Photo required</span>
|
||
{% endif %}
|
||
</div>
|
||
<span class="badge bg-secondary text-uppercase" style="font-size:.65rem;">{{ item.scoring_type|replace('_',' ') }}</span>
|
||
</div>
|
||
|
||
{# ── Pass/Fail ── #}
|
||
{% if item.scoring_type == 'pass_fail' %}
|
||
<div class="pass-fail-group d-flex gap-2 mb-2">
|
||
<input class="btn-check" type="radio" name="item_{{ result.id }}_passed"
|
||
id="pass_{{ result.id }}" value="pass" {{ 'checked' if result.passed == true }}>
|
||
<label class="btn btn-outline-success btn-sm" for="pass_{{ result.id }}">
|
||
<i class="bi bi-check-lg"></i> Pass
|
||
</label>
|
||
<input class="btn-check" type="radio" name="item_{{ result.id }}_passed"
|
||
id="fail_{{ result.id }}" value="fail" {{ 'checked' if result.passed == false }}>
|
||
<label class="btn btn-outline-danger btn-sm" for="fail_{{ result.id }}">
|
||
<i class="bi bi-x-lg"></i> Fail
|
||
</label>
|
||
</div>
|
||
|
||
{# ── Rating 5 ── #}
|
||
{% elif item.scoring_type == 'rating_5' %}
|
||
<div class="mb-2">
|
||
<label class="form-label small">Rating (1–5)</label>
|
||
<div class="d-flex gap-2">
|
||
{% for v in [1,2,3,4,5] %}
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="radio" name="item_{{ result.id }}_score"
|
||
id="r5_{{ result.id }}_{{ v }}" value="{{ v }}"
|
||
{{ 'checked' if result.score == v }}>
|
||
<label class="form-check-label" for="r5_{{ result.id }}_{{ v }}">{{ v }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
{# ── Rating 10 ── #}
|
||
{% elif item.scoring_type == 'rating_10' %}
|
||
<div class="mb-2">
|
||
<label class="form-label small">Rating (1–10)</label>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
{% for v in range(1,11) %}
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="radio" name="item_{{ result.id }}_score"
|
||
id="r10_{{ result.id }}_{{ v }}" value="{{ v }}"
|
||
{{ 'checked' if result.score == v }}>
|
||
<label class="form-check-label" for="r10_{{ result.id }}_{{ v }}">{{ v }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="row g-2">
|
||
<div class="col-md-6">
|
||
<textarea class="form-control form-control-sm" name="item_{{ result.id }}_comments"
|
||
rows="2" placeholder="Comments (optional)">{{ result.comments or '' }}</textarea>
|
||
</div>
|
||
<div class="col-md-6">
|
||
{% if result.photo_path %}
|
||
<div class="mb-1">
|
||
<img src="{{ url_for('static', filename=result.photo_path) }}"
|
||
class="img-thumbnail" style="max-height:80px;" alt="Photo">
|
||
</div>
|
||
{% endif %}
|
||
<input type="file" class="form-control form-control-sm"
|
||
name="item_{{ result.id }}_photo" accept="image/*">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
{# Bottom submit bar #}
|
||
<div class="d-flex justify-content-end gap-2 mt-4 pb-4">
|
||
<button type="submit" name="action" value="save" class="btn btn-outline-secondary">
|
||
<i class="bi bi-floppy"></i> Save Progress
|
||
</button>
|
||
<button type="submit" name="action" value="complete" class="btn btn-success btn-lg"
|
||
onclick="return confirm('Mark this inspection as complete? This cannot be undone.')">
|
||
<i class="bi bi-check-circle-fill"></i> Complete Inspection
|
||
</button>
|
||
</div>
|
||
</form>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
// Auto-mark item cards as answered on interaction
|
||
document.querySelectorAll('input[type=radio]').forEach(r => {
|
||
r.addEventListener('change', () => {
|
||
const card = r.closest('.item-card');
|
||
if (card) card.classList.add('answered');
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|