Make dashboard, person, settings page htmx driven
This commit is contained in:
16
app.py
16
app.py
@@ -4,11 +4,13 @@ import jinja_partials
|
||||
from decorators import validate_person, validate_topset, validate_workout
|
||||
from db import DataBase
|
||||
from utils import get_people_and_exercise_rep_maxes
|
||||
from flask_htmx import HTMX
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile('config.py')
|
||||
jinja_partials.register_extensions(app)
|
||||
db = DataBase(app)
|
||||
htmx = HTMX(app)
|
||||
|
||||
|
||||
@ app.route("/")
|
||||
@@ -16,7 +18,10 @@ def dashboard():
|
||||
all_topsets = db.get_all_topsets()
|
||||
people_and_exercise_rep_maxes = get_people_and_exercise_rep_maxes(
|
||||
all_topsets)
|
||||
return render_template('index.html', model=people_and_exercise_rep_maxes)
|
||||
if htmx:
|
||||
return render_template('partials/page/dashboard.html',
|
||||
model=people_and_exercise_rep_maxes), 200, {"HX-Trigger": "updatedPeople"}
|
||||
return render_template('dashboard.html', model=people_and_exercise_rep_maxes)
|
||||
|
||||
|
||||
@ app.route("/person/list", methods=['GET'])
|
||||
@@ -29,10 +34,14 @@ def get_person_list():
|
||||
@ validate_person
|
||||
def get_person(person_id):
|
||||
person = db.get_person(person_id)
|
||||
if htmx:
|
||||
return render_template('partials/page/person.html',
|
||||
person=person, is_filtered=False), 200, {"HX-Trigger": "updatedPeople"}
|
||||
|
||||
return render_template('person.html', person=person)
|
||||
|
||||
|
||||
@app.route("/person/<int:person_id>/exercise_filter", methods=['POST'])
|
||||
@ app.route("/person/<int:person_id>/exercise_filter", methods=['POST'])
|
||||
@ validate_person
|
||||
def filter_exercises_for_person(person_id):
|
||||
selected_exercise_ids = [int(i)
|
||||
@@ -204,6 +213,9 @@ def delete_exercise(exercise_id):
|
||||
def settings():
|
||||
people = db.get_people()
|
||||
exercises = db.get_exercises()
|
||||
if htmx:
|
||||
return render_template('partials/page/settings.html',
|
||||
people=people, exercises=exercises), 200, {"HX-Trigger": "updatedPeople"}
|
||||
return render_template('settings.html', people=people, exercises=exercises)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Flask==2.0.1
|
||||
Flask==2.2.2
|
||||
gunicorn==19.7.1
|
||||
Jinja2==3.0.1
|
||||
jinja-partials==0.1.1
|
||||
psycopg2-binary==2.9.3
|
||||
psycopg2-binary==2.9.3
|
||||
flask-htmx==0.2.0
|
||||
@@ -75,8 +75,8 @@
|
||||
<div class="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
|
||||
<div class="flex-1 px-3 bg-white divide-y space-y-1">
|
||||
<div class="space-y-2 pt-2">
|
||||
<a href="{{ url_for('dashboard') }}"
|
||||
class="text-base text-gray-900 font-normal rounded-lg flex items-center p-2 hover:bg-gray-100 group {{ is_selected_page(url_for('dashboard')) }}">
|
||||
<a hx-get="{{ url_for('dashboard') }}" hx-push-url="true" hx-target="#container"
|
||||
class="text-base text-gray-900 font-normal rounded-lg flex items-center p-2 hover:bg-gray-100 group cursor-pointer {{ is_selected_page(url_for('dashboard')) }}">
|
||||
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
|
||||
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path>
|
||||
@@ -92,8 +92,8 @@
|
||||
|
||||
</ul>
|
||||
<div class="space-y-2 pt-2">
|
||||
<a href="{{ url_for('settings') }}"
|
||||
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 {{ is_selected_page(url_for('settings')) }}">
|
||||
<a hx-get="{{ url_for('settings') }}" hx-push-url="true" hx-target="#container"
|
||||
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 cursor-pointer {{ is_selected_page(url_for('settings')) }}">
|
||||
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
|
||||
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
|
||||
data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;">
|
||||
@@ -110,7 +110,7 @@
|
||||
</aside>
|
||||
<div class="h-full w-full bg-gray-50 relative overflow-y-auto lg:ml-64">
|
||||
<main>
|
||||
<div class="pt-6 px-4">
|
||||
<div class="pt-6 px-4" id="container">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
8
templates/dashboard.html
Normal file
8
templates/dashboard.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ render_partial('partials/page/dashboard.html',
|
||||
people=people, exercises=exercises) }}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,7 +1,3 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<script>
|
||||
let layout = {
|
||||
margin: { t: 0 }, xaxis: { type: 'date', showgrid: false }, yaxis: {
|
||||
@@ -92,6 +88,4 @@
|
||||
|
||||
</div>
|
||||
|
||||
{{ render_partial('partials/stats.html', stats=model['Stats']) }}
|
||||
|
||||
{% endblock %}
|
||||
{{ render_partial('partials/stats.html', stats=model['Stats']) }}
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="person-view">
|
||||
<div>
|
||||
<div class="flex justify-center">
|
||||
<div class="bg-white shadow rounded-lg p-4 sm:w-full xl:p-8 md:w-full lg:w-10/12 xl:w-8/12 2xl:w-6/12">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{% for e in person['Exercises'] %}
|
||||
<div class="flex items-center mr-4"
|
||||
hx-post="{{ url_for('filter_exercises_for_person', person_id=person['PersonId']) }}"
|
||||
hx-include="[name='exercise_id']" hx-target="#person-view" hx-swap="outerHTML">
|
||||
hx-include="[name='exercise_id']" hx-target="#container" hx-swap="outerHTML">
|
||||
<input {{ is_checked(e['ExerciseId'], selected_exercise_ids) }} type="checkbox" name="exercise_id"
|
||||
value="{{ e['ExerciseId'] }}"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
|
||||
133
templates/partials/page/settings.html
Normal file
133
templates/partials/page/settings.html
Normal file
@@ -0,0 +1,133 @@
|
||||
<div class="mt-4 w-full grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 gap-4">
|
||||
<div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Users</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="overflow-x-auto rounded-lg">
|
||||
<div class="align-middle inline-block min-w-full">
|
||||
<div class="shadow overflow-hidden sm:rounded-lg">
|
||||
<table class="table-fixed min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col"
|
||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-3/5">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col"
|
||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white" id="new-person" hx-target="closest tr"
|
||||
hx-swap="outerHTML swap:0.5s">
|
||||
{% for p in people %}
|
||||
{{ render_partial('partials/person.html', person_id=p['PersonId'],
|
||||
name=p['Name'])}}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="w-full mt-3" hx-post="{{ url_for('create_person') }}" hx-swap="beforeend" hx-target="#new-person">
|
||||
<div class="flex flex-wrap -mx-3 mb-2">
|
||||
<div class="grow px-3">
|
||||
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-city">
|
||||
New user
|
||||
</label>
|
||||
<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="name">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row pt-6 px-3 w-36">
|
||||
<button
|
||||
class="w-full flex text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center items-center h-12"
|
||||
type="submit">
|
||||
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
|
||||
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
|
||||
data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Exercises</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="overflow-x-auto rounded-lg">
|
||||
<div class="align-middle inline-block min-w-full">
|
||||
<div class="shadow overflow-hidden sm:rounded-lg">
|
||||
<table class="table-fixed min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col"
|
||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-3/5">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col"
|
||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white" id="new-exercise" hx-target="closest tr"
|
||||
hx-swap="outerHTML swap:0.5s">
|
||||
{% for e in exercises %}
|
||||
{{ render_partial('partials/exercise.html', exercise_id=e['ExerciseId'],
|
||||
name=e['Name'])}}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="w-full mt-3" hx-post="{{ url_for('create_exercise') }}" hx-swap="beforeend"
|
||||
hx-target="#new-exercise">
|
||||
<div class="flex flex-wrap -mx-3 mb-2">
|
||||
<div class="grow px-3">
|
||||
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-city">
|
||||
New exercise
|
||||
</label>
|
||||
<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="name">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row pt-6 px-3 w-36">
|
||||
<button
|
||||
class="w-full flex text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center items-center h-12 cursor-pointer"
|
||||
type="submit">
|
||||
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
|
||||
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
|
||||
data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
{% for p in people %}
|
||||
<li>
|
||||
<a href="{{ url_for('get_person' ,person_id=p['PersonId']) }}"
|
||||
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group {% if p['IsActive']==1 %} bg-gray-200 {% endif %}">
|
||||
<a hx-get="{{ url_for('get_person' ,person_id=p['PersonId']) }}" hx-push-url="true" hx-target="#container" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group cursor-pointer {% if
|
||||
p['IsActive']==1 %} bg-gray-200 {% endif %}">
|
||||
<svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75"
|
||||
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd">
|
||||
|
||||
@@ -2,139 +2,7 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="mt-4 w-full grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 gap-4">
|
||||
<div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Users</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="overflow-x-auto rounded-lg">
|
||||
<div class="align-middle inline-block min-w-full">
|
||||
<div class="shadow overflow-hidden sm:rounded-lg">
|
||||
<table class="table-fixed min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col"
|
||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-3/5">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col"
|
||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white" id="new-person" hx-target="closest tr"
|
||||
hx-swap="outerHTML swap:0.5s">
|
||||
{% for p in people %}
|
||||
{{ render_partial('partials/person.html', person_id=p['PersonId'],
|
||||
name=p['Name'])}}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="w-full mt-3" hx-post="{{ url_for('create_person') }}" hx-swap="beforeend" hx-target="#new-person">
|
||||
<div class="flex flex-wrap -mx-3 mb-2">
|
||||
<div class="grow px-3">
|
||||
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-city">
|
||||
New user
|
||||
</label>
|
||||
<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="name">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row pt-6 px-3 w-36">
|
||||
<button
|
||||
class="w-full flex text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center items-center h-12"
|
||||
type="submit">
|
||||
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
|
||||
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
|
||||
data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Exercises</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="overflow-x-auto rounded-lg">
|
||||
<div class="align-middle inline-block min-w-full">
|
||||
<div class="shadow overflow-hidden sm:rounded-lg">
|
||||
<table class="table-fixed min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col"
|
||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-3/5">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col"
|
||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white" id="new-exercise" hx-target="closest tr"
|
||||
hx-swap="outerHTML swap:0.5s">
|
||||
{% for e in exercises %}
|
||||
{{ render_partial('partials/exercise.html', exercise_id=e['ExerciseId'],
|
||||
name=e['Name'])}}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="w-full mt-3" hx-post="{{ url_for('create_exercise') }}" hx-swap="beforeend"
|
||||
hx-target="#new-exercise">
|
||||
<div class="flex flex-wrap -mx-3 mb-2">
|
||||
<div class="grow px-3">
|
||||
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-city">
|
||||
New exercise
|
||||
</label>
|
||||
<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="name">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row pt-6 px-3 w-36">
|
||||
<button
|
||||
class="w-full flex text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center items-center h-12 cursor-pointer"
|
||||
type="submit">
|
||||
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
|
||||
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
|
||||
data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ render_partial('partials/page/settings.html',
|
||||
model=model) }}
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user