Files
LT_Janitorial_Quality_Control/app/templates/issues/view.html
T

676 lines
32 KiB
HTML

{% extends "base.html" %}
{% block title %}Issue #{{ issue.id }}{% endblock %}
{% block extra_css %}
<style>
.comment-avatar {
width: 38px; height: 38px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: .9rem; color: #fff;
flex-shrink: 0;
}
.comment-bubble {
background: #f8f9fa; border: 1px solid #e9ecef;
border-radius: .5rem; padding: .75rem 1rem;
flex-grow: 1;
}
</style>
{% endblock %}
{% block content %}
{% set can_edit = current_user.role in ['admin','director','auditor'] or issue.assigned_to == current_user.id %}
{# The filtered list URL this page was opened from (phase: filter
preservation). Threaded into every action so an update or delete
returns to the same filtered page, and used by the Back button. #}
{% set back_url = request.args.get('next') or url_for('issues.index') %}
{# Is the viewer OUR staff? Drives the internal-only chrome on this page: the
"comments are visible to everyone" warning and the per-comment
"Customer visible" / "Staff only" badges. Both are instructions about how WE
work and must never reach a customer account.
Written as an explicit ALLOWLIST of our own roles, deliberately:
* It FAILS CLOSED. The obvious form, `not current_user.is_customer_account`,
fails OPEN — if the attribute is missing for any reason (a process still
running an older models/user.py after a template-only reload, say) Jinja
yields Undefined, `not Undefined` is true, and the internal text is shown
to exactly the people it must be hidden from. An allowlist of literal role
strings can only ever be true for a role we listed.
* `external_inspector` is absent ON PURPOSE. This is NOT the rule-87 case:
rule 87 is about capability/scoping, where a Customer Inspector must
behave exactly like our own inspector. Here the question is "does this
person work for us?", which is the one place the two genuinely differ.
Do not "fix" this by adding external_inspector to the list. #}
{% set viewer_is_our_staff = current_user.role in
['admin', 'director', 'project_manager', 'auditor', 'inspector'] %}
<div class="row">
{# ══════════════════════════════════ LEFT COLUMN ══════════════════════════════════ #}
<div class="col-lg-8">
{# ── Issue details ──────────────────────────────────────────────────── #}
<div class="card shadow-sm mb-4">
<div class="card-header d-flex justify-content-between align-items-center
bg-{{ 'danger' if issue.severity in ['critical','high'] else 'warning' if issue.severity == 'medium' else 'secondary' }}
text-{{ 'white' if issue.severity in ['critical','high','low'] else 'dark' }}">
<h5 class="mb-0"><i class="bi bi-exclamation-triangle"></i> Issue #{{ issue.id }} — {{ issue.severity|title }} Severity</h5>
<span class="badge bg-{{ 'info text-dark' if issue.status == 'pending_verification' else 'light text-dark' }}">
{{ issue.status|replace('_',' ')|title }}
</span>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-3">Reported</dt>
<dd class="col-sm-9">{{ issue.reported_at.strftime('%Y-%m-%d %H:%M') }}</dd>
<dt class="col-sm-3">Reported By</dt>
<dd class="col-sm-9">
{% if issue.reporter %}
{{ issue.reporter.display_name }}
{% if issue.reporter.role == 'customer' %}
<span class="badge bg-info text-dark ms-1">Customer</span>
{% else %}
<span class="badge bg-secondary ms-1">{{ issue.reporter.role|replace('_',' ')|title }}</span>
{% endif %}
{% else %}
<span class="text-muted"></span>
{% endif %}
</dd>
<dt class="col-sm-3">Contract</dt>
<dd class="col-sm-9">
{% if issue.resolved_facility and issue.resolved_facility.project %}
{{ issue.resolved_facility.project.name }}
{% else %}—{% endif %}
</dd>
<dt class="col-sm-3">Facility</dt>
<dd class="col-sm-9">{{ issue.resolved_facility.name if issue.resolved_facility else '—' }}</dd>
<dt class="col-sm-3">Area</dt>
<dd class="col-sm-9">{{ issue.area.name if issue.area else '—' }}</dd>
{% if issue.inspection %}
<dt class="col-sm-3">Inspection</dt>
<dd class="col-sm-9">
<a href="{{ url_for('inspections.view', inspection_id=issue.inspection_id) }}">#{{ issue.inspection_id }}</a>
</dd>
{% endif %}
<dt class="col-sm-3">Handled By</dt>
<dd class="col-sm-9">
{% if issue.handler_type == 'facility' %}
<span class="badge bg-info text-dark" title="The facility's own on-site staff handle it."><i class="bi bi-building me-1"></i>Facility Staff</span>
{% elif issue.handler_type == 'vendor' %}
<span class="badge bg-warning text-dark" title="An outside contractor handles it."><i class="bi bi-person-gear me-1"></i>External Vendor</span>
{% else %}
<span class="badge bg-primary" title="Our janitorial crew handles it."><i class="bi bi-people me-1"></i>Janitorial Staff</span>
{% endif %}
</dd>
<dt class="col-sm-3">
{{ 'Follow-up Owner' if issue.handler_type in ['facility','vendor'] else 'Assigned To' }}
</dt>
<dd class="col-sm-9">{{ issue.assigned_user.display_name if issue.assigned_user else '— Unassigned —' }}</dd>
{% if (issue.handler_type or 'internal') == 'internal' and issue.internal_handler_name %}
<dt class="col-sm-3">Staff</dt>
<dd class="col-sm-9">
<i class="bi bi-people text-secondary me-1"></i>
<strong>{{ issue.internal_handler_name }}</strong>
{% if issue.internal_handler_contact %}
<span class="text-muted ms-2">{{ issue.internal_handler_contact }}</span>
{% endif %}
</dd>
{% endif %}
{% if issue.handler_type == 'facility' and issue.facility_handler_name %}
<dt class="col-sm-3">Facility Contact</dt>
<dd class="col-sm-9">
<i class="bi bi-building text-secondary me-1"></i>
<strong>{{ issue.facility_handler_name }}</strong>
{% if issue.facility_handler_contact %}
<span class="text-muted ms-2">{{ issue.facility_handler_contact }}</span>
{% endif %}
{% if issue.facility_handler_notes %}
<div class="text-muted small mt-1" style="white-space:pre-wrap;">{{ issue.facility_handler_notes }}</div>
{% endif %}
</dd>
{% endif %}
{% if issue.handler_type == 'vendor' and issue.vendor_name %}
<dt class="col-sm-3">Contractor</dt>
<dd class="col-sm-9">
<i class="bi bi-person-gear text-secondary me-1"></i>
<strong>{{ issue.vendor_name }}</strong>
{% if issue.vendor_contact %}
<span class="text-muted ms-2">{{ issue.vendor_contact }}</span>
{% endif %}
{% if issue.vendor_notes %}
<div class="text-muted small mt-1" style="white-space:pre-wrap;">{{ issue.vendor_notes }}</div>
{% endif %}
</dd>
{% endif %}
{% if issue.resolved_at %}
<dt class="col-sm-3">Resolved</dt>
<dd class="col-sm-9">{{ issue.resolved_at.strftime('%Y-%m-%d %H:%M') }}</dd>
{% endif %}
</dl>
<hr>
<h6>Description</h6>
<p class="mb-0" style="white-space:pre-wrap;">{{ issue.description }}</p>
{% if issue.photo_path or issue.mobile_photo_paths %}
<hr>
<h6>Photo Evidence</h6>
<div class="d-flex flex-wrap gap-2">
{% if issue.photo_path %}
<a href="{{ media_url(issue.photo_path) }}" target="_blank">
<img src="{{ media_url(issue.photo_path) }}"
class="img-fluid rounded" style="max-height:300px; max-width:100%;">
</a>
{% endif %}
{% for photo in (issue.mobile_photo_paths or []) %}
<a href="{{ media_url(photo) }}" target="_blank">
<img src="{{ media_url(photo) }}"
class="rounded border" style="max-height:300px; max-width:100%; object-fit:cover;">
</a>
{% endfor %}
</div>
{% endif %}
{% if issue.result_notes or issue.result_photos %}
<hr>
<h6><i class="bi bi-clipboard2-check text-success"></i> Resolution Details</h6>
{% if issue.result_notes %}
<p class="mb-2" style="white-space:pre-wrap;">{{ issue.result_notes }}</p>
{% endif %}
{% if issue.result_photos %}
<div class="d-flex flex-wrap gap-2 mt-2">
{% for photo in issue.result_photos %}
<a href="{{ media_url(photo) }}" target="_blank">
<img src="{{ media_url(photo) }}"
class="rounded border" style="max-height:120px; max-width:160px; object-fit:cover;"
alt="Result photo">
</a>
{% endfor %}
</div>
{% endif %}
{% endif %}
{# ── Verification panel ── #}
{% if issue.verified_at %}
<hr>
<div class="alert alert-success py-2 mb-0">
<i class="bi bi-patch-check-fill me-1"></i>
<strong>Verified</strong> by {{ issue.verifier.display_name if issue.verifier else 'unknown' }}
on {{ issue.verified_at.strftime('%Y-%m-%d %H:%M') }}.
{% if issue.verification_note %}<br><span class="small">{{ issue.verification_note }}</span>{% endif %}
</div>
{% elif issue.status == 'pending_verification' %}
<hr>
<div class="alert alert-info py-2 mb-0">
<i class="bi bi-hourglass-split me-1"></i>
<strong>Awaiting director verification.</strong>
{% if current_user.role in ['admin','director','auditor'] %}
<form method="POST" action="{{ url_for('issues.verify', issue_id=issue.id) }}" class="mt-2">
<input type="hidden" name="next" value="{{ back_url }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-2">
<input type="text" name="verification_note" class="form-control form-control-sm"
placeholder="Verification note (optional)">
</div>
<button type="submit" class="btn btn-sm btn-success">
<i class="bi bi-patch-check me-1"></i>Verify &amp; Close
</button>
</form>
{% endif %}
</div>
{% endif %}
</div>
</div>
{# ── Comments ───────────────────────────────────────────────────────── #}
<div class="card shadow-sm mb-4" id="comments-section">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="bi bi-chat-left-text me-1"></i>Comments
<span class="badge bg-secondary rounded-pill ms-1">{{ comments|length }}</span>
</h6>
</div>
{# Comment list #}
{% if comments %}
<div class="p-3 pb-0" id="comments-list">
{% set avatar_colors = ['#4f46e5','#0891b2','#059669','#d97706','#dc2626','#7c3aed','#db2777'] %}
{% for c in comments %}
{% set avatar_color = avatar_colors[c.author.id % (avatar_colors | length)] %}
<div class="d-flex gap-3 mb-3">
<div class="comment-avatar" style="background:{{ avatar_color }};">
{{ c.author.display_name[0] | upper }}
</div>
<div class="comment-bubble">
<div class="d-flex justify-content-between align-items-start flex-wrap gap-1 mb-1">
<div class="d-flex align-items-center gap-2">
<span class="fw-semibold" style="font-size:.9rem;">{{ c.author.display_name }}</span>
{% if c.author.role == 'customer' %}
<span class="badge bg-info text-dark" style="font-size:.65rem;">Customer</span>
{% else %}
<span class="badge bg-secondary" style="font-size:.65rem;">{{ c.author.role|replace('_',' ')|title }}</span>
{% endif %}
{# Visibility indicator — staff only.
While comments_open is set, EVERY comment is visible to the
customer, so the old "Staff only" badge would be a lie. It is
suppressed rather than shown incorrectly. #}
{% if viewer_is_our_staff and not comments_open %}
{% if c.is_customer_visible %}
<span class="badge bg-success bg-opacity-10 text-success border border-success"
style="font-size:.6rem;" title="Customer can see this comment">
<i class="bi bi-eye me-1"></i>Customer visible
</span>
{% else %}
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary"
style="font-size:.6rem;" title="Hidden from customer">
<i class="bi bi-eye-slash me-1"></i>Staff only
</span>
{% endif %}
{% endif %}
</div>
<div class="d-flex align-items-center gap-2">
<span class="badge bg-{{ 'success' if c.status_at_time == 'resolved' else 'info text-dark' if c.status_at_time == 'pending_verification' else 'warning text-dark' if c.status_at_time == 'in_progress' else 'danger' }}"
style="font-size:.65rem;">
{{ c.status_at_time|replace('_',' ')|title }}
</span>
<small class="text-muted">{{ c.created_at.strftime('%b %d, %Y %H:%M') }}</small>
</div>
</div>
<p class="mb-0" style="white-space:pre-wrap; font-size:.9rem;">{{ c.body }}</p>
</div>
</div>
{% endfor %}
</div>
<hr class="mx-3 my-0">
{% elif current_user.role == 'customer' %}
<div class="px-3 pt-3 pb-0">
<p class="text-muted small"><i class="bi bi-chat-left me-1"></i>No comments yet.</p>
</div>
<hr class="mx-3 my-0">
{% endif %}
{# ── Add Comment form ─────────────────────────────────────────────── #}
{% set can_customer_comment = current_user.role == 'customer' and (is_following or issue.reported_by == current_user.id) %}
{% if can_edit %}
{# Staff comment form with visibility checkbox #}
<div class="card-body">
<p class="fw-semibold small mb-2">Add Comment</p>
<form method="post">
<input type="hidden" name="next" value="{{ back_url }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="status" value="{{ issue.status }}">
<input type="hidden" name="assigned_to" value="{{ issue.assigned_to or 0 }}">
<div class="mb-2">
<textarea name="update_notes" class="form-control" rows="3"
placeholder="Write a comment…" required></textarea>
</div>
{# While comments_open is set, every comment reaches the customer, so
the "Share with customer" tick decides nothing. Saying so plainly
matters: a staff member must not write something they believe is
private. The checkbox is still posted and recorded, so turning the
setting off restores its meaning immediately.
OUR STAFF ONLY. `can_edit` is also true for a Customer Inspector
assigned to the issue, and this banner is an internal-process
warning ("do not post internal-only notes") — showing it to a
customer account exposes how we work and reads as nonsense to
them, since nothing they write was ever private. #}
{% if comments_open and viewer_is_our_staff %}
<div class="alert alert-warning py-2 px-3 small mb-2">
<i class="bi bi-eye me-1"></i>
<strong>Comments are currently visible to everyone,</strong> including
the customer. Do not post internal-only notes here.
</div>
{% endif %}
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
<div class="form-check form-check-inline mb-0 {{ 'd-none' if comments_open }}">
<input class="form-check-input" type="checkbox"
name="is_customer_visible" id="is_customer_visible" value="1">
<label class="form-check-label small text-muted" for="is_customer_visible">
<i class="bi bi-eye me-1"></i>Share with customer
</label>
</div>
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-send me-1"></i>Post Comment
</button>
</div>
</form>
</div>
{% elif can_customer_comment %}
{# Customer comment form — visible to all by design #}
<div class="card-body">
<p class="fw-semibold small mb-2">Add Comment</p>
<form method="post">
<input type="hidden" name="next" value="{{ back_url }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-2">
<textarea name="update_notes" class="form-control" rows="3"
placeholder="Write a comment…" required></textarea>
</div>
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-send me-1"></i>Post Comment
</button>
</form>
</div>
{% elif current_user.role == 'customer' %}
<div class="card-body py-2 d-flex align-items-center gap-2 flex-wrap">
<p class="text-muted small mb-0">
<i class="bi bi-bell me-1"></i>To add comments, click on the Follow button
</p>
<form method="post" action="{{ url_for('issues.follow', issue_id=issue.id) }}" class="mb-0">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-outline-primary btn-sm">
<i class="bi bi-bell"></i> Follow
</button>
</form>
</div>
{% else %}
<div class="card-body py-2">
<p class="text-muted small mb-0">
<i class="bi bi-lock me-1"></i>Only assigned staff can add comments.
</p>
</div>
{% endif %}
</div>
</div>{# /col-lg-8 #}
{# ══════════════════════════════════ RIGHT COLUMN ═════════════════════════════════ #}
<div class="col-lg-4">
{# ── Follow / Unfollow ──────────────────────────────────────────────── #}
<div class="card shadow-sm mb-3">
<div class="card-body d-flex align-items-center justify-content-between py-2">
<div>
<i class="bi bi-bell{{ '-fill text-primary' if is_following else ' text-muted' }} me-1"></i>
<span class="fw-semibold" style="font-size:.9rem;">
{% if is_following %}Following{% else %}Not following{% endif %}
</span>
<span class="text-muted ms-2" style="font-size:.8rem;">
{{ issue.followers.count() }} follower{{ 's' if issue.followers.count() != 1 else '' }}
</span>
</div>
{% if is_following %}
<form method="post" action="{{ url_for('issues.unfollow', issue_id=issue.id) }}" class="mb-0">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-bell-slash"></i> Unfollow
</button>
</form>
{% else %}
<form method="post" action="{{ url_for('issues.follow', issue_id=issue.id) }}" class="mb-0">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-outline-primary btn-sm">
<i class="bi bi-bell"></i> Follow
</button>
</form>
{% endif %}
</div>
</div>
{# ── Update Form ────────────────────────────────────────────────────── #}
{% if can_edit %}
<div class="card shadow-sm">
<div class="card-header bg-light"><h6 class="mb-0">Update Issue</h6></div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="next" value="{{ back_url }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
{{ form.status.label(class="form-label fw-semibold") }}
{{ form.status(class="form-select") }}
</div>
{% if current_user.role in ['admin','director','project_manager','auditor'] %}
{# ── Who handles this issue ── #}
<div class="mb-3">
{{ form.handler_type.label(class="form-label fw-semibold") }}
{{ form.handler_type(class="form-select", id="handler_type_select") }}
<div class="form-text" id="handler_desc"></div>
</div>
{% endif %}
{% if current_user.role in ['admin','director','auditor'] %}
<div class="mb-3" id="assigned_to_wrap">
<label class="form-label fw-semibold" id="assigned_to_label">Assign To</label>
{{ form.assigned_to(class="form-select") }}
<div class="form-text" id="assigned_to_help" style="display:none;">
The facility/vendor does the work; this is our follow-up owner.
</div>
</div>
{% endif %}
{% if current_user.role in ['admin','director','project_manager','auditor'] %}
{# ── Janitorial-staff handler (shown when Handled By = Janitorial Staff) ── #}
<div id="internal_handler_block" style="display:none;">
<hr class="my-3">
<p class="fw-semibold small mb-2">
<i class="bi bi-people me-1 text-secondary"></i>Janitorial Staff
</p>
<div class="mb-2">
{{ form.internal_handler_name.label(class="form-label small fw-semibold mb-1") }}
{{ form.internal_handler_name(class="form-control form-control-sm",
placeholder="Name of the crew member who will handle it",
value=issue.internal_handler_name or '') }}
</div>
<div class="mb-3">
{{ form.internal_handler_contact.label(class="form-label small fw-semibold mb-1") }}
{{ form.internal_handler_contact(class="form-control form-control-sm",
placeholder="Phone or email",
value=issue.internal_handler_contact or '') }}
</div>
</div>
{# ── Facility-staff handler (shown when Handled By = Facility Staff) ── #}
<div id="facility_handler_block" style="display:none;">
<hr class="my-3">
<p class="fw-semibold small mb-2">
<i class="bi bi-building me-1 text-secondary"></i>Facility Staff Contact
</p>
<div class="mb-2">
{{ form.facility_handler_name.label(class="form-label small fw-semibold mb-1") }}
{{ form.facility_handler_name(class="form-control form-control-sm",
placeholder="Facility staff / point of contact",
value=issue.facility_handler_name or '') }}
</div>
<div class="mb-2">
{{ form.facility_handler_contact.label(class="form-label small fw-semibold mb-1") }}
{{ form.facility_handler_contact(class="form-control form-control-sm",
placeholder="Phone or email",
value=issue.facility_handler_contact or '') }}
</div>
<div class="mb-3">
{{ form.facility_handler_notes.label(class="form-label small fw-semibold mb-1") }}
{{ form.facility_handler_notes(class="form-control form-control-sm", rows=2,
placeholder="What the facility staff are handling…") }}
</div>
</div>
{# ── External vendor (shown when Handled By = External Vendor) ── #}
<div id="vendor_block" style="display:none;">
<hr class="my-3">
<p class="fw-semibold small mb-2">
<i class="bi bi-person-gear me-1 text-secondary"></i>External Contractor
</p>
<div class="mb-2">
{{ form.vendor_name.label(class="form-label small fw-semibold mb-1") }}
{{ form.vendor_name(class="form-control form-control-sm",
placeholder="Contractor or vendor name",
value=issue.vendor_name or '') }}
</div>
<div class="mb-2">
{{ form.vendor_contact.label(class="form-label small fw-semibold mb-1") }}
{{ form.vendor_contact(class="form-control form-control-sm",
placeholder="Phone or email",
value=issue.vendor_contact or '') }}
</div>
<div class="mb-3">
{{ form.vendor_notes.label(class="form-label small fw-semibold mb-1") }}
{{ form.vendor_notes(class="form-control form-control-sm", rows=2,
placeholder="Notes about what the contractor is handling…") }}
</div>
</div>
{% endif %}
<hr class="my-3">
<div class="mb-3">
{{ form.result_notes.label(class="form-label fw-semibold") }}
{{ form.result_notes(class="form-control", rows=3,
placeholder="Describe what was done to resolve this issue…",
value=issue.result_notes or '') }}
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Result Photos</label>
<input type="file" name="result_photos" id="result_photos"
class="form-control" accept="image/*" multiple>
<div class="form-text">Attach one or more photos showing the resolution.</div>
{% if issue.result_photos %}
<div class="mt-2">
<small class="text-muted">{{ issue.result_photos|length }} photo(s) already uploaded</small>
</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary w-100">Save Update</button>
</form>
{% if issue.status in ['in_progress', 'resolved'] and current_user.role not in ['customer'] %}
<form method="POST"
action="{{ url_for('issues.request_verification', issue_id=issue.id) }}"
class="mt-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-outline-info w-100"
onclick="return confirm('Mark this issue as pending director verification?')">
<i class="bi bi-hourglass-split me-1"></i> Request Verification
</button>
</form>
{% endif %}
</div>
</div>
{% endif %}
</div>{# /col-lg-4 #}
</div>
<div class="d-flex align-items-center gap-2 mt-2">
<a href="{{ back_url }}" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-arrow-left"></i> Back to Issues
</a>
<a href="{{ url_for('issues.export_pdf', issue_id=issue.id) }}" class="btn btn-outline-primary btn-sm">
<i class="bi bi-file-earmark-pdf"></i> Export PDF
</a>
{% if current_user.role in ['admin', 'director'] %}
<button type="button" class="btn btn-outline-danger btn-sm"
data-bs-toggle="modal" data-bs-target="#deleteIssueModal">
<i class="bi bi-trash"></i> Delete Issue
</button>
{% endif %}
</div>
{% if current_user.role in ['admin', 'director'] %}
<div class="modal fade" id="deleteIssueModal" tabindex="-1" aria-labelledby="deleteIssueModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteIssueModalLabel">
<i class="bi bi-exclamation-triangle-fill"></i> Confirm Deletion
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>You are about to permanently delete:</p>
<p class="fw-bold">Issue #{{ issue.id }} — {{ issue.severity|title }} severity in {{ issue.area.name if issue.area else '—' }}</p>
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-triangle-fill"></i>
This action is <strong>irreversible</strong>. All comments, photos, and
follower records associated with this issue will also be deleted.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Cancel
</button>
<form method="POST" action="{{ url_for('issues.delete', issue_id=issue.id) }}" class="d-inline">
<input type="hidden" name="next" value="{{ back_url }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash-fill"></i> Delete Permanently
</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
{% block extra_js %}
<script>
(function () {
'use strict';
// Refresh bell badge after a successful update
if (document.querySelector('.alert-success')) {
if (typeof fetchNotifications === 'function') {
fetchNotifications();
}
}
// Scroll to bottom of comments list after posting a comment
if (document.querySelector('.alert-success')) {
var section = document.getElementById('comments-section');
if (section) {
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
// ── "Handled By" — show the relevant sub-block (facility vs vendor) and
// relabel the assignee as a follow-up owner for facility/vendor. ──
var handlerSelect = document.getElementById('handler_type_select');
var HANDLER_DESC = {
'internal': 'Our janitorial crew handles it.',
'facility': "The facility's own on-site staff handle it.",
'vendor': 'An outside contractor handles it.'
};
function syncHandlerUI() {
if (!handlerSelect) { return; }
var v = handlerSelect.value;
var intBlock = document.getElementById('internal_handler_block');
var facBlock = document.getElementById('facility_handler_block');
var venBlock = document.getElementById('vendor_block');
if (intBlock) { intBlock.style.display = (v === 'internal') ? '' : 'none'; }
if (facBlock) { facBlock.style.display = (v === 'facility') ? '' : 'none'; }
if (venBlock) { venBlock.style.display = (v === 'vendor') ? '' : 'none'; }
var desc = document.getElementById('handler_desc');
if (desc) { desc.textContent = HANDLER_DESC[v] || ''; }
var label = document.getElementById('assigned_to_label');
var help = document.getElementById('assigned_to_help');
if (label) {
label.textContent = (v === 'facility' || v === 'vendor')
? 'Follow-up Owner' : 'Assign To';
}
if (help) {
help.style.display = (v === 'facility' || v === 'vendor') ? '' : 'none';
}
}
if (handlerSelect) {
handlerSelect.addEventListener('change', syncHandlerUI);
syncHandlerUI(); // set initial state on load
}
})();
</script>
{% endblock %}
{% endblock %}