Add view to list workout notes for a person

This commit is contained in:
Peter Stockings
2024-03-09 18:50:44 +11:00
parent e3de9f886b
commit dae4fcbf44
6 changed files with 165 additions and 5 deletions

10
app.py
View File

@@ -125,6 +125,8 @@ def get_calendar(person_id):
if selected_view == 'all':
return redirect(url_for('get_person', person_id=person_id))
elif selected_view == 'notes':
return redirect(url_for('get_person_notes', person_id=person_id))
# selected_view = month | year | all
date_info = get_date_info(selected_date, selected_view)
@@ -133,6 +135,14 @@ def get_calendar(person_id):
return render_block(app.jinja_env, 'calendar.html', 'content', person=person, selected_date=selected_date, selected_view=selected_view, **date_info, datetime=datetime, timedelta=timedelta, relativedelta=relativedelta, first_and_last_visible_days_in_month=first_and_last_visible_days_in_month)
return render_template('calendar.html', person=person, selected_date=selected_date, selected_view=selected_view, **date_info, datetime=datetime, timedelta=timedelta, relativedelta=relativedelta, first_and_last_visible_days_in_month=first_and_last_visible_days_in_month)
@ app.route("/person/<int:person_id>/notes", methods=['GET'])
@ validate_person
def get_person_notes(person_id):
(person_name, workout_notes) = db.get_workout_notes_for_person(person_id)
if htmx:
return render_block(app.jinja_env, 'notes.html', 'content', person_id=person_id, person_name=person_name, workout_notes=workout_notes)
return render_template('notes.html', person_id=person_id, person_name=person_name, workout_notes=workout_notes)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/modal", methods=['GET'])
@ validate_workout

50
db.py
View File

@@ -487,3 +487,53 @@ class DataBase():
exercise_progress = get_exercise_graph_model(topsets[0]['exercise_name'], estimated_1rm, repetitions, weight, start_dates, messages)
return exercise_progress
def get_workout_notes_for_person(self, person_id):
sql_query = """
SELECT
p.name AS person_name,
w.workout_id,
to_char(w.start_date, 'Mon DD YYYY') AS formatted_start_date,
w.note,
t.filter as tag_filter,
t.name AS tag_name
FROM person p
LEFT JOIN workout w ON p.person_id = w.person_id AND w.note IS NOT NULL AND w.note <> ''
LEFT JOIN workout_tag wt ON w.workout_id = wt.workout_id
LEFT JOIN tag t ON wt.tag_id = t.tag_id
WHERE p.person_id = %s
ORDER BY w.start_date DESC, w.workout_id, t.name;
"""
# Execute the SQL query
raw_workout_notes = self.execute(sql_query, [person_id])
# Initialize variables to hold the person's name and the workouts
person_name = None
workout_notes = {}
for row in raw_workout_notes:
# Update person_name (it will be the same for all rows)
if person_name is None:
person_name = row['person_name']
# Process workout notes and tags if there's a note associated with the workout
if row['workout_id'] and row['note']: # Check if workout_id exists and note is not None or empty
workout_id = row['workout_id']
if workout_id not in workout_notes:
workout_notes[workout_id] = {
'workout_id': workout_id,
'formatted_start_date': row['formatted_start_date'],
'note': row['note'],
'tags': []
}
if row['tag_name']: # Only add the tag if it is not None
workout_notes[workout_id]['tags'].append({'tag_filter': row['tag_filter'], 'tag_name': row['tag_name'], 'person_id': person_id})
# Convert the workout_notes dictionary back into a list as the final result
workout_notes_list = list(workout_notes.values())
# Return a tuple containing the person's name and their workout notes
return (person_name, workout_notes_list)

View File

@@ -46,6 +46,7 @@
_="init js(me) tail.select(me, {}) end">
<option value="month" {% if selected_view=='month' %}selected{% endif %}>Month</option>
<option value="year" {% if selected_view=='year' %}selected{% endif %}>Year</option>
<option value="notes">Notes</option>
<option value="all">All</option>
</select>
</div>

98
templates/notes.html Normal file
View File

@@ -0,0 +1,98 @@
{% extends 'base.html' %}
{% block content %}
<a hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container" hx-vals='{"view": "notes"}'
hx-trigger="refreshView" id="refreshViewElement"></a>
<div class="flex flex-grow flex-col bg-white sm:rounded shadow overflow-hidden">
<div class="flex items-center justify-between pt-2 pb-2">
<div class="flex">
<h2 class="ml-2 text-xl font-bold leading-none">Notes</h2>
<span
class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-blue-200 dark:text-blue-800 ml-1 sm:ml-5 flex justify-center items-center ">{{person_name}}</span>
</div>
<div class="mr-4">
<select name="view" hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
x-push-url="true" _="init js(me) tail.select(me, {}) end">
<option value="month">Month</option>
<option value="year">Year</option>
<option value="notes" selected>Notes</option>
<option value="all">All</option>
</select>
</div>
</div>
{% if workout_notes %}
<div class="relative w-full overflow-auto">
<table class="w-full caption-bottom text-sm">
<thead class="[&amp;_tr]:border-b">
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Date
</th>
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Note
</th>
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Tags
</th>
</tr>
</thead>
<tbody class="[&amp;_tr:last-child]:border-0">
{% for workout_note in workout_notes %}
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0 cursor-pointer"
hx-get="{{ url_for('get_workout_modal', person_id=person_id, workout_id=workout_note.workout_id) }}"
hx-target='body' hx-swap='beforeend'>
<div class="flex flex-row items-center justify-center">
<button
class="inline-flex items-center justify-center whitespace-nowrap text-sm font-medium h-10 px-2 py-1 bg-transparent text-black rounded hidden md:block"
type="button">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>
</button>
<div class="font-semibold">
{{ workout_note.formatted_start_date
}}
</div>
</div>
</td>
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">
{{ render_partial('partials/workout_note.html', person_id=person_id,
workout_id=workout_note.workout_id,
note=workout_note.note) }}
</td>
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">
{{ render_partial('partials/workout_tags_list.html', workout_tags=workout_note.tags) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
<button
class="fixed z-90 bottom-10 right-8 bg-blue-600 w-20 h-20 rounded-full drop-shadow-lg flex justify-center items-center text-white text-4xl hover:bg-blue-700 hover:drop-shadow-2xl hover:animate-bounce duration-300"
hx-post="{{ url_for('create_workout', person_id=person_id) }}" hx-target='body' hx-swap='beforeend'>
<svg viewBox="0 0 20 20" enable-background="new 0 0 20 20" class="w-6 h-6 inline-block">
<path fill="#FFFFFF" d="M16,10c0,0.553-0.048,1-0.601,1H11v4.399C11,15.951,10.553,16,10,16c-0.553,0-1-0.049-1-0.601V11H4.601
C4.049,11,4,10.553,4,10c0-0.553,0.049-1,0.601-1H9V4.601C9,4.048,9.447,4,10,4c0.553,0,1,0.048,1,0.601V9h4.399
C15.952,9,16,9.447,16,10z" />
</svg>
</button>
{% endblock %}

View File

@@ -1,16 +1,16 @@
<div id="workout-note" hx-target="this" hx-swap="outerHTML swap:0.5s">
<div id="workout-note-{{workout_id}}" hx-target="this" hx-swap="outerHTML swap:0.5s">
{% if is_edit|default(false, true) == false %}
{% if note|length > 0 %}
<span class="text-base font-normal text-gray-500 whitespace-normal">{{ note | replace('\n', '<br>') | safe }}</span>
<a class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer"
hx-get="{{ url_for('get_workout_note_edit_form', person_id=person_id, workout_id=workout_id) }}"
hx-target="#workout-note">
hx-target="#workout-note-{{workout_id}}">
Edit
</a>
{% else %}
<a class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer float-none sm:float-right"
hx-get="{{ url_for('get_workout_note_edit_form', person_id=person_id, workout_id=workout_id) }}"
hx-target="#workout-note">
hx-target="#workout-note-{{workout_id}}">
Add note
</a>
{% endif %}
@@ -25,7 +25,7 @@
<button
class="inline-flex justify-center p-2 text-blue-600 rounded-full cursor-pointer hover:bg-blue-100 dark:text-blue-500 dark:hover:bg-gray-600"
hx-get="{{ url_for('get_workout_note', person_id=person_id, workout_id=workout_id) }}"
hx-target="#workout-note">
hx-target="#workout-note-{{workout_id}}">
<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="M6 18L18 6M6 6l12 12" />

View File

@@ -28,6 +28,7 @@
hx-target="#container" hx-push-url="true" _="init js(me) tail.select(me, {}) end">
<option value="month">Month</option>
<option value="year">Year</option>
<option value="notes">Notes</option>
<option value="all" selected>All</option>
</select>
</div>