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> <a class="navbar-brand fw-bold" href="{% url 'dashboard' %}">🌸 BloomBase</a>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="{% url 'plant_list' %}" class="btn btn-sm btn-outline-light">Plants</a> <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> <a href="{% url 'identify_upload' %}" class="btn btn-sm btn-light fw-bold">+</a>
</div> </div>
</nav> </nav>

View file

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

View file

@ -1,6 +1,6 @@
{% load plant_extras %} {% load plant_extras %}
<div id="pruning-strip" class="pruning-strip {% if pruning_status == 'overdue' %}overdue{% endif %} mb-3 rounded-end"> <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> <div>
<strong>✂️ Pruning</strong> <strong>✂️ Pruning</strong>
{% if plant.pruning_months %} {% if plant.pruning_months %}
@ -8,16 +8,18 @@
{% endif %} {% endif %}
{% with plant.pruning_logs.first as last_log %} {% with plant.pruning_logs.first as last_log %}
{% if 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 %} {% endif %}
{% endwith %} {% endwith %}
</div> </div>
<div class="flex-shrink-0">
{% if pruning_status == 'overdue' %} {% if pruning_status == 'overdue' %}
<span class="badge bg-danger">Overdue</span> <span class="badge bg-danger">Overdue</span>
{% elif pruning_status == 'due_this_month' %} {% elif pruning_status == 'due_this_month' %}
<span class="badge bg-warning text-dark">Due this month</span> <span class="badge bg-warning text-dark">Due this month</span>
{% endif %} {% endif %}
</div> </div>
</div>
<form <form
hx-post="{% url 'log_pruning' plant.pk %}" hx-post="{% url 'log_pruning' plant.pk %}"
@ -26,7 +28,7 @@
class="mt-2" class="mt-2"
> >
{% csrf_token %} {% 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 }} {{ log_form.pruned_on }}
<button type="submit" class="btn btn-sm btn-success">Log pruning</button> <button type="submit" class="btn btn-sm btn-success">Log pruning</button>
</div> </div>

View file

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

View file

@ -15,10 +15,11 @@
> >
</div> </div>
<div class="btn-group w-100 mb-3" role="group"> <div class="btn-group w-100 mb-3 flex-wrap" 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' %}" class="btn btn-sm {% if not location_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> {% for loc in locations %}
<a href="{% url 'plant_list' %}?filter=outdoor" class="btn btn-sm {% if filter == 'outdoor' %}btn-success{% else %}btn-outline-success{% endif %}">Outdoor</a> <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>
<div id="plant-list"> <div id="plant-list">

View file

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

View file

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