now you can create and edit invoices from one form.

This commit is contained in:
BC
2019-02-18 17:04:51 -08:00
parent e2b99961e4
commit 5b622cd4cc
2 changed files with 175 additions and 311 deletions

View File

@@ -13,7 +13,7 @@
valid-matches))
(defn typeahead [{:keys [matches on-change field text-field value class not-found-description
not-found-value auto-focus]}]
disabled not-found-value auto-focus]}]
(let [text (r/atom (or (second (first (filter #(= (first %) value) matches))) ""))
highlighted (r/atom nil)
selected (r/atom (first (first (filter #(= (first %) value) matches))))
@@ -25,68 +25,75 @@
(on-change nil text-description text-value)
(on-change id text-description (or text-value text-description)))))]
(r/create-class
{:reagent-render (fn [{:keys [matches on-change field text-field value class not-found-description]}]
{:reagent-render (fn [{:keys [matches on-change disabled field text-field value class not-found-description]}]
(let [text @text
valid-matches (get-valid-matches matches not-found-description not-found-value text)]
[:div.typeahead
(if @selected
^{:key "typeahead"} [:div.input {:class class
:tab-index "0"
:on-key-up (fn [e]
(if (= 8 (.-keyCode e))
(do
(select [nil "" nil])
true)
false))}
[:div.control
[:div.tags.has-addons
[:span.tag text]
[:a.tag.is-delete {:on-click (fn [] (select [nil "" nil]))}]]]]
^{:key "typeahead"} [:input.input {:type "text"
:class class
:value text
:auto-focus auto-focus
:on-blur (fn [e]
(cond @selected
nil
(if disabled
(#{"" nil} text)
nil
^{:key (str "typeahead" text) } [:input.input {:disabled "disabled" :value text} ]
@highlighted
(do (select @highlighted)
true)
(if @selected
^{:key "typeahead"} [:div.input {:class class
:tab-index "0"
:on-key-up (fn [e]
(if (= 8 (.-keyCode e))
(do
(select [nil "" nil])
true)
false))}
[:div.control
[:div.tags.has-addons
[:span.tag text]
[:a.tag.is-delete {:on-click (fn [] (select [nil "" nil]))}]]]]
^{:key "typeahead"} [:input.input {:type "text"
:class class
:else
(do (select [nil ""])
true)))
:on-key-down (fn [e]
(condp = (.-keyCode e)
; up
38 (do
(when-let [new-match (->> valid-matches
(take-while #(not= % @highlighted))
(last))]
(reset! highlighted new-match))
true)
;; dwon
40 (do
(when-let [new-match (->> valid-matches
(drop-while #(not= % @highlighted))
(drop 1)
(first))]
(reset! highlighted new-match))
true)
13 (do (.preventDefault e)
(when @highlighted
(select @highlighted)
false))
true))
:on-change (fn [e]
(let [new-matches (get-valid-matches matches not-found-description not-found-value (.. e -target -value))]
(reset! highlighted (first new-matches)))
(select [nil (.. e -target -value)]))}])
:value text
:auto-focus auto-focus
:on-blur (fn [e]
(cond @selected
nil
(#{"" nil} text)
nil
@highlighted
(do (select @highlighted)
true)
:else
(do (select [nil ""])
true)))
:on-key-down (fn [e]
(condp = (.-keyCode e)
; up
38 (do
(when-let [new-match (->> valid-matches
(take-while #(not= % @highlighted))
(last))]
(reset! highlighted new-match))
true)
;; dwon
40 (do
(when-let [new-match (->> valid-matches
(drop-while #(not= % @highlighted))
(drop 1)
(first))]
(reset! highlighted new-match))
true)
13 (do (.preventDefault e)
(when @highlighted
(select @highlighted)
false))
true))
:on-change (fn [e]
(let [new-matches (get-valid-matches matches not-found-description not-found-value (.. e -target -value))]
(reset! highlighted (first new-matches)))
(select [nil (.. e -target -value)]))}]))
(cond
(and (seq text)
(not @selected)

View File

@@ -64,16 +64,6 @@
(fn [db]
(-> db ::check-results)))
(re-frame/reg-sub
::new-invoice
(fn [db]
(-> db ::new-invoice)))
(re-frame/reg-sub
::edit-invoice
(fn [db]
(-> db ::edit-invoice)))
(re-frame/reg-sub
::params
(fn [db]
@@ -261,18 +251,31 @@
(re-frame/reg-event-fx
::new-invoice-clicked
(fn [{:keys [db]} _]
{:db (forms/start-form db ::new-invoice {:client-id (:id @(re-frame/subscribe [::subs/client]))
:status :unpaid
:date (date->str (c/now) standard)
:location (first (:locations @(re-frame/subscribe [::subs/client])))})}))
{:db
(-> db
(forms/start-form ::new-invoice {:client-id (:id @(re-frame/subscribe [::subs/client]))
:status :unpaid
:date (date->str (c/now) standard)
:location (first (:locations @(re-frame/subscribe [::subs/client])))}))}))
(re-frame/reg-event-fx
(re-frame/reg-event-db
::edit-invoice
(fn [{:keys [db]} [_ which]]
(fn [db [_ which]]
(let [edit-invoice (update which :date #(date->str % standard))
edit-invoice (assoc edit-invoice :original edit-invoice)]
{:dispatch [::events/modal-status ::edit-invoice {:visible? true}]
:db (assoc-in db [::edit-invoice] edit-invoice)})))
(-> db
(forms/start-form ::new-invoice {:id (:id edit-invoice)
:status (:status edit-invoice)
:date (:date edit-invoice) #_(date->str (:date edit-invoice) standard)
:invoice-number (:invoice-number edit-invoice)
:total (:total edit-invoice)
:original edit-invoice
:vendor-id (:id (:vendor edit-invoice))
:vendor-name (:name (:vendor edit-invoice))
:client-id (:id (:client edit-invoice))
:client-name (:name (:client edit-invoice))})))))
@@ -300,9 +303,9 @@
(re-frame/reg-event-fx
::edit-invoice-save
::edit-invoice-saving
(fn [{:keys [db]} _]
(let [{:keys [date total invoice-number id]} @(re-frame/subscribe [::edit-invoice])]
(let [{{:keys [date total invoice-number id]} :data} @(re-frame/subscribe [::forms/form ::new-invoice])]
{:graphql
{:token (-> db :user)
:query-obj {:venia/operation {:operation/type :mutation
@@ -312,7 +315,7 @@
{:invoice {:id id :invoice-number invoice-number :date date :total total}}
invoice-read]}]}
:on-success [::invoice-edited]
:on-error [::invoice-edit-failed]}})))
:on-error [::forms/save-error ::new-invoice]}})))
(re-frame/reg-event-fx
::unvoid-invoice
@@ -372,10 +375,7 @@
invoices)))
(dissoc ::handwrite-checks))})))
(re-frame/reg-event-fx
::invoice-create-failed
(fn [{:keys [db]} [_ data]]
{:dispatch [::events/modal-failed ::new-invoice (:message data)]}))
(re-frame/reg-event-fx
::invoice-created
@@ -385,27 +385,22 @@
(update-in [::invoice-page :invoices]
(fn [is]
(into [(assoc add-invoice :class "live-added")]
is)))
(dissoc ::new-invoice))}))
is))))}))
(re-frame/reg-event-fx
::invoice-edited
(fn [{:keys [db]} [_ {:keys [edit-invoice]}]]
{:dispatch [::events/modal-completed ::edit-invoice]
:db (-> db
{:db (-> db
(forms/stop-form ::new-invoice)
(update-in [::invoice-page :invoices]
(fn [is]
(mapv (fn [i]
(if (= (:id i) (:id edit-invoice))
(assoc edit-invoice :class "live-added")
i)) is)))
(dissoc ::edit-invoice))}))
i)) is))))}))
(re-frame/reg-event-fx
::invoice-edit-failed
(fn [{:keys [db]} [_ data]]
{:dispatch [::events/modal-failed ::edit-invoice "That invoice already exists."]}))
(re-frame/reg-event-fx
::invoice-unvoided
@@ -561,91 +556,12 @@
[:client-id] value
[:location] first-location]})))
(defn new-invoice-modal []
(let [data @(re-frame/subscribe [::new-invoice])
change-event [::events/change-form [::new-invoice]]
locations (get-in @(re-frame/subscribe [::subs/clients-by-id]) [(:client-id data) :locations])
should-select-location? (and locations
(> (count locations) 1))]
[action-modal {:id ::new-invoice
:title "New Invoice"
:action-text "Create"
:save-event [::create-invoice]
:can-submit? (s/valid? ::invoice/invoice data)}
(when-not @(re-frame/subscribe [::subs/client])
[horizontal-field
[:label.label "Client"]
[bind-field
[typeahead {:matches (map (fn [x] [(:id x) (:name x)]) @(re-frame/subscribe [::subs/clients]))
:type "typeahead"
:field [:client-id]
:event [::change-new-invoice-client [::new-invoice]]
:spec ::invoice/client-id
:subscription data}]]])
(when should-select-location?
[horizontal-field
[:label.label "Location"]
[:div.select
[bind-field
[:select {:type "select"
:field [:location]
:spec (set locations)
:event change-event
:subscription data}
(map (fn [l] [:option {:value l} l]) locations)]]]])
[horizontal-field
[:label.label "Vendor"]
[bind-field
[typeahead {:matches (map (fn [x] [(:id x) (:name x)]) @(re-frame/subscribe [::subs/vendors]))
:type "typeahead"
:auto-focus true
:field [:vendor-id]
:text-field [:vendor-name]
:event change-event
:spec (s/nilable ::invoice/vendor-id)
:subscription data}]]]
[horizontal-field
[:label.label "Date"]
[bind-field
[:input.input {:type "date"
:field [:date]
:event change-event
:spec ::invoice/date
:subscription data}]]]
[horizontal-field
[:label.label "Invoice #"]
[bind-field
[:input.input {:type "text"
:field [:invoice-number]
:event change-event
:spec ::invoice/invoice-number
:subscription data}]]]
[horizontal-field
[:label.label "Total"]
[:div.field.has-addons.is-extended
[:p.control [:a.button.is-static "$"]]
[:p.control
[bind-field
[:input.input {:type "number"
:field [:total]
:event change-event
:subscription data
:spec ::invoice/total
:step "0.01"}]]]]]
]))
(defn edit-invoice-form [{:keys [can-change-amount?]}]
[forms/side-bar-form {:form ::new-invoice }
(let [{:keys [data status active? error]} @(re-frame/subscribe [::forms/form ::new-invoice])
can-change-amount? (= (:status data) :unpaid)
(let [{:keys [data active? error]} @(re-frame/subscribe [::forms/form ::new-invoice])
_ (println data)
exists? (:id data)
can-change-amount? (#{:unpaid ":unpaid"} (:status data))
change-event [::forms/change ::new-invoice]
locations (get-in @(re-frame/subscribe [::subs/clients-by-id]) [(:client-id data) :locations])
min-total (if (= (:total (:original data)) (:outstanding-balance (:original data)))
@@ -653,6 +569,7 @@
(- (:total (:original data)) (:outstanding-balance (:original data))))
should-select-location? (and locations
(> (count locations) 1))]
^{:key (or (:id data) "new")}
[:form
[:h1.title.is-2 "New Invoice"]
(when-not @(re-frame/subscribe [::subs/client])
@@ -663,27 +580,30 @@
[typeahead {:matches (map (fn [x] [(:id x) (:name x)]) @(re-frame/subscribe [::subs/clients]))
:type "typeahead"
:field [:client-id]
:disabled exists?
:event [::change-new-invoice-client [::new-invoice]]
:spec ::invoice/client-id
:subscription data}]]]])
(when should-select-location?
[horizontal-field
[:label.label "Location"]
[:div.select
[bind-field
[:select {:type "select"
:field [:location]
:spec (set locations)
:event change-event
:subscription data}
(map (fn [l] [:option {:value l} l]) locations)]]]])
(when (and should-select-location? (not exists?))
[:div.field
[:p.help "Location"]
[:div.control
[:div.select
[bind-field
[:select {:type "select"
:field [:location]
:spec (set locations)
:event change-event
:subscription data}
(map (fn [l] [:option {:value l} l]) locations)]]]]])
[:div.field
[:p.help "Vendor"]
[:div.control
[bind-field
[typeahead {:matches (map (fn [x] [(:id x) (:name x)]) @(re-frame/subscribe [::subs/vendors]))
:type "typeahead"
:disabled exists?
:auto-focus true
:field [:vendor-id]
:text-field [:vendor-name]
@@ -730,85 +650,23 @@
(when error
[:div.notification.is-warning.animated.fadeInUp
error])
(println (s/explain-data ::invoice/invoice data))
[:submit.button.is-large.is-primary {:disabled (if (and (s/valid? ::invoice/invoice data)
(or (not min-total) (>= (:total data) min-total)))
""
"disabled")
:on-click (dispatch-event [::create-invoice])
:class (str @(re-frame/subscribe [::forms/loading-class ::new-invoice]) (when error " animated shake"))} "Save"]
]
#_[action-modal {:id ::edit-invoice
:title "Update Invoice"
:action-text "Save"
:save-event [::edit-invoice-save]
:can-submit? (and #_(s/valid? ::invoice/invoice data)
(or (not min-total) (>= (:total data) min-total)))}
])]
:on-click (fn [e]
#_(when (.-stopPropagation e)
(.stopPropagation e)
(.preventDefault e))
(if exists?
(re-frame/dispatch-sync [::edit-invoice-saving])
(re-frame/dispatch-sync [::create-invoice])))
:class (str @(re-frame/subscribe [::forms/loading-class ::new-invoice])
(when error " animated shake"))} "Save"]])]
)
(defn edit-invoice-modal [{:keys [can-change-amount?]}]
(let [data @(re-frame/subscribe [::edit-invoice])
change-event [::events/change-form [::edit-invoice]]
locations (get-in @(re-frame/subscribe [::subs/clients-by-id]) [(:client-id data) :locations])
min-total (if (= (:total (:original data)) (:outstanding-balance (:original data)))
nil
(- (:total (:original data)) (:outstanding-balance (:original data))))
should-select-location? (and locations
(> (count locations) 1))]
[action-modal {:id ::edit-invoice
:title "Update Invoice"
:action-text "Save"
:save-event [::edit-invoice-save]
:can-submit? (and #_(s/valid? ::invoice/invoice data)
(or (not min-total) (>= (:total data) min-total)))}
[horizontal-field
[:label.label "Date"]
[bind-field
[:input.input {:type "date"
:field [:date]
:event change-event
:spec ::invoice/date
:subscription data}]]]
[horizontal-field
[:label.label "Invoice #"]
[bind-field
[:input.input {:type "text"
:field [:invoice-number]
:event change-event
:spec ::invoice/invoice-number
:subscription data}]]]
[horizontal-field
[:label.label "Total"]
[:div.field.has-addons.is-extended
[:p.control [:a.button.is-static "$"]]
[:p.control
[bind-field
[:input.input {:type "number"
:field [:total]
:disabled (if can-change-amount? "" "disabled")
:event change-event
:min min-total
:subscription data
:spec ::invoice/total
:step "0.01"}]]]]]]))
(re-frame/reg-event-db
::change-selected-vendor
@@ -852,59 +710,60 @@
[:div.is-pulled-right
[:button.button.is-success {:on-click (dispatch-event [::new-invoice-clicked])} "New Invoice"]
(when current-client
[:div.dropdown.is-right {:class (if print-checks-shown?
"is-active"
"")}
[:div.dropdown-trigger
[:button.button.is-success {:aria-haspopup true
:on-click (dispatch-event [::print-checks-clicked ])
:disabled (if (and (seq checked-invoices)
(->> checked-invoices
vals
(group-by #(get-in % [:vendor :id]))
(reduce-kv (fn [negative? _ invoices]
(or negative? (< (reduce + 0 (map (fn [x] (-> x :outstanding-balance js/parseFloat)) invoices) ) 0)))
false)
not))
""
"disabled")
[:div.buttons
[:button.button.is-success {:on-click (dispatch-event [::new-invoice-clicked])} "New Invoice"]
(when current-client
[:div.dropdown.is-right {:class (if print-checks-shown?
"is-active"
"")}
[:div.dropdown-trigger
[:button.button.is-success {:aria-haspopup true
:on-click (dispatch-event [::print-checks-clicked ])
:disabled (if (and (seq checked-invoices)
(->> checked-invoices
vals
(group-by #(get-in % [:vendor :id]))
(reduce-kv (fn [negative? _ invoices]
(or negative? (< (reduce + 0 (map (fn [x] (-> x :outstanding-balance js/parseFloat)) invoices) ) 0)))
false)
not))
""
"disabled")
:class (if print-checks-loading?
"is-loading"
"")}
"Pay "
(when (> (count checked-invoices ))
(str
(count checked-invoices)
" invoices "
"(" (->> checked-invoices
vals
(map (comp js/parseFloat :outstanding-balance))
(reduce + 0)
(gstring/format "$%.2f" ))
")"))
[:span " "]
[:span.icon.is-small [:i.fa.fa-angle-down {:aria-hidden "true"}]]]]
[:div.dropdown-menu {:role "menu"}
[:div.dropdown-content
(list
(for [{:keys [id number name type]} (->> (:bank-accounts current-client) (filter :visible) (sort-by :sort-order))]
(if (= :cash type)
^{:key id} [:a.dropdown-item {:on-click (dispatch-event [::print-checks id :cash])} "With cash"]
(list
^{:key (str id "-check")} [:a.dropdown-item {:on-click (dispatch-event [::print-checks id :check])} "Print checks from " name]
^{:key (str id "-debit")} [:a.dropdown-item {:on-click (dispatch-event [::print-checks id :debit])} "Debit from " name])))
^{:key "advanced-divider"} [:hr.dropdown-divider]
:class (if print-checks-loading?
"is-loading"
"")}
"Pay "
(when (> (count checked-invoices ))
(str
(count checked-invoices)
" invoices "
"(" (->> checked-invoices
vals
(map (comp js/parseFloat :outstanding-balance))
(reduce + 0)
(gstring/format "$%.2f" ))
")"))
[:span " "]
[:span.icon.is-small [:i.fa.fa-angle-down {:aria-hidden "true"}]]]]
[:div.dropdown-menu {:role "menu"}
[:div.dropdown-content
(list
(for [{:keys [id number name type]} (->> (:bank-accounts current-client) (filter :visible) (sort-by :sort-order))]
(if (= :cash type)
^{:key id} [:a.dropdown-item {:on-click (dispatch-event [::print-checks id :cash])} "With cash"]
(list
^{:key (str id "-check")} [:a.dropdown-item {:on-click (dispatch-event [::print-checks id :check])} "Print checks from " name]
^{:key (str id "-debit")} [:a.dropdown-item {:on-click (dispatch-event [::print-checks id :debit])} "Debit from " name])))
^{:key "advanced-divider"} [:hr.dropdown-divider]
(when (= 1 (count checked-invoices))
^{:key "handwritten"} [:a.dropdown-item {:on-click (dispatch-event [::handwrite-checks])} "Handwritten Check..."])
^{:key "advanced"} [:a.dropdown-item {:on-click (dispatch-event [::advanced-print-checks])} "Advanced..."])]]])]
(when (= 1 (count checked-invoices))
^{:key "handwritten"} [:a.dropdown-item {:on-click (dispatch-event [::handwrite-checks])} "Handwritten Check..."])
^{:key "advanced"} [:a.dropdown-item {:on-click (dispatch-event [::advanced-print-checks])} "Advanced..."])]]])]]
[:div.is-pulled-right
(into [:div.tags {:style {:margin-right ".5 rem;"}}] (map (fn [[id invoice]] [:span.tag.is-medium (:invoice-number invoice) [:button.delete.is-small {:on-click (dispatch-event [::toggle-check id invoice])}]]) checked-invoices))]]
))
@@ -946,8 +805,6 @@
:expense-event [::expense-accounts-dialog/change-expense-accounts]}]
[print-checks-modal]
[new-invoice-modal]
[edit-invoice-modal {:can-change-amount? (= status "unpaid")}]
[handwrite-checks-modal]
[change-expense-accounts-modal {:updated-event [::expense-accounts-updated]}]
(when check-results-shown?