Add view to list workout notes for a person
This commit is contained in:
10
app.py
10
app.py
@@ -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
50
db.py
@@ -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)
|
||||
|
||||
|
||||
@@ -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
98
templates/notes.html
Normal 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="[&_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 [&:has([role=checkbox])]:pr-0">
|
||||
Date
|
||||
</th>
|
||||
<th
|
||||
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0">
|
||||
Note
|
||||
</th>
|
||||
<th
|
||||
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0">
|
||||
Tags
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="[&_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 [&: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 [&: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 [&: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 %}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user