Integrate session note and tag editing and center progress controls
This commit is contained in:
+3
-3
@@ -84,7 +84,7 @@ def get_person_notes(person_id):
|
||||
def get_workout_note_edit_form(person_id, workout_id):
|
||||
"""Returns the form to edit a specific workout note."""
|
||||
note = _fetch_workout_note(person_id, workout_id) # Use local helper
|
||||
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note, is_edit=True)
|
||||
return render_template('partials/session_note.html' if request.args.get('view') == 'session' else 'partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note, is_edit=True)
|
||||
|
||||
@notes_bp.route("/person/<int:person_id>/workout/<int:workout_id>/note", methods=['PUT'])
|
||||
@validate_workout
|
||||
@@ -93,11 +93,11 @@ def update_workout_note(person_id, workout_id):
|
||||
note = request.form.get('note')
|
||||
_update_workout_note_for_person(person_id, workout_id, note) # Use local helper
|
||||
db.activityRequest.log(current_user.id, 'UPDATE_NOTE', 'workout', workout_id, f"Updated note for workout {workout_id}")
|
||||
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note)
|
||||
return render_template('partials/session_note.html' if request.args.get('view') == 'session' else 'partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note)
|
||||
|
||||
@notes_bp.route("/person/<int:person_id>/workout/<int:workout_id>/note", methods=['GET'])
|
||||
@validate_workout
|
||||
def get_workout_note(person_id, workout_id):
|
||||
"""Returns the display partial for a specific workout note."""
|
||||
note = _fetch_workout_note(person_id, workout_id) # Use local helper
|
||||
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note)
|
||||
return render_template('partials/session_note.html' if request.args.get('view') == 'session' else 'partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note)
|
||||
|
||||
+2
-2
@@ -186,7 +186,7 @@ def add_tag_to_workout(workout_id):
|
||||
|
||||
|
||||
# Render the partial with the complete list of tags (selected and unselected)
|
||||
return render_template('partials/workout_tags_list.html', tags=all_person_tags, person_id=person_id, workout_id=workout_id)
|
||||
return render_template('partials/workout_tag_editor.html' if request.args.get('view') == 'session' else 'partials/workout_tags_list.html', tags=all_person_tags, person_id=person_id, workout_id=workout_id)
|
||||
|
||||
@tags_bp.route("/workout/<int:workout_id>/new", methods=['POST'])
|
||||
@login_required
|
||||
@@ -271,4 +271,4 @@ def create_new_tag_for_workout(workout_id):
|
||||
|
||||
# Render the partial with the complete list of tags
|
||||
# Use 'tags' as the variable name consistent with the other endpoint and likely the partial
|
||||
return render_template('partials/workout_tags_list.html', tags=all_person_tags, person_id=person_id, workout_id=workout_id)
|
||||
return render_template('partials/workout_tag_editor.html' if request.args.get('view') == 'session' else 'partials/workout_tags_list.html', tags=all_person_tags, person_id=person_id, workout_id=workout_id)
|
||||
|
||||
@@ -32,3 +32,8 @@
|
||||
.forecast-method{margin:14px 0;border-top:1px solid #e2e8f0;padding-top:12px}.forecast-method summary{font-size:12px;color:#475569;cursor:pointer;font-weight:500}.forecast-method p{margin:8px 0;font-size:12px;line-height:1.7;color:#64748b}
|
||||
.forecast-facts{display:flex;flex-wrap:wrap;gap:8px 18px;margin:10px 0;font-size:11px;color:#64748b}.forecast-facts strong{color:#334155}
|
||||
@media(max-width:520px){.progress-heading{flex-wrap:wrap;gap:7px}.progress-title{flex:1 1 180px}.progress-actions{margin-left:auto}.forecast-summary{padding:12px}.forecast-summary-grid{gap:10px}.forecast-primary{font-size:21px}.forecast-horizon-summary{padding-left:10px}.forecast-secondary{font-size:18px}.forecast-secondary span{display:block}.forecast-chart{height:190px}.forecast-readout-values{grid-template-columns:1fr 1fr}.forecast-readout-values>div:last-child{grid-column:1/-1;display:flex;align-items:center;gap:8px}.forecast-readout{min-height:120px}.forecast-chart-hint{font-size:9px}.forecast-table td,.forecast-table th{padding:9px}.exercise-progress>.inline-flex{display:flex}.exercise-progress>.inline-flex>div{padding:7px 12px;font-size:12px}}
|
||||
|
||||
/* Keep the exercise's achievements and time controls visually centered. */
|
||||
.progress-badges{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px;margin:6px 0 12px}
|
||||
.progress-badges:empty{display:none}
|
||||
.progress-epochs,.forecast-controls{justify-content:center}
|
||||
|
||||
+26
-6
@@ -22,9 +22,7 @@
|
||||
.workout-add-button:hover{background:#066975}
|
||||
.workout-workspace button:focus-visible,.workout-workspace summary:focus-visible{outline:2px solid #0891a0;outline-offset:3px}
|
||||
.workout-button{display:flex;align-items:center;gap:16px;padding:10px 14px;border:1px solid #dbe3e9;background:#fff;border-radius:8px;font-size:12px;cursor:pointer;list-style:none}
|
||||
.workout-menu{position:relative;flex-shrink:0}
|
||||
.workout-menu-content{position:absolute;right:0;top:calc(100% + 8px);z-index:20;padding:6px;background:#fff;border:1px solid #e1e7ec;border-radius:10px;min-width:180px;box-shadow:0 8px 24px #182c4214}
|
||||
.workout-delete{color:#b42335;padding:10px;width:100%;text-align:left;border-radius:6px;font-size:13px}
|
||||
.workout-delete{color:#8c5960;flex-shrink:0}.workout-delete:hover{color:#b42335}
|
||||
.workout-delete:hover{background:#fff1f2}
|
||||
.workout-results>.workout-section-heading{padding:22px 24px 18px;margin:0}
|
||||
.workout-table{width:100%;border-collapse:collapse;table-layout:fixed;text-align:left}
|
||||
@@ -55,9 +53,6 @@
|
||||
.workout-disclosure[open]>summary:after{content:'−'}
|
||||
.workout-disclosure>summary .workout-subtle{display:block;margin-top:3px}
|
||||
.workout-disclosure-body{padding:0 20px 20px}
|
||||
.workout-disclosure-body [id^='workout-note-'] a{float:none;padding-left:0}
|
||||
.workout-disclosure-body [id^='tag-form-w-']>div{display:block}
|
||||
.workout-disclosure-body [id^='tag-form-w-']>div>div{width:100%;padding:8px 0;margin:0}
|
||||
.workout-breakdown h3{font-size:11px;font-weight:650;color:#64748b;margin:0 0 12px}
|
||||
.workout-distribution-bar{display:flex;gap:2px;height:7px;border-radius:4px;overflow:hidden;margin-bottom:14px}
|
||||
.workout-distribution-legend{display:grid;gap:11px;font-size:12px}
|
||||
@@ -82,3 +77,28 @@
|
||||
@media(max-width:1100px){.workout-layout{grid-template-columns:minmax(0,1fr) 260px;gap:16px}.workout-logger{padding:20px}.workout-set-fields{grid-template-columns:minmax(0,1fr) 76px 90px;gap:10px}.workout-set-submit{grid-column:1/-1}.workout-add-button{width:100%}}
|
||||
@media(max-width:900px){.workout-layout{grid-template-columns:minmax(0,1fr)}.workout-sidebar{grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.workout-set-fields{grid-template-columns:minmax(140px,1fr) 84px 104px auto}.workout-set-submit{grid-column:auto}}
|
||||
@media(max-width:600px){.workout-workspace{padding:4px 0 24px}.workout-workspace nav{margin-bottom:20px}.workout-header{gap:8px;align-items:flex-start;margin-bottom:18px}.workout-header h1{font-size:24px}.workout-header h1 span{display:block;font-size:16px;letter-spacing:0;margin-top:4px}.workout-button{gap:8px;padding:8px 10px;font-size:11px}.workout-main,.workout-sidebar{gap:14px}.workout-sidebar{grid-template-columns:minmax(0,1fr)}.workout-logger{padding:18px 16px}.workout-section-heading{margin-bottom:18px}.workout-section-heading>.workout-subtle{display:none}.workout-set-fields{grid-template-columns:1fr 1fr;gap:14px 10px}.workout-set-field:first-child,.workout-set-submit{grid-column:1/-1}.workout-results>.workout-section-heading{padding:18px 14px}.workout-table>thead th{padding:10px 12px}.workout-table>thead th:first-child{width:49%}.workout-table>thead th:last-child{width:44px}.workout-table .workout-set-row>td{padding:16px 12px;font-size:12px}.workout-table .workout-set-row>td:last-child{padding:6px 0}.workout-table .workout-set-row>td:last-child>div{flex-direction:column}.workout-exercise-tools{gap:8px}.workout-exercise-tools button{font-size:10px;min-height:30px}.workout-exercise-tools svg{display:none}.workout-table .extra-content-container:not(:empty){padding:14px 10px}.workout-workspace .forecast-chart{height:190px}.workout-summary dl{margin:14px 0}.workout-summary{padding:18px 20px}}
|
||||
|
||||
/* Direct session editing: the card itself is the control. */
|
||||
.workout-note-trigger{display:block;width:100%;padding:20px;text-align:left;border-radius:14px}
|
||||
.workout-note-trigger:hover{background:#f8fafc}
|
||||
.workout-inline-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:14px;font-weight:650;margin-bottom:7px}
|
||||
.workout-inline-action{font-size:11px;font-weight:500;color:#087f8c}
|
||||
.workout-note-preview{display:block;white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;line-height:1.7;color:#475569;max-height:160px;overflow:auto}
|
||||
.workout-note-editor{padding:20px}
|
||||
.workout-note-editor>label,.workout-tags-card>h2{display:block;font-size:14px;font-weight:650;margin-bottom:12px}
|
||||
.workout-note-editor textarea,.workout-tag-create input{width:100%;min-width:0;background:#f8fafc;border:1px solid #dbe3e9;border-radius:8px;padding:10px 12px;font-size:13px;color:#253449}
|
||||
.workout-note-editor textarea{display:block;resize:vertical;line-height:1.6;min-height:132px}
|
||||
.workout-note-editor textarea:focus,.workout-tag-create input:focus{outline:2px solid #0891a0;outline-offset:2px}
|
||||
.workout-editor-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}
|
||||
.workout-editor-actions button{min-height:38px;font-size:12px;padding:8px 12px}
|
||||
.workout-editor-error{font-size:12px;line-height:1.5;color:#b42335;margin-top:10px}
|
||||
.workout-tags-card{padding:20px}
|
||||
.workout-tag-choices{display:flex;flex-wrap:wrap;gap:8px;max-height:180px;overflow:auto;padding:4px;margin:8px -4px 12px}
|
||||
.workout-tag-choice{display:flex;align-items:center;gap:6px;padding:7px 9px;border:1px solid #dbe3e9;border-radius:8px;font-size:12px;color:#64748b;cursor:pointer;overflow-wrap:anywhere;max-width:100%}
|
||||
.workout-tag-choice:has(input:checked){background:#edf7f8;border-color:#9acbd0;color:#086e79}
|
||||
.workout-tag-choice:hover{border-color:#0891a0}
|
||||
.workout-tag-choice input{width:13px;height:13px;flex-shrink:0;accent-color:#087f8c}
|
||||
.workout-tag-choice:has(input:focus-visible){outline:2px solid #0891a0;outline-offset:2px}
|
||||
.workout-tag-create{display:flex;gap:8px}
|
||||
.workout-tag-create button{flex-shrink:0;padding:8px 10px;color:#087f8c}
|
||||
.workout-tag-editor input:disabled,.workout-tag-editor button:disabled,.workout-note-editor button:disabled{opacity:.55;cursor:wait}
|
||||
|
||||
+2
-2
@@ -15,10 +15,10 @@
|
||||
<link href="/static/css/tailwind.css" rel="stylesheet">
|
||||
|
||||
<link href="/static/css/style.css" rel="stylesheet">
|
||||
<link href="/static/css/workout.css?v=1" rel="stylesheet">
|
||||
<link href="/static/css/workout.css?v=2" rel="stylesheet">
|
||||
<script src="/static/js/htmx.min.js" defer></script>
|
||||
<script src="/static/js/hyperscript.min.js" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/forecast.css?v=2">
|
||||
<link rel="stylesheet" href="/static/css/forecast.css?v=3">
|
||||
<script src="/static/js/forecast.js?v=2" defer></script>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<div class="progress-heading">
|
||||
<div class="progress-title">
|
||||
<h4>{{ title }}</h4>
|
||||
<div hx-get="{{ url_for('workout.get_topset_achievements', person_id=person_id, workout_id=latest_workout_id, topset_id=latest_topset_id) }}"
|
||||
hx-trigger="load" hx-target="this" hx-swap="innerHTML"></div>
|
||||
</div>
|
||||
<button class="progress-action" title="Show Progress Graph"
|
||||
hx-get="{{ url_for('get_exercise_progress_for_user', person_id=person_id, exercise_id=exercise_id, epoch=selected_epoch, forecast='true' if forecast_enabled else 'false', forecast_weeks=forecast_weeks, forecast_reps=forecast_target_reps, min_date=min_date, max_date=max_date) }}"
|
||||
hx-target="#exercise-history-container" hx-swap="outerHTML">Graph</button>
|
||||
</div>
|
||||
<div class="progress-badges" aria-label="Progress achievements" hx-get="{{ url_for('workout.get_topset_achievements', person_id=person_id, workout_id=latest_workout_id, topset_id=latest_topset_id) }}"
|
||||
hx-trigger="load" hx-target="this" hx-swap="innerHTML"></div>
|
||||
{% if best_fit_formula and not forecast_enabled %}
|
||||
<h2 class="text-xs font-semibold text-blue-200 mb-4 text-center">
|
||||
{{ best_fit_formula.kg_per_week }} kg/week, {{ best_fit_formula.kg_per_month }} kg/month
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<section id="workout-note-{{ workout_id }}" class="workout-card workout-note-card"
|
||||
hx-target="this" hx-swap="outerHTML">
|
||||
{% if is_edit|default(false) %}
|
||||
<form class="workout-note-editor"
|
||||
hx-put="{{ url_for('notes.update_workout_note', person_id=person_id, workout_id=workout_id, view='session') }}"
|
||||
hx-disabled-elt="find button"
|
||||
_="on htmx:responseError remove .hidden from #note-error-{{ workout_id }}">
|
||||
<label for="session-note-input-{{ workout_id }}">Session notes</label>
|
||||
<textarea id="session-note-input-{{ workout_id }}" name="note" rows="5"
|
||||
placeholder="Cues, energy, how it felt…"
|
||||
_="init call me.focus() end">{{ note or '' }}</textarea>
|
||||
<p id="note-error-{{ workout_id }}" class="workout-editor-error hidden" role="alert">Could not save your note. Please try again.</p>
|
||||
<div class="workout-editor-actions">
|
||||
<button type="button" class="workout-button"
|
||||
hx-get="{{ url_for('notes.get_workout_note', person_id=person_id, workout_id=workout_id, view='session') }}">Cancel</button>
|
||||
<button type="submit" class="workout-add-button">Save note</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<button type="button" class="workout-note-trigger"
|
||||
hx-get="{{ url_for('notes.get_workout_note_edit_form', person_id=person_id, workout_id=workout_id, view='session') }}"
|
||||
aria-label="{{ 'Edit session notes' if note else 'Add session notes' }}">
|
||||
<span class="workout-inline-heading">Session notes <span class="workout-inline-action">{{ 'Edit' if note else '+ Add' }}</span></span>
|
||||
{% if note %}<span class="workout-note-preview">{% for line in note.split('\n') %}{{ line }}{% if not loop.last %}<br>{% endif %}{% endfor %}</span>
|
||||
{% else %}<span class="workout-subtle">Cues, energy, how it felt</span>{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</section>
|
||||
@@ -64,8 +64,6 @@ M{% for value, position in lower_points %}{{ (((position * vb_width) + margin) |
|
||||
<div class="progress-heading">
|
||||
<div class="progress-title">
|
||||
<h4>{{ title }}</h4>
|
||||
<div hx-get="{{ url_for('workout.get_topset_achievements', person_id=person_id, workout_id=latest_workout_id, topset_id=latest_topset_id) }}"
|
||||
hx-trigger="load" hx-target="this" hx-swap="innerHTML"></div>
|
||||
</div>
|
||||
<div class="progress-actions">
|
||||
<button class="progress-action" title="History and forecast tables"
|
||||
@@ -78,6 +76,8 @@ M{% for value, position in lower_points %}{{ (((position * vb_width) + margin) |
|
||||
hx-target="#svg-plot-{{ unique_id }}" hx-swap="outerHTML">Forecast {{ 'on' if forecast_enabled else 'off' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-badges" aria-label="Progress achievements" hx-get="{{ url_for('workout.get_topset_achievements', person_id=person_id, workout_id=latest_workout_id, topset_id=latest_topset_id) }}"
|
||||
hx-trigger="load" hx-target="this" hx-swap="innerHTML"></div>
|
||||
<h2 class="text-xs font-semibold text-blue-200 mb-1 text-center">
|
||||
{% if best_fit_formula and not forecast_enabled %}
|
||||
{{ best_fit_formula.kg_per_week }} kg/week, {{ best_fit_formula.kg_per_month }} kg/month
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<div id="workout-tag-editor-{{ workout_id }}" class="workout-tag-editor">
|
||||
<form hx-post="{{ url_for('tags.add_tag_to_workout', workout_id=workout_id, view='session') }}"
|
||||
hx-trigger="change" hx-target="#workout-tag-editor-{{ workout_id }}" hx-swap="outerHTML"
|
||||
hx-disabled-elt="#workout-tag-editor-{{ workout_id }} input, #workout-tag-editor-{{ workout_id }} button"
|
||||
_="on htmx:responseError remove .hidden from #tag-error-{{ workout_id }}">
|
||||
<fieldset>
|
||||
<legend class="workout-subtle">Select tags for this session</legend>
|
||||
<div class="workout-tag-choices">
|
||||
{% for tag in tags %}
|
||||
<label class="workout-tag-choice">
|
||||
<input id="workout-{{ workout_id }}-tag-{{ tag.tag_id }}" type="checkbox" name="tag_id" value="{{ tag.tag_id }}" {% if tag.is_selected %}checked{% endif %}>
|
||||
<span>{{ tag.tag_name }}</span>
|
||||
</label>
|
||||
{% else %}<p class="workout-subtle">Create your first tag below.</p>{% endfor %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<form class="workout-tag-create"
|
||||
hx-post="{{ url_for('tags.create_new_tag_for_workout', workout_id=workout_id, view='session') }}"
|
||||
hx-target="#workout-tag-editor-{{ workout_id }}" hx-swap="outerHTML"
|
||||
hx-disabled-elt="#workout-tag-editor-{{ workout_id }} input, #workout-tag-editor-{{ workout_id }} button"
|
||||
_="on htmx:responseError remove .hidden from #tag-error-{{ workout_id }}">
|
||||
<label for="new-workout-tag-{{ workout_id }}" class="sr-only">New tag name</label>
|
||||
<input id="new-workout-tag-{{ workout_id }}" type="text" name="tag_name" placeholder="New tag…" required>
|
||||
<button type="submit" class="workout-button">Add tag</button>
|
||||
</form>
|
||||
<p id="tag-error-{{ workout_id }}" class="workout-editor-error hidden" role="alert">Could not save tags. Please try again.</p>
|
||||
</div>
|
||||
@@ -1,73 +1,4 @@
|
||||
<div class="flex">
|
||||
<div id="tag-wrapper-w-{{ workout_id }}">
|
||||
{{ render_partial('partials/workout_tags_list.html', tags=tags)}}
|
||||
</div>
|
||||
<span id="tag-form-show-w-{{ workout_id }}" class="cursor-pointer" _="on click
|
||||
remove .hidden from #tag-form-w-{{ workout_id }}
|
||||
then remove .hidden from #tag-form-hide-w-{{ workout_id }}
|
||||
then add .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" 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
|
||||
add .hidden to #tag-form-w-{{ workout_id }}
|
||||
then remove .hidden from #tag-form-show-w-{{ workout_id }}
|
||||
then add .hidden" class="cursor-pointer 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">
|
||||
{{ render_partial('partials/custom_select.html',
|
||||
name='tag_id',
|
||||
options=tags,
|
||||
multiple=true,
|
||||
search=true,
|
||||
placeholder='Select tags',
|
||||
hx_post=url_for('tags.add_tag_to_workout', workout_id=workout_id),
|
||||
hx_target='#tag-wrapper-w-' + workout_id|string)
|
||||
}}
|
||||
</div>
|
||||
</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('tags.create_new_tag_for_workout', 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>
|
||||
<section class="workout-card workout-tags-card" aria-labelledby="workout-tags-heading-{{ workout_id }}">
|
||||
<h2 id="workout-tags-heading-{{ workout_id }}">Tags</h2>
|
||||
{{ render_partial('partials/workout_tag_editor.html', person_id=person_id, workout_id=workout_id, tags=tags) }}
|
||||
</section>
|
||||
|
||||
+2
-17
@@ -46,14 +46,9 @@
|
||||
<h1>{{ person_name }}<span> / Workout</span></h1>
|
||||
{{ render_partial('partials/start_date.html', person_id=person_id, workout_id=workout_id, start_date=start_date) }}
|
||||
</div>
|
||||
<details class="workout-menu">
|
||||
<summary class="workout-button">Session options <span aria-hidden="true">···</span></summary>
|
||||
<div class="workout-menu-content">
|
||||
<button type="button" class="workout-delete"
|
||||
<button type="button" class="workout-button workout-delete"
|
||||
hx-get="{{ url_for('workout.delete_workout', person_id=person_id, workout_id=workout_id) }}"
|
||||
hx-confirm="Are you sure you wish to delete this workout?" hx-push-url="true" hx-target="#container">Delete workout</button>
|
||||
</div>
|
||||
</details>
|
||||
</header>
|
||||
|
||||
<div class="workout-layout">
|
||||
@@ -96,18 +91,8 @@
|
||||
</details>
|
||||
</div>
|
||||
</details>
|
||||
<details class="workout-card workout-disclosure" {% if note %}open{% endif %}>
|
||||
<summary>Session notes <span class="workout-subtle">Cues, energy, how it felt</span></summary>
|
||||
<div class="workout-disclosure-body">
|
||||
{{ render_partial('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note or '') }}
|
||||
</div>
|
||||
</details>
|
||||
<details class="workout-card workout-disclosure">
|
||||
<summary>Tags <span class="workout-subtle">Organize this session</span></summary>
|
||||
<div class="workout-disclosure-body">
|
||||
{{ render_partial('partials/session_note.html', person_id=person_id, workout_id=workout_id, note=note or '') }}
|
||||
{{ render_partial('partials/workout_tags.html', person_id=person_id, workout_id=workout_id, tags=tags) }}
|
||||
</div>
|
||||
</details>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -120,6 +120,55 @@ class WorkoutViewTests(unittest.TestCase):
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.db.execute.assert_not_called()
|
||||
|
||||
def test_session_note_editor_round_trip_keeps_integrated_card(self):
|
||||
import routes.notes as notes
|
||||
text = 'Good energy\nKeep <cues> simple'
|
||||
with patch.object(notes, 'db', self.db), patch.object(notes, '_fetch_workout_note', return_value=text), patch.object(notes, '_update_workout_note_for_person') as save:
|
||||
edit = self.client.get(self.base + '/note/edit?view=session')
|
||||
self.assertIn(b'workout-note-editor', edit.data)
|
||||
# The HTML minifier can unescape RCDATA inside a textarea.
|
||||
self.assertIn(b'Keep <cues> simple</textarea>', edit.data)
|
||||
self.assertIn(b'Save note', edit.data)
|
||||
saved = self.client.put(self.base + '/note?view=session', data={'note': text})
|
||||
save.assert_called_once_with(1, 1, text)
|
||||
self.assertIn(b'workout-note-trigger', saved.data)
|
||||
self.assertIn(b'Good energy<br>Keep <cues> simple', saved.data)
|
||||
self.assertNotIn(b'<textarea', saved.data)
|
||||
canceled = self.client.get(self.base + '/note?view=session')
|
||||
self.assertIn(b'workout-note-preview', canceled.data)
|
||||
legacy = self.client.get(self.base + '/note')
|
||||
self.assertNotIn(b'workout-note-card', legacy.data)
|
||||
|
||||
def test_tags_return_updated_controls_after_select_clear_and_create(self):
|
||||
import routes.tags as tags
|
||||
tag = dict(tag_id=12, tag_name='Push', is_selected=True)
|
||||
|
||||
def execute(query, args=(), **kwargs):
|
||||
if 'SELECT person_id' in query:
|
||||
return {'person_id': 1}
|
||||
if 'SELECT tag_id FROM Tag' in query:
|
||||
return {'tag_id': 12}
|
||||
if 'SELECT DISTINCT' in query:
|
||||
return []
|
||||
if 'FROM tag' in query:
|
||||
return [tag]
|
||||
return []
|
||||
|
||||
self.db.execute.side_effect = execute
|
||||
with patch.object(tags, 'db', self.db):
|
||||
for selected in ({'tag_id': '12'}, {}):
|
||||
tag['is_selected'] = bool(selected)
|
||||
response = self.client.post('/tag/workout/1/add?view=session', data=selected)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'workout-tag-editor-1', response.data)
|
||||
self.assertIn(b'type=checkbox', response.data)
|
||||
self.assertEqual(b' checked' in response.data, bool(selected))
|
||||
tag['is_selected'] = True
|
||||
response = self.client.post('/tag/workout/1/new?view=session', data={'tag_name': 'Push'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'workout-tag-editor-1', response.data)
|
||||
self.assertIn(b' checked', response.data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user