(ns auto-ap.views.components.vendor-dialog (:require [re-frame.core :as re-frame] [auto-ap.views.utils :refer [dispatch-event horizontal-field bind-field with-user active-when]] [auto-ap.views.components.modal :refer [action-modal]] [auto-ap.views.components.address :refer [address-field]] [auto-ap.views.components.typeahead :refer [typeahead-entity]] [auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]] [auto-ap.events :as events] [clj-fuzzy.metrics :refer [jaccard jaro-winkler]] [clojure.spec.alpha :as s] [clojure.string :as str] [auto-ap.entities.vendors :as entity] [auto-ap.entities.contact :as contact] [auto-ap.subs :as subs] [auto-ap.forms :as forms])) (defn ngrams [text len] (mapv #(.toLowerCase (str/join "" %)) (partition len 1 text))) (defn partial-matches-vendor? [vendor vendors] (when (> (count (:name vendor)) 5) (let [text (.toLowerCase (:name vendor))] (->> vendors (filter #(not= (:id %) (:id vendor))) (map :name) (mapcat (fn [v] (mapv (fn [n] [v (jaro-winkler text n ) n text]) (ngrams v (count text))))) (filter #(> (second %) 0.9)) #_(map (fn [x] (doto x println))) (map first) first)))) (re-frame/reg-event-fx ::started (fn [{:keys [db]} [_ vendor]] (println vendor) {:db (-> db (forms/start-form ::vendor-form (-> vendor (update :account-overrides #(mapv (fn [ao] {:id (:id ao) :client (:client ao) :override (:account ao)}) %)) (update :terms-overrides #(mapv (fn [to] {:id (:id to) :client (:client to) :override (:terms to)}) %)) (update :hidden #(if (nil? %) false %)) (update :default-account (fn [da] @(re-frame/subscribe [::subs/account (:id da)])))))) :dispatch [::events/modal-status ::dialog {:visible? true}]})) (re-frame/reg-event-fx ::save-complete [(forms/triggers-stop ::vendor-form)] (fn [{:keys [db]} [_ {vendor :upsert-vendor} ]] {:dispatch [::events/modal-completed ::dialog ] :db (-> db (assoc-in [:vendors (:id vendor)] vendor))})) (re-frame/reg-event-fx ::save [with-user (forms/triggers-loading ::vendor-form) (forms/in-form ::vendor-form)] (fn [{:keys [user] {{:keys [name hidden print-as terms invoice-reminder-schedule primary-contact secondary-contact address default-account terms-overrides account-overrides id] :as data} :data} :db} _] (when (s/valid? ::entity/vendor data) { :graphql {:token user :query-obj {:venia/operation {:operation/type :mutation :operation/name "UpsertVendor"} :venia/queries [{:query/data [:upsert-vendor {:vendor {:id id :name name :hidden hidden :print-as print-as :terms terms :terms-overrides (mapv (fn [{:keys [client override id]}] {:id id :client-id (:id client) :terms override}) terms-overrides) :default-account-id (:id default-account) :account-overrides (mapv (fn [{:keys [client override id]}] {:id id :client-id (:id client) :account-id (:id override)}) account-overrides) :address address :primary-contact primary-contact :secondary-contact secondary-contact :invoice-reminder-schedule invoice-reminder-schedule}} events/vendor-query]}]} :on-success [::save-complete] :on-error [::forms/save-error ::vendor-form]}}))) (defn default-with-overrides [{:keys [override-key override-value-key change-event default-key data]} template] (let [clients @(re-frame/subscribe [::subs/clients])] [:div [horizontal-field [:label.label "Default"] [bind-field (assoc-in template [1 :field ] default-key)]] [horizontal-field [:label.label "Overrides"] (for [[i overrides] (map vector (range) (conj (override-key data) {}))] ^{:key i} [:div [:div.columns [:div.column [bind-field [typeahead-entity {:matches clients :match->text :name :type "typeahead-entity" :field [override-key i :client] :event change-event :subscription data}]]] [:div.column [bind-field (assoc-in template [1 :field ] [override-key i :override])]]]])]])) (defn form-content [{:keys [data change-event]}] (let [chooseable-expense-accounts @(re-frame/subscribe [::subs/chooseable-expense-accounts]) clients @(re-frame/subscribe [::subs/clients])] [:div [horizontal-field [:label.label "Name"] [:div.control [bind-field [:input.input {:type "text" :auto-focus true :field :name :spec ::entity/name :event change-event :subscription data}]]]] [horizontal-field [:label.label "Print Checks As"] [:div.control [bind-field [:input.input {:type "text" :field :print-as :spec ::entity/print-as :event change-event :subscription data}]]]] [horizontal-field [:label.label "Hidden"] [:div.control [bind-field [:input {:type "checkbox" :field :hidden :spec ::entity/hidden :event change-event :subscription data}]]]] [:h2.subtitle "Terms"] [default-with-overrides {:data data :change-event change-event :default-key :terms :override-key :terms-overrides} [:input.input {:type "number" :step "1" :style {:width "4em"} :size 3 :spec ::entity/terms :event change-event :subscription data}]] [:h2.subtitle "Expense Accounts"] [default-with-overrides {:data data :change-event change-event :default-key :default-account :override-key :account-overrides} [typeahead-entity {:matches chooseable-expense-accounts :match->text (fn [x ] (str (:numeric-code x) " - " (:name x))) :type "typeahead-entity" :event change-event :subscription data}]] [:h2.subtitle "Address"] [address-field {:field [:address] :event change-event :subscription data}] [:h2.subtitle "Contact"] [horizontal-field [:label.label "Primary"] [:div.control.has-icons-left [bind-field [:input.input.is-expanded {:type "text" :field [:primary-contact :name] :spec ::contact/name :event change-event :subscription data}]] [:span.icon.is-small.is-left [:i.fa.fa-user]]] [:div.control.has-icons-left [:span.icon.is-small.is-left [:i.fa.fa-envelope]] [bind-field [:input.input {:type "email" :field [:primary-contact :email] :spec ::contact/email :event change-event :subscription data}]]] [:div.control.has-icons-left [bind-field [:input.input {:type "phone" :field [:primary-contact :phone] :spec ::contact/phone :event change-event :subscription data}]] [:span.icon.is-small.is-left [:i.fa.fa-phone]]]] [horizontal-field [:label.label "Secondary"] [:div.control.has-icons-left [bind-field [:input.input.is-expanded {:type "text" :field [:secondary-contact :name] :spec ::contact/name :event change-event :subscription data}]] [:span.icon.is-small.is-left [:i.fa.fa-user]]] [:div.control.has-icons-left [:span.icon.is-small.is-left [:i.fa.fa-envelope]] [bind-field [:input.input {:type "email" :field [:secondary-contact :email] :spec ::contact/email :event change-event :subscription data}]]] [:div.control.has-icons-left [bind-field [:input.input {:type "phone" :field [:secondary-contact :phone] :spec ::contact/phone :event change-event :subscription data}]] [:span.icon.is-small.is-left [:i.fa.fa-phone]]]] [horizontal-field [:label.label "Invoice Reminders"] [:div.control [:label.radio [bind-field [:input {:type "radio" :name "schedule" :value "Weekly" :field :invoice-reminder-schedule :spec ::entity/invoice-reminder-schedule :event change-event :subscription data}]] " Send weekly"] [:label.radio [bind-field [:input {:type "radio" :name "schedule" :value "Never" :field :invoice-reminder-schedule :spec ::entity/invoice-reminder-schedule :event change-event :subscription data}]] " Never"]]]])) (defn vendor-dialog [{:keys [save-event] }] (let [clients @(re-frame/subscribe [::subs/clients]) all-vendors @(re-frame/subscribe [::subs/vendors]) {:keys [data error ] :as f} @(re-frame/subscribe [::forms/form ::vendor-form]) change-event [::forms/change ::vendor-form]] [action-modal {:id ::dialog :title [:span (if (:id data) (str "Edit " (or (:name data) "")) (str "Add " (or (:name data) ""))) (when error [:span.icon.has-text-danger [:i.fa.fa-exclamation-triangle]])] :status-from ::vendor-form #_#_:warning (when-let [vendor (partial-matches-vendor? data all-vendors)] (str "Are you sure you don't mean " vendor "?")) :action-text "Save" :save-event [::save] :can-submit? (s/valid? ::entity/vendor data)} [:div [form-content {:data data :change-event change-event}]]]))