ux improvements for erros.

This commit is contained in:
Bryce Covert
2019-02-20 11:39:19 -08:00
parent 91d9ea346d
commit 985b5339af
12 changed files with 134 additions and 56 deletions

View File

@@ -14,10 +14,17 @@
@keyframes appear { @keyframes appear {
from { opacity: 0; } from { opacity: 0; }
} }
@keyframes slideOut {
to {
-webkit-transform: translateY(10px);
transform: translateY(10px);
opacity: 0;
}
}
@keyframes slideIn { @keyframes slideIn {
from { from {
-webkit-transform: translateY(-100%); -webkit-transform: translateY(-10px);
transform: translateY(-100%); transform: translateY(-10px);
opacity: 0; 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 { .slide-in-right {
animation: grow-width 0.5s ease both; animation: grow-width 0.5s ease both;
} }

View File

@@ -151,6 +151,7 @@
:body :body
:errors :errors
(dates->date-times) (dates->date-times)
(map #(assoc % :status (:status response)))
(conj on-error) (conj on-error)
(re-frame/dispatch))) (re-frame/dispatch)))
(->> response (->> response

View File

@@ -124,8 +124,10 @@
(if (and (not= :login handler) (not (:user db))) (if (and (not= :login handler) (not (:user db)))
{:redirect "/login" {:redirect "/login"
:db (assoc db :active-page :login :db (assoc db :active-page :login
:page-failure nil
:auto-ap.forms/forms nil)} :auto-ap.forms/forms nil)}
{:db (assoc db :active-page handler {:db (assoc db :active-page handler
:page-failure nil
:query-params params :query-params params
:auto-ap.forms/forms nil)}))) :auto-ap.forms/forms nil)})))
@@ -219,6 +221,12 @@
::save-complete ::save-complete
(fn [{:keys [db]} [_ vendor]] (fn [{:keys [db]} [_ vendor]]
{:dispatch [::modal-completed :auto-ap.views.main/user-editing-vendor ] {:dispatch [::modal-completed :auto-ap.views.main/user-editing-vendor ]
:db (-> db :db (-> db
(dissoc :user-editing-vendor) (dissoc :user-editing-vendor)
(assoc-in [:vendors (:id vendor)] (:upsert-vendor 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)))

View File

@@ -119,3 +119,11 @@
(fn [db] (fn [db]
(map (fn [[k v]] (assoc v :id k)) (map (fn [[k v]] (assoc v :id k))
chooseable-expense-accounts))) 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)))))

View File

@@ -1,6 +1,7 @@
(ns auto-ap.views.components.dropdown (ns auto-ap.views.components.dropdown
(:require [reagent.core :as r] (:require [reagent.core :as r]
[auto-ap.events :as events] [auto-ap.events :as events]
[auto-ap.views.utils :refer [appearing]]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[auto-ap.subs :as subs])) [auto-ap.subs :as subs]))
@@ -22,9 +23,11 @@
"")} "")}
[:div.dropdown-trigger header] [:div.dropdown-trigger header]
[:div.dropdown-menu {:role "menu"} [appearing {:visible? menu-active? :enter-class "appear" :exit-class "disappear" :timeout 200}
[:div.dropdown-content [:div.dropdown-menu {:role "menu"}
(when menu-active? [:div.dropdown-content
[drop-down-contents {:id id} (when menu-active?
child])]]]))}))) [drop-down-contents {:id id}
child])]]]
]))})))

View File

@@ -1,7 +1,7 @@
(ns auto-ap.views.components.invoice-table (ns auto-ap.views.components.invoice-table
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[auto-ap.subs :as subs] [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.paginator :refer [paginator]]
[auto-ap.views.components.sorter :refer [sorted-column]] [auto-ap.views.components.sorter :refer [sorted-column]]
[auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]] [auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]]
@@ -9,7 +9,9 @@
[reagent.core :as reagent] [reagent.core :as reagent]
[clojure.string :as str] [clojure.string :as str]
[cljs-time.format :as format] [cljs-time.format :as format]
[goog.string :as gstring])) [goog.string :as gstring]
[goog.i18n.NumberFormat.Format])
)
;; TODO graphql schema enforcement ;; TODO graphql schema enforcement
;; TODO postgres constraints for data integrity ;; TODO postgres constraints for data integrity
@@ -56,8 +58,6 @@
:start :start
:end]]]}) :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]}] (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]) (let [visible-checks @(re-frame/subscribe [::visible-checks])
visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts]) visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts])
@@ -120,6 +120,7 @@
[sorted-column {:on-sort opc [sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"} :style {:width "8em" :cursor "pointer"}
:sort-key "total" :sort-key "total"
:class "has-text-right"
:sort-by sort-by :sort-by sort-by
:asc asc} :asc asc}
"Amount"] "Amount"]
@@ -127,6 +128,7 @@
[sorted-column {:on-sort opc [sorted-column {:on-sort opc
:style {:width "10em" :cursor "pointer"} :style {:width "10em" :cursor "pointer"}
:sort-key "outstanding-balance" :sort-key "outstanding-balance"
:class "has-text-right"
:sort-by sort-by :sort-by sort-by
:asc asc} :asc asc}
"Outstanding"] "Outstanding"]
@@ -162,8 +164,8 @@
[:td (date->str date) ] [:td (date->str date) ]
[:td (str/join ", " (set (map :location expense-accounts)))] [:td (str/join ", " (set (map :location expense-accounts)))]
[:td (gstring/format "$%.2f" total )] [:td.has-text-right (nf total )]
[:td (gstring/format "$%.2f" outstanding-balance )] [:td.has-text-right (nf outstanding-balance )]
[:td.expandable [:td.expandable
@@ -192,7 +194,7 @@
(when (and on-void-invoice (= (:outstanding-balance i) (:total i)) (not= ":voided" (:status i))) (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"}}]]]) [: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))) (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) (when (seq payments)
[drop-down {:id [::payments id] [drop-down {:id [::payments id]
:header [:button.button.badge {:data-badge (str (clojure.core/count payments)) :header [:button.button.badge {:data-badge (str (clojure.core/count payments))
@@ -211,15 +213,4 @@
"has-text-success")]} "has-text-success")]}
[:i.fa.fa-money]] (str " " (:check-number (:payment payment)) " (" (gstring/format "$%.2f" (:amount payment) ) ") " [:i.fa.fa-money]] (str " " (:check-number (:payment payment)) " (" (gstring/format "$%.2f" (:amount payment) ) ") "
(when (= :cleared (:status (:payment payment))) (when (= :cleared (:status (:payment payment)))
(str " - " (:post-date (:transaction (: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
]]])]
]]))]]]))))

View File

@@ -7,11 +7,10 @@
[auto-ap.routes :as routes] [auto-ap.routes :as routes]
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.events :as events] [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]])) [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 ] (defn navbar-drop-down-contents [{:keys [id]} children ]
(let [toggle-fn (fn [] (re-frame/dispatch [::events/toggle-menu id]))] (let [toggle-fn (fn [] (re-frame/dispatch [::events/toggle-menu id]))]
@@ -32,10 +31,10 @@
(.stopPropagation e) (.stopPropagation e)
(re-frame/dispatch [::events/toggle-menu id]) (re-frame/dispatch [::events/toggle-menu id])
true)} header] true)} header]
[:div {:class "navbar-dropdown"} [appearing {:visible? menu-active? :enter-class "appear" :exit-class "disappear" :timeout 200}
(when menu-active? [:div {:class "navbar-dropdown"}
[navbar-drop-down-contents {:id id} [navbar-drop-down-contents {:id id}
[:div child]])]]))}))) [:div child]]]]]))})))
(defn login-dropdown [] (defn login-dropdown []
(let [user (re-frame/subscribe [::subs/user]) (let [user (re-frame/subscribe [::subs/user])
@@ -105,13 +104,9 @@
[:strong "Integreat"] ]]]) [:strong "Integreat"] ]]])
(defn appearing-side-bar [{:keys [visible?]} & children ] (defn appearing-side-bar [{:keys [visible?]} & children ]
(let [final-state (reagent/atom visible?)] [appearing {:visible? visible? :enter-class "slide-in-right" :exit-class "slide-out-right" :timeout 500}
(fn [{:keys [visible?]} & children] [:aside {:class "column is-3 aside menu" :style {:height "calc(100vh - 46px)" :overflow "auto"}}
[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))} [:div.sub-main {} children ]]])
(if (or @final-state visible?)
[:aside {:class "column is-3 aside menu" :style {:height "calc(100vh - 46px)" :overflow "auto"}}
[:div.sub-main {} children ]]
[:div])])))
(defn side-bar-layout [{:keys [side-bar main ap bottom right-side-bar]}] (defn side-bar-layout [{:keys [side-bar main ap bottom right-side-bar]}]
(let [ap @(re-frame/subscribe [::subs/active-page]) (let [ap @(re-frame/subscribe [::subs/active-page])
@@ -125,7 +120,11 @@
]]] ]]]
[:div {:class "column messages hero " :style { :overflow "auto" }, :id "message-feed"} [:div {:class "column messages hero " :style { :overflow "auto" }, :id "message-feed"}
^{:key (str "active-page-" (:name client))} ^{: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 (when right-side-bar
right-side-bar) right-side-bar)
] ]

View File

@@ -25,9 +25,10 @@
[:span.icon [:span.icon
[:i.fa.fa-sort]])) [: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))) [:th {:on-click (fn [e] (on-sort (toggle-sort-by {:asc asc} sort-key)))
:style style} :style style
:class class}
rest rest
(sort-icon sort-key sort-by asc)]) (sort-icon sort-key sort-by asc)])

View File

@@ -12,7 +12,7 @@
[auto-ap.views.components.typeahead :refer [typeahead]] [auto-ap.views.components.typeahead :refer [typeahead]]
[auto-ap.views.components.paginator :refer [paginator]] [auto-ap.views.components.paginator :refer [paginator]]
[auto-ap.events :as events] [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.utils :refer [by]]
[auto-ap.views.pages.check :as check] [auto-ap.views.pages.check :as check]
[auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table] [auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table]
@@ -45,7 +45,8 @@
:total :total
:start :start
:end]]]} :end]]]}
:on-success [::received]}})) :on-success [::received]
:on-error [::events/page-failed]}}))
(re-frame/reg-event-fx (re-frame/reg-event-fx
::void-check ::void-check
@@ -145,6 +146,7 @@
"Date"] "Date"]
[sorted-column {:on-sort opc [sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"} :style {:width "8em" :cursor "pointer"}
:class "has-text-right"
:sort-key "amount" :sort-key "amount"
:sort-by sort-by :sort-by sort-by
:asc asc} :asc asc}
@@ -176,7 +178,7 @@
(= :debit type) "Debit" (= :debit type) "Debit"
:else check-number)] :else check-number)]
[:td (date->str date) ] [:td (date->str date) ]
[:td (gstring/format "$%.2f" amount )] [:td.has-text-right (nf amount )]
[:td status] [:td status]
[:td [:td
(when (or (= :pending status) (when (or (= :pending status)

View File

@@ -10,7 +10,7 @@
[auto-ap.views.components.layouts :refer [side-bar-layout]] [auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.components.bank-account-filter :refer [bank-account-filter]] [auto-ap.views.components.bank-account-filter :refer [bank-account-filter]]
[auto-ap.events :as events] [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.utils :refer [by]]
[auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table] [auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table]
[auto-ap.subs :as subs])) [auto-ap.subs :as subs]))
@@ -114,6 +114,7 @@
[sorted-column {:on-sort opc [sorted-column {:on-sort opc
:style {:width "8em" :cursor "pointer"} :style {:width "8em" :cursor "pointer"}
:sort-key "amount" :sort-key "amount"
:class "has-text-right"
:sort-by sort-by :sort-by sort-by
:asc asc} :asc asc}
"Amount"] "Amount"]
@@ -142,7 +143,7 @@
[:td (:name client)]) [:td (:name client)])
[:td description-original] [:td description-original]
[:td (date->str date) ] [:td (date->str date) ]
[:td (gstring/format "$%.2f" amount )] [:td.has-text-right (nf amount )]
[:td status] [:td status]
[:td (:name bank-account )] [:td (:name bank-account )]
[:td (:yodlee-account-id bank-account )] [:td (:yodlee-account-id bank-account )]

View File

@@ -79,7 +79,11 @@
(assoc-in [::params] params)) (assoc-in [::params] params))
:graphql {:token (-> cofx :db :user) :graphql {:token (-> cofx :db :user)
:query-obj (invoice-table/query (-> params (assoc :import-status "imported") (dissoc :invoice-number-like-current)) ) :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 (re-frame/reg-event-db
::unmount-invoices ::unmount-invoices
(fn [db [_ data]] (fn [db [_ data]]
@@ -805,15 +809,14 @@
(defn unpaid-invoices-content [{:keys [status]}] (defn unpaid-invoices-content [{:keys [status]}]
(r/create-class {:display-name "unpaid-invoices-content" (r/create-class {:display-name "unpaid-invoices-content"
:component-will-unmount (fn [this] :component-will-unmount (fn [this]
(re-frame/dispatch [::unmount-invoices]) (re-frame/dispatch [::unmount-invoices]))
)
:reagent-render (fn [{:keys [status]}] :reagent-render (fn [{:keys [status]}]
(let [{:keys [checked print-checks-shown? print-checks-loading? advanced-print-shown? vendor-filter]} @(re-frame/subscribe [::invoice-page]) (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]) current-client @(re-frame/subscribe [::subs/client])
{check-results-shown? :shown? pdf-url :pdf-url} @(re-frame/subscribe [::check-results])] {check-results-shown? :shown? pdf-url :pdf-url} @(re-frame/subscribe [::check-results])]
[:div [:div
[:h1.title (str (str/capitalize status) " invoices")] [:h1.title (str (str/capitalize status) " invoices")]
(when (= status "unpaid") (when (= status "unpaid")
[pay-button {:print-checks-shown? print-checks-shown? :checked-invoices checked :print-checks-loading? print-checks-loading?}]) [pay-button {:print-checks-shown? print-checks-shown? :checked-invoices checked :print-checks-loading? print-checks-loading?}])

View File

@@ -1,11 +1,25 @@
(ns auto-ap.views.utils (ns auto-ap.views.utils
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[cljsjs.react-transition-group]
[reagent.core :as reagent]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cljs-time.coerce :as c] [cljs-time.coerce :as c]
[cljs-time.core :as time] [cljs-time.core :as time]
[auto-ap.events :as events] [auto-ap.events :as events]
[auto-ap.subs :as subs] [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] (defn active-when= [active-page candidate]
(when (= active-page candidate) " is-active")) (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])])))