- Create database tables: workout_program, program_session, person_program_assignment.
- Add Flask blueprint `routes/programs.py` with routes for creating, listing, viewing, and deleting programs.
- Implement program creation form (`templates/program_create.html`):
- Allows defining program name, description, and multiple sessions.
- Each session includes a name and dynamically added exercise selections.
- Uses `tail.select` for searchable exercise dropdowns.
- JavaScript handles dynamic addition/removal of sessions and exercises.
- Implement backend logic for program creation:
- Parses form data including multiple exercises per session.
- Automatically finds or creates non-person-specific tags based on selected exercises for each session.
- Saves program and session data, linking sessions to appropriate tags.
- Implement program list view (`templates/program_list.html`):
- Displays existing programs.
- Includes HTMX-enabled delete button for each program.
- Links program names to the view page using HTMX for dynamic loading.
- Implement program detail view (`templates/program_view.html`):
- Displays program name, description, and sessions.
- Parses session tag filters to retrieve and display associated exercises.
- Update changelog with details of the new feature.
85 lines
4.7 KiB
HTML
85 lines
4.7 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Workout Programs{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mx-auto px-4 py-8">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h1 class="text-2xl font-bold">Workout Programs</h1>
|
|
<a href="{{ url_for('programs.create_program') }}"
|
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
|
Create New Program
|
|
</a>
|
|
</div>
|
|
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
<div class="mb-4">
|
|
{% for category, message in messages %}
|
|
<div class="p-4 rounded-md {{ 'bg-green-100 border-green-400 text-green-700' if category == 'success' else 'bg-red-100 border-red-400 text-red-700' }}"
|
|
role="alert">
|
|
<p class="font-bold">{{ category.title() }}</p>
|
|
<p>{{ message }}</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
<div class="bg-white shadow overflow-hidden sm:rounded-md">
|
|
<ul role="list" class="divide-y divide-gray-200">
|
|
{% if programs %}
|
|
{% for program in programs %}
|
|
<li id="program-{{ program.program_id }}">
|
|
{# Use HTMX for dynamic loading #}
|
|
<a href="{{ url_for('programs.view_program', program_id=program.program_id) }}"
|
|
class="block hover:bg-gray-50"
|
|
hx-get="{{ url_for('programs.view_program', program_id=program.program_id) }}"
|
|
hx-target="#container" hx-push-url="true" hx-swap="innerHTML">
|
|
<div class="px-4 py-4 sm:px-6">
|
|
<div class="flex items-center justify-between">
|
|
<p class="text-sm font-medium text-indigo-600 truncate">{{ program.name }}</p>
|
|
<div class="ml-2 flex-shrink-0 flex space-x-2"> {# Added space-x-2 #}
|
|
{# TODO: Add View/Edit/Assign buttons later #}
|
|
<span
|
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800 items-center">
|
|
{# Added items-center #}
|
|
ID: {{ program.program_id }}
|
|
</span>
|
|
{# Delete Button #}
|
|
<button type="button" class="text-red-600 hover:text-red-800 focus:outline-none"
|
|
hx-delete="{{ url_for('programs.delete_program', program_id=program.program_id) }}"
|
|
hx-target="closest li" hx-swap="outerHTML"
|
|
hx-confirm="Are you sure you want to delete the program '{{ program.name }}'? This cannot be undone.">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
|
fill="currentColor">
|
|
<path fill-rule="evenodd"
|
|
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
|
clip-rule="evenodd" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2 sm:flex sm:justify-between">
|
|
<div class="sm:flex">
|
|
<p class="flex items-center text-sm text-gray-500">
|
|
{{ program.description | default('No description provided.') }}
|
|
</p>
|
|
</div>
|
|
{# <div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0">
|
|
Created: {{ program.created_at | strftime('%Y-%m-%d') }}
|
|
</div> #}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
{% endfor %}
|
|
{% else %}
|
|
<li class="px-4 py-4 sm:px-6">
|
|
<p class="text-sm text-gray-500">No workout programs found. Create one!</p>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{% endblock %} |