Phase 3: initiatal code

This commit is contained in:
2026-02-21 12:29:26 -05:00
parent c2fe59da0d
commit 55a1ef053b
19 changed files with 1975 additions and 87 deletions
+162
View File
@@ -0,0 +1,162 @@
{% 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 (15)</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 (110)</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 %}