(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]] [auto-ap.views.components.paginator :refer [paginator]] [auto-ap.views.components.sorter :refer [sorted-column]] [reagent.core :as reagent] [clojure.string :as str] [cljs-time.format :as format] [goog.string :as gstring])) ;; 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 (assoc params :company-id (:id @(re-frame/subscribe [::subs/company]))) [[:invoices [:id :total :outstanding-balance :invoice-number :date [:vendor [:name :id]] [:expense_accounts [:amount :id :expense_account_id :location [:expense_account [:id :name [:parent [:id :name]]]]]] [:company [:name :id :locations]] [:checks [:amount [:check [:amount :s3_url :check_number ]]]] ]] :total :start :end]]]}) (defn invoice-table [{:keys [id invoice-page status on-params-change vendors params check-boxes checked on-check-changed on-edit-invoice expense-event]}] (let [state (reagent/atom (or @params {})) visible-checks @(re-frame/subscribe [::visible-checks]) visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts]) selected-company @(re-frame/subscribe [::subs/company]) opc (fn [p] (swap! state merge p) (on-params-change p))] (fn [{:keys [id invoice-page status on-params-change vendors checked]}] (let [{:keys [sort-by asc]} @state {: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-company @(re-frame/subscribe [::subs/company]) percentage-size (if selected-company "%50%" "33%") ] [:div [paginator {:start start :end end :count count :total total :on-change (fn [p ] (on-params-change (swap! state merge p)))}] "Showing " (inc start) "-" end "/" total [:table.table.is-fullwidth [:thead [:tr (when check-boxes [:th {:style {"width" "20px"}}]) (when-not selected-company [sorted-column {:on-sort opc :style {:width percentage-size :cursor "pointer"} :sort-key "company" :sort-by sort-by :asc asc} "Company"]) [sorted-column {:on-sort opc :style {:width percentage-size :cursor "pointer"} :sort-key "vendor" :sort-by sort-by :asc asc} "Vendor"] [sorted-column {:on-sort opc :style {:width percentage-size :cursor "pointer"} :sort-key "invoice-number" :sort-by sort-by :asc asc} "Invoice #"] [sorted-column {:on-sort opc :style {:width "8em" :cursor "pointer"} :sort-key "date" :sort-by sort-by :asc asc} "Date"] [sorted-column {:on-sort opc :style {:width "8em" :cursor "pointer"} :sort-key "location" :sort-by sort-by :asc asc} "Location"] [sorted-column {:on-sort opc :style {:width "8em" :cursor "pointer"} :sort-key "total" :sort-by sort-by :asc asc} "Amount"] [sorted-column {:on-sort opc :style {:width "10em" :cursor "pointer"} :sort-key "outstanding" :sort-by sort-by :asc asc} "Outstanding"] [:th { :style {:width "15em" :cursor "pointer"} } ""]]] [:tbody (if (:loading @status) [:tr [:td {:col-span 5} [:i.fa.fa-spin.fa-spinner]]] (for [{:keys [company checks expense-accounts invoice-number date total outstanding-balance id vendor] :as i} (:invoices @invoice-page)] ^{:key 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)))} ]]) (when-not selected-company [:td (:name company)]) [:td (:name vendor)] [:td invoice-number] [:td (date->str date) ] [:td (str/join ", " (set (map :location expense-accounts)))] [:td (gstring/format "$%.2f" total )] [:td (gstring/format "$%.2f" outstanding-balance )] [:td.expandable (when (seq expense-accounts) [:div.dropdown.is-right {:class (if (= id visible-expense-accounts) "is-active" "")} [:div.dropdown-trigger [:a.button.badge {:data-badge (str (clojure.core/count expense-accounts)) :aria-haspopup true :tab-index "0" :on-blur (delayed-dispatch [::toggle-expense-accounts id]) :on-focus (dispatch-event [::toggle-expense-accounts id]) } "Accounts"]] [:div.dropdown-menu {:role "menu"} [:div.dropdown-content (for [e expense-accounts] ^{:key (:id e)} [:span.dropdown-item (:name (:expense-account e)) " "(gstring/format "$%.2f" (:amount e) ) ]) [:hr.dropdown-divider] [:a.dropdown-item.is-primary {:on-click (dispatch-event (conj expense-event id))} "Change"]]]]) [:span {:style {:margin-left "1em"}}] [:button.button {:on-click (fn [] (on-edit-invoice i))} [:span [:span.icon [:i.fa.fa-pencil]] " Edit"]] (when (seq checks) [:div.dropdown.is-right {:class (if (= id visible-checks) "is-active" "")} [:div.dropdown-trigger [:a.button.badge {:data-badge (str (clojure.core/count checks)) :aria-haspopup true :tab-index "0" :on-blur (delayed-dispatch [::toggle-checks id]) :on-focus (dispatch-event [::toggle-checks id]) } "Checks"]] [:div.dropdown-menu {:role "menu"} [:div.dropdown-content (for [check checks] (if (:s3-url (:check check)) ^{:key (:id check)} [:a.dropdown-item {:href (:s3-url (:check check)) :target "_new"} [:i.fa.fa-money-check] [:span.icon [:i.fa.fa-money]] (str " " (:check-number (:check check)) " (" (gstring/format "$%.2f" (:amount check) ) ")")] ^{:key (:id check)} [:span.dropdown-item [:i.fa.fa-money-check] [:span.icon [:i.fa.fa-money]] (str " " (:check-number (:check check)) " (" (gstring/format "$%.2f" (:amount check) ) ")")]))]]]) ]]))]]]))))