diff --git a/resources/public/css/main.css b/resources/public/css/main.css index 95cff5c7..9200b922 100644 --- a/resources/public/css/main.css +++ b/resources/public/css/main.css @@ -14,10 +14,17 @@ @keyframes appear { from { opacity: 0; } } + @keyframes slideOut { + to { + -webkit-transform: translateY(10px); + transform: translateY(10px); + opacity: 0; + } + } @keyframes slideIn { from { - -webkit-transform: translateY(-100%); - transform: translateY(-100%); + -webkit-transform: translateY(-10px); + transform: translateY(-10px); opacity: 0; } } @@ -113,6 +120,36 @@ } } + @keyframes grow-height { + from { + height: 0px; + opacity: 0; + } + } + + @keyframes shrink-height { + to { + height: 0px; + opacity: 0; + } + } + + + .appear { + animation: slideIn 0.2s ease both; + } + + .disappear { + animation: slideOut 0.2s ease both; + } + + .slide-in-top { + animation: grow-height 0.5s ease both; + } + .slide-out-top { + animation: shrink-height 0.5s ease both; + } + .slide-in-right { animation: grow-width 0.5s ease both; } diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs index 2496e384..f275fac0 100644 --- a/src/cljs/auto_ap/effects.cljs +++ b/src/cljs/auto_ap/effects.cljs @@ -151,6 +151,7 @@ :body :errors (dates->date-times) + (map #(assoc % :status (:status response))) (conj on-error) (re-frame/dispatch))) (->> response diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index d9136b0c..a2fe0f29 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -124,8 +124,10 @@ (if (and (not= :login handler) (not (:user db))) {:redirect "/login" :db (assoc db :active-page :login + :page-failure nil :auto-ap.forms/forms nil)} {:db (assoc db :active-page handler + :page-failure nil :query-params params :auto-ap.forms/forms nil)}))) @@ -219,6 +221,12 @@ ::save-complete (fn [{:keys [db]} [_ vendor]] {:dispatch [::modal-completed :auto-ap.views.main/user-editing-vendor ] - :db (-> db - (dissoc :user-editing-vendor) - (assoc-in [:vendors (:id vendor)] (:upsert-vendor vendor)))})) + :db (-> db + (dissoc :user-editing-vendor) + (assoc-in [:vendors (:id vendor)] (:upsert-vendor vendor)))})) +(re-frame/reg-event-db + ::page-failed + (fn [db [_ result]] + (println "Page failure" result) + (assoc db :page-failure result + :status nil))) diff --git a/src/cljs/auto_ap/subs.cljs b/src/cljs/auto_ap/subs.cljs index 3e0d6512..7037ac70 100644 --- a/src/cljs/auto_ap/subs.cljs +++ b/src/cljs/auto_ap/subs.cljs @@ -119,3 +119,11 @@ (fn [db] (map (fn [[k v]] (assoc v :id k)) chooseable-expense-accounts))) + +(re-frame/reg-sub + ::page-failure + (fn [db] + (when-let [error (-> db :page-failure first)] + (if (= 500 (:status error)) + "System error occured. If you are stuck, please notify ben@integreatconsult.com." + (:message error))))) diff --git a/src/cljs/auto_ap/views/components/dropdown.cljs b/src/cljs/auto_ap/views/components/dropdown.cljs index 50e94118..665044d8 100644 --- a/src/cljs/auto_ap/views/components/dropdown.cljs +++ b/src/cljs/auto_ap/views/components/dropdown.cljs @@ -1,6 +1,7 @@ (ns auto-ap.views.components.dropdown (:require [reagent.core :as r] [auto-ap.events :as events] + [auto-ap.views.utils :refer [appearing]] [re-frame.core :as re-frame] [auto-ap.subs :as subs])) @@ -22,9 +23,11 @@ "")} [:div.dropdown-trigger header] - [:div.dropdown-menu {:role "menu"} - [:div.dropdown-content - (when menu-active? - [drop-down-contents {:id id} - child])]]]))}))) + [appearing {:visible? menu-active? :enter-class "appear" :exit-class "disappear" :timeout 200} + [:div.dropdown-menu {:role "menu"} + [:div.dropdown-content + (when menu-active? + [drop-down-contents {:id id} + child])]]] + ]))}))) diff --git a/src/cljs/auto_ap/views/components/invoice_table.cljs b/src/cljs/auto_ap/views/components/invoice_table.cljs index 32363317..e7e9515a 100644 --- a/src/cljs/auto_ap/views/components/invoice_table.cljs +++ b/src/cljs/auto_ap/views/components/invoice_table.cljs @@ -1,7 +1,7 @@ (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.utils :refer [date->str dispatch-event delayed-dispatch nf]] [auto-ap.views.components.paginator :refer [paginator]] [auto-ap.views.components.sorter :refer [sorted-column]] [auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]] @@ -9,7 +9,9 @@ [reagent.core :as reagent] [clojure.string :as str] [cljs-time.format :as format] - [goog.string :as gstring])) + [goog.string :as gstring] + [goog.i18n.NumberFormat.Format]) + ) ;; TODO graphql schema enforcement ;; TODO postgres constraints for data integrity @@ -56,8 +58,6 @@ :start :end]]]}) - - (defn invoice-table [{:keys [id invoice-page status on-params-change vendors params check-boxes checked on-check-changed on-edit-invoice on-void-invoice on-unvoid-invoice expense-event]}] (let [visible-checks @(re-frame/subscribe [::visible-checks]) visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts]) @@ -120,6 +120,7 @@ [sorted-column {:on-sort opc :style {:width "8em" :cursor "pointer"} :sort-key "total" + :class "has-text-right" :sort-by sort-by :asc asc} "Amount"] @@ -127,6 +128,7 @@ [sorted-column {:on-sort opc :style {:width "10em" :cursor "pointer"} :sort-key "outstanding-balance" + :class "has-text-right" :sort-by sort-by :asc asc} "Outstanding"] @@ -162,8 +164,8 @@ [:td (date->str date) ] [:td (str/join ", " (set (map :location expense-accounts)))] - [:td (gstring/format "$%.2f" total )] - [:td (gstring/format "$%.2f" outstanding-balance )] + [:td.has-text-right (nf total )] + [:td.has-text-right (nf outstanding-balance )] [:td.expandable @@ -192,7 +194,7 @@ (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 {:on-click (fn [] (on-unvoid-invoice i))} [:span [:span.icon [:i.fa.fa-undo]]]]) + [: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)) @@ -211,15 +213,4 @@ "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))))))]))]] - - #_[:div.dropdown.is-right {:class (if (= id visible-checks) - "is-active" - "")} - [:div.dropdown-trigger - ] - [:div.dropdown-menu {:role "menu"} - [:div.dropdown-content - ]]])] - - ]]))]]])))) + (str " - " (:post-date (:transaction (:payment payment))))))]))]])]]]))]]])))) diff --git a/src/cljs/auto_ap/views/components/layouts.cljs b/src/cljs/auto_ap/views/components/layouts.cljs index 175dd91f..3e644dff 100644 --- a/src/cljs/auto_ap/views/components/layouts.cljs +++ b/src/cljs/auto_ap/views/components/layouts.cljs @@ -7,11 +7,10 @@ [auto-ap.routes :as routes] [auto-ap.subs :as subs] [auto-ap.events :as events] - [auto-ap.views.utils :refer [active-when active-when= login-url dispatch-event]] + [auto-ap.views.utils :refer [active-when active-when= login-url dispatch-event appearing css-transition-group]] [auto-ap.views.components.vendor-dialog :refer [vendor-dialog]])) -(def css-transition-group - (reagent/adapt-react-class js/ReactTransitionGroup.CSSTransition)) + (defn navbar-drop-down-contents [{:keys [id]} children ] (let [toggle-fn (fn [] (re-frame/dispatch [::events/toggle-menu id]))] @@ -32,10 +31,10 @@ (.stopPropagation e) (re-frame/dispatch [::events/toggle-menu id]) true)} header] - [:div {:class "navbar-dropdown"} - (when menu-active? - [navbar-drop-down-contents {:id id} - [:div child]])]]))}))) + [appearing {:visible? menu-active? :enter-class "appear" :exit-class "disappear" :timeout 200} + [:div {:class "navbar-dropdown"} + [navbar-drop-down-contents {:id id} + [:div child]]]]]))}))) (defn login-dropdown [] (let [user (re-frame/subscribe [::subs/user]) @@ -105,13 +104,9 @@ [:strong "Integreat"] ]]]) (defn appearing-side-bar [{:keys [visible?]} & children ] - (let [final-state (reagent/atom visible?)] - (fn [{:keys [visible?]} & children] - [css-transition-group {:in visible? :class-names {:exitDone "bounce animated" :exit "slide-out-right" :enter "slide-in-right"} :timeout 500 :onEnter (fn [] (reset! final-state true )) :onExited (fn [] (reset! final-state false))} - (if (or @final-state visible?) - [:aside {:class "column is-3 aside menu" :style {:height "calc(100vh - 46px)" :overflow "auto"}} - [:div.sub-main {} children ]] - [:div])]))) + [appearing {:visible? visible? :enter-class "slide-in-right" :exit-class "slide-out-right" :timeout 500} + [:aside {:class "column is-3 aside menu" :style {:height "calc(100vh - 46px)" :overflow "auto"}} + [:div.sub-main {} children ]]]) (defn side-bar-layout [{:keys [side-bar main ap bottom right-side-bar]}] (let [ap @(re-frame/subscribe [::subs/active-page]) @@ -125,7 +120,11 @@ ]]] [:div {:class "column messages hero " :style { :overflow "auto" }, :id "message-feed"} ^{:key (str "active-page-" (:name client))} - [:div.inbox-messages main]] + [:div.inbox-messages + (when-let [error @(re-frame/subscribe [::subs/page-failure])] + [:div.notification.is-warning.animated.fadeInUp + error]) + main]] (when right-side-bar right-side-bar) ] diff --git a/src/cljs/auto_ap/views/components/sorter.cljs b/src/cljs/auto_ap/views/components/sorter.cljs index 13502ae6..38d17956 100644 --- a/src/cljs/auto_ap/views/components/sorter.cljs +++ b/src/cljs/auto_ap/views/components/sorter.cljs @@ -25,9 +25,10 @@ [:span.icon [:i.fa.fa-sort]])) -(defn sorted-column [{:keys [on-sort sort-key sort-by asc style]} & rest] +(defn sorted-column [{:keys [on-sort sort-key sort-by asc style class]} & rest] [:th {:on-click (fn [e] (on-sort (toggle-sort-by {:asc asc} sort-key))) - :style style} + :style style + :class class} rest (sort-icon sort-key sort-by asc)]) diff --git a/src/cljs/auto_ap/views/pages/checks.cljs b/src/cljs/auto_ap/views/pages/checks.cljs index 6fcdde0e..9bd8f794 100644 --- a/src/cljs/auto_ap/views/pages/checks.cljs +++ b/src/cljs/auto_ap/views/pages/checks.cljs @@ -12,7 +12,7 @@ [auto-ap.views.components.typeahead :refer [typeahead]] [auto-ap.views.components.paginator :refer [paginator]] [auto-ap.events :as events] - [auto-ap.views.utils :refer [dispatch-event date->str bind-field]] + [auto-ap.views.utils :refer [dispatch-event date->str bind-field nf]] [auto-ap.utils :refer [by]] [auto-ap.views.pages.check :as check] [auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table] @@ -45,7 +45,8 @@ :total :start :end]]]} - :on-success [::received]}})) + :on-success [::received] + :on-error [::events/page-failed]}})) (re-frame/reg-event-fx ::void-check @@ -145,6 +146,7 @@ "Date"] [sorted-column {:on-sort opc :style {:width "8em" :cursor "pointer"} + :class "has-text-right" :sort-key "amount" :sort-by sort-by :asc asc} @@ -176,7 +178,7 @@ (= :debit type) "Debit" :else check-number)] [:td (date->str date) ] - [:td (gstring/format "$%.2f" amount )] + [:td.has-text-right (nf amount )] [:td status] [:td (when (or (= :pending status) diff --git a/src/cljs/auto_ap/views/pages/transactions.cljs b/src/cljs/auto_ap/views/pages/transactions.cljs index 72b505b9..b1173d1b 100644 --- a/src/cljs/auto_ap/views/pages/transactions.cljs +++ b/src/cljs/auto_ap/views/pages/transactions.cljs @@ -10,7 +10,7 @@ [auto-ap.views.components.layouts :refer [side-bar-layout]] [auto-ap.views.components.bank-account-filter :refer [bank-account-filter]] [auto-ap.events :as events] - [auto-ap.views.utils :refer [dispatch-event date->str bind-field]] + [auto-ap.views.utils :refer [dispatch-event date->str bind-field nf]] [auto-ap.utils :refer [by]] [auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table] [auto-ap.subs :as subs])) @@ -114,6 +114,7 @@ [sorted-column {:on-sort opc :style {:width "8em" :cursor "pointer"} :sort-key "amount" + :class "has-text-right" :sort-by sort-by :asc asc} "Amount"] @@ -142,7 +143,7 @@ [:td (:name client)]) [:td description-original] [:td (date->str date) ] - [:td (gstring/format "$%.2f" amount )] + [:td.has-text-right (nf amount )] [:td status] [:td (:name bank-account )] [:td (:yodlee-account-id bank-account )] diff --git a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs index 9e7c07aa..73feca48 100644 --- a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs @@ -79,7 +79,11 @@ (assoc-in [::params] params)) :graphql {:token (-> cofx :db :user) :query-obj (invoice-table/query (-> params (assoc :import-status "imported") (dissoc :invoice-number-like-current)) ) - :on-success [::received]}})) + :on-success [::received] + :on-error [::events/page-failed]}})) + + + (re-frame/reg-event-db ::unmount-invoices (fn [db [_ data]] @@ -805,15 +809,14 @@ (defn unpaid-invoices-content [{:keys [status]}] (r/create-class {:display-name "unpaid-invoices-content" :component-will-unmount (fn [this] - (re-frame/dispatch [::unmount-invoices]) - - ) + (re-frame/dispatch [::unmount-invoices])) :reagent-render (fn [{:keys [status]}] (let [{:keys [checked print-checks-shown? print-checks-loading? advanced-print-shown? vendor-filter]} @(re-frame/subscribe [::invoice-page]) current-client @(re-frame/subscribe [::subs/client]) {check-results-shown? :shown? pdf-url :pdf-url} @(re-frame/subscribe [::check-results])] [:div [:h1.title (str (str/capitalize status) " invoices")] + (when (= status "unpaid") [pay-button {:print-checks-shown? print-checks-shown? :checked-invoices checked :print-checks-loading? print-checks-loading?}]) diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index 3af81e96..42ebad3a 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -1,11 +1,25 @@ (ns auto-ap.views.utils (:require [re-frame.core :as re-frame] + [cljsjs.react-transition-group] + [reagent.core :as reagent] [clojure.spec.alpha :as s] [cljs-time.coerce :as c] [cljs-time.core :as time] [auto-ap.events :as events] [auto-ap.subs :as subs] - [cljs-time.format :as format])) + [cljs-time.format :as format] + [goog.i18n.NumberFormat.Format]) + (:import + (goog.i18n NumberFormat) + (goog.i18n.NumberFormat Format))) +(def nff + (NumberFormat. Format/CURRENCY)) + +(defn- nf + [num] + (.format nff (str num))) + + (defn active-when= [active-page candidate] (when (= active-page candidate) " is-active")) @@ -143,3 +157,13 @@ +(def css-transition-group + (reagent/adapt-react-class js/ReactTransitionGroup.CSSTransition)) + +(defn appearing [{:keys [visible? enter-class exit-class timeout]} & children ] + (let [final-state (reagent/atom visible?)] + (fn [{:keys [visible?]} & children] + [css-transition-group {:in visible? :class-names {:exit exit-class :enter enter-class} :timeout timeout :onEnter (fn [] (reset! final-state true )) :onExited (fn [] (reset! final-state false))} + (if (or @final-state visible?) + (first children) + [:div])])))