Add exercise category search in settings
This commit is contained in:
@@ -3,16 +3,42 @@ class Exercises:
|
||||
self.execute = db_connection_method
|
||||
|
||||
def get(self, query):
|
||||
# Add wildcards to the query
|
||||
search_query = f"%{query}%"
|
||||
# We need to fetch exercises with their attributes.
|
||||
# Since an exercise can have many attributes, we'll fetch basic info first or use a join.
|
||||
# But wait, the settings page just lists names. We can fetch attributes separately for each row or do a group_concat-like join.
|
||||
# However, for the settings list, we want to show the tags.
|
||||
|
||||
# Let's use a simpler approach: fetch exercises and then for each one (or via a single join) get attributes.
|
||||
exercises = self.execute("SELECT exercise_id, name FROM exercise WHERE LOWER(name) LIKE LOWER(%s) ORDER BY name ASC;", [search_query])
|
||||
|
||||
if not query:
|
||||
exercises = self.execute("SELECT exercise_id, name FROM exercise ORDER BY name ASC;")
|
||||
for ex in exercises:
|
||||
ex['attributes'] = self.get_exercise_attributes(ex['exercise_id'])
|
||||
return exercises
|
||||
|
||||
# Check for category:value syntax
|
||||
if ':' in query:
|
||||
category_part, value_part = query.split(':', 1)
|
||||
category_part = f"%{category_part.strip().lower()}%"
|
||||
value_part = f"%{value_part.strip().lower()}%"
|
||||
|
||||
query = """
|
||||
SELECT DISTINCT e.exercise_id, e.name
|
||||
FROM exercise e
|
||||
JOIN exercise_to_attribute eta ON e.exercise_id = eta.exercise_id
|
||||
JOIN exercise_attribute attr ON eta.attribute_id = attr.attribute_id
|
||||
JOIN exercise_attribute_category cat ON attr.category_id = cat.category_id
|
||||
WHERE LOWER(cat.name) LIKE LOWER(%s) AND LOWER(attr.name) LIKE LOWER(%s)
|
||||
ORDER BY e.name ASC;
|
||||
"""
|
||||
exercises = self.execute(query, [category_part, value_part])
|
||||
else:
|
||||
# Fallback: search in name OR attribute name
|
||||
search_term = query.strip().lower()
|
||||
search_query = f"%{search_term}%"
|
||||
query = """
|
||||
SELECT DISTINCT e.exercise_id, e.name
|
||||
FROM exercise e
|
||||
LEFT JOIN exercise_to_attribute eta ON e.exercise_id = eta.exercise_id
|
||||
LEFT JOIN exercise_attribute attr ON eta.attribute_id = attr.attribute_id
|
||||
WHERE LOWER(e.name) LIKE LOWER(%s) OR LOWER(attr.name) LIKE LOWER(%s)
|
||||
ORDER BY e.name ASC;
|
||||
"""
|
||||
exercises = self.execute(query, [search_query, search_query])
|
||||
|
||||
for ex in exercises:
|
||||
ex['attributes'] = self.get_exercise_attributes(ex['exercise_id'])
|
||||
|
||||
|
||||
@@ -93,6 +93,20 @@ def add_exercise():
|
||||
person_id = request.args.get('person_id', type=int)
|
||||
return render_template('partials/exercise/exercise_list_item.html', exercise=new_exercise, person_id=person_id)
|
||||
|
||||
@exercises_bp.route("/exercises/search")
|
||||
@login_required
|
||||
def search_exercises():
|
||||
query = request.args.get('q', '')
|
||||
exercises = db.exercises.get(query)
|
||||
|
||||
html = ""
|
||||
for exercise in exercises:
|
||||
html += render_template('partials/exercise.html',
|
||||
exercise_id=exercise['exercise_id'],
|
||||
name=exercise['name'],
|
||||
attributes=exercise['attributes'])
|
||||
return html
|
||||
|
||||
@exercises_bp.route("/exercise/<int:exercise_id>/delete", methods=['DELETE'])
|
||||
@login_required
|
||||
@admin_required
|
||||
|
||||
@@ -189,16 +189,30 @@
|
||||
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<input type="search" id="exercise-search"
|
||||
<input type="search" id="exercise-search" name="q"
|
||||
class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-cyan-500 focus:border-cyan-500 transition-all shadow-sm"
|
||||
placeholder="Search exercises..."
|
||||
_="on input show <tbody>tr/> in closest <table/> when its textContent.toLowerCase() contains my value.toLowerCase()">
|
||||
placeholder="Search e.g. 'muscle:chest'..."
|
||||
hx-get="/exercises/search"
|
||||
hx-trigger="input changed delay:250ms, search"
|
||||
hx-target="#new-exercise" hx-indicator="#search-spinner">
|
||||
<div id="search-spinner"
|
||||
class="htmx-indicator absolute inset-y-0 right-3 flex items-center">
|
||||
<svg class="animate-spin h-4 w-4 text-cyan-600"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10"
|
||||
stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-100" id="new-exercise"
|
||||
hx-target="closest tr" hx-swap="outerHTML swap:0.5s">
|
||||
hx-target="closest tr" hx-swap="innerHTML swap:0.5s">
|
||||
{% for exercise in exercises %}
|
||||
{{ render_partial('partials/exercise.html', exercise_id=exercise.exercise_id,
|
||||
name=exercise.name, attributes=exercise.attributes)}}
|
||||
|
||||
Reference in New Issue
Block a user