Aug 3 - Update the dashboard
This commit is contained in:
+198
-287
@@ -3,8 +3,34 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{# Handler ("Handled by") breakdown chips for an issue KPI card. `base` is a
|
||||
dict of extra issues.index query params identifying the card's scope. #}
|
||||
{# ── 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>
|
||||
@@ -20,6 +46,36 @@
|
||||
</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>
|
||||
@@ -29,302 +85,157 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ── Scheduled inspections: upcoming / overdue (phase36) ─────────────────── #}
|
||||
{% if current_user.role != 'customer' and (sched_upcoming or sched_overdue_count) %}
|
||||
<div class="card shadow-sm mb-4 border-0" style="border-left:4px solid #6366f1 !important;">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="fw-bold"><i class="bi bi-calendar-check text-primary"></i> Scheduled Inspections</span>
|
||||
<a href="{{ url_for('scheduled_inspections.index') }}" class="btn btn-sm btn-outline-primary">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-sm table-hover mb-0 align-middle">
|
||||
<thead class="table-light">
|
||||
<tr><th>Facility</th><th>Template</th><th>Inspector</th><th>Repeats</th><th>Due</th><th></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">
|
||||
{# 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 %}
|
||||
<p class="text-muted small mb-0">No inspections due in the next 7 days.</p>
|
||||
{% endif %}
|
||||
{# ══════════════════════════════════════════════════════════════════════════
|
||||
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">A. 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 %}
|
||||
|
||||
{# ── Inspections section ─────────────────────────────────────────────────── #}
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<i class="bi bi-clipboard-data-fill text-primary"></i>
|
||||
<span class="fw-bold text-uppercase" style="font-size:.78rem;letter-spacing:.07em;color:#64748b;">Inspections</span>
|
||||
<div style="flex:1;height:1px;background:#e2e8f0;"></div>
|
||||
</div>
|
||||
<div class="row g-3 mb-4">
|
||||
|
||||
<div class="col-6 col-md">
|
||||
<a href="{{ url_for('inspections.index', status='completed', date_from=today_str, date_to=today_str) }}" class="text-decoration-none">
|
||||
<div class="card text-white bg-success h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small text-white-50 fw-semibold">Submitted Today</span>
|
||||
<i class="bi bi-check-circle" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ completed_today }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">Fully completed & submitted today</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{# ══════════════════════════════════════════════════════════════════════════
|
||||
B. QUICK REVIEW (green)
|
||||
══════════════════════════════════════════════════════════════════════════ #}
|
||||
{% set G = '#4e8a2f' %}
|
||||
<div class="group-card group-green">
|
||||
<div class="group-title gt-green">B. 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 class="col-6 col-md">
|
||||
<a href="{{ url_for('inspections.index', status='completed', date_from=week_start_str, date_to=today_str) }}" class="text-decoration-none">
|
||||
<div class="card text-white bg-primary h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small text-white-50 fw-semibold">Submitted This Week</span>
|
||||
<i class="bi bi-calendar-check" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ submitted_this_week }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">Fully completed & submitted this week</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if current_user.role != 'customer' %}
|
||||
<div class="col-6 col-md">
|
||||
<a href="{{ url_for('inspections.index', status='in_progress') }}" class="text-decoration-none">
|
||||
<div class="card text-white h-100" style="background:#b45309;">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small fw-semibold" style="color:rgba(255,255,255,.6);">Stale In-Progress</span>
|
||||
<i class="bi bi-hourglass-split" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ stale_in_progress }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">Started >24 h ago, not yet submitted</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md">
|
||||
<a href="{{ url_for('inspections.index', status='follow_up') }}" class="text-decoration-none">
|
||||
<div class="card text-white h-100" style="background:#7c3aed;">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small fw-semibold" style="color:rgba(255,255,255,.6);">Pending Follow-ups</span>
|
||||
<i class="bi bi-arrow-repeat" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ pending_followups }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">Flagged for a follow-up re-inspection</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{# ── Issues section ──────────────────────────────────────────────────────── #}
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<i class="bi bi-exclamation-triangle-fill text-danger"></i>
|
||||
<span class="fw-bold text-uppercase" style="font-size:.78rem;letter-spacing:.07em;color:#64748b;">Issues</span>
|
||||
<div style="flex:1;height:1px;background:#e2e8f0;"></div>
|
||||
</div>
|
||||
<div class="row g-3 mb-4">
|
||||
|
||||
{# Open issues split into three cards by who handles them. #}
|
||||
<div class="col-6 col-md">
|
||||
<a href="{{ url_for('issues.index', status='open', handler_type='internal') }}" class="text-decoration-none">
|
||||
<div class="card text-white bg-primary h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small text-white-50 fw-semibold">Open · Janitorial</span>
|
||||
<i class="bi bi-people" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ handler_breakdown.internal }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">Open issues handled by Janitorial crew</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{# ══════════════════════════════════════════════════════════════════════════
|
||||
C. REVIEW ISSUES (orange)
|
||||
══════════════════════════════════════════════════════════════════════════ #}
|
||||
{% set O = '#d5761a' %}
|
||||
<div class="group-card group-orange">
|
||||
<div class="group-title gt-orange">C. 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 class="col-6 col-md">
|
||||
<a href="{{ url_for('issues.index', status='open', handler_type='facility') }}" class="text-decoration-none">
|
||||
<div class="card text-dark bg-info h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small fw-semibold" style="color:rgba(0,0,0,.55);">Open · Facility Staff</span>
|
||||
<i class="bi bi-building" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ handler_breakdown.facility }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;color:rgba(0,0,0,.6);">Open issues the facility staff handle</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md">
|
||||
<a href="{{ url_for('issues.index', status='open', handler_type='vendor') }}" class="text-decoration-none">
|
||||
<div class="card text-dark bg-warning h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small fw-semibold" style="color:rgba(0,0,0,.55);">Open · Vendor</span>
|
||||
<i class="bi bi-person-gear" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ handler_breakdown.vendor }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;color:rgba(0,0,0,.6);">Open issues an outside vendor handles</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md">
|
||||
<div class="card text-white h-100" style="background:#ea580c;">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small fw-semibold" style="color:rgba(255,255,255,.6);">Issues Opened Today</span>
|
||||
<i class="bi bi-flag" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<a href="{{ url_for('issues.index', date_from=today_str, date_to=today_str) }}" class="text-white text-decoration-none">
|
||||
<div class="fs-1 fw-bold lh-1">{{ issues_opened_today }}</div>
|
||||
</a>
|
||||
{% if issues_opened_today > 0 %}
|
||||
{{ handler_chips(opened_today_handler, {'date_from': today_str, 'date_to': today_str}) }}
|
||||
{% else %}
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">New issues reported today</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md">
|
||||
<a href="{{ url_for('issues.index', status='resolved', date_from=today_str, date_to=today_str) }}" class="text-decoration-none">
|
||||
<div class="card text-white bg-info h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small text-white-50 fw-semibold">Resolved Today</span>
|
||||
<i class="bi bi-check2-all" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ resolved_today }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">Issues closed and resolved today</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if current_user.role != 'customer' %}
|
||||
<div class="col-6 col-md">
|
||||
<a href="{{ url_for('issues.index', status='pending_verification') }}" class="text-decoration-none">
|
||||
<div class="card text-white h-100" style="background:#2563eb;">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small fw-semibold" style="color:rgba(255,255,255,.6);">Pending Verification</span>
|
||||
<i class="bi bi-clipboard2-check" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<div class="fs-1 fw-bold lh-1">{{ pending_verification }}</div>
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">Resolved but awaiting supervisor sign-off</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md">
|
||||
<div class="card text-white h-100" style="background:#16a34a;">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="small fw-semibold" style="color:rgba(255,255,255,.6);">Unassigned Open</span>
|
||||
<i class="bi bi-person-dash" style="font-size:1.4rem;opacity:.3;"></i>
|
||||
</div>
|
||||
<a href="{{ url_for('issues.index', status='open', unassigned='1') }}" class="text-white text-decoration-none">
|
||||
<div class="fs-1 fw-bold lh-1">{{ unassigned_open }}</div>
|
||||
</a>
|
||||
{% if unassigned_open > 0 %}
|
||||
{{ handler_chips(unassigned_handler, {'status': 'open', 'unassigned': '1'}) }}
|
||||
{% else %}
|
||||
<div class="mt-2" style="font-size:.72rem;opacity:.7;">Open issues with no one assigned</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{# ── SLA Summary ─────────────────────────────────────────────────────────── #}
|
||||
{# ══════════════════════════════════════════════════════════════════════════
|
||||
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 %}
|
||||
<div class="row g-3 mb-4">
|
||||
{% if sla_breached > 0 %}
|
||||
<div class="col-6 col-md-3">
|
||||
<a href="{{ url_for('issues.index', sla='breached') }}" class="text-decoration-none">
|
||||
<div class="card border-danger h-100">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="small text-danger fw-semibold">SLA Breached</div>
|
||||
<div class="fs-2 fw-bold text-danger">{{ sla_breached }}</div>
|
||||
</div>
|
||||
<i class="bi bi-alarm text-danger" style="font-size:2.5rem;opacity:.3;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% set R = '#cc0000' %}
|
||||
<div class="group-card group-grey">
|
||||
<div class="group-title gt-red">D. 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>
|
||||
{% endif %}
|
||||
{% if sla_at_risk > 0 %}
|
||||
<div class="col-6 col-md-3">
|
||||
<a href="{{ url_for('issues.index', sla='at_risk') }}" class="text-decoration-none">
|
||||
<div class="card border-warning h-100">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="small text-warning fw-semibold">SLA At Risk</div>
|
||||
<div class="fs-2 fw-bold text-warning">{{ sla_at_risk }}</div>
|
||||
</div>
|
||||
<i class="bi bi-alarm text-warning" style="font-size:2.5rem;opacity:.3;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -594,4 +505,4 @@
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user