Files
ShooterHub/templates/sessions/detail.html

219 lines
9.9 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ session.label }} — The Shooter's Network{% endblock %}
{% block content %}
<div style="display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:1rem;margin-bottom:1.5rem;">
<div>
<div style="font-size:0.82rem;color:#888;margin-bottom:.2rem;">
{% if is_owner %}<a href="{{ url_for('sessions.index') }}">Sessions</a> &rsaquo; {% endif %}
{{ session.session_date.strftime('%d %b %Y') }}
{% if session.is_public %}
<span style="background:#e8f5e9;color:#27ae60;font-size:0.75rem;padding:.1rem .45rem;border-radius:3px;margin-left:.4rem;">Public</span>
{% endif %}
</div>
<h1 style="margin:0;">{{ session.label }}</h1>
<div style="font-size:0.88rem;color:#666;margin-top:.4rem;">
by {{ session.user.display_name or session.user.email.split('@')[0] }}
</div>
</div>
{% if is_owner %}
<div style="display:flex;gap:.75rem;">
<a href="{{ url_for('sessions.edit', session_id=session.id) }}"
style="background:#f0f4ff;color:#1a1a2e;padding:0.5rem 1.1rem;border-radius:4px;font-size:0.9rem;text-decoration:none;">
Edit
</a>
<form method="post" action="{{ url_for('sessions.delete', session_id=session.id) }}"
onsubmit="return confirm('Delete this session? This cannot be undone.');">
<button type="submit"
style="background:#fff0f0;color:#c0392b;border:1px solid #f5c6c6;border-radius:4px;padding:0.5rem 1.1rem;font-size:0.9rem;cursor:pointer;">
Delete
</button>
</form>
</div>
{% endif %}
</div>
{# ---- Stats cards ---- #}
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:1rem;margin-bottom:2rem;">
{% if session.location_name or session.distance_m %}
<div style="background:#f8f9fb;border-radius:6px;padding:1rem;">
<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:.05em;color:#888;margin-bottom:.4rem;">Location</div>
{% if session.location_name %}<div style="font-weight:600;">{{ session.location_name }}</div>{% endif %}
{% if session.distance_m %}<div style="color:#555;font-size:0.9rem;">{{ session.distance_m }} m</div>{% endif %}
</div>
{% endif %}
{% if session.weather_cond or session.weather_temp_c is not none or session.weather_wind_kph is not none %}
<div style="background:#f8f9fb;border-radius:6px;padding:1rem;">
<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:.05em;color:#888;margin-bottom:.4rem;">Weather</div>
{% if session.weather_cond %}<div style="font-weight:600;">{{ session.weather_cond.replace('_',' ').title() }}</div>{% endif %}
<div style="color:#555;font-size:0.9rem;">
{% if session.weather_temp_c is not none %}{{ session.weather_temp_c }}°C{% endif %}
{% if session.weather_wind_kph is not none %}&nbsp; {{ session.weather_wind_kph }} km/h wind{% endif %}
</div>
</div>
{% endif %}
{% if session.rifle %}
<div style="background:#f8f9fb;border-radius:6px;padding:1rem;">
<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:.05em;color:#888;margin-bottom:.4rem;">Rifle / Handgun</div>
<div style="font-weight:600;">{{ session.rifle.name }}</div>
{% if session.rifle.caliber %}<div style="color:#555;font-size:0.9rem;">{{ session.rifle.caliber }}</div>{% endif %}
</div>
{% endif %}
{% if session.scope %}
<div style="background:#f8f9fb;border-radius:6px;padding:1rem;">
<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:.05em;color:#888;margin-bottom:.4rem;">Scope</div>
<div style="font-weight:600;">{{ session.scope.name }}</div>
</div>
{% endif %}
{% if session.ammo_brand or session.ammo_weight_gr is not none %}
<div style="background:#f8f9fb;border-radius:6px;padding:1rem;">
<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:.05em;color:#888;margin-bottom:.4rem;">Ammo</div>
{% if session.ammo_brand %}<div style="font-weight:600;">{{ session.ammo_brand }}</div>{% endif %}
<div style="color:#555;font-size:0.9rem;">
{% if session.ammo_weight_gr is not none %}{{ session.ammo_weight_gr }} gr{% endif %}
{% if session.ammo_lot %}&nbsp; lot {{ session.ammo_lot }}{% endif %}
</div>
</div>
{% endif %}
</div>
{% if session.notes %}
<h2>Notes</h2>
<p style="color:#555;white-space:pre-wrap;">{{ session.notes }}</p>
{% endif %}
{# ---- Photos ---- #}
{% if session.photos or is_owner %}
<h2>Photos</h2>
{% if session.photos %}
<div style="display:flex;flex-wrap:wrap;gap:1rem;margin-bottom:1.5rem;">
{% for photo in session.photos %}
<div>
<div style="position:relative;display:inline-block;">
<img src="{{ photo.photo_url }}"
data-gallery="session-{{ session.id }}"
data-src="{{ photo.photo_url }}"
data-caption="{{ photo.caption or '' }}"
alt="{{ photo.caption or '' }}"
style="height:180px;width:auto;border-radius:6px;object-fit:cover;display:block;">
{% if is_owner %}
<form method="post"
action="{{ url_for('sessions.delete_photo', session_id=session.id, photo_id=photo.id) }}"
onsubmit="return confirm('Delete this photo?');"
style="position:absolute;top:4px;right:4px;">
<button type="submit"
style="background:rgba(0,0,0,.5);color:#fff;border:none;border-radius:3px;padding:.2rem .45rem;font-size:0.8rem;cursor:pointer;line-height:1.2;">
&#x2715;
</button>
</form>
{% endif %}
</div>
{% if photo.annotations and photo.annotations.stats %}
{% set s = photo.annotations.stats %}
<div style="font-size:0.78rem;background:#f0f4ff;color:#1a1a2e;padding:.2rem .45rem;border-radius:3px;margin-top:.3rem;font-weight:600;">
{{ s.shot_count }} shots &middot; {{ '%.2f'|format(s.group_size_moa) }} MOA ES
</div>
{% endif %}
{% if photo.caption %}
<div style="font-size:0.78rem;color:#666;margin-top:.25rem;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
{{ photo.caption }}
</div>
{% endif %}
{% if is_owner %}
<div style="display:flex;gap:.35rem;margin-top:.35rem;">
{% for label, deg in [('↺', -90), ('↻', 90), ('180°', 180)] %}
<form method="post" action="{{ url_for('sessions.rotate_photo_view', session_id=session.id, photo_id=photo.id) }}">
<input type="hidden" name="degrees" value="{{ deg }}">
<button type="submit"
style="background:#f0f4ff;color:#1a1a2e;border:1px solid #c8d4f0;border-radius:4px;padding:.2rem .55rem;font-size:0.8rem;cursor:pointer;">
{{ label }}
</button>
</form>
{% endfor %}
</div>
<a href="{{ url_for('sessions.annotate_photo', session_id=session.id, photo_id=photo.id) }}"
style="display:inline-block;margin-top:.4rem;padding:.3rem .75rem;border-radius:4px;font-size:0.82rem;text-decoration:none;
{% if photo.annotations and photo.annotations.stats %}
background:#e8f5e9;color:#27ae60;border:1px solid #a5d6a7;
{% else %}
background:#1a1a2e;color:#fff;border:1px solid #1a1a2e;
{% endif %}">
{% if photo.annotations and photo.annotations.stats %}&#10003;{% else %}&#9654;{% endif %}
Measure group
</a>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% if is_owner %}
<form method="post"
action="{{ url_for('sessions.upload_photo', session_id=session.id) }}"
enctype="multipart/form-data"
style="display:flex;flex-wrap:wrap;gap:.75rem;align-items:flex-end;margin-bottom:2rem;">
<div>
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.25rem;">Add photo</label>
<input type="file" name="photo" accept="image/*" required style="font-size:0.9rem;">
</div>
<div>
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.25rem;">Caption (optional)</label>
<input type="text" name="caption" placeholder="e.g. 300 m target"
style="padding:.45rem .7rem;border:1px solid #ccc;border-radius:4px;font-size:0.9rem;">
</div>
<button type="submit"
style="background:#1a1a2e;color:#fff;border:none;border-radius:4px;padding:.5rem 1.1rem;font-size:0.9rem;cursor:pointer;">
Upload
</button>
</form>
{% endif %}
{% endif %}
{# ---- Analyses ---- #}
<h2>Analyses{% if analyses %} ({{ analyses|length }}){% endif %}</h2>
{% if analyses %}
<table style="margin-bottom:1.5rem;">
<thead>
<tr><th>Title</th><th>Date</th><th>Shots</th><th>Groups</th><th>Mean speed</th></tr>
</thead>
<tbody>
{% for a in analyses %}
<tr style="cursor:pointer;" onclick="location.href='{{ url_for('analyses.detail', analysis_id=a.id) }}'">
<td><a href="{{ url_for('analyses.detail', analysis_id=a.id) }}" style="color:inherit;text-decoration:none;">{{ a.title }}</a></td>
<td style="color:#666;font-size:0.88rem;">{{ a.created_at.strftime('%d %b %Y') }}</td>
<td>{{ a.shot_count }}</td>
<td>{{ a.group_count }}</td>
<td>{{ "%.2f"|format(a.overall_stats.mean_speed) }} m/s</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p style="color:#888;margin-bottom:1.5rem;">No analyses yet.</p>
{% endif %}
{% if is_owner %}
<form method="post"
action="{{ url_for('sessions.upload_csv', session_id=session.id) }}"
enctype="multipart/form-data"
style="display:flex;flex-wrap:wrap;gap:.75rem;align-items:flex-end;margin-bottom:2rem;">
<div>
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.25rem;">Upload chronograph CSV</label>
<input type="file" name="csv_file" accept=".csv,text/csv" required style="font-size:0.9rem;">
</div>
<button type="submit"
style="background:#1a1a2e;color:#fff;border:none;border-radius:4px;padding:.5rem 1.1rem;font-size:0.9rem;cursor:pointer;">
Analyse &amp; link
</button>
</form>
{% endif %}
{% endblock %}