feat(trip-cluster): master/detail review routes + templates + clusterReview JS

This commit is contained in:
2026-06-27 17:35:58 +02:00
parent 44c05444d3
commit edf0735098
6 changed files with 508 additions and 3 deletions
@@ -0,0 +1,41 @@
{% from "macros.html" import lightbox %}
<div data-cluster-id="{{ c.id }}">
<div class="flex flex-wrap items-center gap-2 mb-3">
<input id="cluster-name" class="input input-bordered input-sm"
value="{{ c.decided_name or c.suggested_name }}">
<button class="btn btn-sm btn-success" @click="approve()">Approve (A)</button>
<button class="btn btn-sm" @click="nonTrip()">Non-trip (N)</button>
<!-- F7: Split disabled until a boundary photo is focused -->
<button class="btn btn-sm" :disabled="!focused" @click="split()">Split (S)</button>
<button class="btn btn-sm" @click="skip()">Skip (X)</button>
<button class="btn btn-sm btn-ghost" @click="apply()">Apply</button>
{% if prev_id %}<button class="btn btn-xs" @click="merge({{ prev_id }})">⤺ merge prev</button>{% endif %}
{% if next_id %}<button class="btn btn-xs" @click="merge({{ next_id }})">merge next ⤻</button>{% endif %}
</div>
<!-- F7: visible instruction for Split -->
<div class="text-xs opacity-60 mb-2" x-show="!focused">
Focus the first photo of the second trip (click it or use ← →), then Split.
</div>
<div class="text-sm opacity-60 mb-2">
{{ c.start_at[:10] }} → {{ c.end_at[:10] }} · {{ members | length }} assets · status {{ c.status }}
</div>
<div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2">
{% for a, m in members %}
<div class="photo-card relative cursor-pointer rounded-lg overflow-hidden border-2
{% if m.flagged_coverage %}border-info{% elif m.is_outlier %}border-warning{% else %}border-transparent{% endif %}"
data-asset-id="{{ a.immich_id }}" data-included="{{ '1' if m.included else '' }}"
tabindex="0" @click="openLightbox($el)" @focus="select($el)">
<img src="/thumb/{{ a.immich_id }}" class="w-full aspect-square object-cover" loading="lazy" alt="">
{% if m.flagged_coverage %}
<button class="absolute bottom-1 left-1 badge badge-xs badge-info"
@click.stop="setMember('{{ a.immich_id }}', true)">+ include</button>
{% elif m.is_outlier %}
<button class="absolute bottom-1 left-1 badge badge-xs badge-warning"
@click.stop="setMember('{{ a.immich_id }}', false)"> exclude</button>
{% endif %}
{% if not m.included %}<div class="absolute inset-0 bg-black/50 pointer-events-none"></div>{% endif %}
</div>
{% endfor %}
</div>
{{ lightbox() }}
</div>