Revert "Revert "Enabling signature editing.""

This reverts commit b52f3231e8.
This commit is contained in:
Bryce Covert
2020-12-21 08:47:53 -08:00
parent b52f3231e8
commit 3d0b190702
17 changed files with 1917 additions and 140 deletions

View File

@@ -67,6 +67,7 @@
{:fields {:id {:type :id}
:name {:type 'String}
:code {:type 'String}
:signature_file {:type 'String}
:week_a_debits {:type :money}
:week_a_credits {:type :money}
:week_b_debits {:type :money}
@@ -651,6 +652,7 @@
:edit_client {:fields {:id {:type :id}
:name {:type 'String}
:code {:type 'String}
:signature_data {:type 'String}
:email {:type 'String}
:week_a_credits {:type :money}
:week_a_debits {:type :money}

View File

@@ -3,9 +3,14 @@
[auto-ap.datomic.clients :as d-clients]
[auto-ap.graphql.utils :refer [->graphql assert-admin can-see-client?]]
[clj-time.coerce :as coerce]
[config.core :refer [env]]
[clojure.string :as str]
[clojure.tools.logging :as log]
[datomic.api :as d]))
[datomic.api :as d]
[clojure.java.io :as io]
[amazonica.aws.s3 :as s3])
(:import [org.apache.commons.codec.binary Base64]
java.util.UUID))
(defn assert-client-code-is-unique [code]
(when (seq (d/query {:query {:find '[?id]
@@ -14,6 +19,21 @@
:args [(d/db conn) code]}))
(throw (ex-info "Client is not unique" {:validation-error (str "Client code '" code "' is not unique.")}))))
(defn upload-signature-data [signature-data]
(let [prefix "data:image/jpeg;base64,"]
(when signature-data
(when-not (str/starts-with? signature-data prefix)
(throw (ex-info "Invalid signature image" {:validation-error (str "Invalid signature image.")})))
(let [signature-id (str (UUID/randomUUID))
raw-bytes (Base64/decodeBase64 (subs signature-data (count prefix)))]
(s3/put-object :bucket-name "integreat-signature-images" #_(:data-bucket env)
:key (str signature-id ".jpg")
:input-stream (io/make-input-stream raw-bytes {})
:metadata {:content-type "image/jpeg"}
:canned-acl "public-read")
(str "https://integreat-signature-images.s3.amazonaws.com/" signature-id ".jpg")
))))
(defn edit-client [context {:keys [edit_client new_bank_accounts] :as args} value]
(assert-admin (:id context))
(when-not (:id edit_client)
@@ -21,6 +41,7 @@
(let [client (when (:id edit_client) (d-clients/get-by-id (:id edit_client)))
id (or (:db/id client) "new-client")
signature-file (upload-signature-data (:signature_data edit_client))
_ (when client
(audit-transact (into
(mapv (fn [lm] [:db/retractEntity (:db/id lm)]) (:client/location-matches client))
@@ -36,6 +57,7 @@
(:client/code client))
:client/name (:name edit_client)
:client/matches (:matches edit_client)
:client/signature-file signature-file
:client/email (:email edit_client)
:client/locations (filter identity (:locations edit_client))
:client/week-a-debits (:week_a_debits edit_client)

View File

@@ -11,8 +11,7 @@
[auto-ap.effects :as effects]
[pushy.core :as pushy]
[auto-ap.history :as p]
[bidi.bidi :as bidi]
[cljsjs.recharts]))
[bidi.bidi :as bidi]))
(defn dev-setup []
(when true

View File

@@ -56,7 +56,7 @@
:graphql {:token token
:query-obj {:venia/queries [[:client
[:id :name :code :email :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits :locations [:location-matches [:id :location :match]] [:bank-accounts [:id :start-date :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations :include-in-reports] ]
[:id :name :signature-file :code :email :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits :locations [:location-matches [:id :location :match]] [:bank-accounts [:id :start-date :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations :include-in-reports] ]
[:address [:street1 :street2 :city :state :zip]]
[:forecasted-transactions [:id :amount :identifier :day-of-month]]]]
[:vendor

View File

@@ -119,7 +119,7 @@
(defn settles [{:keys [event time key]}]
(i/->interceptor
:id :settles
:befor (fn [context]
:before (fn [context]
context)
:after (fn [context]
(i/assoc-effect context :dispatch-debounce {:event event

View File

@@ -1,6 +1,5 @@
(ns auto-ap.views.components.layouts
(:require
[cljsjs.react-transition-group]
[reagent.core :as reagent]
[re-frame.core :as re-frame]
[bidi.bidi :as bidi]

View File

@@ -9,7 +9,58 @@
[cljs-time.core :as t]
[clojure.spec.alpha :as s]
[re-frame.core :as re-frame]
[clojure.string :as str]))
[reagent.core :as r]
[clojure.string :as str]
[react-signature-canvas :as canvas]
[auto-ap.views.components.buttons :as buttons]))
(def signature-canvas (r/adapt-react-class (.-default canvas)))
(defn signature [{:keys [signature-file signature-data on-change]}]
(let [canvas (atom nil)
edit-mode? (r/atom false)
w (* 1.5 464)
h (* 1.5 174)]
(fn [{:keys [signature-file signature-data on-change]}]
[:div
(if @edit-mode?
[:div
[signature-canvas {"canvasProps" {"width" w
"height" h
"style" #js {"border" "1px solid rgb(219,219,219)"
"border-radius" "4px"}}
"backgroundColor" "#FFF"
:ref (fn [el]
(reset! canvas el))}]
[:div.buttons
[:a.button.is-primary.is-outlined {:on-click (fn []
(on-change (.toDataURL @canvas "image/jpeg"))
(reset! edit-mode? false))}
"Accept"]
[:a.button.is-warning.is-outlined {:on-click (fn []
(.clear @canvas)
(reset! edit-mode? false))}
"Cancel"]]]
(if (or signature-data signature-file)
[:div
[:img {:src (or signature-data signature-file)
:style {:width w
:height h}}]
[:div.buttons
[:a.button {:on-click (fn []
(reset! edit-mode? true))}
"Replace Signature"]]]
[:div
[:div.has-text-centered.is-vcentered {:style {:width w
:height h
:margin-bottom "8px"
:background "#EEE"}}
"No signature"]
[:div.buttons
[:a.button.is-primary.is-outlined {:on-click (fn []
(reset! edit-mode? true))}
"New Signature"]]]))
])))
(re-frame/reg-sub
::can-submit
@@ -23,56 +74,58 @@
::new-client-request
:<- [::forms/form ::form]
(fn [{new-client-data :data} _]
{:id (:id new-client-data),
:name (:name new-client-data)
:code (:code new-client-data) ;; TODO add validation can't change
:email (:email new-client-data)
:locations (mapv :location (:locations new-client-data))
:matches (mapv :match (:matches new-client-data))
:location-matches (:location-matches new-client-data)
:week-a-credits (:week-a-credits new-client-data)
:week-a-debits (:week-a-debits new-client-data)
:week-b-credits (:week-b-credits new-client-data)
:week-b-debits (:week-b-debits new-client-data)
:address {:street1 (:street1 (:address new-client-data))
:street2 (:street2 (:address new-client-data)),
:city (:city (:address new-client-data))
:state (:state (:address new-client-data))
:zip (:zip (:address new-client-data))}
:forecasted-transactions (map (fn [{:keys [id day-of-month identifier amount]}]
{:id id
:day-of-month day-of-month
:identifier identifier
:amount amount})
(:forecasted-transactions new-client-data))
:bank-accounts (map (fn [{:keys [number name check-number include-in-reports type id code start-date bank-name routing bank-code new? sort-order visible yodlee-account-id locations]}]
{:number number
:name name
:check-number check-number
:include-in-reports include-in-reports
:start-date (cond (not start-date)
nil
(cond->
{:id (:id new-client-data),
:name (:name new-client-data)
:code (:code new-client-data) ;; TODO add validation can't change
:email (:email new-client-data)
:locations (mapv :location (:locations new-client-data))
:matches (mapv :match (:matches new-client-data))
:location-matches (:location-matches new-client-data)
:week-a-credits (:week-a-credits new-client-data)
:week-a-debits (:week-a-debits new-client-data)
:week-b-credits (:week-b-credits new-client-data)
:week-b-debits (:week-b-debits new-client-data)
:address {:street1 (:street1 (:address new-client-data))
:street2 (:street2 (:address new-client-data)),
:city (:city (:address new-client-data))
:state (:state (:address new-client-data))
:zip (:zip (:address new-client-data))}
:signature-data (:signature-data new-client-data)
:forecasted-transactions (map (fn [{:keys [id day-of-month identifier amount]}]
{:id id
:day-of-month day-of-month
:identifier identifier
:amount amount})
(:forecasted-transactions new-client-data))
:bank-accounts (map (fn [{:keys [number name check-number include-in-reports type id code start-date bank-name routing bank-code new? sort-order visible yodlee-account-id locations]}]
{:number number
:name name
:check-number check-number
:include-in-reports include-in-reports
:start-date (cond (not start-date)
nil
(instance? goog.date.Date start-date)
(date->str start-date standard)
(instance? goog.date.Date start-date)
(date->str start-date standard)
:else
start-date
)
:type type
:id id
:sort-order sort-order
:visible visible
:locations (mapv :location locations)
:yodlee-account-id (when-not (str/blank? yodlee-account-id)
(js/parseInt yodlee-account-id))
:code (if new?
(str (:code new-client-data) "-" code)
code)
:bank-name bank-name
:routing routing
:bank-code bank-code})
(:bank-accounts new-client-data))}))
:else
start-date
)
:type type
:id id
:sort-order sort-order
:visible visible
:locations (mapv :location locations)
:yodlee-account-id (when-not (str/blank? yodlee-account-id)
(js/parseInt yodlee-account-id))
:code (if new?
(str (:code new-client-data) "-" code)
code)
:bank-name bank-name
:routing routing
:bank-code bank-code})
(:bank-accounts new-client-data))})))
(re-frame/reg-event-fx
::editing
@@ -107,7 +160,7 @@
:operation/name "EditClient"}
:venia/queries [{:query/data [:edit-client
{:edit-client new-client-req}
[:id :name :code :email :locations :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits
[:id :name :signature-file :code :email :locations :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits
[:location-matches [:location :match :id]]
[:address [:street1 :street2 :city :state :zip]]
[:forecasted-transactions [:id :amount :identifier :day-of-month]]
@@ -362,6 +415,7 @@
:spec ::entity/name
}])
[:div.field
[:p.help "Client code"
]
@@ -380,7 +434,12 @@
:field :email
:spec ::entity/email}])
[:div.field
[:p.help "Signature"]
[signature {:signature-file (:signature-file new-client)
:signature-data (:signature-data new-client)
:on-change (fn [uri]
(re-frame/dispatch [::forms/change ::form [:signature-data] uri]))}]]
[:h2.subtitle.is-5 "Name matches"]
[:div.control

View File

@@ -11,17 +11,18 @@
[cljs-time.core :as t]
[pushy.core :as pushy]
[re-frame.core :as re-frame]
[recharts]
[reagent.core :as r]))
(def pie-chart (r/adapt-react-class js/Recharts.PieChart))
(def pie (r/adapt-react-class js/Recharts.Pie))
(def bar-chart (r/adapt-react-class js/Recharts.BarChart))
(def x-axis (r/adapt-react-class js/Recharts.XAxis))
(def y-axis (r/adapt-react-class js/Recharts.YAxis))
(def bar (r/adapt-react-class js/Recharts.Bar))
(def legend (r/adapt-react-class js/Recharts.Legend))
(def cell (r/adapt-react-class js/Recharts.Cell))
(def tool-tip (r/adapt-react-class js/Recharts.Tooltip))
(def pie-chart (r/adapt-react-class recharts/PieChart))
(def pie (r/adapt-react-class recharts/Pie))
(def bar-chart (r/adapt-react-class recharts/BarChart))
(def x-axis (r/adapt-react-class recharts/XAxis))
(def y-axis (r/adapt-react-class recharts/YAxis))
(def bar (r/adapt-react-class recharts/Bar))
(def legend (r/adapt-react-class recharts/Legend))
(def cell (r/adapt-react-class recharts/Cell))
(def tool-tip (r/adapt-react-class recharts/Tooltip))
(def colors ["#79b52e" "#009cea" "#209b1c" "#f48017" " #ff0303" "hsl(217, 71%, 53%)" "hsl(141, 53%, 53%)"])
(def light-colors ["#a6d869" "#8ad8ff" "#2cd327" "#fac899" "#ff6b6b" "hsl(217, 71%, 53%)"])
@@ -46,11 +47,9 @@
[tool-tip]
[bar {:dataKey "paid" :fill (get colors 0) :stackId "a" :name "Paid"}]
[bar {:dataKey "unpaid" :fill (get light-colors 0) :stackId "a" :name "Unpaid"}]
[x-axis {:dataKey "name"}]
[y-axis]
[legend]]
)
[legend]])
(defn make-cash-flow-chart [{:keys [width height data] }]
(let [redirect-fn (fn [x]

View File

@@ -12,12 +12,12 @@
[auto-ap.entities.vendors :as vendor]
[auto-ap.views.components.typeahead :refer [typeahead-entity]]
[auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table]
[cljsjs.dropzone :as dropzone]
[cljs.reader :as edn]
[clojure.string :as str]
[vimsical.re-frame.cofx.inject :as inject]
[auto-ap.status :as status]
[vimsical.re-frame.fx.track :as track]
[dropzone :as dz]
[auto-ap.views.pages.data-page :as data-page]
[clojure.set :as set]
[auto-ap.effects.forward :as forward]))
@@ -29,23 +29,23 @@
(reagent/create-class
{:display-name "dropzone"
:component-did-mount (fn [this]
(js/Dropzone. (rdom/dom-node this)
(clj->js {:init (fn []
(.on (js-this) "addedfiles"
(fn []
(re-frame/dispatch [::status/completed ::import])))
(.on (js-this) "success" (fn [_ files]
(re-frame/dispatch [::invalidated])))
(.on (js-this) "error" (fn [_ error]
(re-frame/dispatch [::status/error ::import [(edn/read-string error)]]))))
:paramName "file"
:headers {"Authorization" (str "Token " @token)}
:url (str "/api/invoices/upload"
(when-let [client-name (-> @client :id)]
(str "?client=" client-name)))
:previewsContainer "#dz-hidden"
:previewTemplate "<div class='dz-hidden-preview'></div>"})))
(dz. (rdom/dom-node this)
(clj->js {:init (fn []
(.on (js-this) "addedfiles"
(fn []
(re-frame/dispatch [::status/completed ::import])))
(.on (js-this) "success" (fn [_ files]
(re-frame/dispatch [::invalidated])))
(.on (js-this) "error" (fn [_ error]
(re-frame/dispatch [::status/error ::import [(edn/read-string error)]]))))
:paramName "file"
:headers {"Authorization" (str "Token " @token)}
:url (str "/api/invoices/upload"
(when-let [client-name (-> @client :id)]
(str "?client=" client-name)))
:previewsContainer "#dz-hidden"
:previewTemplate "<div class='dz-hidden-preview'></div>"})))
:reagent-render (fn []
{:key batch}
[:form.dz {:action "/api/invoices/upload"}

View File

@@ -1,8 +1,8 @@
(ns auto-ap.views.utils
(:require [re-frame.core :as re-frame]
[cljsjs.react-transition-group]
[cljsjs.react-datepicker]
[reagent.core :as reagent]
[react-transition-group :as react-transition-group]
[react-datepicker]
[clojure.spec.alpha :as s]
[cljs-time.coerce :as c]
[cljs-time.core :as time]
@@ -20,7 +20,7 @@
(def nff
(NumberFormat. Format/CURRENCY))
(defn- nf
(defn nf
[num]
(.format nff (str num)))
@@ -60,9 +60,10 @@
(defn delayed-dispatch [e]
(fn [x]
(js/setTimeout #(re-frame/dispatch e) 150)
(js/setTimeout #(re-frame/dispatch e) 151)
false))
(defn dispatch-event [event]
(fn [e]
(when (.-stopPropagation e)
@@ -103,7 +104,7 @@
(def css-transition-group
(reagent/adapt-react-class js/ReactTransitionGroup.CSSTransition))
(reagent/adapt-react-class react-transition-group/CSSTransition))
(defn appearing [{:keys [visible? enter-class exit-class timeout]} & children ]
@@ -410,8 +411,7 @@
(def date-picker
(do
(reagent/adapt-react-class (aget js/DatePicker "default"))))
(reagent/adapt-react-class (.-default react-datepicker))))
(defn local-now []