diff --git a/migrator/migrations/1522973754-DOWN-create-vendors.sql b/migrator/migrations/1522973754-DOWN-create-vendors.sql new file mode 100644 index 00000000..dc20d2d3 --- /dev/null +++ b/migrator/migrations/1522973754-DOWN-create-vendors.sql @@ -0,0 +1,2 @@ +-- 1522973754 DOWN create-vendors +drop table vendors; diff --git a/migrator/migrations/1522973754-UP-create-vendors.sql b/migrator/migrations/1522973754-UP-create-vendors.sql new file mode 100644 index 00000000..1994c2df --- /dev/null +++ b/migrator/migrations/1522973754-UP-create-vendors.sql @@ -0,0 +1,12 @@ +-- 1522973754 UP create-vendors + +CREATE TABLE public.vendors ( + id serial PRIMARY KEY, + name character varying(255), + data text, + email character varying(255), + invoice_reminder_schedule character varying(255) +); + + +INSERT into vendors (name, data, email, invoice_reminder_schedule) values ('CINTAS', '{}', 'ses@brycecovertoperations.com', 'Weekly'); diff --git a/src/clj/auto_ap/db/vendors.clj b/src/clj/auto_ap/db/vendors.clj new file mode 100644 index 00000000..7fc9fdf3 --- /dev/null +++ b/src/clj/auto_ap/db/vendors.clj @@ -0,0 +1,22 @@ +(ns auto-ap.db.vendors + (:require [clojure.java.jdbc :as j] + [auto-ap.db.utils :refer [clj->db db->clj get-conn]] + [clojure.edn :as edn])) + +(defn merge-data [{:keys [data] :as x}] + (merge x (edn/read-string data))) + +(defn parse [x] + (-> x + (db->clj) + merge-data + )) + + +(defn get-all [] + (->> (j/query (get-conn) "SELECT * FROM vendors") + (map parse))) + +(defn upsert [id data] + (j/update! (get-conn) :vendors (clj->db data) ["id = ?" (Integer/parseInt id)] ) + (merge-data (db->clj (first (j/query (get-conn) ["SELECT * FROM vendors WHERE id = ?" (Integer/parseInt id)]))))) diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj index 8f12826f..e54690d6 100644 --- a/src/clj/auto_ap/handler.clj +++ b/src/clj/auto_ap/handler.clj @@ -24,6 +24,7 @@ [buddy.auth.backends.token :refer [jws-backend]] [buddy.auth.middleware :refer [wrap-authorization wrap-authentication]] [auto-ap.db.companies :as companies] + [auto-ap.db.vendors :as vendors] [amazonica.core :refer [defcredential]] [amazonica.aws.simpleemail :as ses])) (defn best-match [companies company-identifier] @@ -88,6 +89,11 @@ {:status 200 :body (pr-str (companies/get-all)) :headers {"Content-Type" "application/edn"}}) + + (GET "/api/vendors" [] + {:status 200 + :body (pr-str (vendors/get-all)) + :headers {"Content-Type" "application/edn"}}) (GET "/api/invoices" [] {:status 200 @@ -105,7 +111,7 @@ :headers {"Content-Type" "application/edn"}}) (POST "/api/reminders/send" {:keys [query-params]} - (doseq [{:keys [name email invoice-reminder-schedule]} (companies/get-all)] + (doseq [{:keys [name email invoice-reminder-schedule]} (vendors/get-all)] (when (= "Weekly" invoice-reminder-schedule) (println "Sending email to" email) (ses/send-email :destination {:to-addresses [email]} @@ -122,6 +128,11 @@ :body (pr-str (companies/upsert id edn-params)) :headers {"Content-Type" "application/edn"}}) + (PUT "/api/vendors/:id" {:keys [edn-params] {:keys [id]} :route-params :as r} + {:status 200 + :body (pr-str (vendors/upsert id edn-params)) + :headers {"Content-Type" "application/edn"}}) + (POST "/api/invoices" {:keys [edn-params]} (invoices/insert-multi! (:rows edn-params)) {:status 200 diff --git a/src/cljs/auto_ap/events/admin/vendors.cljs b/src/cljs/auto_ap/events/admin/vendors.cljs new file mode 100644 index 00000000..1b34b61e --- /dev/null +++ b/src/cljs/auto_ap/events/admin/vendors.cljs @@ -0,0 +1,55 @@ +(ns auto-ap.events.admin.vendors + (:require [re-frame.core :as re-frame] + [auto-ap.db :as db] + [auto-ap.routes :as routes] + [auto-ap.effects :as effects] + [bidi.bidi :as bidi])) + +(re-frame/reg-event-db + ::edit + (fn [db [_ vendor-id]] + (assoc-in db [:admin-vendors :editing] + (get (:vendors db) vendor-id)))) + +(re-frame/reg-event-fx + ::save + (fn [{:keys [db]} _] + (let [edited-vendor (get-in db [:admin-vendors :editing])] + {:db (assoc-in db [:admin-vendors :editing :saving?] true) + :http {:method :put + :token (:user db) + :body (pr-str (select-keys edited-vendor [:name :email :data :invoice-reminder-schedule])) + :headers {"Content-Type" "application/edn"} + :uri (str "/api/vendors/" (:id edited-vendor)) + :on-success [::save-complete]}}))) + +(re-frame/reg-event-db + ::save-complete + (fn [db [_ vendor]] + (-> db + + (assoc-in [:admin-vendors :editing] nil) + (assoc-in [:vendors (:id vendor)] vendor)))) + +(re-frame/reg-event-db + ::change + (fn [db [_ path value]] + (assoc-in db (concat [:admin-vendors :editing] path) + value))) + +(re-frame/reg-event-fx + ::mounted + (fn [{:keys [db]} _] + {:http {:method :get + :token (:user db) + :uri "/api/vendors" + :on-success [::received-vendors]}})) + +(re-frame/reg-event-db + ::received-vendors + (fn [db [_ vendors]] + + (assoc db :vendors (reduce (fn [vendors vendor] + (assoc vendors (:id vendor) vendor)) + {} + vendors)))) diff --git a/src/cljs/auto_ap/routes.cljs b/src/cljs/auto_ap/routes.cljs index 26025285..50b73442 100644 --- a/src/cljs/auto_ap/routes.cljs +++ b/src/cljs/auto_ap/routes.cljs @@ -4,7 +4,8 @@ (def routes ["/" {"" :index "login/" :login "admin/" {"" :admin - "companies" :admin-companies} + "companies" :admin-companies + "vendors" :admin-vendors} "invoices/" {"" :invoices "import" :import-invoices "unpaid" :unpaid-invoices diff --git a/src/cljs/auto_ap/subs.cljs b/src/cljs/auto_ap/subs.cljs index bee704cc..e88c94e0 100644 --- a/src/cljs/auto_ap/subs.cljs +++ b/src/cljs/auto_ap/subs.cljs @@ -29,6 +29,16 @@ (fn [db] (:admin-companies db))) +(re-frame/reg-sub + ::vendors + (fn [db] + (vals (:vendors db)))) + +(re-frame/reg-sub + ::admin-vendors + (fn [db] + (:admin-vendors db))) + (re-frame/reg-sub ::user (fn [db] diff --git a/src/cljs/auto_ap/views/main.cljs b/src/cljs/auto_ap/views/main.cljs index 35590077..25fe061c 100644 --- a/src/cljs/auto_ap/views/main.cljs +++ b/src/cljs/auto_ap/views/main.cljs @@ -17,6 +17,7 @@ :paid-invoices :left-panel :admin :admin-left-panel :admin-companies :admin-left-panel + :admin-vendors :admin-left-panel :new-invoice :blank} page)) (defn login-dropdown [] @@ -70,6 +71,12 @@ [:i {:class "fa fa-star-o"}]] [:span {:class "name"} "Companies"]]] + [:li.menu-item + [:a {:href (bidi/path-for routes/routes :admin-vendors) , :class (str "item" (active-when= ap :admin-vendors))} + [:span {:class "icon"} + [:i {:class "fa fa-star-o"}]] + + [:span {:class "name"} "Vendors"]]] [:li.menu-item [:a {:href (bidi/path-for routes/routes :admin-users), :class (str "item" (active-when= ap :admin-users))} [:span {:class "icon"} diff --git a/src/cljs/auto_ap/views/pages.cljs b/src/cljs/auto_ap/views/pages.cljs index c5b72c7b..a2ad8c66 100644 --- a/src/cljs/auto_ap/views/pages.cljs +++ b/src/cljs/auto_ap/views/pages.cljs @@ -8,6 +8,7 @@ [auto-ap.views.pages.index :refer [index-page]] [auto-ap.views.pages.admin :refer [admin-page]] [auto-ap.views.pages.admin.companies :refer [admin-companies-page]] + [auto-ap.views.pages.admin.vendors :refer [admin-vendors-page]] [auto-ap.views.pages.unpaid-invoices :refer [unpaid-invoices-page]] [auto-ap.views.pages.new-invoice :refer [new-invoice-page]] [auto-ap.views.pages.import-invoices :refer [import-invoices-page]] @@ -34,12 +35,15 @@ (defmethod active-page :admin-companies [] [admin-companies-page]) +(defmethod active-page :admin-vendors [] + [admin-vendors-page]) + + (defmethod active-page :unpaid-invoices [] [unpaid-invoices-page]) (defmethod active-page :paid-invoices [] - paid-invoices-page - ) + paid-invoices-page) (defmethod active-page :invoices [] [(with-meta diff --git a/src/cljs/auto_ap/views/pages/admin/vendors.cljs b/src/cljs/auto_ap/views/pages/admin/vendors.cljs new file mode 100644 index 00000000..f30399c4 --- /dev/null +++ b/src/cljs/auto_ap/views/pages/admin/vendors.cljs @@ -0,0 +1,102 @@ +(ns auto-ap.views.pages.admin.vendors + (:require-macros [cljs.core.async.macros :refer [go]]) + (:require [re-frame.core :as re-frame] + [reagent.core :as reagent] + [auto-ap.subs :as subs] + [auto-ap.events.admin.vendors :as events] + [auto-ap.views.utils :refer [login-url dispatch-value-change]] + [cljs.reader :as edn] + [auto-ap.routes :as routes] + [bidi.bidi :as bidi])) +(defn vendors-table [] + (let [vendors (re-frame/subscribe [::subs/vendors]) + admin-vendors (re-frame/subscribe [::subs/admin-vendors]) + editing-vendor (:editing @admin-vendors)] + (println @vendors) + [:table {:class "table", :style {:width "100%"}} + [:thead + [:tr + [:th "Name"] + [:th "Email"] + [:th "Invoice Reminders"]]] + [:tbody (for [{:keys [id name email data invoice-reminder-schedule]} @vendors] + ^{:key (str name "-" id )} + [:tr {:on-click (fn [] (re-frame/dispatch [::events/edit id])) + :style {"cursor" "pointer"}} + [:td name] + [:td email] + [:td invoice-reminder-schedule]])]])) + +(defn admin-vendors-page [] + [(with-meta + (fn [] + [:div {:class "inbox-messages"} + [:div.hero + [:div.hero-body + [:div.container + (let [vendors (re-frame/subscribe [::subs/vendors]) + admin-vendors (re-frame/subscribe [::subs/admin-vendors]) + editing-vendor (:editing @admin-vendors)] + + [:div + [:h1.title "Vendors"] + [vendors-table] + + + + (when editing-vendor + [:div.modal.is-active + [:div.modal-background {:on-click (fn [] (re-frame/dispatch [::events/edit nil]))}] + + [:div.modal-card + [:header.modal-card-head + [:p.modal-card-title + (str "Edit " (:name editing-vendor))] + [:button.delete {:on-click (fn [] (re-frame/dispatch [::events/edit nil]))}]] + [:section.modal-card-body + [:div.field + [:label.label "Name"] + [:div.control + [:input.input {:type "text" :value (:name editing-vendor) + :on-change (dispatch-value-change [::events/change [:name]])}]]] + + [:div.field + [:label.label "Email"] + [:div.control + [:input.input {:type "email" + :value (:email editing-vendor) + :on-change (dispatch-value-change [::events/change [:email]])}]]] + + [:div.field + [:labal.label "Invoice Reminders"] + [:div.control + [:label.radio + [:input {:type "radio" + :name "schedule" + :value "Weekly" + :checked (if (= "Weekly" (:invoice-reminder-schedule editing-vendor)) + "checked" + "") + :on-change (dispatch-value-change [::events/change [:invoice-reminder-schedule]])}] + " Send weekly"]] + [:div.control + [:label.radio + [:input {:type "radio" + :name "schedule" + :value "Never" + :checked (if (= "Never" (:invoice-reminder-schedule editing-vendor)) + "checked" + "") + :on-change (dispatch-value-change [::events/change [:invoice-reminder-schedule]])}] + " Never"]]] + + (when (:saving? editing-vendor) [:div.is-overlay {:style {"backgroundColor" "rgba(150,150,150, 0.5)"}}])] + + [:footer.modal-card-foot + [:a.button.is-primary {:on-click (fn [] (re-frame/dispatch [::events/save]))} + [:span "Save"] + (when (:saving? editing-vendor) + [:span.icon + [:i.fa.fa-spin.fa-spinner]])]]]])])]]]]) + {:component-did-mount (fn [] + (re-frame/dispatch [::events/mounted]))})])