Add exercise category search in settings
This commit is contained in:
@@ -3,16 +3,42 @@ class Exercises:
|
|||||||
self.execute = db_connection_method
|
self.execute = db_connection_method
|
||||||
|
|
||||||
def get(self, query):
|
def get(self, query):
|
||||||
# Add wildcards to the query
|
if not query:
|
||||||
search_query = f"%{query}%"
|
exercises = self.execute("SELECT exercise_id, name FROM exercise ORDER BY name ASC;")
|
||||||
# We need to fetch exercises with their attributes.
|
for ex in exercises:
|
||||||
# Since an exercise can have many attributes, we'll fetch basic info first or use a join.
|
ex['attributes'] = self.get_exercise_attributes(ex['exercise_id'])
|
||||||
# But wait, the settings page just lists names. We can fetch attributes separately for each row or do a group_concat-like join.
|
return exercises
|
||||||
# However, for the settings list, we want to show the tags.
|
|
||||||
|
# Check for category:value syntax
|
||||||
# Let's use a simpler approach: fetch exercises and then for each one (or via a single join) get attributes.
|
if ':' in query:
|
||||||
exercises = self.execute("SELECT exercise_id, name FROM exercise WHERE LOWER(name) LIKE LOWER(%s) ORDER BY name ASC;", [search_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:
|
for ex in exercises:
|
||||||
ex['attributes'] = self.get_exercise_attributes(ex['exercise_id'])
|
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)
|
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)
|
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'])
|
@exercises_bp.route("/exercise/<int:exercise_id>/delete", methods=['DELETE'])
|
||||||
@login_required
|
@login_required
|
||||||
@admin_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>
|
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</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"
|
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..."
|
placeholder="Search e.g. 'muscle:chest'..."
|
||||||
_="on input show <tbody>tr/> in closest <table/> when its textContent.toLowerCase() contains my value.toLowerCase()">
|
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>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-100" id="new-exercise"
|
<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 %}
|
{% for exercise in exercises %}
|
||||||
{{ render_partial('partials/exercise.html', exercise_id=exercise.exercise_id,
|
{{ render_partial('partials/exercise.html', exercise_id=exercise.exercise_id,
|
||||||
name=exercise.name, attributes=exercise.attributes)}}
|
name=exercise.name, attributes=exercise.attributes)}}
|
||||||
|
|||||||
Reference in New Issue
Block a user