Vibe coded a bit more ... now we have session, attached picture and analysis, MOA group computation
This commit is contained in:
72
templates/equipment/detail.html
Normal file
72
templates/equipment/detail.html
Normal 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> ›
|
||||
{{ 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 %}
|
||||
130
templates/equipment/form.html
Normal file
130
templates/equipment/form.html
Normal 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 %}
|
||||
62
templates/equipment/list.html
Normal file
62
templates/equipment/list.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user