feat: restyle identify_confirm — match cards with confidence badges, location chips

This commit is contained in:
Stephan Kerkman 2026-06-01 17:54:17 +02:00
parent 2c45a6f2ee
commit 4e7655bf46
2 changed files with 96 additions and 45 deletions

View file

@ -1,62 +1,109 @@
{# plants/templates/plants/identify_confirm.html #}
{% extends "plants/base.html" %}
{% block title %}Confirm species — BloomBase{% endblock %}
{% block content %}
<div class="d-flex align-items-center gap-2 mb-3">
<a href="{% url 'identify_upload' %}" class="btn btn-sm btn-outline-secondary"></a>
<h5 class="mb-0">Is this your plant?</h5>
</div>
<div class="px-4 pt-6">
<p class="text-muted small mb-3">Tap the correct species, then confirm.</p>
<div class="flex items-center gap-3 mb-2">
<a href="{% url 'identify_upload' %}" class="btn-pill no-underline text-sm"></a>
<h1 class="font-nunito font-black text-[20px] text-[#1A1208]">Is this your plant?</h1>
</div>
<p class="font-nunito font-semibold text-[11px] text-[#B0A090] mb-4">
Tap the correct species, then confirm.
</p>
<form method="post" id="confirm-form">
{% csrf_token %}
<input type="hidden" name="match_idx" id="match_idx_input" value="0">
<div class="d-flex flex-column gap-2 mb-4" id="match-list">
<div class="flex flex-col gap-3 mb-5" id="match-list">
{% for m in matches %}
<div class="match-card d-flex align-items-center gap-3 p-3 border rounded"
style="cursor:pointer; {% if forloop.first %}border-color:#52b788 !important; background:#f0faf4;{% endif %}"
<div class="match-card flex items-center gap-3 bg-white rounded-2xl p-3 shadow-sm cursor-pointer
{% if forloop.first %}ring-2 ring-[#F07040]{% endif %}"
data-idx="{{ forloop.counter0 }}"
data-name="{{ m.scientific_name }}"
onclick="selectMatch(this)">
{% if m.image_url %}
<img src="{{ m.image_url }}" alt="{{ m.scientific_name }}"
style="width:64px;height:64px;object-fit:cover;border-radius:8px;flex-shrink:0;">
class="w-16 h-16 rounded-xl flex-shrink-0 object-cover">
{% else %}
<div style="width:64px;height:64px;background:#e9ecef;border-radius:8px;flex-shrink:0;"></div>
<div class="w-16 h-16 bg-[#D8F3DC] rounded-xl flex-shrink-0 flex items-center justify-center text-2xl">🌿</div>
{% endif %}
<div class="flex-grow-1">
<div class="fw-bold">{{ m.scientific_name }}</div>
<div class="flex-1 min-w-0">
<div class="font-nunito font-extrabold text-[13px] text-[#1A1208]">{{ m.scientific_name }}</div>
{% if m.common_names %}
<div class="text-muted small">{{ m.common_names|join:", " }}</div>
{% endif %}
<div class="text-muted small fst-italic">{{ m.family }}</div>
<div class="font-nunito text-[10px] font-semibold text-[#B0A090] mt-0.5 truncate">
{{ m.common_names|join:", " }}
</div>
<div>
<span class="badge {% if m.score >= 0.70 %}bg-success{% elif m.score >= 0.30 %}bg-warning text-dark{% else %}bg-secondary{% endif %}">
{% endif %}
<div class="font-nunito text-[10px] font-semibold text-[#B0A090] italic">{{ m.family }}</div>
</div>
<span class="font-nunito font-bold text-[11px] px-2 py-1 rounded-full flex-shrink-0
{% if m.score >= 0.70 %}bg-green-100 text-green-700
{% elif m.score >= 0.30 %}bg-orange-100 text-orange-700
{% else %}bg-gray-100 text-gray-500{% endif %}">
{% widthratio m.score 1 100 %}%
</span>
</div>
</div>
{% endfor %}
</div>
<button type="submit" class="btn btn-success w-100" id="confirm-btn">
Use <span id="selected-name">{{ matches.0.scientific_name }}</span>
<!-- Plant name override -->
<div class="mb-4">
<label class="font-nunito font-bold text-[11px] text-[#B0A090] block mb-1">Plant name (optional)</label>
<input type="text" name="plant_name" id="plant-name-input"
class="w-full bg-white border border-[#F0E4D4] rounded-full px-4 py-2.5
font-nunito text-sm text-[#1A1208] placeholder-[#C0B0A0]
outline-none focus:border-[#F07040]"
placeholder="Leave blank to use species name">
</div>
<!-- Location picker -->
{% if locations %}
<div class="mb-5">
<label class="font-nunito font-bold text-[11px] text-[#B0A090] block mb-2">Add to area</label>
<div class="flex flex-wrap gap-2">
<input type="hidden" name="location_id" id="location-id-input" value="">
{% for loc in locations %}
<span class="location-chip font-nunito font-bold text-[11px] px-3 py-1.5 rounded-full
bg-[#F5F0EA] text-[#B0A090] cursor-pointer"
data-id="{{ loc.pk }}"
onclick="selectLocation(this)">
{{ loc.name }}
</span>
{% endfor %}
</div>
</div>
{% endif %}
<button type="submit" class="btn-bloom">
Add to my garden →
</button>
</form>
</div>
<script>
function selectMatch(el) {
document.querySelectorAll('.match-card').forEach(c => {
c.style.borderColor = '';
c.style.background = '';
function selectMatch(card) {
document.querySelectorAll('.match-card').forEach(c => c.classList.remove('ring-2', 'ring-[#F07040]'));
card.classList.add('ring-2', 'ring-[#F07040]');
document.getElementById('match_idx_input').value = card.dataset.idx;
const nameInput = document.getElementById('plant-name-input');
if (!nameInput.value || nameInput.dataset.autoSet) {
nameInput.value = card.dataset.name;
nameInput.dataset.autoSet = '1';
}
}
function selectLocation(chip) {
document.querySelectorAll('.location-chip').forEach(c => {
c.classList.remove('bg-[#F07040]', 'text-white');
c.classList.add('bg-[#F5F0EA]', 'text-[#B0A090]');
});
el.style.borderColor = '#52b788';
el.style.background = '#f0faf4';
document.getElementById('match_idx_input').value = el.dataset.idx;
document.getElementById('selected-name').textContent = el.dataset.name;
chip.classList.remove('bg-[#F5F0EA]', 'text-[#B0A090]');
chip.classList.add('bg-[#F07040]', 'text-white');
document.getElementById('location-id-input').value = chip.dataset.id;
}
</script>
{% endblock %}

View file

@ -100,7 +100,11 @@ def identify_confirm(request):
return redirect('identify_fields')
return render(request, 'plants/identify_confirm.html', {'matches': matches})
locations = list(Location.objects.all())
return render(request, 'plants/identify_confirm.html', {
'matches': matches,
'locations': locations,
})
def _apply_vpc_care_fields(species, vpc_result):