4dcfcbdc45
The map sidebar that replaced Upcoming Actions on the project overview
is now also on the deeper Vibration tab — operators get the same
spatial context when they drill into vibration monitoring locations.
Refactor
- New partial templates/partials/projects/location_map.html.
Self-contained: includes the map div + a self-fetch script that
pulls coords from /api/projects/{p}/locations-json on load.
Accepts:
- project_id (required)
- map_height (default "320px")
- location_type ('vibration' | 'sound' | none = all)
- project_dashboard.html: ~150 lines of inline map JS deleted, replaced
with {% include 'partials/projects/location_map.html' %}. Identical
behavior, less duplication.
- projects/detail.html Vibration tab: locations list converted to a
2/3 + 1/3 grid; right column hosts the same map partial filtered
to location_type=vibration with a taller 450px viewport.
Bidirectional hover-highlight (card ↔ pin) works on both surfaces
since the partial registers its own document-level mouseover/mouseout
handlers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
97 lines
5.1 KiB
HTML
97 lines
5.1 KiB
HTML
<!-- Project Dashboard -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6 mb-6">
|
|
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
|
|
<div>
|
|
<h2 class="text-2xl font-semibold text-gray-900 dark:text-white">{{ project.name }}</h2>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
{% if project_type %}
|
|
{{ project_type.name }}
|
|
{% else %}
|
|
Project
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
{% if project.status == 'upcoming' %}
|
|
<span class="px-3 py-1 text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300 rounded-full">Upcoming</span>
|
|
{% elif project.status == 'active' %}
|
|
<span class="px-3 py-1 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full">Active</span>
|
|
{% elif project.status == 'on_hold' %}
|
|
<span class="px-3 py-1 text-xs font-medium bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400 rounded-full">On Hold</span>
|
|
{% elif project.status == 'completed' %}
|
|
<span class="px-3 py-1 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 rounded-full">Completed</span>
|
|
{% elif project.status == 'archived' %}
|
|
<span class="px-3 py-1 text-xs font-medium bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-full">Archived</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if project.description %}
|
|
<p class="text-gray-600 dark:text-gray-400 mt-4 max-w-3xl">{{ project.description }}</p>
|
|
{% endif %}
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mt-6">
|
|
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Locations</p>
|
|
<p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ locations | length }}</p>
|
|
</div>
|
|
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Assigned Units</p>
|
|
<p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ assigned_units | length }}</p>
|
|
</div>
|
|
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Active Sessions</p>
|
|
<p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ active_sessions | length }}</p>
|
|
</div>
|
|
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Completed Sessions</p>
|
|
<p class="text-2xl font-semibold text-gray-900 dark:text-white">{{ completed_sessions_count }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
{% if 'sound_monitoring' in modules and 'vibration_monitoring' not in modules %}
|
|
NRLs
|
|
{% else %}
|
|
Locations
|
|
{% endif %}
|
|
</h3>
|
|
<button onclick="openLocationModal('{% if 'sound_monitoring' in modules and 'vibration_monitoring' not in modules %}sound{% elif 'vibration_monitoring' in modules and 'sound_monitoring' not in modules %}vibration{% endif %}')" class="text-sm text-seismo-orange hover:text-seismo-navy">
|
|
{% if 'sound_monitoring' in modules and 'vibration_monitoring' not in modules %}
|
|
Add NRL
|
|
{% else %}
|
|
Add Location
|
|
{% endif %}
|
|
</button>
|
|
</div>
|
|
<div id="project-locations"
|
|
hx-get="/api/projects/{{ project.id }}/locations{% if 'sound_monitoring' in modules and 'vibration_monitoring' not in modules %}?location_type=sound{% endif %}"
|
|
hx-trigger="load"
|
|
hx-swap="innerHTML">
|
|
<div class="animate-pulse space-y-3">
|
|
<div class="bg-gray-200 dark:bg-gray-700 h-16 rounded-lg"></div>
|
|
<div class="bg-gray-200 dark:bg-gray-700 h-16 rounded-lg"></div>
|
|
<div class="bg-gray-200 dark:bg-gray-700 h-16 rounded-lg"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Location map — uses the reusable partial that fetches from
|
|
/api/projects/{p}/locations-json. Same render is reused on the
|
|
deeper Vibration tab so both surfaces stay in sync. #}
|
|
{% with project_id=project.id %}
|
|
{% include 'partials/projects/location_map.html' %}
|
|
{% endwith %}
|
|
</div>
|
|
|
|
{% if upcoming_actions %}
|
|
<div class="mt-3 text-xs text-right text-gray-500 dark:text-gray-400">
|
|
<a href="javascript:void(0)" onclick="switchTab('schedules')"
|
|
class="text-seismo-orange hover:text-seismo-navy">
|
|
{{ upcoming_actions | length }} upcoming action{{ '' if upcoming_actions | length == 1 else 's' }} →
|
|
</a>
|
|
</div>
|
|
{% endif %}
|