04/27 Changed facilities list and details views
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user