Files
terra-view/templates/partials/projects/location_list.html
T
serversdown 3f0ec8f30b fix(locations): Remove/Restore buttons broken by quote collision in onclick
The buttons used inline `onclick="...({{ name | tojson }})"`, which
emits the location name as a JSON-quoted string with double quotes —
those double quotes collide with the onclick attribute's own double
quotes, terminating the attribute early.  Result: the browser parses
the attribute as broken HTML and the click handler never fires.

Switched both Remove and Restore to the data-attribute pattern the
Edit button already uses (data-loc-id / data-loc-name read via
this.dataset in the onclick).  Robust against any character in the
location name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 22:42:39 +00:00

147 lines
9.1 KiB
HTML

<!-- Project Locations List — split into Active + Removed sections.
Active locations get the full card with Assign/Edit/Delete/Remove
actions. Removed locations get a greyed-out card with a
Removed-on date, optional reason, and a Restore button. -->
{% if not active_locations and not removed_locations %}
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
<svg class="w-12 h-12 mx-auto mb-3 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"></path>
</svg>
<p>No locations added yet</p>
</div>
{% else %}
{# ─── Active locations ─── #}
{% if active_locations %}
<div class="space-y-3">
{% for item in active_locations %}
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:border-seismo-orange transition-colors">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2">
<a href="/projects/{{ project.id }}/nrl/{{ item.location.id }}"
class="font-semibold text-gray-900 dark:text-white hover:text-seismo-orange truncate">
{{ item.location.name }}
</a>
</div>
{% if item.location.description %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ item.location.description }}</p>
{% endif %}
{% if item.location.address %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ item.location.address }}</p>
{% endif %}
{% if item.location.coordinates %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ item.location.coordinates }}</p>
{% endif %}
</div>
<div class="flex items-center gap-2">
{% if item.assignment %}
<button onclick="unassignUnit('{{ item.assignment.id }}')"
class="text-xs px-3 py-1 rounded-full bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300">
Unassign
</button>
{% else %}
<button onclick="openAssignModal('{{ item.location.id }}', '{{ item.location.location_type or 'sound' }}')"
class="text-xs px-3 py-1 rounded-full bg-seismo-orange text-white hover:bg-seismo-navy">
Assign
</button>
{% endif %}
<button data-location='{{ {"id": item.location.id, "name": item.location.name, "description": item.location.description, "address": item.location.address, "coordinates": item.location.coordinates, "location_type": item.location.location_type} | tojson }}'
onclick="openEditLocationModal(this)"
class="text-xs px-3 py-1 rounded-full bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300">
Edit
</button>
<button data-loc-id="{{ item.location.id }}"
data-loc-name="{{ item.location.name | e }}"
onclick="openRemoveLocationModal(this.dataset.locId, this.dataset.locName)"
class="text-xs px-3 py-1 rounded-full bg-amber-50 text-amber-700 dark:bg-amber-900/20 dark:text-amber-300 hover:bg-amber-100"
title="Mark as no longer actively monitored — preserves historical events">
Remove
</button>
<button onclick="deleteLocation('{{ item.location.id }}')"
class="text-xs px-3 py-1 rounded-full bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300"
title="Permanently delete — only available if there's no history">
Delete
</button>
</div>
</div>
<div class="mt-3 text-xs text-gray-500 dark:text-gray-400 flex flex-wrap gap-4">
<span>Sessions: {{ item.session_count }}</span>
{% if item.assignment and item.assigned_unit %}
<span>Assigned: {{ item.assigned_unit.id }}</span>
{% else %}
<span>No active assignment</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
{# ─── Removed locations (collapsed by default) ─── #}
{% if removed_locations %}
<details class="mt-6 group" {% if not active_locations %}open{% endif %}>
<summary class="cursor-pointer text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 select-none list-none">
<span class="inline-flex items-center gap-2">
<svg class="w-4 h-4 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
Removed locations
<span class="text-xs px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">{{ removed_locations | length }}</span>
</span>
<p class="ml-6 mt-1 text-xs text-gray-400 dark:text-gray-500">Historical only — events stay attributed, but no new assignments or schedules can be created here.</p>
</summary>
<div class="space-y-3 mt-3">
{% for item in removed_locations %}
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-gray-50 dark:bg-slate-900/30 opacity-75">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2 flex-wrap">
<a href="/projects/{{ project.id }}/nrl/{{ item.location.id }}"
class="font-semibold text-gray-700 dark:text-gray-300 hover:text-seismo-orange truncate">
{{ item.location.name }}
</a>
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 font-semibold">
Removed
</span>
<span class="text-xs text-gray-500 dark:text-gray-400">
{{ item.location.removed_at.strftime('%Y-%m-%d') if item.location.removed_at else '—' }}
</span>
</div>
{% if item.location.removal_reason %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 italic">"{{ item.location.removal_reason }}"</p>
{% endif %}
{% if item.location.description %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ item.location.description }}</p>
{% endif %}
{% if item.location.address %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ item.location.address }}</p>
{% endif %}
</div>
<div class="flex items-center gap-2">
<button data-loc-id="{{ item.location.id }}"
data-loc-name="{{ item.location.name | e }}"
onclick="restoreLocation(this.dataset.locId, this.dataset.locName)"
class="text-xs px-3 py-1 rounded-full bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300 hover:bg-green-200"
title="Restore to active monitoring">
Restore
</button>
</div>
</div>
<div class="mt-3 text-xs text-gray-500 dark:text-gray-400 flex flex-wrap gap-4">
<span>Historical sessions: {{ item.session_count }}</span>
</div>
</div>
{% endfor %}
</div>
</details>
{% endif %}
{% endif %}