Vibe coded a bit more ... now we have session, attached picture and analysis, MOA group computation

This commit is contained in:
Gérald Colangelo
2026-03-17 17:20:54 +01:00
parent 120dc70cf5
commit 5b18fadb60
55 changed files with 5419 additions and 59 deletions

View File

@@ -0,0 +1,72 @@
{% extends "base.html" %}
{% block title %}{{ item.name }} — The Shooter's Network{% endblock %}
{% block content %}
<div style="display:flex;align-items:center;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;">
<a href="{{ url_for('equipment.index') }}">Equipment</a> &rsaquo;
{{ categories.get(item.category, item.category).title() }}
</div>
<h1 style="margin:0;">{{ item.name }}</h1>
</div>
<div style="display:flex;gap:0.75rem;">
<a href="{{ url_for('equipment.edit', item_id=item.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('equipment.delete', item_id=item.id) }}"
onsubmit="return confirm('Delete {{ item.name }}?');">
<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>
</div>
{% if item.photo_url %}
<div style="margin-bottom:1.5rem;">
<img src="{{ item.photo_url }}"
data-gallery="equipment-{{ item.id }}"
data-src="{{ item.photo_url }}"
data-caption="{{ item.name }}"
alt="{{ item.name }}"
style="max-width:480px;width:100%;border-radius:8px;display:block;margin-bottom:.6rem;">
<div style="display:flex;gap:.5rem;">
{% for label, deg in [('↺ Left', -90), ('↻ Right', 90), ('180°', 180)] %}
<form method="post" action="{{ url_for('equipment.rotate_photo_view', item_id=item.id) }}">
<input type="hidden" name="degrees" value="{{ deg }}">
<button type="submit"
style="background:#f0f4ff;color:#1a1a2e;border:1px solid #c8d4f0;border-radius:4px;padding:.3rem .75rem;font-size:0.82rem;cursor:pointer;">
{{ label }}
</button>
</form>
{% endfor %}
</div>
</div>
{% endif %}
<table style="max-width:480px;">
<tbody>
<tr><td style="color:#888;width:140px;">Category</td><td>{{ categories.get(item.category, item.category).title() }}</td></tr>
{% if item.brand %}<tr><td style="color:#888;">Brand</td><td>{{ item.brand }}</td></tr>{% endif %}
{% if item.model %}<tr><td style="color:#888;">Model</td><td>{{ item.model }}</td></tr>{% endif %}
{% if item.category == 'scope' %}
{% if item.magnification %}<tr><td style="color:#888;">Magnification</td><td>{{ item.magnification }}</td></tr>{% endif %}
{% if item.reticle %}<tr><td style="color:#888;">Reticle</td><td>{{ item.reticle }}</td></tr>{% endif %}
{% if item.unit %}<tr><td style="color:#888;">Unit</td><td>{{ item.unit }}</td></tr>{% endif %}
{% else %}
{% if item.caliber %}<tr><td style="color:#888;">Caliber</td><td>{{ item.caliber }}</td></tr>{% endif %}
{% endif %}
{% if item.serial_number %}<tr><td style="color:#888;">Serial</td><td>{{ item.serial_number }}</td></tr>{% endif %}
<tr><td style="color:#888;">Added</td><td>{{ item.created_at.strftime('%d %b %Y') }}</td></tr>
</tbody>
</table>
{% if item.notes %}
<div style="margin-top:1.5rem;">
<h3>Notes</h3>
<p style="color:#555;white-space:pre-wrap;">{{ item.notes }}</p>
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,130 @@
{% extends "base.html" %}
{% set editing = item is not none %}
{% block title %}{{ 'Edit' if editing else 'Add' }} Equipment — The Shooter's Network{% endblock %}
{% block content %}
<h1>{{ 'Edit' if editing else 'Add equipment' }}</h1>
{% set f = prefill or item %}
<form method="post"
action="{{ url_for('equipment.edit', item_id=item.id) if editing else url_for('equipment.new') }}"
enctype="multipart/form-data"
style="max-width:520px;">
<div style="margin-bottom:1rem;">
<label class="field-label">Category *</label>
<select name="category" required style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;background:#fff;">
{% for key, label in categories %}
<option value="{{ key }}" {% if (f and f.category == key) or (not f and key == 'rifle') %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div style="margin-bottom:1rem;">
<label class="field-label">Name *</label>
<input type="text" name="name" value="{{ f.name if f else '' }}" required
placeholder="e.g. Tikka T3x, Glock 17"
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1rem;">
<div>
<label class="field-label">Brand</label>
<input type="text" name="brand" value="{{ f.brand if f else '' }}"
placeholder="e.g. Tikka, Leupold"
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
</div>
<div>
<label class="field-label">Model</label>
<input type="text" name="model" value="{{ f.model if f else '' }}"
placeholder="e.g. T3x, VX-3HD"
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
</div>
</div>
<div id="rifle-fields" style="margin-bottom:1rem;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;">
<div>
<label class="field-label">Caliber</label>
<input type="text" name="caliber" value="{{ f.caliber if f else '' }}"
placeholder="e.g. .308 Win, 6.5 CM"
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
</div>
</div>
</div>
<div id="scope-fields" style="display:none;margin-bottom:1rem;">
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;">
<div>
<label class="field-label">Magnification</label>
<input type="text" name="magnification" value="{{ f.magnification if f else '' }}"
placeholder="e.g. 3-15x50"
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
</div>
<div>
<label class="field-label">Reticle</label>
<select name="reticle" style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;background:#fff;">
<option value=""></option>
<option value="FFP" {% if f and f.reticle == 'FFP' %}selected{% endif %}>FFP (First Focal Plane)</option>
<option value="SFP" {% if f and f.reticle == 'SFP' %}selected{% endif %}>SFP (Second Focal Plane)</option>
</select>
</div>
<div>
<label class="field-label">Unit</label>
<select name="unit" style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;background:#fff;">
<option value=""></option>
<option value="MOA" {% if f and f.unit == 'MOA' %}selected{% endif %}>MOA</option>
<option value="MRAD" {% if f and f.unit == 'MRAD' %}selected{% endif %}>MRAD</option>
</select>
</div>
</div>
</div>
<div style="margin-bottom:1rem;">
<label class="field-label">Serial number</label>
<input type="text" name="serial_number" value="{{ f.serial_number if f else '' }}"
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
</div>
<div style="margin-bottom:1rem;">
<label class="field-label">Notes</label>
<textarea name="notes" rows="3"
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;resize:vertical;">{{ f.notes if f else '' }}</textarea>
</div>
<div style="margin-bottom:1.5rem;">
<label class="field-label">Photo</label>
{% if editing and item.photo_url %}
<div style="margin-bottom:0.5rem;">
<img src="{{ item.photo_url }}" alt="Current photo"
style="height:80px;border-radius:4px;object-fit:cover;">
<span style="font-size:0.82rem;color:#888;margin-left:0.5rem;">Upload a new one to replace it.</span>
</div>
{% endif %}
<input type="file" name="photo" accept="image/*"
style="font-size:0.92rem;">
</div>
<div style="display:flex;gap:1rem;align-items:center;">
<button type="submit"
style="background:#1a1a2e;color:#fff;border:none;border-radius:4px;padding:0.6rem 1.5rem;font-size:0.95rem;cursor:pointer;">
{{ 'Save changes' if editing else 'Add equipment' }}
</button>
<a href="{{ url_for('equipment.detail', item_id=item.id) if editing else url_for('equipment.index') }}"
style="font-size:0.9rem;color:#666;">Cancel</a>
</div>
</form>
<style>.field-label { display:block; font-size:.88rem; font-weight:600; color:#444; margin-bottom:.3rem; }</style>
<script>
function toggleCategoryFields() {
var cat = document.querySelector('[name="category"]').value;
var isScope = cat === 'scope';
document.getElementById('scope-fields').style.display = isScope ? '' : 'none';
document.getElementById('rifle-fields').style.display = isScope ? 'none' : '';
}
document.querySelector('[name="category"]').addEventListener('change', toggleCategoryFields);
toggleCategoryFields(); // run on load
</script>
{% endblock %}

View File

@@ -0,0 +1,62 @@
{% extends "base.html" %}
{% block title %}Equipment — The Shooter's Network{% endblock %}
{% block content %}
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:1rem;margin-bottom:1.5rem;">
<h1 style="margin:0;">My Equipment</h1>
<a href="{{ url_for('equipment.new') }}"
style="background:#1a1a2e;color:#fff;padding:0.5rem 1.2rem;border-radius:4px;font-size:0.92rem;text-decoration:none;">
+ Add item
</a>
</div>
{% if items %}
{% set cat_labels = dict(categories) %}
{% for cat_key, cat_label in categories %}
{% set group = items | selectattr('category', 'equalto', cat_key) | list %}
{% if group %}
<h2>{{ cat_label }}s</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:1rem;margin-bottom:2rem;">
{% for item in group %}
<div style="border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;">
{% if item.photo_url %}
<img src="{{ item.photo_url }}" alt="{{ item.name }}"
style="width:100%;height:150px;object-fit:cover;display:block;">
{% else %}
<div style="width:100%;height:80px;background:#f0f4ff;display:flex;align-items:center;justify-content:center;font-size:2rem;color:#c0c8e0;">
{% if item.category == 'rifle' or item.category == 'handgun' %}🔫
{% elif item.category == 'scope' %}🔭
{% else %}🔩{% endif %}
</div>
{% endif %}
<div style="padding:0.9rem 1rem;">
<div style="font-weight:600;color:#1a1a2e;margin-bottom:0.2rem;">{{ item.name }}</div>
{% if item.brand or item.model %}
<div style="font-size:0.85rem;color:#666;margin-bottom:0.3rem;">
{{ [item.brand, item.model] | select | join(' · ') }}
</div>
{% endif %}
{% if item.caliber %}
<div style="font-size:0.82rem;color:#888;margin-bottom:0.6rem;">{{ item.caliber }}</div>
{% endif %}
<div style="display:flex;gap:0.75rem;margin-top:0.5rem;">
<a href="{{ url_for('equipment.detail', item_id=item.id) }}" style="font-size:0.85rem;">View</a>
<a href="{{ url_for('equipment.edit', item_id=item.id) }}" style="font-size:0.85rem;">Edit</a>
<form method="post" action="{{ url_for('equipment.delete', item_id=item.id) }}" style="display:inline;"
onsubmit="return confirm('Delete {{ item.name }}?');">
<button type="submit" class="btn-link" style="font-size:0.85rem;color:#e74c3c;">Delete</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}
{% else %}
<div style="text-align:center;padding:3rem 0;color:#888;">
<div style="font-size:3rem;margin-bottom:1rem;">🔫</div>
<p style="margin-bottom:1rem;">No equipment yet.</p>
<a href="{{ url_for('equipment.new') }}">Add your first item</a>
</div>
{% endif %}
{% endblock %}