Compare commits
2 Commits
d1029485d4
...
39006435b2
Author | SHA1 | Date | |
---|---|---|---|
39006435b2
|
|||
5c0bb602f5
|
@@ -13,12 +13,12 @@
|
|||||||
--text-xs--line-height: calc(1 / 0.75);
|
--text-xs--line-height: calc(1 / 0.75);
|
||||||
--text-sm: 0.875rem;
|
--text-sm: 0.875rem;
|
||||||
--text-sm--line-height: calc(1.25 / 0.875);
|
--text-sm--line-height: calc(1.25 / 0.875);
|
||||||
|
--text-lg: 1.125rem;
|
||||||
|
--text-lg--line-height: calc(1.75 / 1.125);
|
||||||
--text-xl: 1.25rem;
|
--text-xl: 1.25rem;
|
||||||
--text-xl--line-height: calc(1.75 / 1.25);
|
--text-xl--line-height: calc(1.75 / 1.25);
|
||||||
--text-2xl: 1.5rem;
|
--text-2xl: 1.5rem;
|
||||||
--text-2xl--line-height: calc(2 / 1.5);
|
--text-2xl--line-height: calc(2 / 1.5);
|
||||||
--text-3xl: 1.875rem;
|
|
||||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
--default-font-family: var(--font-sans);
|
--default-font-family: var(--font-sans);
|
||||||
--default-mono-font-family: var(--font-mono);
|
--default-mono-font-family: var(--font-mono);
|
||||||
@@ -956,9 +956,18 @@
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: calc(0.25rem * 2);
|
gap: calc(0.25rem * 2);
|
||||||
}
|
}
|
||||||
|
.mt-2 {
|
||||||
|
margin-top: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
.mt-3 {
|
.mt-3 {
|
||||||
margin-top: calc(var(--spacing) * 3);
|
margin-top: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
|
.mr-1 {
|
||||||
|
margin-right: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
|
.mr-3 {
|
||||||
|
margin-right: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
.fieldset-legend {
|
.fieldset-legend {
|
||||||
margin-bottom: calc(0.25rem * -1);
|
margin-bottom: calc(0.25rem * -1);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1022,6 +1031,9 @@
|
|||||||
.h-\[1\.2em\] {
|
.h-\[1\.2em\] {
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
}
|
}
|
||||||
|
.h-\[92px\] {
|
||||||
|
height: 92px;
|
||||||
|
}
|
||||||
.h-full {
|
.h-full {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -1037,6 +1049,15 @@
|
|||||||
.w-56 {
|
.w-56 {
|
||||||
width: calc(var(--spacing) * 56);
|
width: calc(var(--spacing) * 56);
|
||||||
}
|
}
|
||||||
|
.w-\[1\.5em\] {
|
||||||
|
width: 1.5em;
|
||||||
|
}
|
||||||
|
.w-\[1\.6em\] {
|
||||||
|
width: 1.6em;
|
||||||
|
}
|
||||||
|
.w-\[1\.7em\] {
|
||||||
|
width: 1.7em;
|
||||||
|
}
|
||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -1062,9 +1083,18 @@
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
.flex-wrap {
|
.flex-wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
.gap-2 {
|
.gap-2 {
|
||||||
gap: calc(var(--spacing) * 2);
|
gap: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -1082,6 +1112,9 @@
|
|||||||
margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));
|
margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.gap-y-3 {
|
||||||
|
row-gap: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
.overflow-y-auto {
|
.overflow-y-auto {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@@ -1094,20 +1127,21 @@
|
|||||||
.p-3 {
|
.p-3 {
|
||||||
padding: calc(var(--spacing) * 3);
|
padding: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
|
.p-5 {
|
||||||
|
padding: calc(var(--spacing) * 5);
|
||||||
|
}
|
||||||
.py-2 {
|
.py-2 {
|
||||||
padding-block: calc(var(--spacing) * 2);
|
padding-block: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.py-10 {
|
|
||||||
padding-block: calc(var(--spacing) * 10);
|
|
||||||
}
|
|
||||||
.pt-3 {
|
.pt-3 {
|
||||||
padding-top: calc(var(--spacing) * 3);
|
padding-top: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
.pb-6 {
|
.pb-6 {
|
||||||
padding-bottom: calc(var(--spacing) * 6);
|
padding-bottom: calc(var(--spacing) * 6);
|
||||||
}
|
}
|
||||||
.pl-10 {
|
.text-2xl {
|
||||||
padding-left: calc(var(--spacing) * 10);
|
font-size: var(--text-2xl);
|
||||||
|
line-height: var(--tw-leading, var(--text-2xl--line-height));
|
||||||
}
|
}
|
||||||
.text-sm {
|
.text-sm {
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-sm);
|
||||||
@@ -1158,6 +1192,10 @@
|
|||||||
--btn-color: var(--color-error);
|
--btn-color: var(--color-error);
|
||||||
--btn-fg: var(--color-error-content);
|
--btn-fg: var(--color-error-content);
|
||||||
}
|
}
|
||||||
|
.btn-success {
|
||||||
|
--btn-color: var(--color-success);
|
||||||
|
--btn-fg: var(--color-success-content);
|
||||||
|
}
|
||||||
.hover\:cursor-pointer {
|
.hover\:cursor-pointer {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -1183,18 +1221,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h1 {
|
|
||||||
font-size: var(--text-3xl);
|
|
||||||
line-height: var(--tw-leading, var(--text-3xl--line-height));
|
|
||||||
}
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: var(--text-2xl);
|
|
||||||
line-height: var(--tw-leading, var(--text-2xl--line-height));
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: var(--text-xl);
|
font-size: var(--text-xl);
|
||||||
line-height: var(--tw-leading, var(--text-xl--line-height));
|
line-height: var(--tw-leading, var(--text-xl--line-height));
|
||||||
}
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
line-height: var(--tw-leading, var(--text-lg--line-height));
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
opacity: 80%;
|
||||||
|
}
|
||||||
@layer base {
|
@layer base {
|
||||||
:where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light] {
|
:where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light] {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
@@ -2,4 +2,13 @@ mod eye;
|
|||||||
mod search;
|
mod search;
|
||||||
|
|
||||||
pub use eye::eye;
|
pub use eye::eye;
|
||||||
|
use maud::{Markup, html};
|
||||||
pub use search::search;
|
pub use search::search;
|
||||||
|
|
||||||
|
pub fn arrow_left() -> Markup {
|
||||||
|
html! {
|
||||||
|
svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256" {
|
||||||
|
path fill="currentColor" d="M224 128a8 8 0 0 1-8 8H59.31l58.35 58.34a8 8 0 0 1-11.32 11.32l-72-72a8 8 0 0 1 0-11.32l72-72a8 8 0 0 1 11.32 11.32L59.31 120H216a8 8 0 0 1 8 8" {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
use maud::{Markup, html};
|
use maud::{Markup, html};
|
||||||
|
|
||||||
|
use crate::icons;
|
||||||
|
|
||||||
use super::empty;
|
use super::empty;
|
||||||
|
|
||||||
pub fn desktop_minimal(content: Markup, name: &str) -> Markup {
|
pub fn desktop_minimal(content: Markup, name: &str) -> Markup {
|
||||||
@@ -8,7 +10,7 @@ pub fn desktop_minimal(content: Markup, name: &str) -> Markup {
|
|||||||
div class="w-56" {
|
div class="w-56" {
|
||||||
(sidebar())
|
(sidebar())
|
||||||
}
|
}
|
||||||
div class="w-full overflow-y-auto" {
|
div class="w-full h-full overflow-y-auto" {
|
||||||
(content)
|
(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,11 +19,14 @@ pub fn desktop_minimal(content: Markup, name: &str) -> Markup {
|
|||||||
empty(content, name)
|
empty(content, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn desktop(content: Markup, name: &str) -> Markup {
|
pub fn desktop(content: Markup, name: &str, back_button: bool) -> Markup {
|
||||||
let content = html! {
|
let content = html! {
|
||||||
div class="pl-10 py-10 w-full max-w-2xl" {
|
div class="flex justify-center" {
|
||||||
|
div class="p-5 w-full max-w-2xl" {
|
||||||
|
(header(name, back_button))
|
||||||
(content)
|
(content)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
desktop_minimal(content, name)
|
desktop_minimal(content, name)
|
||||||
@@ -47,3 +52,16 @@ fn sidebar() -> Markup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn header(title: &str, back_button: bool) -> Markup {
|
||||||
|
html! {
|
||||||
|
div class="flex items-center mb-5" {
|
||||||
|
@if back_button {
|
||||||
|
button onclick="history.back()" class="w-[1.6em] mr-1" {
|
||||||
|
(icons::arrow_left())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1 class="text-2xl" { (title) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -20,3 +20,15 @@ pub struct WorkoutExercise {
|
|||||||
pub reps: Option<i32>,
|
pub reps: Option<i32>,
|
||||||
pub time: Option<i32>,
|
pub time: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromRow)]
|
||||||
|
pub struct WorkoutExerciseFull {
|
||||||
|
pub exercise_id: Uuid,
|
||||||
|
pub exercise_type: ExerciseVariant,
|
||||||
|
pub position: i32,
|
||||||
|
pub sets: Option<i32>,
|
||||||
|
pub reps: Option<i32>,
|
||||||
|
pub time: Option<i32>,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
@@ -21,17 +21,17 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let content = html! {
|
let content = html! {
|
||||||
h1 { "Exercises" }
|
|
||||||
a href="/exercises/new" { "new exercise +" }
|
a href="/exercises/new" { "new exercise +" }
|
||||||
ul class="list" {
|
|
||||||
|
div class="list" {
|
||||||
@for exercise in exercises {
|
@for exercise in exercises {
|
||||||
li hx-get={ "/exercises/" (exercise.exercise_id) } hx-target="body" hx-push-url="true" class="list-row" {
|
a href={"/exercises/" (exercise.exercise_id)} class="list-row" {
|
||||||
div {}
|
div {}
|
||||||
div {
|
div {
|
||||||
div class="font-bold" { (exercise.name) }
|
div class="font-bold" { (exercise.name) }
|
||||||
div class="text-xs" { (exercise.description) }
|
div class="text-xs" { (exercise.description) }
|
||||||
}
|
}
|
||||||
a class="btn btn-square btn-ghost" {
|
div class="btn btn-square btn-ghost" {
|
||||||
div class="size-[1.6em]" {
|
div class="size-[1.6em]" {
|
||||||
(icons::eye())
|
(icons::eye())
|
||||||
}
|
}
|
||||||
@@ -41,5 +41,5 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(layouts::desktop(content, "Exercises"))
|
Ok(layouts::desktop(content, "Browse Exercises", false))
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use crate::{AppError, layouts, util::AppState};
|
use crate::{AppError, layouts, models::exercises::ExerciseFull, util::AppState};
|
||||||
use axum::{
|
use axum::{
|
||||||
Form, Router,
|
Form, Router,
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
@@ -26,17 +26,16 @@ async fn page(State(state): State<AppState>, Path(id): Path<Uuid>) -> Result<Mar
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let content = html! {
|
let content = html! {
|
||||||
div {
|
h2 { (exercise.name) }
|
||||||
div class="flex w-full" {
|
p class="desc" { (exercise.description) }
|
||||||
h1 { (exercise.name) }
|
|
||||||
a href={"/exercises/" (exercise.exercise_id) "/edit" } class="ml-auto btn" { "Edit" }
|
a href={"/exercises/" (id) "/edit"} class="ml-auto btn" { "Edit" }
|
||||||
}
|
|
||||||
p { (exercise.description) }
|
p { (exercise.description) }
|
||||||
button hx-delete={ "/exercises/" (exercise.exercise_id) } hx-confirm="Are you sure that you want to delete this exercise?" class="btn btn-error" { "Delete Exercise" }
|
button hx-delete={"/exercises/" (id)} hx-confirm="Are you sure that you want to delete this exercise?" class="btn btn-error" { "Delete Exercise" }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(layouts::desktop(content, "Exercises"))
|
Ok(layouts::desktop(content, "View Exercise", true))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(
|
async fn delete(
|
||||||
@@ -70,8 +69,6 @@ async fn edit(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let content = html! {
|
let content = html! {
|
||||||
h1 class="mb-5" { "Edit Exercise" }
|
|
||||||
|
|
||||||
form hx-put={"/exercises/" (exercise.exercise_id)} class="space-y-1" {
|
form hx-put={"/exercises/" (exercise.exercise_id)} class="space-y-1" {
|
||||||
fieldset class="fieldset" {
|
fieldset class="fieldset" {
|
||||||
legend class="fieldset-legend" { "Name" }
|
legend class="fieldset-legend" { "Name" }
|
||||||
@@ -87,7 +84,7 @@ async fn edit(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(layouts::desktop(content, "Edit Exercises"))
|
Ok(layouts::desktop(content, "Edit", true))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@@ -28,8 +28,6 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let content = html! {
|
let content = html! {
|
||||||
h1 class="mb-5" { "New Exercise" }
|
|
||||||
|
|
||||||
form hx-post="/exercises/new" class="space-y-1" {
|
form hx-post="/exercises/new" class="space-y-1" {
|
||||||
fieldset class="fieldset" {
|
fieldset class="fieldset" {
|
||||||
legend class="fieldset-legend" { "Name" }
|
legend class="fieldset-legend" { "Name" }
|
||||||
@@ -49,7 +47,7 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(layouts::desktop(content, "New Exercise"))
|
Ok(layouts::desktop(content, "New Exercise", true))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@@ -9,5 +9,5 @@ pub fn routes() -> Router<AppState> {
|
|||||||
async fn page() -> Markup {
|
async fn page() -> Markup {
|
||||||
let content = html! {};
|
let content = html! {};
|
||||||
|
|
||||||
layouts::desktop(content, "Home")
|
layouts::desktop(content, "Home", false)
|
||||||
}
|
}
|
||||||
|
@@ -2,13 +2,14 @@ use crate::{AppError, icons, layouts, util::AppState};
|
|||||||
use axum::{Router, extract::State, routing::get};
|
use axum::{Router, extract::State, routing::get};
|
||||||
use maud::{Markup, html};
|
use maud::{Markup, html};
|
||||||
|
|
||||||
|
mod id;
|
||||||
mod new;
|
mod new;
|
||||||
|
|
||||||
pub fn routes() -> Router<AppState> {
|
pub fn routes() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(page))
|
.route("/", get(page))
|
||||||
.nest("/new", new::routes())
|
.nest("/new", new::routes())
|
||||||
// .nest("/{id}", id::routes())
|
.nest("/{id}", id::routes())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
||||||
@@ -20,17 +21,16 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let content = html! {
|
let content = html! {
|
||||||
h1 { "Workouts" }
|
|
||||||
a href="/workouts/new" { "new workout +" }
|
a href="/workouts/new" { "new workout +" }
|
||||||
ul class="list" {
|
div class="list" {
|
||||||
@for workout in workouts {
|
@for workout in workouts {
|
||||||
li hx-get={ "/workouts/" (workout.workout_id) } hx-target="body" hx-push-url="true" class="list-row" {
|
a href={ "/workouts/" (workout.workout_id) } class="list-row" {
|
||||||
div {}
|
div {}
|
||||||
div {
|
div {
|
||||||
div class="font-bold" { (workout.name) }
|
div class="font-bold" { (workout.name) }
|
||||||
div class="text-xs" { (workout.description) }
|
div class="text-xs" { (workout.description) }
|
||||||
}
|
}
|
||||||
a class="btn btn-square btn-ghost" {
|
div class="btn btn-square btn-ghost" {
|
||||||
div class="size-[1.6em]" {
|
div class="size-[1.6em]" {
|
||||||
(icons::eye())
|
(icons::eye())
|
||||||
}
|
}
|
||||||
@@ -40,5 +40,5 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(layouts::desktop(content, "Exercises"))
|
Ok(layouts::desktop(content, "Browse Workouts", false))
|
||||||
}
|
}
|
||||||
|
173
src/pages/workouts/id.rs
Normal file
173
src/pages/workouts/id.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
use crate::{
|
||||||
|
AppError, layouts,
|
||||||
|
models::{exercises::ExerciseVariant, workouts::WorkoutExerciseFull},
|
||||||
|
util::AppState,
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
Form, Router,
|
||||||
|
extract::{Path, State},
|
||||||
|
http::HeaderMap,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::get,
|
||||||
|
};
|
||||||
|
use maud::{Markup, html};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub fn routes() -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(page).delete(delete).put(submit_edit))
|
||||||
|
.route("/edit", get(edit))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn page(State(state): State<AppState>, Path(id): Path<Uuid>) -> Result<Markup, AppError> {
|
||||||
|
let workout = sqlx::query_as!(
|
||||||
|
crate::models::Workout,
|
||||||
|
"SELECT workout_id, name, description FROM workouts WHERE workout_id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_one(&state.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let exercises = sqlx::query_as!(
|
||||||
|
WorkoutExerciseFull,
|
||||||
|
r#"
|
||||||
|
SELECT exercises.exercise_id, exercise_type as "exercise_type:ExerciseVariant", sets, reps, time, name, description, position
|
||||||
|
FROM workout_exercises
|
||||||
|
JOIN exercises ON workout_exercises.exercise_id = exercises.exercise_id
|
||||||
|
WHERE workout_id = $1
|
||||||
|
ORDER BY position
|
||||||
|
"#,
|
||||||
|
&id,
|
||||||
|
).fetch_all(&state.pool).await?;
|
||||||
|
|
||||||
|
tracing::info!("{:?}", exercises);
|
||||||
|
|
||||||
|
let content = html! {
|
||||||
|
div {
|
||||||
|
div class="mb-5" {
|
||||||
|
h2 { (workout.name) }
|
||||||
|
p class="desc" { (workout.description) }
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 { "Exercises" }
|
||||||
|
|
||||||
|
div class="flex flex-col gap-y-3" {
|
||||||
|
@for exercise in exercises {
|
||||||
|
(display_exercise(exercise))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a href={"/workouts/" (id) "/start"} class="btn btn-success" { "Start Workout" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(layouts::desktop(content, "View Workout", true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_exercise(exercise: WorkoutExerciseFull) -> Markup {
|
||||||
|
html! {
|
||||||
|
div class="bg-base-200 p-3 rounded flex" {
|
||||||
|
div {
|
||||||
|
h3 { (exercise.name) }
|
||||||
|
p class="text-sm opacity-50" { (exercise.description) }
|
||||||
|
}
|
||||||
|
div class="ml-auto flex items-center" {
|
||||||
|
@match exercise.exercise_type {
|
||||||
|
ExerciseVariant::Time => {
|
||||||
|
div { (exercise.time.unwrap_or(-1))"s" }
|
||||||
|
},
|
||||||
|
ExerciseVariant::Number => {
|
||||||
|
div {
|
||||||
|
div { (exercise.sets.unwrap_or(-1)) " sets" }
|
||||||
|
div { (exercise.reps.unwrap_or(-1)) " reps" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ExerciseVariant::Failure => {
|
||||||
|
"Failure"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
crate::models::Exercise,
|
||||||
|
"DELETE FROM workouts WHERE workout_id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(&state.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("hx-location", "/workouts".parse().unwrap());
|
||||||
|
|
||||||
|
Ok((headers, html! {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn edit(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
|
let exercise = sqlx::query_as!(
|
||||||
|
crate::models::Exercise,
|
||||||
|
"SELECT exercise_id, name, description FROM exercises WHERE exercise_id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_one(&state.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let content = html! {
|
||||||
|
h1 class="mb-5" { "Edit Exercise" }
|
||||||
|
|
||||||
|
form hx-put={"/exercises/" (exercise.exercise_id)} class="space-y-1" {
|
||||||
|
fieldset class="fieldset" {
|
||||||
|
legend class="fieldset-legend" { "Name" }
|
||||||
|
input required="true" name="name" class="input" value={(exercise.name)} {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset class="fieldset" {
|
||||||
|
legend class="fieldset-legend" { "Description" }
|
||||||
|
textarea required="true" name="description" class="textarea" { (exercise.description) }
|
||||||
|
}
|
||||||
|
|
||||||
|
input type="submit" class="btn" value="save" { }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(layouts::desktop(content, "Edit Exercises", true))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct FormData {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn submit_edit(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
Form(form): Form<FormData>,
|
||||||
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
crate::models::Exercise,
|
||||||
|
"UPDATE exercises SET name = $1, description = $2 WHERE exercise_id = $3",
|
||||||
|
form.name,
|
||||||
|
form.description,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(&state.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
|
||||||
|
let location = format!("/exercises/{}", id);
|
||||||
|
headers.insert("hx-location", location.parse().unwrap());
|
||||||
|
|
||||||
|
Ok((headers, html! {}))
|
||||||
|
}
|
@@ -29,8 +29,6 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let content = html! {
|
let content = html! {
|
||||||
h1 class="mb-5" { "New Workout" }
|
|
||||||
|
|
||||||
div x-data="{ exercises: [] }" {
|
div x-data="{ exercises: [] }" {
|
||||||
form hx-post="/workouts/new" class="space-y-1" {
|
form hx-post="/workouts/new" class="space-y-1" {
|
||||||
fieldset class="fieldset" {
|
fieldset class="fieldset" {
|
||||||
@@ -132,7 +130,7 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(layouts::desktop(content, "New Exercise"))
|
Ok(layouts::desktop(content, "New Workout", true))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@@ -224,8 +222,6 @@ async fn submit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("{:?}", exercises);
|
|
||||||
|
|
||||||
let (workout_ids, exercise_ids, exercise_types, positions, sets, reps, times): (
|
let (workout_ids, exercise_ids, exercise_types, positions, sets, reps, times): (
|
||||||
Vec<_>,
|
Vec<_>,
|
||||||
Vec<_>,
|
Vec<_>,
|
||||||
|
12
tailwind.css
12
tailwind.css
@@ -39,14 +39,14 @@
|
|||||||
--noise: 0;
|
--noise: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
|
||||||
@apply text-3xl
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@apply text-2xl
|
@apply text-xl
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@apply text-xl
|
@apply text-lg
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
@apply opacity-80
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user