added workout viewing simple view
This commit is contained in:
parent
d1029485d4
commit
5c0bb602f5
@ -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;
|
||||
}
|
||||
|
@ -20,3 +20,15 @@ pub struct WorkoutExercise {
|
||||
pub reps: 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,
|
||||
}
|
||||
|
@ -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<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(page))
|
||||
.nest("/new", new::routes())
|
||||
// .nest("/{id}", id::routes())
|
||||
.nest("/{id}", id::routes())
|
||||
}
|
||||
|
||||
async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
||||
@ -22,15 +23,15 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
||||
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<AppState>) -> Result<Markup, AppError> {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(layouts::desktop(content, "Exercises"))
|
||||
Ok(layouts::desktop(content, "Workouts"))
|
||||
}
|
||||
|
147
src/pages/workouts/id.rs
Normal file
147
src/pages/workouts/id.rs
Normal file
@ -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<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="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<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"))
|
||||
}
|
||||
|
||||
#[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! {}))
|
||||
}
|
@ -224,8 +224,6 @@ async fn submit(
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("{:?}", exercises);
|
||||
|
||||
let (workout_ids, exercise_ids, exercise_types, positions, sets, reps, times): (
|
||||
Vec<_>,
|
||||
Vec<_>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user