Aug 3 - Update the dashboard
This commit is contained in:
@@ -13,7 +13,8 @@
|
||||
"Bash(python -c \"print\\(len\\('phase47_sched_acknowledged'\\)\\)\")",
|
||||
"Bash(python -c \"import ast,io; ast.parse\\(io.open\\('app/routes/scheduled_inspections.py',encoding='utf-8'\\).read\\(\\)\\); print\\('route OK'\\)\")",
|
||||
"Bash(python -c \"import ast,io; ast.parse\\(io.open\\('app/utils/notifications.py',encoding='utf-8'\\).read\\(\\)\\); print\\('notif OK'\\)\")",
|
||||
"Bash(SECRET_KEY=x DATABASE_URL=sqlite:///:memory: DIGEST_SECRET=x MAIL_SERVER=localhost MAIL_USERNAME=x MAIL_PASSWORD=x MAIL_PORT=587 APP_BASE_URL=http://localhost MAIL_DEFAULT_SENDER=x@x.com python -c ' *)"
|
||||
"Bash(SECRET_KEY=x DATABASE_URL=sqlite:///:memory: DIGEST_SECRET=x MAIL_SERVER=localhost MAIL_USERNAME=x MAIL_PASSWORD=x MAIL_PORT=587 APP_BASE_URL=http://localhost MAIL_DEFAULT_SENDER=x@x.com python -c ' *)",
|
||||
"Bash(python -c \"import ast,io; ast.parse\\(io.open\\('app/routes/dashboard.py',encoding='utf-8'\\).read\\(\\)\\); print\\('dashboard route OK'\\)\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +294,23 @@ def index():
|
||||
stale_q = stale_q.filter(False)
|
||||
stale_in_progress = stale_q.count()
|
||||
|
||||
# ── In-progress inspections (all, not just stale) — "In Progress" tile ─────
|
||||
inprog_q = Inspection.query.filter(Inspection.status == 'in_progress')
|
||||
if is_inspector:
|
||||
if not inspector_facility_ids:
|
||||
inprog_q = inprog_q.filter(False)
|
||||
else:
|
||||
inprog_q = inprog_q.filter(
|
||||
Inspection.facility_id.in_(inspector_facility_ids),
|
||||
Inspection.inspector_id == current_user.id,
|
||||
)
|
||||
elif is_customer:
|
||||
if customer_facility_ids:
|
||||
inprog_q = inprog_q.filter(Inspection.facility_id.in_(customer_facility_ids))
|
||||
else:
|
||||
inprog_q = inprog_q.filter(False)
|
||||
in_progress_total = inprog_q.count()
|
||||
|
||||
# ── Unassigned open issues ────────────────────────────────────────────────
|
||||
from app.models.facility import Area as _AreaU
|
||||
unassigned_q = Issue.query.outerjoin(_AreaU, Issue.area_id == _AreaU.id).filter(
|
||||
@@ -361,6 +378,7 @@ def index():
|
||||
# ── Scheduled inspections (phase36): upcoming / overdue ──────────────
|
||||
sched_upcoming = []
|
||||
sched_overdue_count = 0
|
||||
sched_total = 0
|
||||
sched_open_inspections = {}
|
||||
if not is_customer:
|
||||
from app.models.scheduled_inspection import ScheduledInspection
|
||||
@@ -370,6 +388,7 @@ def index():
|
||||
if is_inspector:
|
||||
_sq = _sq.filter(ScheduledInspection.inspector_id == current_user.id)
|
||||
_all_sched = _sq.order_by(ScheduledInspection.next_due_date.asc()).all()
|
||||
sched_total = len(_all_sched) # active scheduled inspection plans ("On Schedules")
|
||||
sched_overdue_count = sum(1 for s in _all_sched if s.next_due_date < _today)
|
||||
# Upcoming = due today through the next 7 days (overdue shown separately)
|
||||
sched_upcoming = [
|
||||
@@ -383,7 +402,9 @@ def index():
|
||||
'dashboard.html',
|
||||
sched_upcoming = sched_upcoming,
|
||||
sched_overdue_count = sched_overdue_count,
|
||||
sched_total = sched_total,
|
||||
sched_open_inspections = sched_open_inspections,
|
||||
in_progress_total = in_progress_total,
|
||||
submitted_this_week = submitted_this_week,
|
||||
completed_today = completed_today,
|
||||
open_issues = open_issues,
|
||||
|
||||
+145
-234
@@ -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,14 +85,16 @@
|
||||
</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">
|
||||
{# ══════════════════════════════════════════════════════════════════════════
|
||||
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="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>
|
||||
<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>
|
||||
@@ -44,11 +102,15 @@
|
||||
{{ '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>
|
||||
<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 %}
|
||||
@@ -58,7 +120,7 @@
|
||||
<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">
|
||||
<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. #}
|
||||
@@ -88,243 +150,92 @@
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted small mb-0">No inspections due in the next 7 days.</p>
|
||||
{% endif %}
|
||||
<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>
|
||||
</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>
|
||||
|
||||
{# ══════════════════════════════════════════════════════════════════════════
|
||||
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' %}
|
||||
<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>
|
||||
</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>
|
||||
{{ 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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
{# ══════════════════════════════════════════════════════════════════════════
|
||||
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' %}
|
||||
<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>
|
||||
{{ 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>
|
||||
</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>
|
||||
{% 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>
|
||||
<i class="bi bi-alarm text-danger" style="font-size:2.5rem;opacity:.3;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</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 %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user