Files
JQC_multi_tenant/app/templates/inspection_schedules/index.html
T

214 lines
11 KiB
HTML

{% extends "base.html" %}
{% block title %}Inspection Schedules{% endblock %}
{% block content %}
{# Who may create/edit/delete a schedule — mirrors schedule_manager_required
in routes/inspection_schedules.py. 'customer' is the Customer DIRECTOR, who
plans work for their own facilities; a Customer Inspector ('external_inspector')
performs schedules and is covered by is_inspector below. #}
{% set can_manage_schedules = current_user.role in
['admin','director','project_manager','auditor','customer'] %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-calendar2-week"></i> Inspection Schedules</h2>
{% if can_manage_schedules %}
<a href="{{ url_for('inspection_schedules.create') }}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> New Schedule
</a>
{% endif %}
</div>
<p class="text-muted small mb-4">
{% if current_user.is_inspector %}
Inspections scheduled for you. <strong>Auto</strong> schedules appear in your
Inspections list on their own each period; <strong>Plan</strong> schedules wait
for you to press Start.
{% else %}
<strong>Auto</strong> schedules automatically create an in-progress inspection
for the assigned inspector each period. <strong>Plan</strong> schedules assign a
due date and let the inspector press Start when they begin — with reminders the
day before, on the day, and an alert to managers once overdue.
{% endif %}
</p>
{# Pending / Completed tabs (phase51). The partition is on `active`, so it is
exhaustive — no schedule can fall between the two tabs. #}
<ul class="nav nav-tabs mb-0">
<li class="nav-item">
<a class="nav-link {{ 'active' if tab == 'pending' }}"
href="{{ url_for('inspection_schedules.index', tab='pending') }}">
<i class="bi bi-hourglass-split"></i> Pending
<span class="badge rounded-pill bg-{{ 'primary' if tab == 'pending' else 'secondary' }} ms-1">{{ pending_count }}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if tab == 'completed' }}"
href="{{ url_for('inspection_schedules.index', tab='completed') }}">
<i class="bi bi-check2-circle"></i> Completed
<span class="badge rounded-pill bg-{{ 'primary' if tab == 'completed' else 'secondary' }} ms-1">{{ completed_count }}</span>
</a>
</li>
</ul>
{% if schedules %}
<div class="card shadow-sm border-top-0 rounded-top-0">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Name</th><th>Template</th><th>Facility / Area</th>
<th>Inspector</th><th>Frequency</th><th>Mode</th><th>Next Due</th>
<th>Ends</th><th>Last Run</th><th>Status</th><th width="190"></th>
</tr>
</thead>
<tbody>
{% for s in schedules %}
<tr class="{{ 'text-muted' if not s.active else '' }}">
<td>
<strong>{{ s.name }}</strong>
{% if s.is_follow_up %}
{# phase48 — a schedule planned as the deferred twin of
"Re-inspect Now". Starting it produces a linked re-inspection. #}
<a href="{{ url_for('inspections.view', inspection_id=s.parent_inspection_id) }}"
class="badge bg-warning text-dark text-decoration-none ms-1"
title="Follow-up of inspection #{{ s.parent_inspection_id }}">
<i class="bi bi-arrow-repeat"></i> Follow-up #{{ s.parent_inspection_id }}
</a>
{% endif %}
</td>
<td>{{ s.template.name if s.template else '—' }}</td>
<td>
{{ s.facility.name if s.facility else '—' }}
{% if s.area %}<span class="text-muted small">/ {{ s.area.name }}</span>{% endif %}
</td>
<td>{{ s.inspector.display_name if s.inspector else '—' }}</td>
<td><span class="badge bg-secondary">{{ s.recurrence_label }}</span></td>
<td>
{% if s.mode == 'plan' %}
<span class="badge bg-info text-dark" title="Inspector presses Start">Plan</span>
{% else %}
<span class="badge bg-light text-dark border" title="Cron creates the inspection">Auto</span>
{% endif %}
</td>
<td class="small {{ 'text-danger fw-semibold' if s.active and s.next_run_at and s.next_run_at <= now else 'text-muted' }}">
{{ s.next_run_at.strftime('%Y-%m-%d %H:%M') if s.next_run_at else '—' }}
{% if s.is_overdue(today) %}
<span class="badge bg-danger ms-1">Overdue</span>
{% endif %}
</td>
<td class="small text-muted">
{% if s.frequency == 'once' %}—
{% elif s.end_date %}{{ s.end_date.strftime('%Y-%m-%d') }}
{% else %}No end{% endif %}
</td>
<td class="small text-muted">
{{ s.last_run_at.strftime('%Y-%m-%d %H:%M') if s.last_run_at else 'Never' }}
</td>
<td>
{# "Ended" separates a schedule that reached its end date from one a
manager switched off — both are inactive, for different reasons. #}
{% if s.active %}<span class="badge bg-success">Active</span>
{% elif s.is_expired %}<span class="badge bg-dark">Ended</span>
{% else %}<span class="badge bg-secondary">Paused</span>{% endif %}
{# phase50 — receipt acknowledgement. Only meaningful for plan
mode: an auto schedule materialises itself, so there is no
request for anyone to receive. #}
{% if s.mode == 'plan' and s.inspector_id %}
{% if s.is_acknowledged %}
<span class="badge bg-light text-success border border-success ms-1"
title="Inspector confirmed receipt on {{ s.acknowledged_at.strftime('%b %d, %Y %I:%M %p') }}">
<i class="bi bi-check-circle"></i> Confirmed
</span>
{% elif s.active %}
<span class="badge bg-light text-warning border border-warning ms-1"
title="The assigned inspector has not confirmed receipt yet">
<i class="bi bi-hourglass-split"></i> Awaiting confirmation
</span>
{% endif %}
{% endif %}
</td>
<td class="text-end">
{# Assignee-only, like Start: a manager must not be able to confirm
on someone's behalf, since the record means "this person saw it". #}
{% if s.active and s.mode == 'plan' and not s.is_acknowledged
and s.inspector_id == current_user.id %}
<form method="POST" class="d-inline"
action="{{ url_for('inspection_schedules.acknowledge', schedule_id=s.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-success"
title="Confirm you have received this request">
<i class="bi bi-check-lg"></i> Confirm
</button>
</form>
{% endif %}
{# Start is for whoever must DO the work. A Customer Director can
plan a schedule but never execute one — the route 403s them, so
the button must not be offered either. #}
{% if s.active and s.mode == 'plan'
and current_user.role != 'customer'
and (not current_user.is_inspector or s.inspector_id == current_user.id) %}
<a href="{{ url_for('inspection_schedules.start', schedule_id=s.id) }}"
class="btn btn-sm btn-primary" title="Start this inspection now">
<i class="bi bi-play-fill"></i> Start
</a>
{% endif %}
{% if can_manage_schedules %}
<a href="{{ url_for('inspection_schedules.edit', schedule_id=s.id) }}"
class="btn btn-sm btn-outline-secondary" title="Edit">
<i class="bi bi-pencil"></i>
</a>
<form method="POST"
action="{{ url_for('inspection_schedules.run_now', schedule_id=s.id) }}"
class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-primary" title="Create inspection now"
onclick="return confirm('Create an inspection from this schedule now?')">
<i class="bi bi-play-circle"></i>
</button>
</form>
<form method="POST"
action="{{ url_for('inspection_schedules.delete', schedule_id=s.id) }}"
class="d-inline"
onsubmit="return confirm('Delete this schedule? Existing inspections are not affected.')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete">
<i class="bi bi-trash3"></i>
</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% else %}
<div class="card shadow-sm border-top-0 rounded-top-0">
<div class="card-body text-center py-5 text-muted">
{% if tab == 'completed' %}
<i class="bi bi-check2-circle fs-1 d-block mb-3 opacity-25"></i>
<p class="mb-0">No completed schedules yet.</p>
{% elif completed_count %}
{# Nothing pending but there IS history — offer the other tab rather than
inviting them to create a duplicate of something already closed. #}
<i class="bi bi-calendar-check fs-1 d-block mb-3 opacity-25"></i>
<p class="mb-3">Nothing pending — all schedules are complete.</p>
<a href="{{ url_for('inspection_schedules.index', tab='completed') }}"
class="btn btn-outline-secondary">
<i class="bi bi-check2-circle"></i> View Completed ({{ completed_count }})
</a>
{% else %}
<i class="bi bi-calendar-x fs-1 d-block mb-3 opacity-25"></i>
<p class="mb-3">No inspection schedules configured yet.</p>
{% if can_manage_schedules %}
<a href="{{ url_for('inspection_schedules.create') }}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Create First Schedule
</a>
{% endif %}
{% endif %}
</div>
</div>
{% endif %}
{% endblock %}