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:
parent
21f9759d3a
commit
b5cdda54f8
7 changed files with 28 additions and 19 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,16 +8,18 @@
|
|||
{% 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
|
||||
hx-post="{% url 'log_pruning' plant.pk %}"
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue