diff options
Diffstat (limited to 'src/cashflow/frontend')
| -rw-r--r-- | src/cashflow/frontend/navigation.clj | 13 | ||||
| -rw-r--r-- | src/cashflow/frontend/transactions/one_time.clj | 139 | ||||
| -rw-r--r-- | src/cashflow/frontend/transactions/recurring.clj | 90 | ||||
| -rw-r--r-- | src/cashflow/frontend/utils.clj | 32 |
4 files changed, 274 insertions, 0 deletions
diff --git a/src/cashflow/frontend/navigation.clj b/src/cashflow/frontend/navigation.clj new file mode 100644 index 0000000..4c04bfc --- /dev/null +++ b/src/cashflow/frontend/navigation.clj @@ -0,0 +1,13 @@ +(ns cashflow.frontend.navigation) + +(defn gen [current-page] + [:nav {:class "tabbed"} + [:a {:class (when (= current-page :one-time) "active") + :_ "on click go to url /transactions/one-time/"} + [:i "attach_money"] + [:span "einmalige"]] + [:a {:class (when (= current-page :recurring) "active") + :_ "on click go to url /transactions/recurring/"} + [:i "calendar_month"] + [:span "wiederkehrende"]]]) + diff --git a/src/cashflow/frontend/transactions/one_time.clj b/src/cashflow/frontend/transactions/one_time.clj new file mode 100644 index 0000000..d658dad --- /dev/null +++ b/src/cashflow/frontend/transactions/one_time.clj @@ -0,0 +1,139 @@ +(ns cashflow.frontend.transactions.one-time + (:require [ring.util.response :as ruresp] + [cashflow.utils :as cutils] + [cashflow.frontend.utils :as cfutils] + [cashflow.database :as cdatabase] + [clojure.string :as cstr] + [dotenv :as env] + [cashflow.frontend.navigation :as cfnavigation]) + (:import java.lang.Integer + java.lang.Double + java.time.LocalDate)) + +(defn- gen-table [year month] + [:div {:class ["scroll" "surface"] :id "transaction-table" :style {:height "30em"}} + [:table {:class ["stripes" "border" "large-space"]} + [:thead {:class "fixed"} + [:tr + [:th "Datum"] + [:th "Beschreibung"] + [:th "Betrag"] + [:th]]] + [:tbody + [:tr + [:td] + [:td "wiederkehrende Umsätze"] + [:td (cfutils/render-amount (cdatabase/recurring-transactions-applying-total year month))]] + (for [transaction (cdatabase/list-one-time-transactions year month)] + [:tr + [:td (cstr/join "." (-> transaction :from (cstr/split #"-") reverse))] + [:td (:description transaction)] + [:td (cfutils/render-amount (:amount transaction))] + [:td [:button {:class ["transparent" "circle"] + :hx-delete (str "/transactions/one-time/" (:db/id transaction) "/") + :hx-target "#transaction-table" + :hx-swap "outerHTML"} + [:i "delete"]]]])] + [:tfoot {:class "fixed"} + [:tr + [:th {:scope "row" :colspan "2"} "Gesamt: "] + [:td (-> (cdatabase/one-time-transactions-total year month) cfutils/render-amount)]]]]]) + +(defn- gen-month-switcher [year month] + (let [actual-date (LocalDate/now)] + [:div {:style {:display :flex} + :_ "on change go to url `/transactions/one-time/${#month-switcher-year.value}/${#month-switcher-month.value}/`"} + [:p {:style {:margin-top :auto + :margin-right "1em" + :font-weight :bold}} "Monat: "] + [:div {:class ["field" "border" "small"]} + [:input {:id "month-switcher-month" + :type :number :value month}]] + + [:p {:style {:margin-top :auto + :font-weight :bold + :margin-left "1em" + :margin-right "1em"}} "Jahr: "] + [:div {:class ["field" "border" "small"]} + [:input {:id "month-switcher-year" + :type :number :value year}]] + (if (or (not= (.getMonthValue actual-date) month) + (not= (.getYear actual-date) year)) + [:button {:style {:margin-top :auto} + :_ "on click go to url /transactions/one-time/"} + [:i "today"] + [:span "Springe zum aktuellen Monat"]])])) + +(defn- gen [req] + (let [year-param (-> req + (get-in [:path-params :year]) + (cutils/string-min-length 2 "0" :before)) + month-param (-> req + (get-in [:path-params :month]) + (cutils/string-min-length 2 "0" :before)) + default-day (or (try (LocalDate/parse (str year-param "-" month-param "-01")) + (catch Exception _ nil)) + (LocalDate/now)) + year (.getYear default-day) + month (.getMonthValue default-day)] + (list [:h1 "cashflow - Umsätze"] + (cfnavigation/gen :one-time) + (gen-month-switcher year month) + (gen-table year month) + [:article {:style {:width :fit-content :margin "3em auto"}} + [:form {:class "add-transaction" :hx-post "/transactions/one-time/" :hx-target "#transaction-table" :hx-swap "outerHTML"} + [:div {:class ["field" "label" "round" "border"] :style {:width :fit-content}} + [:input {:type :date :placeholder " " :name "date" :value (str default-day)}] + [:label "Datum"]] + [:div {:class ["field" "label" "round" "border"] :style {:width :fit-content}} + [:input {:type :text :placeholder " " :name "description"}] + [:label "Beschreibung"]] + [:div {:class ["field" "label" "round" "border"] :style {:width :fit-content}} + [:input {:type :number :placeholder " " :step "0.01" :name "amount"}] + [:label "Betrag"]] + [:button {:class ["small-round" "fill"] :style {:margin-top "1em"} :type :submit} "Hinzufügen"]]]))) + +(defn handle-get [req] + (->> req + gen + (cfutils/render-page "cashflow - eimalige Umsätze") + ruresp/response)) + +(defn- table-response [year month] + (-> (gen-table year month) + cfutils/render-component + ruresp/response)) + +(defn handle-post [req] + (let [params (:form-params req) + description (get params "description") + amount (-> params + (get "amount") + Double/parseDouble) + date (get params "date") + split-date (cstr/split date #"-") + year (-> split-date + first + Integer/parseInt) + month (-> split-date + second + Integer/parseInt) + day (-> split-date + last + Integer/parseInt)] + (cdatabase/create-transaction! {:type :month + :description description + :amount amount + :year year + :month month + :day day}) + (table-response year month))) + +(defn handle-delete [req] + (let [id (-> req + (get-in [:path-params :id]) + Integer/parseInt) + transaction (cdatabase/id->transaction id)] + (cdatabase/delete-transaction! id) + (table-response (:year transaction) (:month transaction)))) + diff --git a/src/cashflow/frontend/transactions/recurring.clj b/src/cashflow/frontend/transactions/recurring.clj new file mode 100644 index 0000000..2d85bf8 --- /dev/null +++ b/src/cashflow/frontend/transactions/recurring.clj @@ -0,0 +1,90 @@ +(ns cashflow.frontend.transactions.recurring + (:require [cashflow.frontend.utils :as cfutils] + [cashflow.database :as cdatabase] + [ring.util.response :as ruresp] + [clojure.string :as cstr] + [cashflow.frontend.navigation :as cfnavigation]) + (:import java.time.LocalDate)) + +(defn- gen-table [] + [:div {:class ["scroll" "surface"] :id "transaction-table" :style {:height "30em"}} + [:table {:class ["stripes" "border" "large-space"]} + [:thead {:class "fixed"} + [:tr + [:th "Beschreibung"] + [:th "von"] + [:th "bis"] + [:th "Betrag"] + [:th "Interval"] + [:th]]] + [:tbody + (for [transaction (cdatabase/list-recurring-transactions)] + [:tr + [:td (:description transaction)] + [:td (cstr/join "." (-> transaction :from (cstr/split #"-") reverse))] + [:td (cstr/join "." (-> transaction :to (cstr/split #"-") reverse))] + [:td (cfutils/render-amount (:amount transaction))] + [:td (:month-interval transaction) " Monate"] + [:td [:button {:class ["transparent" "circle"] + :hx-delete (str "/transactions/recurring/" (:db/id transaction) "/") + :hx-target "#transaction-table" + :hx-swap "outerHTML"} + [:i "delete"]]]])]]]) + +(defn- gen [_req] + (list [:h1 "cashflow - Umsätze"] + (cfnavigation/gen :recurring) + (gen-table) + [:article {:style {:width :fit-content :margin "3em auto"}} + [:form {:class "add-transaction" :hx-post "/transactions/recurring/" :hx-target "#transaction-table" :hx-swap "outerHTML"} + [:div {:class ["field" "label" "round" "border"] :style {:width :fit-content}} + [:input {:type :text :placeholder " " :name "description"}] + [:label "Beschreibung"]] + [:div {:class ["field" "label" "round" "border"] :style {:width :fit-content}} + [:input {:type :date :placeholder " " :name "from"}] + [:label "von"]] + [:div {:class ["field" "label" "round" "border"] :style {:width :fit-content}} + [:input {:type :date :placeholder " " :name "to"}] + [:label "bis"]] + [:div {:class ["field" "label" "round" "border"] :style {:width :fit-content}} + [:input {:type :number :placeholder " " :step "0.01" :name "amount"}] + [:label "Betrag"]] + [:div {:class ["field" "label" "round" "border"] :style {:width :fit-content}} + [:input {:type :number :placeholder " " :name "month-interval"}] + [:label "Interval (monate)"]] + [:button {:class ["small-round" "fill"] :style {:margin-top "1em"} :type :submit} "Hinzufügen"]]])) + +(defn handle-get [req] + (->> req + gen + (cfutils/render-page "cashflow - wiederkehrende Umsätze") + ruresp/response)) + +(defn- table-response [] + (-> (gen-table) + cfutils/render-component + ruresp/response)) + +(defn handle-post [req] + (let [params (:form-params req) + description (get params "description") + from (get params "from") + to (get params "to") + amount (-> params (get "amount") Double/parseDouble) + month-interval (-> params (get "month-interval") Integer/parseInt bigint)] + (cdatabase/create-transaction! {:type :recurring + :description description + :amount amount + :from from + :to to + :month-interval month-interval}) + (table-response))) + +(defn handle-delete [req] + (let [id (-> req + (get-in [:path-params :id]) + Integer/parseInt) + transaction (cdatabase/id->transaction id)] + (cdatabase/delete-transaction! id) + (table-response))) + diff --git a/src/cashflow/frontend/utils.clj b/src/cashflow/frontend/utils.clj new file mode 100644 index 0000000..25091a6 --- /dev/null +++ b/src/cashflow/frontend/utils.clj @@ -0,0 +1,32 @@ +(ns cashflow.frontend.utils + (:require [hiccup2.core :as html] + [dotenv :as env])) + +(defn render-component [component] + (-> component + html/html + str)) + +(defn render-page [title & body] + (-> [:html + [:head + [:meta {:name "viewport" :content "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"}] + [:meta {:charset "UTF-8"}] + [:title title] + [:link {:rel "stylesheet" :href "/static/style.css"}] + [:link {:rel "stylesheet" :href "/static/beer.css"}] + [:meta {:name "robots" :content "noindex,nofollow"}]] + (apply conj [:body] body [[:script {:src "/static/beer.js"}] + [:script {:src "/static/htmx.js"}] + [:script {:src "/static/hyperscript.js"}]])] + render-component)) + +(defn render-amount [amount] + [:p {:style {:color (cond + (pos? amount) :limegreen + (neg? amount) :red + :else :orange)}} + (str (when (pos? amount) "+") + amount + (or (env/env "CONCURRENCY") "€"))]) + |
