diff --git a/.claude/settings.json b/.claude/settings.json index 6162dc8..19e2d57 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -14,7 +14,8 @@ "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(python -c \"import ast,io; ast.parse\\(io.open\\('app/routes/dashboard.py',encoding='utf-8'\\).read\\(\\)\\); print\\('dashboard route OK'\\)\")" + "Bash(python -c \"import ast,io; ast.parse\\(io.open\\('app/routes/dashboard.py',encoding='utf-8'\\).read\\(\\)\\); print\\('dashboard route OK'\\)\")", + "Bash(python -c ' *)" ] } } diff --git a/app/__init__.py b/app/__init__.py index 6154a26..4c049ed 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -183,12 +183,16 @@ def create_app(config_name='default'): """Give base.html the shell to extend.""" from app.utils.time_utils import now_eastern theme = getattr(g, 'jqc_theme', 'classic') + _now = now_eastern() return { 'jqc_theme': theme, 'jqc_layout': 'layouts/modern.html' if theme == 'modern' else 'layouts/classic.html', - # Long-form date shown in the modern dashboard header. - 'now_display': now_eastern().strftime('%A, %B %-d, %Y'), + # Long-form date shown in the modern dashboard header. The day is + # interpolated rather than formatted with '%-d' — that flag is a + # glibc extension and raises ValueError on Windows, which would + # 500 every page (this context processor runs on both themes). + 'now_display': f'{_now.strftime("%A, %B")} {_now.day}, {_now.year}', } # ── Inject unread notification count into every template context ────── diff --git a/app/templates/modern/inspections/list.html b/app/templates/modern/inspections/list.html new file mode 100644 index 0000000..a1a9a05 --- /dev/null +++ b/app/templates/modern/inspections/list.html @@ -0,0 +1,326 @@ +{% extends "base.html" %} +{% block title %}Inspections{% endblock %} + +{# + MODERN inspections list (design A/B test). + + Same context variables, same query params, same form field names and the same + three JS blocks as templates/inspections/list.html — only the chrome differs. + Nothing was dropped: every filter, column, badge, the pagination links and the + delete modal are carried over verbatim. `insp-list-link` is preserved on the + View/Continue buttons so filter-state restore on Back still works. +#} + +{% block content %} + +{# ── Header ───────────────────────────────────────────────────────────── #} +
| # | Date | Contract | Facility | Area | +Template | Inspector | Score | +Status | + |
|---|---|---|---|---|---|---|---|---|---|
| #{{ ins.id }} | +{{ ins.inspection_date.strftime('%Y-%m-%d %H:%M') }} | +{{ ins.facility.project.name if ins.facility and ins.facility.project else '—' }} | +{{ ins.facility.name }} | +{% if ins.area %}{{ ins.area.name }}{% else %}—{% endif %} | ++ {{ ins.template.name }} + {% if ins.scheduled_inspection_id %} + + Scheduled + + {% endif %} + | +{{ ins.inspector.display_name }} | ++ {% if ins.overall_score %} + + {{ ins.overall_score }}% + + {% else %}—{% endif %} + | ++ + {{ 'Submitted' if ins.status == 'completed' else ins.status|replace('_',' ')|title }} + + {% if ins.status == 'in_progress' %} + {% set hours_open = ((now - ins.inspection_date).total_seconds() / 3600) %} + {% if hours_open > 24 %} + + Stale + + {% endif %} + {% endif %} + {% if ins.follow_up_required and not ins.follow_ups.count() %} + + Follow-up + + {% endif %} + | ++ {% if ins.status == 'in_progress' or ins.status == 'flagged' %} + Continue + {% else %} + View + {% endif %} + {% if current_user.role in ['admin', 'director'] %} + + {% endif %} + | +
| # | +Reported | +Severity | +Contract | +Facility / Area | +Description | +Status | +SLA | +Reporter | +Assigned | ++ |
|---|---|---|---|---|---|---|---|---|---|---|
| #{{ issue.id }} | +{{ issue.reported_at.strftime('%Y-%m-%d %H:%M') }} | ++ + {{ issue.severity|title }} + + | ++ {% set _c = issue.resolved_facility.project if issue.resolved_facility else none %} + {{ _c.name if _c else '—' }} + | +
+ {{ issue.resolved_facility.name if issue.resolved_facility else '—' }} + {{ issue.area.name if issue.area else '—' }} + |
+ + 60 %} title="{{ issue.description }}" style="cursor:help;"{% endif %}> + {{ issue.description[:60] }}{% if issue.description|length > 60 %}…{% endif %} + + | ++ + {{ issue.status|replace('_',' ')|title }} + + | ++ {% if sla == 'breached' %} + Breached + {% elif sla == 'at_risk' %} + {% set hrs = sla_hours_remaining(issue) %} + {{ hrs|abs|round(1) }}h left + {% elif sla == 'ok' %} + OK + {% else %} + — + {% endif %} + | ++ {% if issue.reporter %} + {{ issue.reporter.display_name }} + {% else %}—{% endif %} + | +
+ {% if current_user.role in ['admin', 'director', 'auditor'] and issue.status != 'resolved' %}
+
+
+
+
+ {% else %}
+ {% if issue.assigned_user %}{{ issue.assigned_user.display_name }}
+ {% else %}—{% endif %}
+ {% endif %}
+ {% if issue.handler_type == 'facility' %}
+ Facility
+ {% elif issue.handler_type == 'vendor' %}
+ Vendor
+ {% endif %}
+ |
+ + {# Following badge + inline unfollow #} + {% if is_following %} + + Following + + + {% endif %} + + + {% if current_user.role in ['admin','director','auditor'] or issue.assigned_to == current_user.id %} + Edit + {% else %} + View + {% endif %} + + {% if current_user.role in ['admin', 'director'] %} + + {% endif %} + | +