Add logic to handle adding/creating new tags for workouts

This commit is contained in:
Peter Stockings
2023-08-06 22:15:36 +10:00
parent 5ccb1f1905
commit fdbb60b490
5 changed files with 201 additions and 104 deletions

18
app.py
View File

@@ -167,7 +167,9 @@ def get_calendar(person_id):
@ validate_workout @ validate_workout
def get_workout_modal(person_id, workout_id): def get_workout_modal(person_id, workout_id):
workout = db.get_workout(person_id, workout_id) workout = db.get_workout(person_id, workout_id)
return render_template('partials/workout_modal.html', workout=workout) (person_tags, workout_tags, selected_workout_tag_ids) = db.get_workout_tags(
person_id, workout_id)
return render_template('partials/workout_modal.html', workout=workout, person_tags=person_tags, workout_tags=workout_tags, selected_workout_tag_ids=selected_workout_tag_ids)
@ app.route("/person/<int:person_id>/workout", methods=['POST']) @ app.route("/person/<int:person_id>/workout", methods=['POST'])
@@ -384,6 +386,20 @@ def get_workout_note(person_id, workout_id):
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=workout['Note']) return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=workout['Note'])
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/tag/add", methods=['POST'])
def add_tag_to_workout(person_id, workout_id):
tags_id = [int(i) for i in request.form.getlist('tag_id')]
workout_tags = db.add_tag_for_workout(workout_id, tags_id)
return render_template('partials/workout_tags_list.html', workout_tags=workout_tags)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/tag/new", methods=['POST'])
def create_new_tag_for_workout(person_id, workout_id):
tag_name = request.form.get('tag_name')
workout_tags = db.create_tag_for_workout(person_id, workout_id, tag_name)
return render_template('partials/workout_tags_list.html', workout_tags=workout_tags)
@app.teardown_appcontext @app.teardown_appcontext
def closeConnection(exception): def closeConnection(exception):
db.close_connection() db.close_connection()

92
db.py
View File

@@ -347,3 +347,95 @@ class DataBase():
def update_workout_note_for_person(self, person_id, workout_id, note): def update_workout_note_for_person(self, person_id, workout_id, note):
self.execute('UPDATE workout SET note=%s WHERE person_id=%s AND workout_id=%s', [ self.execute('UPDATE workout SET note=%s WHERE person_id=%s AND workout_id=%s', [
note, person_id, workout_id], commit=True) note, person_id, workout_id], commit=True)
def add_tag_for_workout(self, workout_id, tags_id):
# First, delete tags that are not in the new selection
self.execute(
"""
DELETE FROM workout_tag
WHERE workout_id = %s AND tag_id NOT IN %s
""",
[workout_id, tuple(tags_id)], commit=True
)
# Then, attempt to insert the new tags
for tag_id in tags_id:
self.execute(
"""
INSERT INTO workout_tag (workout_id, tag_id)
VALUES (%s, %s)
ON CONFLICT (workout_id, tag_id) DO NOTHING
""",
[workout_id, tag_id], commit=True
)
# Now fetch updated list of workout tags
workout_tags = self.execute("""
SELECT
T.tag_id AS "tag_id",
T.person_id AS "person_id",
T.name AS "tag_name",
T.filter AS "tag_filter"
FROM Workout_Tag WT
LEFT JOIN Tag T ON WT.tag_id=T.tag_id
WHERE WT.workout_id=%s""", [workout_id])
return workout_tags
def create_tag_for_workout(self, person_id, workout_id, tag_name):
workout_exercises = self.execute("""
SELECT
E.exercise_id AS "exercise_id",
E.name AS "exercise_name"
FROM Workout W
LEFT JOIN TopSet T ON W.workout_id=T.workout_id
LEFT JOIN Exercise E ON T.exercise_id=E.exercise_id
WHERE W.workout_id=%s""", [workout_id])
tag_filter = "?" + \
"&".join(
f"exercise_id={e['exercise_id']}" for e in workout_exercises)
# create tag for person
row = self.execute('INSERT INTO Tag (person_id, name, filter) VALUES (%s, %s, %s) RETURNING tag_id AS "tag_id"', [
person_id, tag_name, tag_filter], commit=True, one=True)
# add tag to workout
self.execute('INSERT INTO Workout_Tag (workout_id, tag_id) VALUES (%s, %s)', [
workout_id, row['tag_id']], commit=True)
# Now fetch updated list of workout tags
workout_tags = self.execute("""
SELECT
T.tag_id AS "tag_id",
T.person_id AS "person_id",
T.name AS "tag_name",
T.filter AS "tag_filter"
FROM Workout_Tag WT
LEFT JOIN Tag T ON WT.tag_id=T.tag_id
WHERE WT.workout_id=%s""", [workout_id])
return workout_tags
def get_workout_tags(self, person_id, workout_id):
person_tags = self.execute("""
SELECT
T.tag_id AS "tag_id",
T.name AS "tag_name",
T.filter AS "tag_filter"
FROM Tag T
WHERE T.person_id=%s""", [person_id])
workout_tags = self.execute("""
SELECT
T.tag_id AS "tag_id",
T.person_id AS "person_id",
T.name AS "tag_name",
T.filter AS "tag_filter"
FROM Workout_Tag WT
LEFT JOIN Tag T ON WT.tag_id=T.tag_id
WHERE WT.workout_id=%s""", [workout_id])
selected_workout_tag_ids = [wt['tag_id'] for wt in workout_tags]
return (person_tags, workout_tags, selected_workout_tag_ids)

View File

@@ -16,109 +16,10 @@
<div class="w-full"> <div class="w-full">
<h3 class="text-xl font-bold text-gray-900">{{ workout['PersonName'] }}</h3> <h3 class="text-xl font-bold text-gray-900">{{ workout['PersonName'] }}</h3>
<div class="flex"> {{ render_partial('partials/workout_tags.html', person_id=workout['PersonId'],
<div id="tag-wrapper-w-{{ workout['WorkoutId'] }}"> workout_id=workout['WorkoutId'],
<span person_tags=person_tags, workout_tags=workout_tags,
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-200 uppercase last:mr-0 mr-1 max-h-fit">Push selected_workout_tag_ids=selected_workout_tag_ids) }}
</span>
<span
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-200 uppercase last:mr-0 mr-1 max-h-fit">Accessories
</span>
</div>
<span id="tag-form-show-w-{{ workout['WorkoutId'] }}" class="" _="on click
toggle .hidden on #tag-form-w-{{ workout['WorkoutId'] }}
then toggle .hidden on #tag-form-hide-w-{{ workout['WorkoutId'] }}
then toggle .hidden on #tag-wrapper-w-{{ workout['WorkoutId'] }}
then toggle .hidden on me">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-6 h-6"
data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke: currentColor;">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 4.5v15m7.5-7.5h-15"></path>
</svg>
</span>
<span id="tag-form-hide-w-{{ workout['WorkoutId'] }}" _="on click
toggle .hidden on #tag-form-w-{{ workout['WorkoutId'] }}
then toggle .hidden on #tag-form-show-w-{{ workout['WorkoutId'] }}
then toggle .hidden on #tag-wrapper-w-{{ workout['WorkoutId'] }}
then toggle .hidden on me" class="hidden">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
</svg>
</span>
</div>
<div id="tag-form-w-{{workout['WorkoutId']}}" class="hidden pt-2 pb-3">
<span class="text-base font-normal text-gray-500">Add tag to workout</span>
<div class="flex pt-2 flex-col sm:flex-row pb-2">
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
for="grid-state">
Tag
</label>
<div class="relative">
<div class="w-full">
<select id="workout-tag-select-{{ workout['WorkoutId'] }}"
data-te-select-init data-te-select-filter="true"
data-te-select-size="lg" name="exercise_id"
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500">
{% for e in workout['Exercises'] %}
<option value="{{ e['ExerciseId'] }}">{{
e['Name']
}}</option>
{% endfor %}
</select>
</div>
<script>
te.Select.getOrCreateInstance(document.querySelector("#workout-tag-select-{{ workout['WorkoutId'] }}"));
</script>
</div>
</div>
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
New tag name
</label>
<div class="flex">
<input
class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
type="text" name="repetitions">
<button type="submit"
class="p-2.5 ml-2 text-sm font-medium text-white bg-blue-700 rounded-lg border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
<svg xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-6 h-6" data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke: currentColor;">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 4.5v15m7.5-7.5h-15"></path>
</svg>
<span class="sr-only">Search</span>
</button>
</div>
</div>
</div>
<span class="text-base font-normal text-gray-500 pt-2">Remove tag from
workout</span>
<div>
<span
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-100 uppercase last:mr-0 mr-1 max-h-fit">Push
</span>
<span
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-100 uppercase last:mr-0 mr-1 max-h-fit">Accessories
</span>
</div>
</div>
</div> </div>

View File

@@ -0,0 +1,80 @@
<div class="flex">
<div id="tag-wrapper-w-{{ workout_id }}">
{{ render_partial('partials/workout_tags_list.html', workout_tags=workout_tags)
}}
</div>
<span id="tag-form-show-w-{{ workout_id }}" class="" _="on click
toggle .hidden on #tag-form-w-{{ workout_id }}
then toggle .hidden on #tag-form-hide-w-{{ workout_id }}
then toggle .hidden on me">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-6 h-6" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: currentColor;">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"></path>
</svg>
</span>
<span id="tag-form-hide-w-{{ workout_id }}" _="on click
toggle .hidden on #tag-form-w-{{ workout_id }}
then toggle .hidden on #tag-form-show-w-{{ workout_id }}
then toggle .hidden on me" class="hidden">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
</svg>
</span>
</div>
<div id="tag-form-w-{{ workout_id }}" class="hidden pt-2 pb-3">
<span class="text-base font-normal text-gray-500">Add tag to workout</span>
<div class="flex pt-2 flex-col sm:flex-row pb-2">
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-state">
Tag
</label>
<div class="relative">
<div class="w-full">
<select id="workout-tag-select-{{ workout_id }}" data-te-select-init data-te-select-filter="true"
data-te-select-size="lg" multiple name="tag_id"
hx-post="{{ url_for('add_tag_to_workout', person_id=person_id, workout_id=workout_id) }}"
hx-target="#tag-wrapper-w-{{ workout_id }}"
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500">
{% for p in person_tags %}
<option value="{{ p.tag_id }}">{{
p.tag_name
}}</option>
{% endfor %}
</select>
</div>
<script>
te.Select.getOrCreateInstance(document.querySelector("#workout-tag-select-{{ workout_id }}")).setValue({{ selected_workout_tag_ids| list_to_string | safe }});
</script>
</div>
</div>
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
New tag name
</label>
<div class="flex">
<input
class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
type="text" name="tag_name">
<button type="submit"
hx-post="{{ url_for('create_new_tag_for_workout', person_id=person_id, workout_id=workout_id) }}"
hx-include="[name='tag_name']" hx-target="#tag-wrapper-w-{{ workout_id }}"
class="p-2.5 ml-2 text-sm font-medium text-white bg-blue-700 rounded-lg border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-6 h-6" data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke: currentColor;">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"></path>
</svg>
<span class="sr-only">Search</span>
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
{% for tag in workout_tags %}
<span
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-200 uppercase last:mr-0 mr-1 max-h-fit cursor-pointer"
hx-get="{{ url_for('goto_tag') }}" hx-vals='{"filter": "{{ tag.tag_filter }}", "person_id": "{{ tag.person_id }}"}'
hx-target="#container" hx-push-url="true" _='on click trigger closeModal'>{{
tag.tag_name }}
</span>
{% endfor%}