466 lines
23 KiB
HTML
466 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Dashboard{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
{# ── Grouped dashboard styling (colored section cards + white stat tiles) ──── #}
|
|
<style>
|
|
.group-card { border-radius:16px; padding:14px 18px 18px; margin-bottom:22px; }
|
|
.group-title { font-weight:800; font-size:1.4rem; letter-spacing:.2px; margin:2px 0 12px; }
|
|
.group-blue { background:#4a90d9; } /* A — solid blue */
|
|
.group-green { background:#d9ead3; } /* B — light green */
|
|
.group-orange { background:#fce5cd; } /* C — light orange */
|
|
.group-grey { background:#efefef; } /* D — light grey */
|
|
.gt-white { color:#ffffff; }
|
|
.gt-green { color:#38761d; }
|
|
.gt-orange { color:#b45f06; }
|
|
.gt-red { color:#cc0000; }
|
|
|
|
.stat-tile { background:#fff; border:2px solid #cbd3dd; border-radius:11px;
|
|
padding:12px 16px; height:100%; display:flex; align-items:center;
|
|
justify-content:space-between; gap:10px; transition:box-shadow .15s; }
|
|
a.tile-link:hover .stat-tile { box-shadow:0 3px 10px rgba(0,0,0,.12); }
|
|
.st-label { font-size:.9rem; font-weight:600; color:#3f4652; line-height:1.15; }
|
|
.st-sub { font-size:.68rem; color:#8a93a2; margin-top:3px; }
|
|
.st-value { font-size:1.9rem; font-weight:800; line-height:1; white-space:nowrap; }
|
|
|
|
/* Scheduled-inspection table inside the solid-blue section A */
|
|
.sched-table { background:#fff; border-radius:8px; overflow:hidden; margin-bottom:0; }
|
|
.sched-table thead th { background:#5b9bd5; color:#fff; border-color:#4a90d9; font-weight:600; }
|
|
</style>
|
|
|
|
{# Handler ("Handled by") breakdown chips for an issue KPI tile. `base` is a
|
|
dict of extra issues.index query params identifying the tile's scope. #}
|
|
{% macro handler_chips(bd, base) %}
|
|
<div class="mt-1 d-flex flex-wrap align-items-center gap-1" style="font-size:.7rem;">
|
|
<span style="opacity:.75;">Handled by:</span>
|
|
{% if bd.internal > 0 %}
|
|
<a href="{{ url_for('issues.index', handler_type='internal', **base) }}"
|
|
class="badge bg-primary text-decoration-none" title="Janitorial Staff — our crew">Janitorial {{ bd.internal }}</a>{% endif %}
|
|
{% if bd.facility > 0 %}
|
|
<a href="{{ url_for('issues.index', handler_type='facility', **base) }}"
|
|
class="badge bg-info text-dark text-decoration-none" title="Facility Staff — the facility's own on-site staff">Facility {{ bd.facility }}</a>{% endif %}
|
|
{% if bd.vendor > 0 %}
|
|
<a href="{{ url_for('issues.index', handler_type='vendor', **base) }}"
|
|
class="badge bg-light text-dark border text-decoration-none" title="External Vendor — an outside contractor">Vendor {{ bd.vendor }}</a>{% endif %}
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
{# One white stat tile: label (+ optional subtitle OR handler chips) left, big
|
|
colored number right. Numbers render two-digit (01, 02, …) to match the design.
|
|
When chips_bd is given and the value is > 0, the handler chips replace the
|
|
subtitle; because the chips are themselves links, the whole tile is NOT
|
|
wrapped in an anchor (nested <a> is invalid) — only the number links. #}
|
|
{% macro stat_tile(label, value, accent, href=None, subtitle=None, chips_bd=None, chips_base=None) %}
|
|
{% set has_chips = chips_bd is not none and value and value > 0 %}
|
|
<div class="col">
|
|
{% if href and not has_chips %}<a href="{{ href }}" class="text-decoration-none tile-link d-block h-100">{% endif %}
|
|
<div class="stat-tile" style="border-color:{{ accent }};">
|
|
<div class="st-text">
|
|
<div class="st-label">{{ label }}</div>
|
|
{% if has_chips %}
|
|
{{ handler_chips(chips_bd, chips_base) }}
|
|
{% elif subtitle %}
|
|
<div class="st-sub">{{ subtitle }}</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="st-value" style="color:{{ accent }};">
|
|
{% if href and has_chips %}
|
|
<a href="{{ href }}" class="text-decoration-none" style="color:inherit;">{{ '%02d'|format(value) }}</a>
|
|
{% else %}
|
|
{{ '%02d'|format(value) if value is number else value }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if href and not has_chips %}</a>{% endif %}
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
<div class="row mb-3 align-items-center">
|
|
<div class="col">
|
|
<h2 class="mb-0">Welcome, {{ current_user.display_name }}!</h2>
|
|
<span class="badge bg-{% if current_user.role == 'admin' %}danger{% elif current_user.role == 'director' %}warning{% elif current_user.role == 'project_manager' %}primary{% elif current_user.role == 'auditor' %}secondary{% elif current_user.role == 'customer' %}success{% else %}info{% endif %} mt-1">
|
|
{{ current_user.role.replace('_',' ')|title }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{# ══════════════════════════════════════════════════════════════════════════
|
|
A. SCHEDULED INSPECTION (blue)
|
|
══════════════════════════════════════════════════════════════════════════ #}
|
|
{% if current_user.role != 'customer' %}
|
|
<div class="group-card group-blue">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<span class="group-title gt-white mb-0">Scheduled Inspection</span>
|
|
<a href="{{ url_for('scheduled_inspections.index') }}" class="btn btn-sm btn-light">View all</a>
|
|
</div>
|
|
|
|
{% if sched_overdue_count %}
|
|
<div class="alert alert-danger py-2 mb-2">
|
|
<i class="bi bi-alarm-fill"></i>
|
|
<strong>{{ sched_overdue_count }}</strong> scheduled inspection{{ 's' if sched_overdue_count != 1 }}
|
|
{{ 'are' if sched_overdue_count != 1 else 'is' }} <strong>overdue</strong>.
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if sched_upcoming %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle sched-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Facility Name</th><th>Inspection Template</th><th>Inspector Name</th>
|
|
<th>How Often?</th><th>Due</th><th class="text-end"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for s in sched_upcoming %}
|
|
<tr>
|
|
<td>{{ s.facility.name if s.facility else '—' }}</td>
|
|
<td class="small">{{ s.template.name if s.template else '—' }}</td>
|
|
<td class="small">{{ s.inspector.display_name if s.inspector else '—' }}</td>
|
|
<td class="small text-muted">{{ s.recurrence_label }}</td>
|
|
<td class="small">{{ s.next_due_date.strftime('%b %d') }}</td>
|
|
<td class="text-end text-nowrap">
|
|
{# Start is shown only to the assignee — the inspection is theirs to do. #}
|
|
{% if s.inspector_id and s.inspector_id == current_user.id %}
|
|
{# Receipt confirmation (phase47) — assignee confirms, or shows confirmed. #}
|
|
{% if s.is_acknowledged %}
|
|
<span class="badge bg-success" title="You confirmed receipt"><i class="bi bi-check-circle"></i> Confirmed</span>
|
|
{% else %}
|
|
<form method="POST" class="d-inline" action="{{ url_for('scheduled_inspections.acknowledge', schedule_id=s.id) }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-sm btn-outline-success py-0" title="Confirm you received this request">
|
|
<i class="bi bi-check-lg"></i> Confirm</button>
|
|
</form>
|
|
{% endif %}
|
|
{% set open_id = sched_open_inspections.get(s.id) %}
|
|
{% if open_id %}
|
|
<a href="{{ url_for('inspections.execute', inspection_id=open_id) }}"
|
|
class="btn btn-sm btn-warning py-0" title="You already started this — resume it">
|
|
<i class="bi bi-pencil-square"></i> Continue</a>
|
|
{% else %}
|
|
<a href="{{ url_for('scheduled_inspections.start', schedule_id=s.id) }}"
|
|
class="btn btn-sm btn-success py-0"><i class="bi bi-play-fill"></i> Start</a>
|
|
{% endif %}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="bg-white rounded p-3 text-muted small mb-0">
|
|
<i class="bi bi-info-circle me-1"></i>No inspections due in the next 7 days.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# ══════════════════════════════════════════════════════════════════════════
|
|
B. QUICK REVIEW (green)
|
|
══════════════════════════════════════════════════════════════════════════ #}
|
|
{% set G = '#4e8a2f' %}
|
|
<div class="group-card group-green">
|
|
<div class="group-title gt-green">Quick Review</div>
|
|
<div class="row row-cols-2 row-cols-sm-3 row-cols-xl-5 g-3">
|
|
{{ stat_tile('Submitted Today', completed_today, G,
|
|
url_for('inspections.index', status='completed', date_from=today_str, date_to=today_str),
|
|
'Completed & submitted today') }}
|
|
{{ stat_tile('Submitted This Week', submitted_this_week, G,
|
|
url_for('inspections.index', status='completed', date_from=week_start_str, date_to=today_str),
|
|
'Completed & submitted this week') }}
|
|
{% if current_user.role != 'customer' %}
|
|
{{ stat_tile('In Progress', in_progress_total, G,
|
|
url_for('inspections.index', status='in_progress'),
|
|
(stale_in_progress ~ ' stale >24h') if stale_in_progress else 'Started, not yet submitted') }}
|
|
{{ stat_tile('Pending Follow-ups', pending_followups, G,
|
|
url_for('inspections.index', status='follow_up'),
|
|
'Flagged for re-inspection') }}
|
|
{{ stat_tile('On Schedules', sched_total, G,
|
|
url_for('scheduled_inspections.index'),
|
|
'Active scheduled plans') }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{# ══════════════════════════════════════════════════════════════════════════
|
|
C. REVIEW ISSUES (orange)
|
|
══════════════════════════════════════════════════════════════════════════ #}
|
|
{% set O = '#d5761a' %}
|
|
<div class="group-card group-orange">
|
|
<div class="group-title gt-orange">Review Issues</div>
|
|
<div class="row row-cols-2 row-cols-sm-3 row-cols-xl-5 g-3">
|
|
{{ stat_tile('Open · Janitorial', handler_breakdown.internal, O,
|
|
url_for('issues.index', status='open', handler_type='internal'),
|
|
'Handled by our crew') }}
|
|
{{ stat_tile('Open · Facility Staff', handler_breakdown.facility, O,
|
|
url_for('issues.index', status='open', handler_type='facility'),
|
|
'Handled by facility staff') }}
|
|
{{ stat_tile('Open · Vendors', handler_breakdown.vendor, O,
|
|
url_for('issues.index', status='open', handler_type='vendor'),
|
|
'Handled by outside vendor') }}
|
|
{% if current_user.role != 'customer' %}
|
|
{{ stat_tile('Open · Unassigned', unassigned_open, O,
|
|
url_for('issues.index', status='open', unassigned='1'),
|
|
'No one assigned yet',
|
|
chips_bd=unassigned_handler, chips_base={'status': 'open', 'unassigned': '1'}) }}
|
|
{{ stat_tile('Pending · Verification', pending_verification, O,
|
|
url_for('issues.index', status='pending_verification'),
|
|
'Awaiting sign-off') }}
|
|
{% endif %}
|
|
{# All the original Issues cards are kept here in the same colored group. #}
|
|
{{ stat_tile('Issues Opened Today', issues_opened_today, O,
|
|
url_for('issues.index', date_from=today_str, date_to=today_str),
|
|
'New issues reported today',
|
|
chips_bd=opened_today_handler, chips_base={'date_from': today_str, 'date_to': today_str}) }}
|
|
{{ stat_tile('Resolved Today', resolved_today, O,
|
|
url_for('issues.index', status='resolved', date_from=today_str, date_to=today_str),
|
|
'Issues closed today') }}
|
|
</div>
|
|
</div>
|
|
|
|
{# ══════════════════════════════════════════════════════════════════════════
|
|
D. SLA ALERT (grey)
|
|
══════════════════════════════════════════════════════════════════════════ #}
|
|
{# Section D shows only when there is at least one at-risk or breached issue. #}
|
|
{% if sla_breached > 0 or sla_at_risk > 0 %}
|
|
{% set R = '#cc0000' %}
|
|
<div class="group-card group-grey">
|
|
<div class="group-title gt-red">SLA Alert</div>
|
|
<div class="row row-cols-1 row-cols-sm-2 g-3">
|
|
{{ stat_tile('SLA At Risk', sla_at_risk, R,
|
|
url_for('issues.index', sla='at_risk'),
|
|
'Approaching their SLA deadline') }}
|
|
{{ stat_tile('SLA Alert', sla_breached, R,
|
|
url_for('issues.index', sla='breached'),
|
|
'Past their SLA deadline') }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# ── Recent activity ─────────────────────────────────────────────────────── #}
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-light fw-semibold">
|
|
<i class="bi bi-clock-history me-1"></i>Recent Activity
|
|
</div>
|
|
<div class="card-body p-0">
|
|
{% if recent_inspections %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Facility</th>
|
|
<th>Area</th>
|
|
{% if not current_user.is_inspector %}<th>Inspector</th>{% endif %}
|
|
<th>Score</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for insp in recent_inspections %}
|
|
<tr style="cursor:pointer;" onclick="window.location='{{ url_for('inspections.view', inspection_id=insp.id) }}'">
|
|
<td><small>{{ insp.inspection_date.strftime('%Y-%m-%d %H:%M') }}</small></td>
|
|
<td>{{ insp.facility.name }}</td>
|
|
<td>{{ insp.area.name if insp.area else '—' }}</td>
|
|
{% if not current_user.is_inspector %}<td>{{ insp.inspector.display_name }}</td>{% endif %}
|
|
<td>
|
|
{% if insp.overall_score %}
|
|
<span class="badge bg-{% if insp.overall_score >= 90 %}success{% elif insp.overall_score >= 70 %}warning{% else %}danger{% endif %}">
|
|
{{ insp.overall_score }}%
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted">—</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if insp.status == 'completed' %}success{% elif insp.status == 'flagged' %}danger{% else %}secondary{% endif %}">
|
|
{{ 'Submitted' if insp.status == 'completed' else insp.status|title }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-4 text-muted">
|
|
<i class="bi bi-inbox fs-2 d-block mb-2"></i>No recent inspections.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{# ── My open issues (inspector widget) ──────────────────────────────────── #}
|
|
{% if my_issues %}
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
|
<span class="fw-semibold"><i class="bi bi-person-check me-1 text-primary"></i>My Open Issues</span>
|
|
<a href="{{ url_for('issues.index') }}" class="btn btn-sm btn-outline-secondary">View all</a>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<table class="table table-hover mb-0" style="font-size:.85rem;">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th width="50">ID</th>
|
|
<th width="80">Severity</th>
|
|
<th>Facility / Description</th>
|
|
<th width="90">Status</th>
|
|
<th width="110">SLA</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for issue in my_issues %}
|
|
{% set sla = sla_status(issue) %}
|
|
<tr class="{{ 'table-danger' if sla == 'breached' else 'table-warning' if sla == 'at_risk' else '' }}">
|
|
<td class="text-muted">
|
|
<a href="{{ url_for('issues.view', issue_id=issue.id) }}" class="text-decoration-none fw-semibold">#{{ issue.id }}</a>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{{ 'danger' if issue.severity in ['critical','high'] else 'warning text-dark' if issue.severity == 'medium' else 'secondary' }}">
|
|
{{ issue.severity|title }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div>{{ issue.resolved_facility.name if issue.resolved_facility else '—' }}</div>
|
|
<div class="text-muted small">{{ issue.description[:60] }}{% if issue.description|length > 60 %}…{% endif %}</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{{ 'warning text-dark' if issue.status == 'in_progress' else 'danger' }}">
|
|
{{ issue.status|replace('_',' ')|title }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if sla == 'breached' %}
|
|
<span class="badge bg-danger"><i class="bi bi-alarm me-1"></i>Breached</span>
|
|
{% elif sla == 'at_risk' %}
|
|
<span class="badge bg-warning text-dark"><i class="bi bi-hourglass-split me-1"></i>{{ sla_hours_remaining(issue)|abs|round(1) }}h left</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">OK</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# ── Customer portal: scoped facilities panel ───────────────────────────── #}
|
|
{% if current_user.role == 'customer' %}
|
|
<div class="row g-3 mt-2">
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-light fw-semibold d-flex justify-content-between align-items-center"
|
|
role="button" data-bs-toggle="collapse" data-bs-target="#facilitiesBody"
|
|
aria-expanded="true" aria-controls="facilitiesBody" style="cursor:pointer;">
|
|
<span><i class="bi bi-building me-1"></i> Your Facilities
|
|
<i class="bi bi-chevron-down ms-1 small" id="facilitiesChevron"></i>
|
|
</span>
|
|
{% if customer_facilities %}
|
|
<span class="badge bg-secondary rounded-pill">{{ customer_facilities|length }}</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="collapse show" id="facilitiesBody">
|
|
{% if customer_facilities %}
|
|
<div class="card-body pb-2">
|
|
{% if customer_facilities|length > 6 %}
|
|
<div class="mb-3">
|
|
<input type="text" id="facilitySearch" class="form-control form-control-sm"
|
|
placeholder="Search facilities…">
|
|
</div>
|
|
{% endif %}
|
|
<div class="row g-2" id="facilityGrid">
|
|
{% for f in customer_facilities %}
|
|
<div class="col-12 col-sm-6 col-lg-4 facility-col">
|
|
<div class="border rounded p-2 h-100 d-flex flex-column facility-card">
|
|
<div class="fw-semibold mb-1 small">{{ f.name }}</div>
|
|
<div class="text-muted" style="font-size:.8rem;">{{ f.address or '—' }}</div>
|
|
<div class="my-1">
|
|
<span class="badge bg-light text-dark border" style="font-size:.75rem;">
|
|
{{ f.project.name if f.project else '—' }}
|
|
</span>
|
|
</div>
|
|
<div class="mt-auto pt-1">
|
|
<a href="{{ url_for('facilities.view_facility', facility_id=f.id) }}"
|
|
class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-eye"></i> View
|
|
</a>
|
|
<a href="{{ url_for('reports.facility_report', facility_id=f.id) }}"
|
|
class="btn btn-sm btn-outline-secondary ms-1">
|
|
<i class="bi bi-graph-up"></i> Report
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% if customer_facilities|length > 9 %}
|
|
<div id="facilityShowMore" class="text-center mt-3">
|
|
<button class="btn btn-sm btn-link text-muted" id="toggleFacilities">
|
|
Show all {{ customer_facilities|length }} facilities <i class="bi bi-chevron-down"></i>
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="card-body text-muted small">
|
|
<i class="bi bi-info-circle me-1"></i>
|
|
No facilities have been assigned to your account yet. Please contact your administrator.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var collapseEl = document.getElementById('facilitiesBody');
|
|
var chevron = document.getElementById('facilitiesChevron');
|
|
collapseEl.addEventListener('hide.bs.collapse', function () {
|
|
chevron.classList.replace('bi-chevron-down', 'bi-chevron-up');
|
|
});
|
|
collapseEl.addEventListener('show.bs.collapse', function () {
|
|
chevron.classList.replace('bi-chevron-up', 'bi-chevron-down');
|
|
});
|
|
{% if customer_facilities and customer_facilities|length > 9 %}
|
|
var VISIBLE = 9;
|
|
var cols = document.querySelectorAll('#facilityGrid .facility-col');
|
|
var btn = document.getElementById('toggleFacilities');
|
|
var more = document.getElementById('facilityShowMore');
|
|
var expanded = false;
|
|
|
|
cols.forEach(function (c, i) { if (i >= VISIBLE) c.style.display = 'none'; });
|
|
|
|
btn.addEventListener('click', function () {
|
|
expanded = !expanded;
|
|
cols.forEach(function (c, i) {
|
|
if (i >= VISIBLE) c.style.display = expanded ? '' : 'none';
|
|
});
|
|
btn.innerHTML = expanded
|
|
? 'Show fewer <i class="bi bi-chevron-up"></i>'
|
|
: 'Show all {{ customer_facilities|length }} facilities <i class="bi bi-chevron-down"></i>';
|
|
});
|
|
{% endif %}
|
|
{% if customer_facilities and customer_facilities|length > 6 %}
|
|
document.getElementById('facilitySearch').addEventListener('input', function () {
|
|
var q = this.value.toLowerCase();
|
|
document.querySelectorAll('#facilityGrid .facility-col').forEach(function (col) {
|
|
var match = col.querySelector('.facility-card').textContent.toLowerCase().includes(q);
|
|
col.style.display = match ? '' : 'none';
|
|
});
|
|
var more2 = document.getElementById('facilityShowMore');
|
|
if (more2) more2.style.display = this.value ? 'none' : '';
|
|
});
|
|
{% endif %}
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
|
|
{% endblock %}
|