Jul 8 - Implement QR code per facility

This commit is contained in:
2026-07-08 13:06:52 -04:00
parent d0e72af188
commit ff3c76c0d8
11 changed files with 581 additions and 4 deletions
+50
View File
@@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block title %}QR Code — {{ facility.name }}{% endblock %}
{% block content %}
<style>
@media print {
.no-print { display: none !important; }
.navbar, nav, footer { display: none !important; }
.qr-card { border: none !important; box-shadow: none !important; }
}
.qr-card { max-width: 520px; margin: 0 auto; }
</style>
<div class="d-flex justify-content-between align-items-center mb-3 no-print">
<a href="{{ url_for('facilities.view_facility', facility_id=facility.id) }}"
class="btn btn-outline-secondary btn-sm">
<i class="bi bi-arrow-left"></i> Back to Facility
</a>
<button onclick="window.print()" class="btn btn-primary btn-sm">
<i class="bi bi-printer"></i> Print
</button>
</div>
<div class="card shadow-sm qr-card">
<div class="card-body text-center p-4">
<div class="text-muted text-uppercase small" style="letter-spacing:.08em;">
Scan for Facility Status
</div>
<h1 class="h3 fw-bold mt-1 mb-3">{{ facility.name }}</h1>
<img src="{{ url_for('facilities.facility_qr_png', facility_id=facility.id) }}"
alt="QR code for {{ facility.name }}"
style="width:260px;height:260px;max-width:100%;" class="mb-3">
<p class="mb-1">Scan this code with your phone camera to see this facility's
recent cleaning quality and to <strong>report a problem</strong>.</p>
{% if public_url %}
<div class="small text-muted mt-3">
Or visit:<br>
<a href="{{ public_url }}" class="text-break">{{ public_url }}</a>
</div>
{% endif %}
</div>
</div>
<p class="text-center text-muted small mt-3 no-print">
Tip: print this and post it at the facility entrance or in each restroom.
</p>
{% endblock %}
+6
View File
@@ -17,6 +17,12 @@
<i class="bi bi-graph-up-arrow"></i> Scorecard
</a>
{% endif %}
{% if current_user.role in ['admin', 'director', 'project_manager'] %}
<a href="{{ url_for('facilities.facility_qr_page', facility_id=facility.id) }}"
class="btn btn-outline-dark" title="Printable QR code for this facility">
<i class="bi bi-qr-code"></i> QR Code
</a>
{% endif %}
{% if current_user.role in ['admin', 'director'] %}
<a href="{{ url_for('facilities.edit_facility', facility_id=facility.id) }}" class="btn btn-outline-primary">
<i class="bi bi-pencil"></i> Edit
+131
View File
@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>{{ facility.name }} — Facility Status</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<style>
body { background:#f6f8fa; }
.wrap { max-width: 640px; margin: 0 auto; padding: 16px; }
.hpot { position:absolute !important; left:-9999px !important; width:1px; height:1px; overflow:hidden; }
.rating-num { font-size: 2.4rem; font-weight: 700; line-height: 1; }
.stat { background:#fff; border-radius:.75rem; }
</style>
</head>
<body>
<div class="wrap">
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="alert alert-{{ 'success' if category in ['success','info'] else category }} shadow-sm">
{{ message }}
</div>
{% endfor %}
{% endwith %}
{# ── Header ── #}
<div class="text-center mt-2 mb-3">
<div class="text-muted small text-uppercase" style="letter-spacing:.08em;">Facility Status</div>
<h1 class="h3 fw-bold mb-0">{{ facility.name }}</h1>
{% if facility.address %}
<div class="text-muted small mt-1"><i class="bi bi-geo-alt"></i> {{ facility.address }}</div>
{% endif %}
</div>
{# ── Quality rating ── #}
<div class="stat shadow-sm p-4 mb-3 text-center">
<div class="text-muted small text-uppercase mb-2">Cleaning Quality</div>
<span class="badge bg-{{ rating_colour }} fs-6 mb-2">{{ rating_label }}</span>
<div class="rating-num text-{{ rating_colour }}">
{{ ('%.0f' % avg_score) ~ '%' if avg_score is not none else '—' }}
</div>
<div class="text-muted small mt-1">Average score, last 90 days</div>
</div>
{# ── Facts ── #}
<div class="row g-2 mb-3">
<div class="col-6">
<div class="stat shadow-sm p-3 h-100">
<div class="text-muted small"><i class="bi bi-calendar-check"></i> Last Inspected</div>
<div class="fw-semibold">
{{ last_inspected.strftime('%b %d, %Y') if last_inspected else 'Not yet' }}
</div>
{% if last_score is not none %}
<div class="text-muted small">Scored {{ '%.0f' % last_score }}%</div>
{% endif %}
</div>
</div>
<div class="col-6">
<div class="stat shadow-sm p-3 h-100">
<div class="text-muted small"><i class="bi bi-exclamation-triangle"></i> Open Issues</div>
<div class="fw-semibold fs-4">{{ open_issue_count }}</div>
<div class="text-muted small">Currently being tracked</div>
</div>
</div>
</div>
{# ── Report a problem ── #}
<div class="stat shadow-sm p-4 mb-4">
<h2 class="h5 fw-bold"><i class="bi bi-megaphone"></i> Report a Problem</h2>
<p class="text-muted small">
Notice something that needs attention? Let the cleaning team know.
</p>
<form method="POST"
action="{{ url_for('public.report_problem', token=token) }}"
enctype="multipart/form-data" novalidate>
{{ form.hidden_tag() }}
{# Honeypot — hidden from humans; bots that fill it are rejected #}
<div class="hpot" aria-hidden="true">
{{ form.website.label }} {{ form.website(tabindex="-1", autocomplete="off") }}
</div>
<div class="mb-3">
{{ form.area_label.label(class="form-label small fw-semibold") }}
{{ form.area_label(class="form-control", placeholder="e.g. 2nd floor men's restroom") }}
</div>
<div class="mb-3">
{{ form.description.label(class="form-label small fw-semibold") }}
{{ form.description(class="form-control", rows="4",
placeholder="Describe what you noticed…") }}
{% for e in form.description.errors %}
<div class="text-danger small mt-1">{{ e }}</div>
{% endfor %}
</div>
<div class="row g-2">
<div class="col-sm-6 mb-3">
{{ form.reporter_name.label(class="form-label small fw-semibold") }}
{{ form.reporter_name(class="form-control") }}
</div>
<div class="col-sm-6 mb-3">
{{ form.reporter_contact.label(class="form-label small fw-semibold") }}
{{ form.reporter_contact(class="form-control") }}
</div>
</div>
<div class="mb-3">
{{ form.photo.label(class="form-label small fw-semibold") }}
{{ form.photo(class="form-control", accept="image/*") }}
{% for e in form.photo.errors %}
<div class="text-danger small mt-1">{{ e }}</div>
{% endfor %}
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-send"></i> Submit Report
</button>
</form>
</div>
<div class="text-center text-muted small mb-4">
Janitorial Quality Control
</div>
</div>
</body>
</html>