04/27 Changed facilities list and details views

This commit is contained in:
2026-04-27 14:04:59 -04:00
parent b9df76fa46
commit bbfff75d17
3 changed files with 122 additions and 47 deletions
+22 -1
View File
@@ -23,7 +23,28 @@ def list_facilities():
).order_by(Facility.name).all()
else:
facilities = Facility.query.order_by(Facility.name).all()
return render_template('facilities/list.html', facilities=facilities)
# Group facilities by Contract (Project) for the collapsible list view.
# Facilities with no contract are collected under key '__none__' and
# rendered last as "No Contract Assigned".
from collections import OrderedDict
grouped = OrderedDict()
ungrouped = []
for f in facilities:
if f.project:
key = f.project.name
grouped.setdefault(key, {'project': f.project, 'facilities': []})
grouped[key]['facilities'].append(f)
else:
ungrouped.append(f)
grouped = OrderedDict(sorted(grouped.items()))
if ungrouped:
grouped['__none__'] = {'project': None, 'facilities': ungrouped}
logger.info('FACILITIES | list | user=%s | total=%s | groups=%s',
current_user.username, len(facilities), len(grouped))
return render_template('facilities/list.html', facilities=facilities, grouped=grouped)
@bp.route('/new', methods=['GET', 'POST'])
@login_required
+60 -9
View File
@@ -16,9 +16,42 @@
</div>
</div>
<div class="row">
{% for facility in facilities %}
<div class="col-md-6 col-lg-4 mb-4">
{% if grouped %}
{% for group_key, group in grouped.items() %}
{# ── Contract group header ────────────────────────────────────────────── #}
{% set collapse_id = 'contract-' ~ loop.index %}
<div class="mb-4">
<div class="d-flex align-items-center mb-2">
<button class="btn btn-link text-decoration-none p-0 d-flex align-items-center gap-2 fw-semibold fs-5"
type="button"
data-bs-toggle="collapse"
data-bs-target="#{{ collapse_id }}"
aria-expanded="true"
aria-controls="{{ collapse_id }}">
<i class="bi bi-chevron-down contract-chevron" style="transition: transform .2s;"></i>
{% if group.project %}
<i class="bi bi-briefcase text-primary"></i>
{{ group.project.name }}
{% else %}
<i class="bi bi-dash-circle text-secondary"></i>
<span class="text-secondary">No Contract Assigned</span>
{% endif %}
</button>
<span class="badge bg-secondary ms-2">{{ group.facilities|length }}</span>
{% if group.project and current_user.role in ['admin', 'director', 'project_manager'] %}
<a href="{{ url_for('projects.view', project_id=group.project.id) }}"
class="btn btn-sm btn-outline-secondary ms-2"
title="View Contract">
<i class="bi bi-arrow-right-circle"></i>
</a>
{% endif %}
</div>
{# ── Collapsible card grid ─────────────────────────────────────────── #}
<div class="collapse show" id="{{ collapse_id }}">
<div class="row">
{% for facility in group.facilities %}
<div class="col-md-6 col-lg-4 mb-3">
<div class="card shadow-sm h-100">
<div class="card-body">
<h5 class="card-title">
@@ -60,14 +93,17 @@
</div>
</div>
</div>
{% else %}
<div class="col-12">
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> No facilities configured yet.
{% endfor %}
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> No facilities configured yet.
</div>
{% endif %}
{% if current_user.role == 'admin' %}
<!-- Delete Confirmation Modal -->
@@ -110,9 +146,24 @@
{% endblock %}
{% block extra_js %}
{% if current_user.role == 'admin' %}
<script>
document.addEventListener('DOMContentLoaded', function () {
// ── Rotate chevron on collapse toggle ────────────────────────────────
document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(function (btn) {
const target = document.querySelector(btn.getAttribute('data-bs-target'));
if (!target) return;
const chevron = btn.querySelector('.contract-chevron');
target.addEventListener('hide.bs.collapse', function () {
if (chevron) chevron.style.transform = 'rotate(-90deg)';
});
target.addEventListener('show.bs.collapse', function () {
if (chevron) chevron.style.transform = 'rotate(0deg)';
});
});
{% if current_user.role == 'admin' %}
// ── Delete modal wiring ──────────────────────────────────────────────
const deleteModal = document.getElementById('deleteModal');
deleteModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget;
@@ -137,7 +188,7 @@ document.addEventListener('DOMContentLoaded', function () {
confirmBtn.disabled = false;
}
});
{% endif %}
});
</script>
{% endif %}
{% endblock %}
+3
View File
@@ -8,6 +8,9 @@
<h2><i class="bi bi-building"></i> {{ facility.name }}</h2>
</div>
<div class="col-md-4 text-end d-flex gap-2 justify-content-end align-items-start">
<a href="{{ url_for('facilities.list_facilities') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back to Facilities
</a>
{% if current_user.role in ['admin', 'director', 'project_manager', 'customer'] %}
<a href="{{ url_for('reports.facility_report', facility_id=facility.id) }}"
class="btn btn-outline-info">