fulcro-rad-carbon/src/main/com/fulcrologic/rad/rendering/semantic_ui/report.cljc

361 lines
20 KiB
Clojure

(ns com.fulcrologic.rad.rendering.semantic-ui.report
(:require
[clojure.string :as str]
[com.fulcrologic.rad.attributes :as attr]
[com.fulcrologic.rad.report :as report]
[com.fulcrologic.rad.control :as control]
[com.fulcrologic.fulcro.components :as comp]
#?@(:cljs
[[com.fulcrologic.fulcro.dom :as dom :refer [div]]
[com.fulcrologic.semantic-ui.addons.pagination.ui-pagination :as sui-pagination]
[ch.lyrion.carbon.data-table-skeleton.ui-data-table-skeleton :refer [ui-data-table-skeleton]]
[ch.lyrion.carbon.data-table.ui-data-table :refer [ui-data-table]]
[ch.lyrion.carbon.data-table.ui-table :refer [ui-table]]
[ch.lyrion.carbon.data-table.ui-table-container :refer [ui-table-container]]
[ch.lyrion.carbon.data-table.ui-table-head :refer [ui-table-head]]
[ch.lyrion.carbon.data-table.ui-table-header :refer [ui-table-header]]
[ch.lyrion.carbon.data-table.ui-table-body :refer [ui-table-body]]
[ch.lyrion.carbon.data-table.ui-table-row :refer [ui-table-row]]
[ch.lyrion.carbon.data-table.ui-table-cell :refer [ui-table-cell]]
[ch.lyrion.carbon.data-table.ui-table-toolbar :refer [ui-table-toolbar]]
[ch.lyrion.carbon.data-table.ui-table-toolbar-action :refer [ui-table-toolbar-action]]
[ch.lyrion.carbon.data-table.ui-table-toolbar-content :refer [ui-table-toolbar-content]]
[ch.lyrion.carbon.link.ui-link :refer [ui-link]]
[ch.lyrion.carbon.overflow-menu.ui-overflow-menu :refer [ui-overflow-menu]]
[ch.lyrion.carbon.overflow-menu-item.ui-overflow-menu-item :refer [ui-overflow-menu-item]]]
:clj
[[com.fulcrologic.fulcro.dom-server :as dom :refer [div]]])
[com.fulcrologic.fulcro.data-fetch :as df]
[com.fulcrologic.rad.rendering.semantic-ui.form :as sui-form]
[taoensso.timbre :as log]
[com.fulcrologic.rad.options-util :refer [?!]]
[com.fulcrologic.rad.form :as form]
[com.fulcrologic.fulcro.dom.events :as evt]))
(defn row-action-buttons [report-instance row-props]
(let [{::report/keys [row-actions]} (comp/component-options report-instance)]
(when (seq row-actions)
#?(:cljs
(ui-overflow-menu
{}
(map-indexed
(fn [idx {:keys [label reload? visible? disabled? action]}]
(when (or (nil? visible?) (?! visible? report-instance row-props))
(ui-overflow-menu-item
{:key idx
:disabled (boolean (?! disabled? report-instance row-props))
:itemText (?! label report-instance row-props)
:onClick (fn [evt]
(evt/stop-propagation! evt)
(when action
(action report-instance row-props)
(when reload?
(control/run! report-instance))))})))
row-actions))))))
(comp/defsc TableRowLayout [_ {:keys [report-instance props] :as rp}]
{}
#?(:cljs
(let [{::report/keys [columns link links]} (comp/component-options report-instance)
links (or links link)
action-buttons (row-action-buttons report-instance props)
{:keys [highlighted?]
::report/keys [idx]} (comp/get-computed props)]
(ui-table-row
{:isSelected highlighted?
:onClick (fn [evt]
(evt/stop-propagation! evt)
(report/select-row! report-instance idx))}
(let [trow (mapv
(fn [{::attr/keys [qualified-key] :as column}]
(let [column-classes (report/column-classes report-instance column)
{:keys [edit-form entity-id]} (report/form-link report-instance props qualified-key)
link-fn (get links qualified-key)
label (report/formatted-column-value report-instance props column)]
(ui-table-cell
{:key (str "col-" qualified-key)
:classes [column-classes]}
(cond
edit-form (ui-link {:href "#"
:onClick (fn [evt]
(evt/stop-propagation! evt)
(form/edit! report-instance edit-form entity-id))}
label)
(fn? link-fn) (ui-link {:href "#"
:onClick (fn [evt]
(evt/stop-propagation! evt)
(link-fn report-instance props))}
label)
:else label))))
columns)]
(if action-buttons
(conj trow
(ui-table-cell {:key "menu"
:width "5em"
:className "bx--table-column-menu"}
action-buttons))
trow))))))
(let [ui-table-row-layout (comp/factory TableRowLayout)]
(defn render-table-row [report-instance row-class row-props]
(ui-table-row-layout {:report-instance report-instance
:row-class row-class
:props row-props})))
(comp/defsc ListRowLayout [this {:keys [report-instance props]}]
{}
(let [{::report/keys [columns]} (comp/component-options report-instance)]
(let [header-column (first columns)
description-column (second columns)
{:keys [edit-form entity-id]} (some->> header-column (::attr/qualified-key) (report/form-link report-instance props))
header-label (some->> header-column (report/formatted-column-value report-instance props))
description-label (some->> description-column (report/formatted-column-value report-instance props))
action-buttons (row-action-buttons report-instance props)]
(div :.item
(div :.content
(when action-buttons
(div :.right.floated.content
action-buttons))
(when header-label
(if edit-form
(dom/a :.header {:onClick (fn [evt]
(evt/stop-propagation! evt)
(form/edit! report-instance edit-form entity-id))} header-label)
(div :.header header-label)))
(when description-label
(div :.description description-label)))))))
(let [ui-list-row-layout (comp/factory ListRowLayout {:keyfn ::report/idx})]
(defn render-list-row [report-instance row-class row-props]
(ui-list-row-layout {:report-instance report-instance
:row-class row-class
:props row-props})))
(comp/defsc StandardReportControls [this {:keys [report-instance] :as env}]
{:shouldComponentUpdate (fn [_ _ _] true)}
#?(:cljs
(let [controls (control/component-controls report-instance)
{:keys [::report/paginate?]} (comp/component-options report-instance)
{:keys [input-layout action-layout]} (control/standard-control-layout report-instance)
{:com.fulcrologic.rad.container/keys [controlled?]} (comp/get-computed report-instance)]
(comp/fragment
(ui-table-toolbar
{}
(flatten [(map-indexed
(fn [idx row]
(div {:key idx :className (sui-form/n-fields-string (count row))}
(keep #(let [control (get controls %)]
(when (or (not controlled?) (:local? control))
(ui-table-toolbar-content {:key (str (hash %))} (control/render-control report-instance % control))))
row)))
input-layout)
(ui-table-toolbar-content
{:key (str (hash env) "-toolbar-content")}
(dom/div :.bx--btn-set
(keep (fn [k]
(let [control (get controls k)]
(when (or (not controlled?) (:local? control))
(control/render-control report-instance k control))))
action-layout)))]))))))
(let [ui-standard-report-controls (comp/factory StandardReportControls)]
(defn render-standard-controls [report-instance]
(ui-standard-report-controls {:report-instance report-instance})))
(comp/defsc ListReportLayout [this {:keys [report-instance] :as env}]
{:shouldComponentUpdate (fn [_ _ _] true)
:initLocalState (fn [_] {:row-factory (memoize
(fn [cls]
(comp/computed-factory cls
{:keyfn (fn [props] (some-> props (comp/get-computed ::report/idx)))})))})}
(let [{::report/keys [BodyItem]} (comp/component-options report-instance)
render-row ((comp/get-state this :row-factory) BodyItem)
render-controls (report/control-renderer this)
rows (report/current-rows report-instance)
loading? (report/loading? report-instance)]
(div
(when render-controls
(render-controls report-instance))
(div :.ui.attached.segment
(div :.ui.loader {:classes [(when loading? "active")]})
(when (seq rows)
(div :.ui.relaxed.divided.list
(map-indexed (fn [idx row] (render-row row {:report-instance report-instance
:row-class BodyItem
::report/idx idx})) rows)))))))
(let [ui-list-report-layout (comp/factory ListReportLayout {:keyfn ::report/idx})]
(defn render-list-report-layout [report-instance]
(ui-list-report-layout {:report-instance report-instance})))
(defn get-id-key [mapkeys wanted]
;;(first)
(first
(filter #(= (name wanted) (name %))
mapkeys)))
(defn render-standard-table [this {:keys [report-instance]}]
(let [{report-column-headings ::report/column-headings
::report/keys [columns row-actions BodyItem compare-rows table-class]} (comp/component-options report-instance)
render-row ((comp/get-state this :row-factory) BodyItem)
column-headings (mapv (fn [{::report/keys [column-heading]
::attr/keys [qualified-key] :as attr}]
{:column attr
:label (or
(?! (get report-column-headings qualified-key) report-instance)
(?! column-heading report-instance)
(some-> qualified-key name str/capitalize)
"")})
columns)
clj-rows (report/current-rows report-instance)
id-key (get-id-key (keys (first clj-rows)) :id)
props (comp/props report-instance)
sort-params (-> props :ui/parameters ::report/sort)
sortable? (if-not (boolean compare-rows)
(constantly false)
(if-let [sortable-columns (some-> sort-params :sortable-columns set)]
(fn [{::attr/keys [qualified-key]}] (contains? sortable-columns qualified-key))
(constantly true)))
ascending? (and sortable? (:ascending? sort-params))
sorting-by (and sortable? (:sort-by sort-params))
has-row-actions? (seq row-actions)]
#?(:cljs
(ui-table
{:className table-class}
(ui-table-head
{}
(ui-table-row
{}
(let [trow
(into []
(map-indexed (fn [idx {:keys [label column]}]
(let [sorting (atom (if (= sorting-by (::attr/qualified-key column))
(if ascending? "ASC" "DESC")
"NONE"))]
(ui-table-header {:key idx
:sortDirection @sorting #_(when (= sorting-by (::attr/qualified-key column))
(if ascending? "ASC" "DESC"))
:isSortHeader (not= "NONE" @sorting)
:isSortable (sortable? column)
:onClick (fn [evt]
(js/console.log (::attr/qualified-key column))
(js/console.log "Ascending?" ascending?)
(evt/stop-propagation! evt)
(report/sort-rows! report-instance column)
(reset! sorting (if ascending? "ASC" "DESC")))}
label)))
column-headings))]
(if has-row-actions?
(conj trow (ui-table-header {:key "menu"}))
trow))))
(when (seq clj-rows)
(ui-table-body
{} (map-indexed
(fn [idx row]
(let [highlighted-row-idx (report/currently-selected-row report-instance)]
(render-row row {:report-instance report-instance
:row-class BodyItem
:highlighted? (= idx highlighted-row-idx)
::report/idx idx})))
clj-rows)))))))
(defn render-rotated-table [_ {:keys [report-instance] :as env}]
(let [{report-column-headings ::report/column-headings
::report/keys [columns row-actions compare-rows table-class]} (comp/component-options report-instance)
props (comp/props report-instance)
sort-params (-> props :ui/parameters ::report/sort)
sortable? (if-not (boolean compare-rows)
(constantly false)
(if-let [sortable-columns (some-> sort-params :sortable-columns set)]
(fn [{::attr/keys [qualified-key]}] (contains? sortable-columns qualified-key))
(constantly true)))
ascending? (and sortable? (:ascending? sort-params))
sorting-by (and sortable? (:sort-by sort-params))
row-headings (mapv (fn [{::report/keys [column-heading]
::attr/keys [qualified-key] :as attr}]
(let [label (or
(?! (get report-column-headings qualified-key) report-instance)
(?! column-heading report-instance)
(some-> qualified-key name str/capitalize)
"")]
(if (sortable? attr)
(dom/a {:onClick (fn [evt]
(evt/stop-propagation! evt)
(report/sort-rows! report-instance attr))}
label
(when (= sorting-by (::attr/qualified-key attr))
(if ascending?
(dom/i :.angle.down.icon)
(dom/i :.angle.up.icon))))
label)))
columns)
rows (report/current-rows report-instance)
has-row-actions? (seq row-actions)]
(dom/table :.ui.compact.collapsing.definition.selectable.table {:classes [table-class]}
(when (seq rows)
(comp/fragment
(dom/thead
(let [col (first columns)]
(dom/tr {:key "hrow"}
(dom/th
(get row-headings 0))
(map-indexed
(fn [idx row]
(dom/th {:key idx}
(report/formatted-column-value report-instance row col))) rows)
(when has-row-actions?
(dom/td {:key "actions"}
(row-action-buttons report-instance col))))))
(dom/tbody
(map-indexed
(fn [idx col]
(dom/tr {:key idx}
(dom/td (get row-headings (inc idx)))
(map-indexed
(fn [idx row]
(dom/td {:key idx :className "right aligned"}
(report/formatted-column-value report-instance row col))) rows)
(when has-row-actions?
(dom/td {:key "actions"}
(row-action-buttons report-instance col)))))
(rest columns))))))))
(comp/defsc TableReportLayout [this {:keys [report-instance] :as env}]
{:initLocalState (fn [this] {:row-factory (memoize (fn [cls] (comp/computed-factory cls
{:keyfn (fn [props]
(some-> props (comp/get-computed ::report/idx)))})))})
:shouldComponentUpdate (fn [_ _ _] true)}
#?(:cljs
(let [{::report/keys [rotate?]} (comp/component-options report-instance)
rotate? (?! rotate? report-instance)
render-controls (report/control-renderer report-instance)
loading? (report/loading? report-instance)
controlled? (comp/get-computed report-instance :com.fulcrologic.rad.container/controlled?)
props (comp/props report-instance)
busy? (:ui/busy? props)]
(div
(ui-data-table-skeleton
{:style {:width "100%"
:position "absolute"
:opacity (if (or busy? loading?) 1.0 0.0)
:top 0
:left 0}})
(ui-table-container
{:key (str (hash report-instance))
:className (if (or busy? loading?) "bx--skeleton")
:style {:width "100%"
:position "absolute"
:top 0
:left 0
:zIndex 10}
:title (or (some-> report-instance comp/component-options ::report/title (?! report-instance)) "Report")}
(when render-controls
(render-controls report-instance))
(if rotate?
(render-rotated-table this env)
(render-standard-table this env)))))))
(let [ui-table-report-layout (comp/factory TableReportLayout {:keyfn ::report/idx})]
(defn render-table-report-layout [this]
(ui-table-report-layout {:report-instance this})))