diff --git a/assets/css/main.css b/assets/css/main.css index 477b0ec..f9d5fd3 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -1062,6 +1062,9 @@ outline-offset: 2px; } } + .flex-col { + flex-direction: column; + } .flex-wrap { flex-wrap: wrap; } @@ -1082,6 +1085,9 @@ 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; } diff --git a/src/models/workouts.rs b/src/models/workouts.rs index 715073b..c4cf82a 100644 --- a/src/models/workouts.rs +++ b/src/models/workouts.rs @@ -20,3 +20,15 @@ pub struct WorkoutExercise { pub reps: Option, pub time: Option, } + +#[derive(Debug, FromRow)] +pub struct WorkoutExerciseFull { + pub exercise_id: Uuid, + pub exercise_type: ExerciseVariant, + pub position: i32, + pub sets: Option, + pub reps: Option, + pub time: Option, + pub name: String, + pub description: String, +} diff --git a/src/pages/workouts.rs b/src/pages/workouts.rs index f01d54f..0315eba 100644 --- a/src/pages/workouts.rs +++ b/src/pages/workouts.rs @@ -2,13 +2,14 @@ use crate::{AppError, icons, layouts, util::AppState}; use axum::{Router, extract::State, routing::get}; use maud::{Markup, html}; +mod id; mod new; pub fn routes() -> Router { Router::new() .route("/", get(page)) .nest("/new", new::routes()) - // .nest("/{id}", id::routes()) + .nest("/{id}", id::routes()) } async fn page(State(state): State) -> Result { @@ -22,15 +23,15 @@ async fn page(State(state): State) -> Result { let content = html! { h1 { "Workouts" } a href="/workouts/new" { "new workout +" } - ul class="list" { + div class="list" { @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 class="font-bold" { (workout.name) } 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]" { (icons::eye()) } @@ -40,5 +41,5 @@ async fn page(State(state): State) -> Result { } }; - Ok(layouts::desktop(content, "Exercises")) + Ok(layouts::desktop(content, "Workouts")) } diff --git a/src/pages/workouts/id.rs b/src/pages/workouts/id.rs new file mode 100644 index 0000000..cd0b96d --- /dev/null +++ b/src/pages/workouts/id.rs @@ -0,0 +1,147 @@ +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 { + Router::new() + .route("/", get(page).delete(delete).put(submit_edit)) + .route("/edit", get(edit)) +} + +async fn page(State(state): State, Path(id): Path) -> Result { + 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="flex w-full" { + h1 { (workout.name) } + a href={"/workouts/" (workout.workout_id) "/edit" } class="ml-auto btn" { "Edit" } + } + p { (workout.description) } + + h2 { "Exercises" } + div class="flex flex-col gap-y-3" { + @for exercise in exercises { + div class="bg-base-200 p-3 rounded" { + h3 { (exercise.name) } + p class="text-sm opacity-50" { (exercise.description) } + } + } + } + } + }; + + Ok(layouts::desktop(content, "Exercises")) +} + +async fn delete( + State(state): State, + Path(id): Path, +) -> Result { + 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, + Path(id): Path, +) -> Result { + 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")) +} + +#[derive(Deserialize, Debug)] +struct FormData { + name: String, + description: String, +} + +async fn submit_edit( + State(state): State, + Path(id): Path, + Form(form): Form, +) -> Result { + 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! {})) +} diff --git a/src/pages/workouts/new.rs b/src/pages/workouts/new.rs index 3ce12d7..bd07fd9 100644 --- a/src/pages/workouts/new.rs +++ b/src/pages/workouts/new.rs @@ -224,8 +224,6 @@ async fn submit( } } - tracing::info!("{:?}", exercises); - let (workout_ids, exercise_ids, exercise_types, positions, sets, reps, times): ( Vec<_>, Vec<_>,