feat: location filter, mobile overflow fixes, card scan icon

- Replace indoor/outdoor filter with dynamic location buttons
- Fix pruning strip date/badge overflow on narrow screens (flex-shrink-0, gap-2, text-nowrap, flex-wrap)
- Fix pruning calendar badges compressing on long plant names
- Re-add pruning strip to plant detail (was missing)
- Change card scan nav icon from 📷 to 🪪

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Stephan Kerkman 2026-05-31 13:11:22 +02:00
parent 21f9759d3a
commit b5cdda54f8
7 changed files with 28 additions and 19 deletions

View file

@ -30,7 +30,7 @@
<a class="navbar-brand fw-bold" href="{% url 'dashboard' %}">🌸 BloomBase</a>
<div class="d-flex gap-2">
<a href="{% url 'plant_list' %}" class="btn btn-sm btn-outline-light">Plants</a>
<a href="{% url 'add_plant_from_scan' %}" class="btn btn-sm btn-outline-light" title="Add plant by card scan">📷</a>
<a href="{% url 'add_plant_from_scan' %}" class="btn btn-sm btn-outline-light" title="Add plant by card scan">🪪</a>
<a href="{% url 'identify_upload' %}" class="btn btn-sm btn-light fw-bold">+</a>
</div>
</nav>

View file

@ -13,7 +13,6 @@
<div class="fw-semibold" style="color:#1b4332;">{{ plant.name }}</div>
<small class="text-muted">
{{ plant.location }}
· {% if plant.is_indoor %}Indoor{% else %}Outdoor{% endif %}
</small>
</div>
</div>

View file

@ -1,6 +1,6 @@
{% load plant_extras %}
<div id="pruning-strip" class="pruning-strip {% if pruning_status == 'overdue' %}overdue{% endif %} mb-3 rounded-end">
<div class="d-flex justify-content-between align-items-start">
<div class="d-flex justify-content-between align-items-start gap-2">
<div>
<strong>✂️ Pruning</strong>
{% if plant.pruning_months %}
@ -8,15 +8,17 @@
{% endif %}
{% with plant.pruning_logs.first as last_log %}
{% if last_log %}
<div class="small text-muted mt-1">Last pruned: {{ last_log.pruned_on|date:"j M Y" }}</div>
<div class="small text-muted mt-1 text-nowrap">Last pruned: {{ last_log.pruned_on|date:"j M Y" }}</div>
{% endif %}
{% endwith %}
</div>
<div class="flex-shrink-0">
{% if pruning_status == 'overdue' %}
<span class="badge bg-danger">Overdue</span>
{% elif pruning_status == 'due_this_month' %}
<span class="badge bg-warning text-dark">Due this month</span>
{% endif %}
</div>
</div>
<form
@ -26,7 +28,7 @@
class="mt-2"
>
{% csrf_token %}
<div class="d-flex gap-2 align-items-end">
<div class="d-flex gap-2 align-items-end flex-wrap">
{{ log_form.pruned_on }}
<button type="submit" class="btn btn-sm btn-success">Log pruning</button>
</div>

View file

@ -85,6 +85,10 @@
</div>
{% endif %}
{% if plant.pruning_months %}
{% include "plants/partials/pruning_strip.html" %}
{% endif %}
<div id="photo-gallery">
{% include "plants/partials/photo_gallery.html" %}
</div>

View file

@ -15,10 +15,11 @@
>
</div>
<div class="btn-group w-100 mb-3" role="group">
<a href="{% url 'plant_list' %}" class="btn btn-sm {% if not filter %}btn-success{% else %}btn-outline-success{% endif %}">All</a>
<a href="{% url 'plant_list' %}?filter=indoor" class="btn btn-sm {% if filter == 'indoor' %}btn-success{% else %}btn-outline-success{% endif %}">Indoor</a>
<a href="{% url 'plant_list' %}?filter=outdoor" class="btn btn-sm {% if filter == 'outdoor' %}btn-success{% else %}btn-outline-success{% endif %}">Outdoor</a>
<div class="btn-group w-100 mb-3 flex-wrap" role="group">
<a href="{% url 'plant_list' %}" class="btn btn-sm {% if not location_filter %}btn-success{% else %}btn-outline-success{% endif %}">All</a>
{% for loc in locations %}
<a href="{% url 'plant_list' %}?location={{ loc.pk }}" class="btn btn-sm {% if location_filter == loc.pk|stringformat:'s' %}btn-success{% else %}btn-outline-success{% endif %}">{{ loc.name }}</a>
{% endfor %}
</div>
<div id="plant-list">

View file

@ -12,7 +12,7 @@
<div class="fw-semibold">{{ plant.name }}</div>
<small>{{ plant.location }}</small>
</div>
<span class="badge bg-danger">Overdue</span>
<span class="badge bg-danger flex-shrink-0">Overdue</span>
</a>
{% endfor %}
</div>
@ -27,7 +27,7 @@
<div class="fw-semibold">{{ plant.name }}</div>
<small>{{ plant.location }}</small>
</div>
<span class="badge bg-warning text-dark">This month</span>
<span class="badge bg-warning text-dark flex-shrink-0">This month</span>
</a>
{% endfor %}
</div>
@ -42,7 +42,7 @@
<div class="fw-semibold">{{ plant.name }}</div>
<small>{{ plant.location }}</small>
</div>
<span class="badge bg-secondary">{{ next_month_name }}</span>
<span class="badge bg-secondary flex-shrink-0">{{ next_month_name }}</span>
</a>
{% endfor %}
</div>

View file

@ -2,7 +2,7 @@ from datetime import date
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_POST, require_GET
from plants.models import Plant, Species, PruningLog, PlantPhoto, PlantCardPhoto
from plants.models import Plant, Species, PruningLog, PlantPhoto, PlantCardPhoto, Location
from plants.forms import PlantForm, PruningLogForm
from plants.utils.pruning import pruning_status
@ -10,21 +10,24 @@ from plants.utils.pruning import pruning_status
def plant_list(request):
qs = Plant.objects.select_related('species', 'location').prefetch_related('photos').all()
q = request.GET.get('q', '')
indoor_filter = request.GET.get('filter', '')
location_filter = request.GET.get('location', '')
if q:
qs = qs.filter(name__icontains=q) | qs.filter(location__name__icontains=q)
if indoor_filter == 'indoor':
qs = qs.filter(is_indoor=True)
elif indoor_filter == 'outdoor':
qs = qs.filter(is_indoor=False)
if location_filter:
qs = qs.filter(location__pk=location_filter)
locations = Location.objects.all()
template = (
'plants/partials/plant_list_results.html'
if request.htmx
else 'plants/plant_list.html'
)
return render(request, template, {'plants': qs, 'q': q, 'filter': indoor_filter})
return render(request, template, {
'plants': qs, 'q': q,
'location_filter': location_filter,
'locations': locations,
})
def plant_detail(request, pk):