diff --git a/src/clj/auto_ap/datomic.clj b/src/clj/auto_ap/datomic.clj index 30da1c3d..5a007006 100644 --- a/src/clj/auto_ap/datomic.clj +++ b/src/clj/auto_ap/datomic.clj @@ -579,12 +579,14 @@ (defn add-sorter-fields [q sort-map args] (reduce - (fn [q {:keys [sort-key]}] + (fn [q {:keys [sort-key] :as z}] + (prn z) + (println (class sort-key)) (merge-query q {:query {:find [(symbol (str "?sort-" sort-key))] :where (sort-map sort-key - (println "Warning, trying to sort by unsupported field" sort-key))}})) + (println "Warning, trying to sort by unsupported field" sort-key, "sort map" (pr-str sort-map)))}})) q (:sort args))) diff --git a/src/clj/auto_ap/datomic/reports.clj b/src/clj/auto_ap/datomic/reports.clj index 66ce0cf2..777b4d87 100644 --- a/src/clj/auto_ap/datomic/reports.clj +++ b/src/clj/auto_ap/datomic/reports.clj @@ -59,6 +59,7 @@ (map :db/id (:report/client r)))))))) (defn get-graphql [args] + (clojure.pprint/pprint args) (let [db (dc/db conn) {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)] diff --git a/src/clj/auto_ap/ssr/company/reports.clj b/src/clj/auto_ap/ssr/company/reports.clj index 9c0a93e4..1562dfd9 100644 --- a/src/clj/auto_ap/ssr/company/reports.clj +++ b/src/clj/auto_ap/ssr/company/reports.clj @@ -3,120 +3,81 @@ [amazonica.aws.s3 :as s3] [auto-ap.datomic :refer [conn]] [auto-ap.datomic.reports :as r] - [auto-ap.graphql.utils :refer [assert-admin is-admin?]] + [auto-ap.graphql.utils :refer [assert-can-see-client is-admin?]] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] + [auto-ap.ssr.grid-page-helper :as helper] [auto-ap.ssr.svg :as svg] - [auto-ap.ssr.ui :refer [base-page]] [auto-ap.ssr.utils :refer [html-response]] [auto-ap.time :as atime] [bidi.bidi :as bidi] [config.core :refer [env]] - [datomic.api :as dc] - [hiccup2.core :as hiccup])) + [datomic.api :as dc])) -(defn row* [{:keys [flash? report identity delete-after-settle?]}] - (com/data-grid-row - {:class (when flash? - "live-added") - "_" (hiccup/raw (when delete-after-settle?" on htmx:afterSettle wait 400ms then remove me"))} - (com/data-grid-cell - {} - (:report/name report)) - (com/data-grid-cell - {} - (when (:report/creator report) - (com/pill {:color :primary } - (:report/creator report)))) - (com/data-grid-cell - {} - (atime/unparse-local (:report/created report) - atime/normal-date)) - (com/data-grid-right-stack-cell - {} - (com/a-icon-button {:href (:report/url report)} - svg/download) - (when (is-admin? identity) - [:form - [:input {:type :hidden :name "id" :value (:db/id report)}] - (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes - :company-reports-delete - :request-method :delete)) - :hx-target "closest tr"} - svg/trash)])))) +(def grid-page {:id "report-table" + :nav (com/company-aside-nav) + :id-fn :db/id + :fetch-page (fn [user args] + (r/get-graphql (into args {:id user}))) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company"] -(defn table* [{:keys [client start per-page identity session flash-id]}] - (let [start (or start 0) - per-page (or per-page 30) - [reports total] (r/get-graphql {:id identity - :start start - :per-page per-page - :client-id (:db/id client) - :sort nil})] - (com/data-grid-card {:id "report-table" - :title "Reports" - :entity-name "reports" - :route :company-reports-table - :start start - :per-page per-page - :total total - :action-buttons [(com/button {:color :primary} - (com/button-icon {} svg/refresh) - "Add new product") - (com/button {:color :secondary} - (com/button-icon {} svg/refresh) - "Update stocks 1/250") - (com/icon-button {} - svg/upload)] - :rows (for [report reports] - (row* {:report report - :flash? (= flash-id - (:db/id report)) - :identity identity})) - :headers [(com/data-grid-header {} "Name") - (com/data-grid-header {:class "hidden md:table-cell"} "Created by") - (com/data-grid-header {:class "hidden md:table-cell"} "Created") - (com/data-grid-header {})]}))) + [:a {:href (bidi/path-for ssr-routes/only-routes + :company-reports)} + "Reports"]] + :title "Reports" + :entity-name "Reports" + :route :company-reports-table + :action-buttons (fn [user] + nil) + :row-buttons (fn [user e] + (com/a-icon-button {:href (:report/url e)} + svg/download)[ + (when (is-admin? user) + (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes + :company-reports-delete + :request-method :delete)) + :hx-target "closest tr"} + svg/trash))]) + :headers [{:key "name" + :name "Name" + :sort-key "name" + :render :report/name} + {:key "created-by" + :name "Created by" + :sort-key "creator" + :render (fn [report] + (when (:report/creator report) + (com/pill {:color :primary } + (:report/creator report))))} + {:key "created" + :name "Created" + :sort-key "created" + :render #(atime/unparse-local (:report/created %) + atime/normal-date)}]}) -(defn delete-report [{:keys [query-params hx-query-params form-params identity session] :as request}] +(def row* (partial helper/row* grid-page)) +(def table* (partial helper/table* grid-page)) +(def table (partial helper/table grid-page)) +(def page (partial helper/page grid-page)) + +(defn delete-report [{:keys [form-params identity]}] + (let [[id-to-delete key] (first (dc/q '[:find ?i ?k :in $ ?i :where [?i :report/key ?k]] (dc/db conn) (some-> (get form-params "id") not-empty Long/parseLong))) report (dc/pull (dc/db conn) r/default-read id-to-delete)] + (assert-can-see-client identity (:report/client report)) (when id-to-delete (s3/delete-object :bucket-name (:data-bucket env) :key key) @(dc/transact conn [[:db/retractEntity id-to-delete]])) (html-response - (row* {:report report - :flash? true - :identity identity + (row* identity + report + {:flash? true :delete-after-settle? true})))) -(defn reports-table [{:keys [query-params hx-query-params identity session] :as request}] - (html-response (table* {:client (:client (:session request)) - :start (some-> (or (get query-params "start") (get hx-query-params "start")) not-empty (Long/parseLong )) - :per-page (some-> (or (get query-params "per-page") (get hx-query-params "per-page")) not-empty (Long/parseLong )) - :identity identity - :session session}) - :headers {"hx-push-url" (str "?start=" (get (:query-params request) "start"))})) - -(defn page [{:keys [query-params hx-query-params identity session] :as request}] - (base-page - request - (com/page {:nav (com/company-aside-nav) - :active-client (:client (:session request)) - :identity (:identity request)} - (com/breadcrumbs {} - [:a {:href (bidi/path-for ssr-routes/only-routes - :company)} "My Company"] - [:a {:href {:href (bidi/path-for ssr-routes/only-routes - :company-reports)}} "Reports"]) - (table* {:client (:client (:session request)) - :start (some-> (or (get query-params "start") (get hx-query-params "start")) not-empty (Long/parseLong )) - :per-page (some-> (or (get query-params "per-page") (get hx-query-params "per-page")) not-empty (Long/parseLong )) - :identity identity - :session session})) - nil)) diff --git a/src/clj/auto_ap/ssr/company/yodlee.clj b/src/clj/auto_ap/ssr/company/yodlee.clj index 0e736793..10cfb061 100644 --- a/src/clj/auto_ap/ssr/company/yodlee.clj +++ b/src/clj/auto_ap/ssr/company/yodlee.clj @@ -7,6 +7,7 @@ [auto-ap.ssr.components :as com] [auto-ap.ssr.svg :as svg] [auto-ap.ssr.ui :refer [base-page]] + [auto-ap.ssr.grid-page-helper :as helper] [auto-ap.ssr.utils :refer [html-response]] [auto-ap.time :as atime] [auto-ap.yodlee.core2 :as yodlee] @@ -15,89 +16,6 @@ [datomic.api :as dc] [hiccup2.core :as hiccup])) -(defn row* [{:keys [flash? yodlee-provider-account identity delete-after-settle?]}] - (com/data-grid-row - {:class (when flash? - "live-added") - "_" (hiccup/raw (when delete-after-settle?" on htmx:afterSettle wait 400ms then remove me"))} - (com/data-grid-cell - {} - (:yodlee-provider-account/id yodlee-provider-account)) - (com/data-grid-cell - {} - (when-let [status (:yodlee-provider-account/status yodlee-provider-account)] - (com/pill {:color (if (not= status "SUCCESS") - :yellow - :primary) } - status))) - (com/data-grid-cell - {} - (when-let [status (:yodlee-provider-account/detailed-status yodlee-provider-account)] - status) - ) - - (com/data-grid-cell - {} - (atime/unparse-local (:yodlee-provider-account/last-updated yodlee-provider-account) - atime/normal-date)) - (com/data-grid-cell - {} - [:ul - (for [a (:yodlee-provider-account/accounts yodlee-provider-account)] - [:li (:yodlee-account/name a) " - " (:yodlee-account/number a) #_[:div.tag (->$ (:available-balance a))]])]) - (com/data-grid-right-stack-cell - {} - (when (is-admin? identity) - [:form - [:input {:type :hidden :name "id" :value (:db/id yodlee-provider-account)}] - (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes - :company-yodlee-provider-account-refresh) - :hx-target "closest tr"} - svg/refresh)]) - #_(when (is-admin? identity) - [:form - [:input {:type :hidden :name "id" :value (:db/id report)}] - (com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes - :company-reports-delete - :request-method :delete)) - :hx-target "closest tr"} - svg/trash)])))) - -(defn table* [{:keys [identity start per-page client flash-id]}] - (let [start (or start 0) - per-page (or per-page 30) - [yodlee-provider-accounts total] (yodlee2/get-graphql {:id identity - :start start - :per-page per-page - :client-id (:db/id client) - :sort nil})] - [:div - (com/data-grid-card {:id "yodlee-table" - :title "Yodlee Accounts" - :entity-name "Yodlee accounts" - :route :company-yodlee-table - :start start - :per-page per-page - :total total - :action-buttons [(com/button {:color :primary - :on-click "openFastlink()" - :hx-get (bidi/path-for ssr-routes/only-routes - :company-yodlee-fastlink-dialog) - :hx-target "#modal-holder"} - (com/button-icon {} svg/refresh) - "Link new account")] - :rows (for [yodlee-provider-account yodlee-provider-accounts] - (row* {:yodlee-provider-account yodlee-provider-account - :flash? (= flash-id - (:db/id yodlee-provider-account)) - :identity identity})) - :headers [(com/data-grid-header {} "Provider Account") - (com/data-grid-header {} "Status") - (com/data-grid-header {} "Detailed Status") - (com/data-grid-header {} "Last Updated") - (com/data-grid-header {:class "hidden md:table-cell"} "Accounts") - (com/data-grid-header {})]})])) - (def default-read '[:db/id :yodlee-provider-account/last-updated :yodlee-provider-account/status @@ -106,14 +24,7 @@ {:yodlee-provider-account/accounts [:yodlee-account/name :yodlee-account/number] :yodlee-provider-account/client [:client/code]}]) -(defn refresh-provider-account [{:keys [form-params identity]}] - (let [provider-account (dc/pull (dc/db conn) default-read (some-> (get form-params "id") not-empty Long/parseLong))] - (yodlee/refresh-provider-account (:client/code (:yodlee-provider-account/client provider-account)) - (:yodlee-provider-account/id provider-account)) - (html-response - (row* {:yodlee-provider-account provider-account - :flash? true - :identity identity})))) + (defn fastlink-dialog [{:keys [session]}] (html-response @@ -136,33 +47,77 @@ fastlink.open({fastLinkURL: '%s', ] [:div])))) +(def grid-page {:id "yodlee-table" + :nav (com/company-aside-nav) + :id-fn :db/id + :fetch-page (fn [user args] + (yodlee2/get-graphql (assoc args :id user))) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes + :company)} + "My Company"] -(defn table [{:keys [query-params hx-query-params identity session] :as request}] - (html-response (table* {:client (:client (:session request)) - :start (some-> (or (get query-params "start") (get hx-query-params "start")) not-empty (Long/parseLong )) - :per-page (some-> (or (get query-params "per-page") (get hx-query-params "per-page")) not-empty (Long/parseLong )) - :identity identity - :session session}) - :headers {"hx-push-url" (str "?start=" (get (:query-params request) "start"))})) + [:a {:href (bidi/path-for ssr-routes/only-routes + :company-yodlee)} + "Yodlee"]] + :title "Yodlee Accounts" + :entity-name "Yodlee accounts" + :route :company-yodlee-table + :action-buttons (fn [user] + [(com/button {:color :primary + :on-click "openFastlink()" + :hx-get (bidi/path-for ssr-routes/only-routes + :company-yodlee-fastlink-dialog) + :hx-target "#modal-holder"} + (com/button-icon {} svg/refresh) + "Link new account")]) + :row-buttons (fn [user e] + [(when (is-admin? user) + (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes + :company-yodlee-provider-account-refresh) + :hx-target "closest tr"} + svg/refresh))]) + :headers [{:key "provider-account" + :name "Provider Account" + :sort-key "provider-account" + :render :yodlee-provider-account/id} + {:key "status" + :name "Status" + :sort-key "status" + :render #(when-let [status (:yodlee-provider-account/status %)] + (com/pill {:color (if (not= status "SUCCESS") + :yellow + :primary) } + status))} + {:key "detailed-status" + :name "Detailed Status" + :sort-key "detailed-status" + :render #(when-let [status (:yodlee-provider-account/detailed-status %)] + status)} -(defn page [{:keys [identity matched-route query-params :hx-query-params session] :as request}] - (base-page - request - (com/page {:nav (com/company-aside-nav) - :active-client (:client (:session request)) - :identity (:identity request)} - (com/breadcrumbs {} - [:a {:href (bidi/path-for ssr-routes/only-routes - :company)} - "My Company"] + {:key "last-updated" + :name "Last Updated" + :sort-key "last-updated" + :render #(atime/unparse-local (:yodlee-provider-account/last-updated %) + atime/normal-date)} + {:key "accounts" + :name "Accounts" + :show-starting "md" + :render (fn [e] + [:ul + (for [a (:yodlee-provider-account/accounts e)] + [:li (:yodlee-account/name a) " - " (:yodlee-account/number a)])])}]}) - [:a {:href (bidi/path-for ssr-routes/only-routes - :company-yodlee)} - "Yodlee"] - ) - (table* {:client (:client session) - :start (some-> (or (get query-params "start") (get hx-query-params "start")) not-empty (Long/parseLong )) - :per-page (some-> (or (get query-params "per-page") (get hx-query-params "per-page")) not-empty (Long/parseLong )) - :identity identity - :session session})) - nil)) +(def page (partial helper/page grid-page)) +(def table (partial helper/table grid-page)) + +;; TODO delete-after-settle +(defn refresh-provider-account [{:keys [form-params identity]}] + (let [provider-account (dc/pull (dc/db conn) default-read (some-> (get form-params "id") not-empty Long/parseLong))] + (yodlee/refresh-provider-account (:client/code (:yodlee-provider-account/client provider-account)) + (:yodlee-provider-account/id provider-account)) + (html-response + (helper/row* + grid-page + identity + provider-account + {:flash? true})))) diff --git a/src/clj/auto_ap/ssr/components.clj b/src/clj/auto_ap/ssr/components.clj index 75233aa2..0362c0a6 100644 --- a/src/clj/auto_ap/ssr/components.clj +++ b/src/clj/auto_ap/ssr/components.clj @@ -37,6 +37,7 @@ (def data-grid data-grid/data-grid-) (def data-grid-header data-grid/header-) +(def data-grid-sort-header data-grid/sort-header-) (def data-grid-row data-grid/row-) (def data-grid-cell data-grid/cell-) (def data-grid-right-stack-cell data-grid/right-stack-cell-) diff --git a/src/clj/auto_ap/ssr/components/data_grid.clj b/src/clj/auto_ap/ssr/components/data_grid.clj index 7a0dbf73..2f146f83 100644 --- a/src/clj/auto_ap/ssr/components/data_grid.clj +++ b/src/clj/auto_ap/ssr/components/data_grid.clj @@ -3,11 +3,20 @@ [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components.card :refer [content-card-]] [auto-ap.ssr.components.paginator :refer [paginator-]] - [bidi.bidi :as bidi])) + [bidi.bidi :as bidi] + [hiccup2.core :as hiccup])) (defn header- [params & rest] - (into [:th.px-4.py-3 {:scope "col" :class (:class params)} ] rest)) + (into [:th.px-4.py-3 {:scope "col" :class (:class params) + "_" (hiccup/raw (when (:sort-key params ) (format "on click trigger sorted(key:\"%s\")", (:sort-key params))))}] + (if (:sort-key params) + [(into [:a {:href "#"} ] rest)] + rest))) +(defn sort-header- [params & rest] + [:th.px-4.py-3 {:scope "col" :class (:class params) + "_" (hiccup/raw (format "on click trigger sorted(key:\"%s\")", (:sort-key params)))} + (into [:a {:href "#"} ] rest)]) (defn row- [params & rest] (into [:tr (update params @@ -27,9 +36,9 @@ [:input {:id "checkbox-all", :type "checkbox", :class "w-4 h-4 bg-gray-100 border-gray-300 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}] [:label {:for "checkbox-all", :class "sr-only"} "checkbox"]]]) -(defn data-grid- [{:keys [headers]} & rest] +(defn data-grid- [{:keys [headers thead-params]} & rest] [:table {:class "w-full text-sm text-left text-gray-500 dark:text-gray-400"} - [:thead {:class "text-xs text-gray-800 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"} + [:thead (assoc thead-params :class "text-xs text-gray-800 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400") (into [:tr] headers)] @@ -40,9 +49,10 @@ (defn data-grid-card- [{:keys [id route title - entity-name action-buttons total + subtitle + thead-params start per-page flash-id @@ -61,13 +71,14 @@ [:h1.text-2xl.mb-3.font-bold title] [:div {:class "flex items-center flex-1 space-x-4"} [:h5 - [:span (format "Total %s:" entity-name)] - [:span {:class "dark:text-white pl-4"} total]]]] + (when subtitle + [:span subtitle])]]] (into [:div {:class "flex flex-col flex-shrink-0 space-y-3 md:flex-row md:items-center lg:justify-end md:space-y-0 md:space-x-3"} ] action-buttons)] [:div {:class "overflow-x-auto"} (data-grid- {:headers headers + :thead-params thead-params } rows diff --git a/src/clj/auto_ap/ssr/core.clj b/src/clj/auto_ap/ssr/core.clj index b3447b53..76328117 100644 --- a/src/clj/auto_ap/ssr/core.clj +++ b/src/clj/auto_ap/ssr/core.clj @@ -33,7 +33,7 @@ :company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog)) :company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account)) :company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page)) - :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/reports-table)) + :company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table)) :company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report)) :transaction-insights (wrap-client-redirect-unauthenticated (wrap-secure insights/page)) :transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-secure insights/insight-table)) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj new file mode 100644 index 00000000..1c2c1d90 --- /dev/null +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -0,0 +1,182 @@ +(ns auto-ap.ssr.grid-page-helper + (:require + [auto-ap.ssr.components :as com] + [auto-ap.ssr.ui :refer [base-page]] + [auto-ap.ssr.utils :refer [html-response]] + [hiccup2.core :as hiccup] + [bidi.bidi :as bidi] + [auto-ap.ssr-routes :as ssr-routes] + [cemerick.url :as url] + [clojure.string :as str] + [auto-ap.ssr.svg :as svg])) + +(defn row* [gridspec user entity {:keys [flash? delete-after-settle?] :as options}] + (let [cells (mapv (fn [header] + (com/data-grid-cell {} + ((:render header) entity))) + (:headers gridspec)) + cells (conj cells (com/data-grid-right-stack-cell {} + (into [:form + [:input {:type :hidden :name "id" :value ((:id-fn gridspec) entity)}]] + ((:row-buttons gridspec) user entity))))] + (apply com/data-grid-row + {:class (when flash? + "live-added") + "_" (hiccup/raw (when delete-after-settle? + " on htmx:afterSettle wait 400ms then remove me")) + } + cells))) + +(defn sort-icon [sort key] + (->> sort + (filter (comp #(= key %) :sort-key)) + first + :sort-icon)) + +(defn sort-by-list [sort] + (if (seq sort) + (into + [:div.flex.gap-2.items-center + "sorted by" + + ] + (for [{:keys [name sort-icon ]} sort] + [:div.py-1.px-3.text-sm.rounded.bg-gray-100.dark:bg-gray-600.flex.items-center.gap-2.relative name [:div.h-4.w-4.mr-3 sort-icon] + [:div {:class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white hover:scale-110 transition-all duration-300 bg-gray-400 border-2 border-white rounded-full -top-2 -right-2 dark:border-gray-900"} + [:div.h-4.w-4 svg/x] + ]] + )) + "default sort")) + +(defn table* [grid-spec user {:keys [start per-page client flash-id sort]}] + (let [start (or start 0) + per-page (or per-page 30) + [entities total] ((:fetch-page grid-spec) + user + {:start start + :per-page per-page + :client-id (:db/id client) + :sort sort})] + (com/data-grid-card {:id (:id grid-spec) + :title (:title grid-spec) + :route (:route grid-spec) + :start start + :per-page per-page + :total total + :subtitle [:div.flex.items-center.gap-2 + [:span (format "Total %s: %d, " (:entity-name grid-spec) total)] + (sort-by-list sort)] + :action-buttons ((:action-buttons grid-spec) user) + :rows (for [entity entities] + (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity))})) + :thead-params {:hx-get (bidi/path-for ssr-routes/only-routes + (:route grid-spec)) + :hx-target (str "#" (:id grid-spec)) + :hx-trigger "sorted once" + :hx-vals "js:{\"toggle-sort\": event.detail.key || \"\"}"} + :headers + (conj + (mapv + (fn [h] + (if (:sort-key h) + (com/data-grid-sort-header {:class (if-let [show-starting (:show-starting h)] + (format "hidden %s:table-cell" show-starting) + (:class h)) + :sort-key (:sort-key h)} + + [:div.flex.gap-4.items-center + (:name h) + [:div.h-6.w-6.text-gray-400.dark:text-gray-500 (sort-icon sort (:sort-key h))]]) + (com/data-grid-header {:class (if-let [show-starting (:show-starting h)] + (format "hidden %s:table-cell" show-starting) + (:class h)) + :sort-key (:sort-key h)} + (:name h)) + + )) + (:headers grid-spec)) + (com/data-grid-header {}))}))) + + + +(defn parse-sort [grid-spec q] + (if (not-empty q) + (into [] + (map (fn [k] + (let [[k v] (str/split k #":")] + {:sort-key (str k) + :asc (boolean (= "asc" v)) + :name (:name (first (filter #(= (str k) (:sort-key %)) (:headers grid-spec)))) + :sort-icon (if (= (boolean (= "asc" v)) true) + svg/sort-down + svg/sort-up)})) + (str/split q #","))) + [])) + +(defn toggle-sort [grid-spec q k] + (if ((set (map :sort-key q)) k) + (mapv + (fn [s] + (if (= (:sort-key s) + k) + (-> s + (update :asc + #(boolean (not %))) + (update :sort-icon (fn [x] + (if (= x svg/sort-down) + svg/sort-up + svg/sort-down)))) + s)) + q) + (conj q {:sort-key k + :asc true + :name (:name (first (filter #(= (str k) (:sort-key %)) (:headers grid-spec)))) + :sort-icon svg/sort-down}))) + +(defn sort->query [s] + (str/join "," (map (fn [k] (format "%s:%s" (:sort-key k) (if (= true (:asc k)) + "asc" + "desc"))) + s))) + +(defn params->query-string [q] + (-> q + (dissoc :client :session) + (update :sort sort->query) + (url/map->query))) + +(defn extract-params [grid-spec {:keys [query-params hx-query-params identity session] :as request}] + (let [{hx-start "start" hx-per-page "per-page" hx-sort "sort" } hx-query-params + {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort"} query-params] + (cond-> {} + hx-start (assoc :start (some-> hx-start not-empty (Long/parseLong ))) + q-start (assoc :start (some-> q-start not-empty (Long/parseLong ))) + hx-per-page (assoc :per-page (some-> hx-per-page not-empty (Long/parseLong ))) + q-per-page (assoc :per-page (some-> q-per-page not-empty (Long/parseLong ))) + hx-sort (assoc :sort (parse-sort grid-spec hx-sort)) + q-sort (assoc :sort (parse-sort grid-spec q-sort)) + (not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) ) + (:session request) (assoc :session (:session request)) + (:client (:session request)) (assoc :client (:client (:session request)))))) + +(defn table [grid-spec {:keys [query-params hx-query-params identity session] :as request}] + (let [params (extract-params grid-spec request) + query-string (params->query-string params)] + (html-response (table* + grid-spec + identity + params + ) + :headers {"hx-push-url" (str "?" query-string)}))) + +(defn page [grid-spec {:keys [identity] :as request}] + (base-page + request + (com/page {:nav (:nav grid-spec) + :active-client (:client (:session request)) + :identity (:identity request)} + (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) + (table* grid-spec + identity + (extract-params grid-spec request))) + nil)) diff --git a/src/clj/auto_ap/ssr/svg.clj b/src/clj/auto_ap/ssr/svg.clj index e1dc3681..feb60375 100644 --- a/src/clj/auto_ap/ssr/svg.clj +++ b/src/clj/auto_ap/ssr/svg.clj @@ -293,3 +293,17 @@ [:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"} [:circle {:cx "12", :cy "12", :r "11.5", :fill "#FFF", :stroke-linecap "round", :stroke-linejoin "round"}] [:path {:d "M24,12A12,12,0,1,0,12,24,12,12,0,0,0,24,12Zm-7.29,3.28a1,1,0,0,1,0,1.41,1,1,0,0,1-1.42,0l-3.11-3.11a.26.26,0,0,0-.35,0L8.72,16.69a1,1,0,0,1-1.41-1.41l3.11-3.11a.26.26,0,0,0,0-.35L7.31,8.71a1,1,0,0,1,0-1.42,1,1,0,0,1,1.41,0l3.11,3.11a.24.24,0,0,0,.35,0l3.11-3.11a1,1,0,1,1,1.42,1.42L13.6,11.82a.24.24,0,0,0,0,.35Z", :fill "currentColor"}]]) + +(def sort-down + [:svg {:id "Regular", :xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"} + [:defs] + [:title "arrow-thick-down-4"] + [:rect {:y "0.75", :rx "3", :stroke "currentColor", :transform "translate(0 24) rotate(-90)", :fill "none", :stroke-linejoin "round", :width "22.5", :stroke-linecap "round", :stroke-width "1.5px", :x "0.75", :ry "3", :height "22.5"}] + [:path {:d "M9.75,6v7.5L6.53,10.28a.75.75,0,0,0-1.28.531v2.068a1.5,1.5,0,0,0,.439,1.06L11.47,19.72a.749.749,0,0,0,1.06,0l5.781-5.781a1.5,1.5,0,0,0,.439-1.06V10.811a.75.75,0,0,0-1.28-.531L14.25,13.5V6a.75.75,0,0,0-.75-.75h-3A.75.75,0,0,0,9.75,6Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]]) + +(def sort-up + [:svg {:id "Regular", :xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"} + [:defs] + [:title "arrow-thick-up-4"] + [:rect {:y "0.75", :rx "3", :stroke "currentColor", :transform "translate(24 0) rotate(90)", :fill "none", :stroke-linejoin "round", :width "22.5", :stroke-linecap "round", :stroke-width "1.5px", :x "0.75", :ry "3", :height "22.5"}] + [:path {:d "M14.25,18V10.5l3.22,3.22a.75.75,0,0,0,1.28-.531V11.121a1.5,1.5,0,0,0-.439-1.06L12.53,4.28a.749.749,0,0,0-1.06,0L5.689,10.061a1.5,1.5,0,0,0-.439,1.06v2.068a.75.75,0,0,0,1.28.531L9.75,10.5V18a.75.75,0,0,0,.75.75h3A.75.75,0,0,0,14.25,18Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]])