(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})))