diff --git a/src/com/spicy/movements/core.clj b/src/com/spicy/movements/core.clj index f6bbeaf..b0c1f2d 100644 --- a/src/com/spicy/movements/core.clj +++ b/src/com/spicy/movements/core.clj @@ -3,10 +3,9 @@ [clojure.instant :as instant] [clojure.string :as string] [com.biffweb :as biff] + [com.spicy.movements.ui :refer [movement-form strength-set-inputs]] [com.spicy.numbers :refer [parse-int safe-parse-int]] - [com.spicy.route-helpers :refer [wildcard-override]] - [com.spicy.route-helpers :refer [->key htmx-request?]] - [com.spicy.movements.ui :refer [movement-form]] + [com.spicy.route-helpers :refer [->key htmx-request? wildcard-override]] [com.spicy.ui :as ui] [xtdb.api :as xt])) @@ -37,6 +36,8 @@ (defn movement-workout-ui [w] (ui/workout-ui w)) + + (defn index [{:keys [biff/db params] :as ctx}] (let [movement-type (or (keyword (:type params)) :strength) @@ -52,7 +53,7 @@ [:div.w-full.mt-8.flex.flex-wrap.justify-center.items-center.sm:justify-between.mb-4 [:h1.text-5xl.w-fit.self-center.mb-4.md:mb-0 "Movements"] [:a {:class (str "btn bg-brand-teal w-full sm:w-fit self-center") - :href (str "/app/movements/new")} "Add Movement"]] + :href (str "/app/movements/new")} "Add Movement"]] [:div.flex.flex-col.sm:flex-row.justify-end.gap-4 [:select.btn.text-base.w-full.md:w-96.h-12.teal-focus.hover:cursor-pointer {:name "type" :onchange "window.open('?type=' + this.value,'_self')"} @@ -75,15 +76,16 @@ {:id "search-results"} (movements-list movements)]]))))) + (defn new - [ctx] + [ctx] (ui/page ctx [:div.max-w-md.mx-auto (ui/panel - [:div.p-4 - [:h1.text-5xl.mb-14.pt-8.text-center "New Movement"] - (movement-form {}) - ])])) + [:div.p-4 + [:h1.text-5xl.mb-14.pt-8.text-center "New Movement"] + (movement-form {})])])) + (defn create [{:keys [params] :as ctx}] @@ -92,8 +94,7 @@ :db/doc-type :movement :movement/name (:name params) :movement/type (keyword (:type params)) - :xt/id movement-uuid}] - ] + :xt/id movement-uuid}]] (biff/submit-tx ctx new-movement) {:status 303 :headers {"location" (str "/app/movements/" movement-uuid)}})) @@ -277,39 +278,11 @@ (map movement-workout-ui workouts)]])])))) -(defn strength-set-inputs - [{:keys [set-number reps]}] - [:div - [:p.text-2xl.font-medium.text-center.mt-4.whitespace-nowrap (str "Set #" set-number)] - [:input {:value reps - :type "hidden" - :id (str "reps-" set-number) - :name (str "reps-" set-number)}] - [:.flex.justify-center.items-center.m-0 - [:fieldset - [:.flex.gap-2 - [:label.text-lg.sm:text-2xl.font-medium.text-center.h-fit.my-auto - {:for (str "hit-" set-number)} - "Hit"] - [:input {:name (str "hit-miss-" set-number) - :id (str "hit-" set-number) - :class (str "appearance-none p-7 border-2 border-r-0 border-black cursor-pointer " - "checked:bg-brand-teal checked:text-brand-teal checked:color-brand-teal hover:bg-brand-teal checked:border-black checked:ring-0 checked:ring-offset-0 checked:ring-brand-teal checked:ring-opacity-100 focus:outline-none focus:ring-0 focus:ring-offset-0 focus:ring-opacity-100") - :value :hit - :type :checkbox}]]] - [:input {:name (str "weight-" set-number) - :id (str "weight-" set-number) - :class (str "p-4 border-2 border-black w-1/2 text-center font-bold teal-focus ") - :required true - :type :number}] - [:p.m-0.bg-white.p-4.border-2.border-l-0.border-black.font-medium.whitespace-nowrap (str "x " reps " reps")]]]) - - (defn get-constant-strength-sets [{:keys [n] :as opts}] (let [count (atom 0)] (repeatedly n - #(strength-set-inputs (merge {:set-number (swap! count inc)} opts))))) + #(strength-set-inputs (merge {:number (swap! count inc)} opts))))) (defn get-variable-strength-sets @@ -320,7 +293,7 @@ (if (<= sets set-n) result (recur (inc set-n) - (conj result (strength-set-inputs {:set-number (inc set-n) :reps (nth reps set-n)})))))) + (conj result (strength-set-inputs {:number (inc set-n) :reps (nth reps set-n)})))))) (defn params->reps @@ -338,30 +311,30 @@ sets (safe-parse-int (:sets params)) type (:type params) strength-id (random-uuid)] - (biff/form {:hidden {:user (:uid session) - :movement (:id path-params) - :strength strength-id - :reps reps - :sets sets} - :method "POST" - :action (str "/app/movements/" (:id path-params) "/set")} - (when (= type "constant") - [:div (get-constant-strength-sets {:n sets :reps reps})]) - (when (= type "variable") - [:div (get-variable-strength-sets {:sets sets - :reps reps})]) - [:div.flex.flex-col.justify-center.items-center.gap-4 - [:input.pink-input.teal-focus.mt-4.mx-auto - {:type "date" - :name "date" - :value (biff/format-date - (biff/now) "YYYY-MM-dd")}] - [:textarea#notes - {:name "notes" - :placeholder "notes" - :rows 7 - :class (str "w-full pink-input teal-focus")}] - [:button.btn "Submit"]]))) + (biff/form {:hidden {:user (:uid session) + :movement (:id path-params) + :strength strength-id + :reps reps + :sets sets} + :method "POST" + :action (str "/app/movements/" (:id path-params) "/set")} + (when (= type "constant") + [:div (get-constant-strength-sets {:n sets :reps reps})]) + (when (= type "variable") + [:div (get-variable-strength-sets {:sets sets + :reps reps})]) + [:div.flex.flex-col.justify-center.items-center.gap-4 + [:input.pink-input.teal-focus.mt-4.mx-auto + {:type "date" + :name "date" + :value (biff/format-date + (biff/now) "YYYY-MM-dd")}] + [:textarea#notes + {:name "notes" + :placeholder "notes" + :rows 7 + :class (str "w-full pink-input teal-focus")}] + [:button.btn "Submit"]]))) (defn variable-reps-form @@ -527,17 +500,30 @@ (defn update-result-set [{:keys [path-params params] :as ctx}] - (let [tx (->update-tx params)] - (biff/submit-tx ctx tx) + (let [result-tx (if (:date params) + [{:xt/id (parse-uuid (:result-id path-params)) + :db/doc-type :result + :db/op :update + :result/date (instant/read-instant-date (:date params))}] + []) + type-tx (if (and (:notes params) (:strength-result-id params)) + [{:xt/id (parse-uuid (:strength-result-id params)) + :db/doc-type :strength-result + :db/op :update + :result/notes (:notes params)}] + []) + sets-tx (->update-tx params) + location (or (:location params) (str "/app/movements/" (:id path-params) "/results/" (:result-id path-params)))] + (biff/submit-tx ctx (concat result-tx type-tx sets-tx)) {:status 303 - :headers {"Location" (str "/app/movements/" (:id path-params) "/results/" (:result-id path-params))}})) + :headers {"Location" location}})) (def routes ["/movements" ["/" {:get index - :post create}] - ["/:id" + :post create}] + ["/:id" ["" {:get (wildcard-override show {:search search :new new})}] ["/constant-reps" {:get constant-reps-form}] diff --git a/src/com/spicy/movements/ui.clj b/src/com/spicy/movements/ui.clj index c219d3b..2c39274 100644 --- a/src/com/spicy/movements/ui.clj +++ b/src/com/spicy/movements/ui.clj @@ -1,34 +1,70 @@ (ns com.spicy.movements.ui (:require - [com.biffweb :as biff])) + [com.biffweb :as biff])) + (defn movement-form [{:keys [movement]}] (biff/form - (merge {:class "flex flex-col gap-4" - :hx-target :body} - (if (nil? movement) - {:hx-post "/app/movements"} - {:hx-put (str "/app/movements/" (:xt/id movement))})) - [:div.flex.flex-col.w-full - [:label {:for :name} "Title"] - [:input#name - {:placeholder "Name" - :name "name" - :class (str "pink-input p-2 outline-none focus-teal") - :required true - :value (:movement/name movement)}]] - [:div.flex.flex-col.w-full - [:label {:for :type} "Type"] - [:select.pink-input.teal-focus#type - {:name "type" - :required true - :value (when (not (nil? movement)) (name (:movement/type movement)))} - [:option {:value "" :label "--Select a Movement type--"}] - [:option {:value "strength" - :label "Strength"}] - [:option {:value "gymnastic" - :label "Gymnastic"}] - [:option {:value "monostructural" - :label "Monostructural"}]]] - [:button.btn.bg-brand-teal {:type "submit"} (if (nil? movement) "Create movement" "Update movement")])) \ No newline at end of file + (merge {:class "flex flex-col gap-4" + :hx-target :body} + (if (nil? movement) + {:hx-post "/app/movements"} + {:hx-put (str "/app/movements/" (:xt/id movement))})) + [:div.flex.flex-col.w-full + [:label {:for :name} "Title"] + [:input#name + {:placeholder "Name" + :name "name" + :class (str "pink-input p-2 outline-none focus-teal") + :required true + :value (:movement/name movement)}]] + [:div.flex.flex-col.w-full + [:label {:for :type} "Type"] + [:select.pink-input.teal-focus#type + {:name "type" + :required true + :value (when (not (nil? movement)) (name (:movement/type movement)))} + [:option {:value "" :label "--Select a Movement type--"}] + [:option {:value "strength" + :label "Strength"}] + [:option {:value "gymnastic" + :label "Gymnastic"}] + [:option {:value "monostructural" + :label "Monostructural"}]]] + [:button.btn.bg-brand-teal {:type "submit"} (if (nil? movement) "Create movement" "Update movement")])) + + +(defn strength-set-inputs + [{:keys [number reps weight status id]}] + [:div + [:p.text-2xl.font-medium.text-center.mt-4.whitespace-nowrap (str "Set #" number)] + [:input {:value reps + :type "hidden" + :id (str "reps-" number) + :name (str "reps-" number)}] + [:.flex.justify-center.items-center.m-0 + [:fieldset + [:.flex.gap-2 + [:label.text-lg.sm:text-2xl.font-medium.text-center.h-fit.my-auto + {:for (str "hit-" number)} + "Hit"] + (when id + [:input {:name (str "id-" number) + :id (str "id-" number) + :type :hidden + :value id}]) + [:input {:name (str "hit-miss-" number) + :id (str "hit-" number) + :class (str "appearance-none p-7 border-2 border-r-0 border-black cursor-pointer " + "checked:bg-brand-teal checked:text-brand-teal checked:color-brand-teal hover:bg-brand-teal checked:border-black checked:ring-0 checked:ring-offset-0 checked:ring-brand-teal checked:ring-opacity-100 focus:outline-none focus:ring-0 focus:ring-offset-0 focus:ring-opacity-100") + :value :hit + :checked (= :pass status) + :type :checkbox}]]] + [:input {:name (str "weight-" number) + :id (str "weight-" number) + :class (str "p-4 border-2 border-black w-1/2 text-center font-bold teal-focus ") + :required true + :value weight + :type :number}] + [:p.m-0.bg-white.p-4.border-2.border-l-0.border-black.font-medium.whitespace-nowrap (str "x " reps " reps")]]]) diff --git a/src/com/spicy/results/core.clj b/src/com/spicy/results/core.clj index a4c0aad..1012f3b 100644 --- a/src/com/spicy/results/core.clj +++ b/src/com/spicy/results/core.clj @@ -3,7 +3,8 @@ [clojure.instant :as instant] [com.biffweb :as biff] [com.spicy.middleware :as mid] - [com.spicy.results.score :refer [scores->tx ->scores params->score]] + [com.spicy.movements.ui :refer [strength-set-inputs]] + [com.spicy.results.score :refer [scores->tx ->scores]] [com.spicy.results.ui :refer [result-ui result-form normalized-result]] [com.spicy.route-helpers :refer [wildcard-override]] [com.spicy.ui :as ui] @@ -57,6 +58,11 @@ :headers {"Location" (str "/app/results/" (:id path-params))}})) +(defn remove-keys-ns + [m] + (update-keys m (comp keyword name))) + + (defn edit [{:keys [biff/db session path-params] :as _ctx}] (let [{:result/keys [type] :as result} @@ -69,20 +75,45 @@ :where [[result :xt/id result-id] [result :result/user user]]} [(parse-uuid (:id path-params)) (:uid session)])) - result-path (str "/app/results/" (:xt/id result))] + result-path (str "/app/results/" (:xt/id result)) + workout? (seq (:result/workout type)) + movement? (seq (:result/movement type))] [:div#edit-result [:h2.text-2xl.font-bold (:name (normalized-result result))] - (result-form - (assoc {} - :result result - :workout (:result/workout type) - :action result-path - :hx-key :hx-put - :form-props {:hx-target "closest #edit-ui" - :hx-swap "outerHTML"}) - [:button.btn {:type "submit"} "Update Result"] - [:button.btn.bg-red-400.hover:bg-red-600 {:hx-get result-path - :hx-target "closest #edit-result"} "Cancel"])])) + (when movement? + (biff/form {:hidden {:sets (count (-> result :result/type :result-set/_parent)) + :strength-result-id (:xt/id type) + :location result-path} + :hx-put (str "/app/movements/" (-> result :result/type :result/movement :xt/id) "/results/" (:xt/id result)) + :hx-target "closest #edit-result"} + (map (comp strength-set-inputs remove-keys-ns) (sort-by :result-set/number (:result-set/_parent type))) + [:div.flex.flex-col.justify-center.items-center.gap-4 + [:input.pink-input.teal-focus.mt-4.mx-auto + {:type "date" + :name "date" + :value (biff/format-date + (or (:result/date result) (biff/now)) "YYYY-MM-dd")}] + [:textarea#notes + {:name "notes" + :placeholder "notes" + :value (:result/notes type) + :rows 7 + :class (str "w-full pink-input teal-focus")}] + [:button.btn.w-full {:type "submit"} "Update Result"] + [:button.btn.bg-red-400.hover:bg-red-600.w-full {:hx-get result-path + :hx-target "closest #edit-result"} "Cancel"]])) + (when workout? + (result-form + (assoc {} + :result result + :workout (:result/workout type) + :action result-path + :hx-key :hx-put + :form-props {:hx-target "closest #edit-result" + :hx-swap "outerHTML"}) + [:button.btn {:type "submit"} "Update Result"] + [:button.btn.bg-red-400.hover:bg-red-600 {:hx-get result-path + :hx-target "closest #edit-result"} "Cancel"]))])) (defn index diff --git a/src/com/spicy/results/ui.clj b/src/com/spicy/results/ui.clj index 5bf6191..0e2c4a0 100644 --- a/src/com/spicy/results/ui.clj +++ b/src/com/spicy/results/ui.clj @@ -2,7 +2,7 @@ (:require [clojure.string :as string] [com.biffweb :as biff] - [com.spicy.movements.core :refer [movement-results-ui]] + [com.spicy.movements.core :refer [sets-n-reps]] [com.spicy.workouts.ui :refer [display-summed-score]])) @@ -12,52 +12,70 @@ movement (-> result :result/type :result/movement) sets (-> result :result/type :result-set/_parent) notes (-> result :result/type :result/notes) - description (-> workout :workout/description) - name (or (:movement/name movement) - (:workout/name workout))] + description (if (seq workout) + (-> workout :workout/description) + (str (sets-n-reps sets))) + score (if (seq workout) + (display-summed-score {:workout workout + :sets sets}) + (let [best-set (first + (sort-by :result-set/weight > + (filter + #(= :pass (:result-set/status %)) sets)))] + (str (:result-set/reps best-set) "x" (:result-set/weight best-set)))) + name (or (some-> movement + :movement/name + string/capitalize) + (:workout/name workout)) + href (if (:workout/name workout) + (str "/app/workouts/" (:xt/id workout)) + (str "/app/movements/" (:xt/id movement)))] {:workout workout :movement movement :sets sets + :href href :name name :description description :notes notes + :score score :date (biff/format-date (:result/date result) "YYYY-MM-dd")})) +(defn card + [{:keys [name href date description notes score]} & children] + [:div.flex.flex-col.max-w-sm.sm:max-w-xl.mx-auto#result-ui + [:.flex.flex-row.items-baseline.justify-between.mb-3 + [:a.text-2xl.font-bold {:href href} name] + [:p.whitespace-pre-wrap.sm:text-left.max-w-xs.text-gray-700.mb-0 date]] + [:.flex.justify-between + [:div.flex.flex-col.gap-2 + [:div.flex.justify-between + [:p.hidden.sm:block.whitespace-pre-wrap.sm:text-left.max-w-xs.text-gray-700.italic description] + [:div.ml-1.flex.flex-col.self-center.sm:hidden + (when (some? score) + [:span.text-xl + score]) + (when (some? notes) + [:span + notes])]]] + [:div.flex.gap-4.h-fit.self-center.flex-col.justify-between + [:div.hidden.sm:flex.sm:flex-col.ml-1.self-center.text-right + (when (some? score) + [:span.text-xl + score]) + (when (some? notes) + [:span + notes])] + children]]]) + + (defn result-ui [result] - (let [{:keys [workout movement description sets name notes date]} (normalized-result result)] - [:div.flex.flex-col.max-w-sm.sm:max-w-xl.mx-auto#result-ui - [:.flex.flex-row.items-baseline.justify-between.mb-3 - [:a.text-2xl.font-bold {:href (str "/app/workouts/" (:xt/id workout))} name] - [:p.whitespace-pre-wrap.sm:text-left.max-w-xs.text-gray-700.mb-0 date]] - [:.flex.justify-between - [:div.flex.flex-col.gap-2 - [:div.flex.justify-between - [:p.hidden.sm:block.whitespace-pre-wrap.sm:text-left.max-w-xs.text-gray-700.italic description] - [:div.ml-1.flex.flex-col.self-center.sm:hidden - (when (some? workout) - [:span.text-xl - (display-summed-score {:workout workout - :sets sets})]) - (when (some? notes) - [:span - notes])]]] - (when (some? movement) - (movement-results-ui result)) - [:div.flex.gap-4.h-fit.self-center.flex-col.justify-between - [:div.hidden.sm:flex.sm:flex-col.ml-1.self-center.text-right - (when (some? workout) - [:span.text-xl - (display-summed-score {:workout workout - :sets sets})]) - (when (some? notes) - [:span - notes])] - [:button {:hx-get (str "/app/results/" (:xt/id result) "/edit") - :hx-target "closest #result-ui" - :hx-swap "outerHTML" - :class (str "self-end btn-no-shadow bg-white text-sm px-4 py-2 font-normal ")} "edit"]]]])) + (card (normalized-result result) + [:button {:hx-get (str "/app/results/" (:xt/id result) "/edit") + :hx-target "closest #result-ui" + :hx-swap "outerHTML" + :class (str "self-end btn-no-shadow bg-white text-sm px-4 py-2 font-normal ")} "edit"])) (defn scheme-forms @@ -152,7 +170,7 @@ {:type "date" :name "date" :value (biff/format-date - (or (:result/date workout-result) (biff/now)) "YYYY-MM-dd")}] + (or (:result/date result) (biff/now)) "YYYY-MM-dd")}] [:div.flex.gap-2.items-center [:div.flex-1.flex.gap-2.items-center [:input#rx {:type "radio" diff --git a/src/com/spicy/workouts/core.clj b/src/com/spicy/workouts/core.clj index d525144..29a112e 100644 --- a/src/com/spicy/workouts/core.clj +++ b/src/com/spicy/workouts/core.clj @@ -31,7 +31,6 @@ :where where-clause :order-by '[[name]]} [(string/lower-case (or (:search params) "")) (:uid session)]))] - (tap> workouts) (workouts-list {:workouts workouts :id SEARCH_ID})))