(ns auto-ap.views.components.invoice-table (:require [re-frame.core :as re-frame] [auto-ap.subs :as subs] [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] [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))) (defn query [params] {:venia/queries [[:invoice_page (-> params (assoc :client-id (:id @(re-frame/subscribe [::subs/client])))) [[:invoices [:id :total :outstanding-balance :invoice-number :date :due :status :client-identifier [:vendor [:name :id]] [:expense_accounts [:amount :id :location [:account [:id ]]]] [:client [:name :id :locations]] [:payments [:amount :id [:payment [:id :status :amount :s3_url :check_number [:transaction [:post_date]]]]]]]] :total :start :end]]]}) (re-frame/reg-sub ::table-params (fn [db] (::table-params db))) (re-frame/reg-event-fx ::params-changed [(re-frame/path [::table-params])] (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]}] (let [{:keys [client payments expense-accounts invoice-number date due total outstanding-balance id vendor] :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)} (when check-boxes [:td [:input.checkbox {:type "checkbox" :checked (if (get checked id) "checked" "") :on-change (fn [x e] (when on-check-changed (on-check-changed id i)))} ]]) (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 (date->str due) ] [:td (str/join ", " (set (map :location expense-accounts)))] [:td.has-text-right (nf total )] [:td.has-text-right (nf outstanding-balance )] [:td.expandable [: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"]} [drop-down-contents [:div (for [e expense-accounts] ^{:key (:id e)} [:span.dropdown-item (account->name (:account e)) " " (gstring/format "$%.2f" (:amount e) ) ]) [:hr.dropdown-divider] (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 (seq payments) [drop-down {:id [::payments id] :header [:button.button.badge {:data-badge (str (clojure.core/count payments)) :aria-haspopup true :tab-index "0" :on-click (dispatch-event [::events/toggle-menu [::payments id]]) } "Payments"]} [:div (for [payment payments] (if (:s3-url (:payment payment)) ^{:key (:id payment)} [:a.dropdown-item {:href (:s3-url (:payment payment)) :target "_new"} [:i.fa.fa-money-check] [:span.icon [:i.fa.fa-money]] (str " " (:check-number (:payment payment)) " (" (gstring/format "$%.2f" (:amount payment) ) ")")] ^{:key (:id payment)} [:span.dropdown-item [:span.icon {:class [(when (= :cleared (:status (:payment payment))) "has-text-success")]} [:i.fa.fa-money]] (str " " (:check-number (:payment payment)) " (" (gstring/format "$%.2f" (:amount payment) ) ") " (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]) {:keys [sort]} @(re-frame/subscribe [::table-params]) {:keys [invoices 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%")] [: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}]]]] [: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} "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"] [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 (:loading @status) [: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 @invoice-page)] ^{: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}]))]]]))