From 9dede12b6161b02485ed40571074f00d00311c5d Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Mon, 10 Aug 2020 13:26:14 -0700 Subject: [PATCH] started work on simplifying invoice page --- src/cljs/auto_ap/core.cljs | 1 + src/cljs/auto_ap/effects.cljs | 7 +- src/cljs/auto_ap/views/components/grid.cljs | 18 +- .../views/components/invoice_table.cljs | 289 ++++++++---------- .../auto_ap/views/pages/unpaid_invoices.cljs | 108 +++---- 5 files changed, 174 insertions(+), 249 deletions(-) diff --git a/src/cljs/auto_ap/core.cljs b/src/cljs/auto_ap/core.cljs index 8a76e0cd..ee7dc349 100644 --- a/src/cljs/auto_ap/core.cljs +++ b/src/cljs/auto_ap/core.cljs @@ -5,6 +5,7 @@ [auto-ap.events :as events] [auto-ap.views.main :refer [page active-page] ] [auto-ap.reload :as reload] + [auto-ap.events :as events] [auto-ap.config :as config] [auto-ap.effects :as effects] diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs index a9ac0c88..dc788bf3 100644 --- a/src/cljs/auto_ap/effects.cljs +++ b/src/cljs/auto_ap/effects.cljs @@ -168,10 +168,15 @@ (let [headers (if token {"Authorization" (str "Token " token)} {}) + on-success (if (fn? on-success) + on-success + (fn [result] + (conj on-success result))) method (if (= (get-in query-obj [:venia/operation :operation/type]) :mutation) :post :get) + headers (if (= method :post) (assoc headers "Content-Type" "text/plain") headers) @@ -214,5 +219,5 @@ :body :data (dates->date-times) - (conj on-success) + (on-success) (re-frame/dispatch)))))))) diff --git a/src/cljs/auto_ap/views/components/grid.cljs b/src/cljs/auto_ap/views/components/grid.cljs index 0e939efa..a8c13e97 100644 --- a/src/cljs/auto_ap/views/components/grid.cljs +++ b/src/cljs/auto_ap/views/components/grid.cljs @@ -108,14 +108,16 @@ (r/as-element (into [:div {:style {:margin-bottom "1rem"}} [:div.level - [:div.level-left - [:div.level-item - [paginator {:start start :end end :count count :total total - :on-change on-params-change}]] - [:div.level-item - [sort-by-list {:sort (:sort params) - :on-change on-params-change}]]]]] - children))))])) + (into + [:div.level-left + [:div.level-item + [paginator {:start start :end end :count count :total total + :on-change on-params-change}]] + [:div.level-item + [sort-by-list {:sort (:sort params) + :on-change on-params-change}]]] + (mapv (fn [c] + [:div.level-item c]) children))]]))))])) (defn table [] (r/create-class {:reagent-render diff --git a/src/cljs/auto_ap/views/components/invoice_table.cljs b/src/cljs/auto_ap/views/components/invoice_table.cljs index b2bb69dc..cdbe11c6 100644 --- a/src/cljs/auto_ap/views/components/invoice_table.cljs +++ b/src/cljs/auto_ap/views/components/invoice_table.cljs @@ -1,49 +1,21 @@ (ns auto-ap.views.components.invoice-table - (:require [re-frame.core :as re-frame] + (:require [auto-ap.events :as events] + [auto-ap.routes :as routes] + [auto-ap.subs :as subs] + [auto-ap.views.components.buttons :as buttons] + [auto-ap.views.components.dropdown + :refer + [drop-down drop-down-contents]] + [auto-ap.views.components.grid :as grid] + [auto-ap.views.pages.invoices.form :as form] + [auto-ap.views.pages.invoices.common :refer [invoice-read]] + [auto-ap.views.utils :refer [date->str dispatch-event nf]] [bidi.bidi :as bidi] [cemerick.url :as url] - [auto-ap.subs :as subs] - [auto-ap.routes :as routes] - [auto-ap.views.utils :refer [date->str dispatch-event delayed-dispatch nf]] - [auto-ap.views.components.paginator :refer [paginator]] - [auto-ap.views.components.sort-by-list :refer [sort-by-list]] - [auto-ap.views.components.sorter :refer [sorted-column]] - [auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]] - [auto-ap.events :as events] - [reagent.core :as reagent] - [clojure.string :as str] - [cljs-time.format :as format] [cljs-time.core :as t] + [clojure.string :as str] [goog.string :as gstring] - [goog.i18n.NumberFormat.Format]) - ) - -;; TODO graphql schema enforcement -;; TODO postgres constraints for data integrity -;; TODO performance -;; TODO refactor graphql - -(re-frame/reg-event-fx - ::toggle-checks - (fn [{:keys [db] } [_ invoice-id]] - {:db - (update-in db [::visible-checks] (fn [i] (if (= i invoice-id) nil invoice-id) ))})) - -(re-frame/reg-sub - ::visible-checks - (fn [db] - (::visible-checks db))) - -(re-frame/reg-event-fx - ::toggle-expense-accounts - (fn [{:keys [db] } [_ invoice-id]] - {:db - (update-in db [::visible-expense-accounts] (fn [i] (if (= i invoice-id) nil invoice-id) ))})) - -(re-frame/reg-sub - ::visible-expense-accounts - (fn [db] - (::visible-expense-accounts db))) + [re-frame.core :as re-frame])) (defn query [params] {:venia/queries [[:invoice_page @@ -80,15 +52,53 @@ (fn [{table-params :db} [_ params :as z]] {:db (merge table-params params)})) -(defn row [{:keys [invoice check-boxes checked on-check-changed selected-client overrides expense-event on-edit-invoice on-void-invoice on-unvoid-invoice]}] +(re-frame/reg-event-fx + ::void-invoice + (fn [{:keys [db]} [_ {id :id}]] + {:graphql + {:token (-> db :user) + :owns-state {:multi ::void + :which id} + :query-obj {:venia/operation {:operation/type :mutation + :operation/name "VoidInvoice"} + + :venia/queries [{:query/data [:void-invoice + {:invoice-id id} + invoice-read]}]} + :on-success (fn [result] + [::invoice-updated (assoc (:void-invoice result) + :class "live-removed")])}})) + +(re-frame/reg-event-fx + ::unvoid-invoice + (fn [{:keys [db]} [_ {id :id}]] + {:graphql + {:token (-> db :user) + :owns-state {:multi ::unvoid + :which id} + :query-obj {:venia/operation {:operation/type :mutation + :operation/name "UnvoidInvoice"} + + :venia/queries [{:query/data [:unvoid-invoice + {:invoice-id id} + invoice-read]}]} + :on-success (fn [result] + [::invoice-updated (assoc (:unvoid-invoice result) + :class "live-added")])}})) + +(re-frame/reg-event-fx + ::invoice-updated + (fn [{:keys [db]} [_ invoice]] + {:db db})) + +(defn row [{:keys [invoice check-boxes checked on-check-changed selected-client overrides expense-event ]}] (let [{:keys [client payments expense-accounts invoice-number date due total outstanding-balance id vendor checkable?] :as i} invoice accounts-by-id @(re-frame/subscribe [::subs/accounts-by-id client]) account->name #(:name (accounts-by-id (:id %)))] - [:tr {:class (:class i)} + [grid/row {:class (:class i)} (when check-boxes - [:td.expandable + [grid/cell {} [:input.checkbox (cond-> {:type "checkbox" - :checked (if (get checked id) "checked" "") @@ -96,13 +106,14 @@ (on-check-changed id i)))} (boolean? checkable?) (assoc :disabled (not checkable?))) ]]) (when-not selected-client - [:td (if-let [client-override (:client overrides)] - (client-override i) - (:name client))]) - [:td (:name vendor)] - [:td invoice-number] - [:td (date->str date) ] - [:td + [grid/cell {} + (if-let [client-override (:client overrides)] + (client-override i) + (:name client))]) + [grid/cell {} (:name vendor)] + [grid/cell {} invoice-number] + [grid/cell {} (date->str date) ] + [grid/cell {} (when due (let [today (t/at-midnight (t/now)) due (t/at-midnight due) @@ -113,19 +124,18 @@ [:span.has-text-success due-in " days"] [:span.has-text-danger due-in " days"]) ))] - [:td (str/join ", " (set (map :location expense-accounts)))] - - [:td.has-text-right (nf total )] - [:td.has-text-right (nf outstanding-balance )] - [:td.expandable + [grid/cell {} (str/join ", " (set (map :location expense-accounts)))] + + [grid/cell {:class "has-text-right"} (nf total )] + [grid/cell {:class "has-text-right"} (nf outstanding-balance )] + [grid/cell {:style {:overflow "visible"}} [:div.buttons (when (seq expense-accounts) [drop-down {:id [::expense-accounts id ] :header [:a.button.badge {:data-badge (str (clojure.core/count expense-accounts)) :aria-haspopup true :on-click (dispatch-event [::events/toggle-menu [::expense-accounts id]]) - :tab-index "0" - } "Accounts"]} + :tab-index "0"} "Accounts"]} [drop-down-contents [:div (for [e expense-accounts] @@ -137,12 +147,15 @@ (when expense-event [:a.dropdown-item.is-primary {:on-click (dispatch-event (conj expense-event i))} "Change"])]]]) [:span {:style {:margin-left "1em"}}] - (when (and on-edit-invoice (not= ":voided" (:status i))) - [:a.button {:on-click (fn [] (on-edit-invoice i))} [:span.icon [:i.fa.fa-pencil]]]) - (when (and on-void-invoice (= (:outstanding-balance i) (:total i)) (not= ":voided" (:status i))) - [:a.button {:on-click (fn [] (on-void-invoice i))} [:span.icon [:span.icon-bin-2 {:style {:font-weight "400"}}]]]) - (when (and on-unvoid-invoice (= ":voided" (:status i))) - [:a.button {:on-click (fn [] (on-unvoid-invoice i))} [:span [:span.icon [:i.fa.fa-undo]]]]) + (when (not= ":voided" (:status i)) + [buttons/fa-icon {:icon "fa-pencil" + :event [::form/editing i]}]) + (when (and (= (:outstanding-balance i) (:total i)) (not= ":voided" (:status i))) + [buttons/sl-icon {:icon "icon-bin-2" + :event [::void-invoice i]}]) + (when (= ":voided" (:status i)) + [buttons/fa-icon {:icon "fa-undo" + :event [::unvoid-invoice i]}]) (when (seq payments) [drop-down {:id [::payments id] :header [:button.button.badge {:data-badge (str (clojure.core/count payments)) @@ -169,19 +182,12 @@ (when (= :cleared (:status (:payment payment))) (str " - " (:post-date (:transaction (:payment payment))))))]))]])]]])) -(defn invoice-table [{:keys [id invoice-page status vendors check-boxes checked on-check-changed on-edit-invoice on-void-invoice on-unvoid-invoice expense-event overrides]}] - (let [opc (fn [p] - (re-frame/dispatch [::params-changed p])) - visible-checks @(re-frame/subscribe [::visible-checks]) - visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts]) - selected-client @(re-frame/subscribe [::subs/client]) +(defn invoice-table [{:keys [id invoice-page status vendors check-boxes checked on-check-changed expense-event overrides]}] + (let [selected-client @(re-frame/subscribe [::subs/client]) {:keys [sort]} @(re-frame/subscribe [::table-params]) {:keys [invoices outstanding start end count total]} @invoice-page - visible-checks @(re-frame/subscribe [::visible-checks]) - visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts]) selected-client @(re-frame/subscribe [::subs/client]) - percentage-size (if selected-client "%50%" "33%") - is-loading? (:loading @status) + is-loading? (= :loading status) is-sorted-by-vendor? (and (= "vendor" (:sort-key (first sort))) (not is-loading?)) [invoice-groups] (if is-sorted-by-vendor? @@ -196,95 +202,46 @@ [[] nil] (:invoices @invoice-page)) [[(:invoices @invoice-page)]])] - [:div - [:div.level - [:div.level-left - [:div.level-item - [paginator {:start start :end end :count count :total total - :on-change opc}]] - [:div.level-item - [sort-by-list {:sort sort - :on-change opc}]] - [:div.level-item - "Outstanding " (nf outstanding)]]] - (doall - (for [invoices invoice-groups] - ^{:key (:id (first invoices))} - [:table.table.is-fullwidth - [:thead - [:tr - (when check-boxes - [:th {:style {"width" "20px"}}]) - (when-not selected-client - [sorted-column {:on-sort opc - :style {:width percentage-size :cursor "pointer"} - :sort-name "Client" - :sort-key "client" - :sort sort} - "Client"]) - [sorted-column {:on-sort opc - :style {:width percentage-size :cursor "pointer"} - :sort-name "Vendor" - :sort-key "vendor" - :sort sort} - (if is-sorted-by-vendor? - (:name (:vendor (first invoices))) - "Vendor")] - [sorted-column {:on-sort opc - :style {:width percentage-size :cursor "pointer"} - :sort-name "Invoice Number" - :sort-key "invoice-number" - :sort sort} - "Invoice #"] - [sorted-column {:on-sort opc - :style {:width "8em" :cursor "pointer"} - :sort-name "Date" - :sort-key "date" - :sort sort} - "Date"] - [sorted-column {:on-sort opc - :style {:width "8em" :cursor "pointer"} - :sort-name "Due" - :sort-key "due" - :sort sort} - "Due"] - [sorted-column {:on-sort opc - :style {:width "5em" :cursor "pointer"} - :sort-name "Location" - :sort-key "location" - :sort sort} - "Loc"] - [sorted-column {:on-sort opc - :style {:width "8em" :cursor "pointer"} - :sort-name "Total" - :sort-key "total" - :class "has-text-right" - :sort sort} - "Amount"] + [grid/grid {:on-params-change (fn [p] + (re-frame/dispatch [::params-changed p])) + :params @(re-frame/subscribe [::table-params]) + :status status + ;; TODO checkboxes + :column-count (if selected-client 8 9)} + [grid/controls @invoice-page + [:div.level-item + "Outstanding " (nf outstanding)]] + (for [invoices invoice-groups] + ^{:key (or (:id (first invoices)) "init")} + [grid/table {:fullwidth true} + [grid/header {} + [grid/row {} + (when check-boxes + [grid/header-cell {:style {:width "22px"}}]) + (when-not selected-client + [grid/sortable-header-cell {:sort-key "client" :sort-name "Client"} "Client"]) + [grid/sortable-header-cell {:sort-key "vendor" :sort-name "Vendor"} + (if is-sorted-by-vendor? + (:name (:vendor (first invoices))) + "Vendor")] - [sorted-column {:on-sort opc - :style {:width "10em" :cursor "pointer"} - :sort-name "Outstanding" - :sort-key "outstanding-balance" - :class "has-text-right" - :sort sort} - "Outstanding"] - [:th {:style {:width "20rem" :cursor "pointer"}} - ""]]] - [:tbody - (if is-loading? - [:tr - [:td {:col-span 5} - [:i.fa.fa-spin.fa-spinner]]] - (for [{:keys [client payments expense-accounts invoice-number date due total outstanding-balance id vendor] :as i} invoices] - ^{:key id} - [row {:invoice i - :check-boxes check-boxes - :checked checked - :on-check-changed on-check-changed - :selected-client selected-client - :overrides overrides - :expense-event expense-event - :on-edit-invoice on-edit-invoice - :on-void-invoice on-void-invoice - :on-unvoid-invoice on-unvoid-invoice}]))]]))])) + [grid/sortable-header-cell {:sort-key "invoice-number" :sort-name "Invoice Number"} "Invoice #"] + [grid/sortable-header-cell {:sort-key "date" :sort-name "Date" :style {:width "8em"}} "Date"] + [grid/sortable-header-cell {:sort-key "due" :sort-name "Due" :style {:width "8em"}} "Due"] + [grid/sortable-header-cell {:sort-key "location" :sort-name "Location" :style {:width "5em"}} "Loc"] + [grid/sortable-header-cell {:sort-key "total" :sort-name "Total" :style {:width "8em"} :class "has-text-right"} "Total"] + + [grid/sortable-header-cell {:sort-key "outstanding-balance" :sort-name "Outstanding" :style {:width "10em"} :class "has-text-right"} "Outstanding"] + [grid/header-cell {:style {:width "20rem" }}] + ]] + + [grid/body + (for [{:keys [client payments expense-accounts invoice-number date due total outstanding-balance id vendor] :as i} invoices] + ^{:key id} + [row {:invoice i + :check-boxes check-boxes + :checked checked + :on-check-changed on-check-changed + :selected-client selected-client + :overrides overrides + :expense-event expense-event}])]])])) diff --git a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs index 47f26d5a..f91cd72d 100644 --- a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs @@ -21,7 +21,10 @@ [goog.string :as gstring] [vimsical.re-frame.cofx.inject :as inject] [re-frame.core :as re-frame] - [reagent.core :as r])) + [reagent.core :as r] + [auto-ap.status :as status] + [vimsical.re-frame.fx.track :as track] + [auto-ap.utils :refer [merge-by]])) (defn does-amount-exceed-outstanding? [amount outstanding-balance] (let [amount (js/parseFloat amount) @@ -71,25 +74,16 @@ (fn [db] (-> db ::check-results))) -(re-frame/reg-sub - ::last-params - (fn [db] - (-> db ::last-params))) - (re-frame/reg-sub ::params - :<- [::last-params] :<- [::subs/client] :<- [::side-bar/filter-params] :<- [::table/table-params] - (fn [[last-params client filter-params table-params]] - (let [params (cond-> {:import-status "imported"} - client (assoc :client-id (:id client)) - (seq filter-params) (merge filter-params) - (seq table-params) (merge table-params))] - (when (not= params last-params) - (re-frame/dispatch [::params-change])) - params))) + (fn [[client filter-params table-params]] + (cond-> {:import-status "imported"} + client (assoc :client-id (:id client)) + (seq filter-params) (merge filter-params) + (seq table-params) (merge table-params)))) (re-frame/reg-event-db ::invoice-updated @@ -101,12 +95,10 @@ (re-frame/reg-event-fx ::params-change - [with-user (re-frame/inject-cofx ::inject/sub [::params])] - (fn [{::keys [params] :as cofx} _] - {:db (-> (:db cofx) - (assoc-in [:status :loading] true) - (assoc-in [::last-params] params)) - :graphql {:token (-> cofx :db :user) + [with-user] + (fn [{:keys [user]} [_ params]] + {:graphql {:token user + :owns-state {:single ::page} :query-obj (table/query params ) :on-success [::received] :on-error [::events/page-failed]} @@ -118,7 +110,20 @@ (re-frame/reg-event-fx ::unmounted (fn [{:keys [db]} _] - {:db (dissoc db ::invoice-page ::table/table-params ::side-bar/filters ::side-bar/settled-filters ::last-params)})) + {:db (dissoc db ::invoice-page ::table/table-params ::side-bar/filters ::side-bar/settled-filters ::last-params) + :forward-events {:unregister ::updated} + ::track/dispose {:id ::params}})) + +(re-frame/reg-event-fx + ::mounted + (fn [{:keys [db]} _] + {::track/register {:id ::params + :subscription [::params] + :event-fn (fn [params] + [::params-change params])} + :forward-events {:register ::updated + :events #{::table/invoice-updated} + :dispatch-to [::invoice-updated]}})) (re-frame/reg-event-db ::received @@ -310,32 +315,6 @@ :location (first (:locations @(re-frame/subscribe [::subs/client])))}]})) -(re-frame/reg-event-fx - ::unvoid-invoice - (fn [{:keys [db]} [_ {id :id}]] - {:graphql - {:token (-> db :user) - :query-obj {:venia/operation {:operation/type :mutation - :operation/name "UnvoidInvoice"} - - :venia/queries [{:query/data [:unvoid-invoice - {:invoice-id id} - invoice-read]}]} - :on-success [::invoice-unvoided]}})) - -(re-frame/reg-event-fx - ::void-invoice - (fn [{:keys [db]} [_ {id :id}]] - {:graphql - {:token (-> db :user) - :query-obj {:venia/operation {:operation/type :mutation - :operation/name "VoidInvoice"} - - :venia/queries [{:query/data [:void-invoice - {:invoice-id id} - invoice-read]}]} - :on-success [::invoice-voided]}})) - (re-frame/reg-event-fx ::handwrite-checks-save (fn [{:keys [db]} _] @@ -380,27 +359,12 @@ (dissoc ::handwrite-checks))}))) -(re-frame/reg-event-fx - ::invoice-unvoided - (fn [{:keys [db]} [_ {:keys [unvoid-invoice]}]] - {:db (-> db - (update-in [::invoice-page :invoices] - (fn [is] - (mapv (fn [i] - (if (= (:id i) (:id unvoid-invoice)) - (assoc unvoid-invoice :class "live-removed") - i)) is))))})) (re-frame/reg-event-fx - ::invoice-voided - (fn [{:keys [db]} [_ {:keys [void-invoice]}]] - {:db (-> db - (update-in [::invoice-page :invoices] - (fn [is] - (mapv (fn [i] - (if (= (:id i) (:id void-invoice)) - (assoc void-invoice :class "live-removed") - i)) is))))})) + ::invoice-updated + [(re-frame/path [::invoice-page :invoices])] + (fn [{:keys [db]} [_ [_ invoice]]] + {:db (merge-by db :id invoice)})) @@ -642,13 +606,8 @@ [pay-button {:print-checks-shown? print-checks-shown? :checked-invoices checked :print-checks-loading? print-checks-loading?}]) [table/invoice-table {:id :unpaid :invoice-page (re-frame/subscribe [::invoice-page]) - :status (re-frame/subscribe [::subs/status]) - :on-edit-invoice (fn [which] - (re-frame/dispatch [::form/editing which])) - :on-unvoid-invoice (fn [which] - (re-frame/dispatch [::unvoid-invoice which])) - :on-void-invoice (fn [which] - (re-frame/dispatch [::void-invoice which])) + :status @(re-frame/subscribe [::status/single ::page]) + :check-boxes (= status :unpaid) :checked checked :on-check-changed (fn [which invoice] @@ -659,6 +618,7 @@ (r/create-class {:display-name "invoices-page" :component-will-unmount #(re-frame/dispatch [::unmounted]) + :component-did-mount #(re-frame/dispatch [::mounted]) :reagent-render (fn [] (let [{invoice-bar-active? :active?} @(re-frame/subscribe [::forms/form ::form/form])