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()
|
).order_by(Facility.name).all()
|
||||||
else:
|
else:
|
||||||
facilities = Facility.query.order_by(Facility.name).all()
|
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'])
|
@bp.route('/new', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
@@ -16,9 +16,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
{% if grouped %}
|
||||||
{% for facility in facilities %}
|
{% for group_key, group in grouped.items() %}
|
||||||
<div class="col-md-6 col-lg-4 mb-4">
|
{# ── 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 shadow-sm h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">
|
<h5 class="card-title">
|
||||||
@@ -60,14 +93,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% endfor %}
|
||||||
<div class="col-12">
|
</div>
|
||||||
<div class="alert alert-info">
|
|
||||||
<i class="bi bi-info-circle"></i> No facilities configured yet.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle"></i> No facilities configured yet.
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user.role == 'admin' %}
|
{% if current_user.role == 'admin' %}
|
||||||
<!-- Delete Confirmation Modal -->
|
<!-- Delete Confirmation Modal -->
|
||||||
@@ -110,9 +146,24 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
{% if current_user.role == 'admin' %}
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
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');
|
const deleteModal = document.getElementById('deleteModal');
|
||||||
deleteModal.addEventListener('show.bs.modal', function (event) {
|
deleteModal.addEventListener('show.bs.modal', function (event) {
|
||||||
const button = event.relatedTarget;
|
const button = event.relatedTarget;
|
||||||
@@ -137,7 +188,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
confirmBtn.disabled = false;
|
confirmBtn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
{% endif %}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
<h2><i class="bi bi-building"></i> {{ facility.name }}</h2>
|
<h2><i class="bi bi-building"></i> {{ facility.name }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-end d-flex gap-2 justify-content-end align-items-start">
|
<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'] %}
|
{% if current_user.role in ['admin', 'director', 'project_manager', 'customer'] %}
|
||||||
<a href="{{ url_for('reports.facility_report', facility_id=facility.id) }}"
|
<a href="{{ url_for('reports.facility_report', facility_id=facility.id) }}"
|
||||||
class="btn btn-outline-info">
|
class="btn btn-outline-info">
|
||||||
|
|||||||
Reference in New Issue
Block a user