added workout viewing simple view
This commit is contained in:
@@ -1062,6 +1062,9 @@
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
.flex-wrap {
|
.flex-wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
@@ -1082,6 +1085,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;
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
|
}
|
||||||
|
@@ -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> {
|
||||||
@@ -22,15 +23,15 @@ async fn page(State(state): State<AppState>) -> Result<Markup, AppError> {
|
|||||||
let content = html! {
|
let content = html! {
|
||||||
h1 { "Workouts" }
|
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 +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): (
|
let (workout_ids, exercise_ids, exercise_types, positions, sets, reps, times): (
|
||||||
Vec<_>,
|
Vec<_>,
|
||||||
Vec<_>,
|
Vec<_>,
|
||||||
|
Reference in New Issue
Block a user