From 2af3752041cee39b97e8e7ca1b5844175cc50b60 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Tue, 19 Dec 2017 22:00:46 -0800 Subject: [PATCH] big updates for re-org. --- src/cljs/auto_ap/core.cljs | 6 +- src/cljs/auto_ap/subs.cljs | 5 + src/cljs/auto_ap/views.cljs | 353 ------------------ src/cljs/auto_ap/views/main.cljs | 123 ++++++ src/cljs/auto_ap/views/pages.cljs | 56 +++ .../auto_ap/views/pages/import_invoices.cljs | 86 +++++ src/cljs/auto_ap/views/pages/index.cljs | 12 + src/cljs/auto_ap/views/pages/login.cljs | 19 + src/cljs/auto_ap/views/pages/new_invoice.cljs | 74 ++++ .../auto_ap/views/pages/paid_invoices.cljs | 34 ++ .../auto_ap/views/pages/unpaid_invoices.cljs | 35 ++ src/cljs/auto_ap/views/utils.cljs | 9 + 12 files changed, 456 insertions(+), 356 deletions(-) delete mode 100644 src/cljs/auto_ap/views.cljs create mode 100644 src/cljs/auto_ap/views/main.cljs create mode 100644 src/cljs/auto_ap/views/pages.cljs create mode 100644 src/cljs/auto_ap/views/pages/import_invoices.cljs create mode 100644 src/cljs/auto_ap/views/pages/index.cljs create mode 100644 src/cljs/auto_ap/views/pages/login.cljs create mode 100644 src/cljs/auto_ap/views/pages/new_invoice.cljs create mode 100644 src/cljs/auto_ap/views/pages/paid_invoices.cljs create mode 100644 src/cljs/auto_ap/views/pages/unpaid_invoices.cljs create mode 100644 src/cljs/auto_ap/views/utils.cljs diff --git a/src/cljs/auto_ap/core.cljs b/src/cljs/auto_ap/core.cljs index 7f8f172e..c715ea38 100644 --- a/src/cljs/auto_ap/core.cljs +++ b/src/cljs/auto_ap/core.cljs @@ -2,7 +2,7 @@ (:require [reagent.core :as reagent] [re-frame.core :as re-frame] [auto-ap.events :as events] - [auto-ap.views :as views] + [auto-ap.views.main :refer [page] ] [auto-ap.config :as config] [auto-ap.effects :as effects] [pushy.core :as pushy] @@ -16,7 +16,7 @@ (defn mount-root [] (re-frame/clear-subscription-cache!) - (reagent/render [views/page] + (reagent/render [page] (.getElementById js/document "app"))) (defn ^:export init [] @@ -27,7 +27,7 @@ (.setItem js/localStorage "jwt" jwt) (re-frame/dispatch-sync [::events/initialize-db jwt])) (do - (re-frame/dispatch-sync [::events/initialize-db nil]))) + (re-frame/dispatch-sync [::events/initialize-db (.getItem js/localStorage "jwt")]))) (mount-root)) diff --git a/src/cljs/auto_ap/subs.cljs b/src/cljs/auto_ap/subs.cljs index 3b2240a0..6c672144 100644 --- a/src/cljs/auto_ap/subs.cljs +++ b/src/cljs/auto_ap/subs.cljs @@ -18,6 +18,11 @@ (fn [db] (:menu db))) +(re-frame/reg-sub + ::token + (fn [db] + (:user db))) + (re-frame/reg-sub ::user (fn [db] diff --git a/src/cljs/auto_ap/views.cljs b/src/cljs/auto_ap/views.cljs deleted file mode 100644 index 027f80c6..00000000 --- a/src/cljs/auto_ap/views.cljs +++ /dev/null @@ -1,353 +0,0 @@ -(ns auto-ap.views - (: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 :as events] - [cljs.reader :as edn] - [cljsjs.dropzone :as dz] - [auto-ap.routes :as routes] - [bidi.bidi :as bidi] - [cljs-http.client :as http] - [cljs.core.async :refer [js {:init (fn [] - (.on (js-this) "success" (fn [_ files] - (re-frame/dispatch [::events/received-invoices :pending (edn/read-string files)])))) - :paramName "file" - :url (str "/pdf-upload" - (when-let [company-name (-> @company :name)] - (str "?company=" company-name))) - :previewsContainer "#dz-hidden" - :previewTemplate "
"})))}))) - - -(defmulti active-page identity) - -(defmethod active-page :login [] - [:div {:class "inbox-messages"} - [:div.hero - [:div.hero-body - [:div.container - [:h1.title "Login"] - [:h2.subtitle "To get started, " - [:a {:href (login-url)} "Login with Google"]]]]]]) - -(defmethod active-page :index [] - [:div {:class "inbox-messages"} - [:div.hero - [:div.hero-body - [:div.container - [:h1.title "Dashboard"] - [:h2.subtitle "To get started, " - [:a {:href (bidi/path-for routes/routes :import-invoices)} "Import some invoices"]]]]]]) - -(defmethod active-page :unpaid-invoices [] - [(with-meta - (fn [] - (let [invoices (re-frame/subscribe [::subs/unpaid-invoices]) - status (re-frame/subscribe [::subs/status])] - [:div {:class "inbox-messages"} - [:h1.title "Unpaid invoices"] - (if (:loading @status) - [:div {:class "inbox-messages"} - [:h1.title - [:i.fa.fa-spin.fa-spinner]]] - [:table {:class "table", :style {:width "100%"}} - [:thead - [:tr - [:th "Vendor"] - [:th "Customer"] - [:th "Invoice #"] - [:th "Date"] - [:th "Amount"]]] - [:tbody (for [{:keys [company invoice-number date total id vendor] :as i} @invoices] - ^{:key (str company "-" invoice-number "-" date "-" total "-" id)} - [:tr - [:td vendor] - [:td company] - [:td invoice-number] - [:td date] - [:td total]])]])])) - {:component-will-mount #(re-frame/dispatch-sync [::events/view-unpaid-invoices]) })]) - -(defmethod active-page :paid-invoices [] - [:div {:class "inbox-messages"} - [:h1.title "Paid invoices"]]) - -(defmethod active-page :invoices [] - [(with-meta - (fn [] - [:div {:class "inbox-messages"} - [:h1.title "All invoices"]]) - {:component-did-mount (fn [] - (go - (re-frame/dispatch [::events/received-invoices (:body (layout [page] + ({:login :blank + :index :left-panel + :invoices :left-panel + :import-invoices :left-panel + :unpaid-invoices :left-panel + :paid-invoices :left-panel + :new-invoice :blank} page)) + +(defn login-dropdown [] + (let [user (re-frame/subscribe [::subs/user]) + menu (re-frame/subscribe [::subs/menu])] + [:div {:id "navMenu", :class "navbar-menu"} + [:div {:class "navbar-end"} + (if @user + [:div {:class (str "navbar-item has-dropdown " (when (get-in @menu [:account :active?]) "is-active"))} + [:a {:class "navbar-link login" :on-click (fn [e] (re-frame/dispatch [::events/toggle-menu :account]))} (:name @user)] + [:div {:class "navbar-dropdown"} + [:a {:class "navbar-item"} "My profile"] + [:hr {:class "navbar-divider"}] + [:a.navbar-item {:on-click (fn [e] (.preventDefault e) (re-frame/dispatch [::events/logout]))} "Logout"]]] + [:a.navbar-item {:href login-url} "Login"])]])) + +(defmulti layout page->layout) + +(defmethod layout :left-panel [ap] + (let [company (re-frame/subscribe [::subs/company]) + companies (re-frame/subscribe [::subs/companies]) + menu (re-frame/subscribe [::subs/menu])] + [:div + [:nav {:class "navbar has-shadow"} + [:div {:class "container"} + [:div {:class "navbar-brand"} + [:a {:class "navbar-item", :href "../"} + [:h1 (str "Auto-ap")]]] + [:div {:id "navMenu", :class "navbar-menu " + :on-click (fn [] (re-frame/dispatch [::events/toggle-menu :company]))} + [:div.navbar-start + [:div { :class (str "navbar-item has-dropdown " (when (get-in @menu [:company :active?]) "is-active"))} + [:a {:class "navbar-link login"} "Company: " (if @company (:name @company) + "All")] + [:div {:class "navbar-dropdown"} + [:a {:class "navbar-item" + :on-click (fn [] (re-frame/dispatch [::events/swap-company nil])) + } "All"] + [:hr {:class "navbar-divider"}] + (for [{:keys [name] :as company} @companies] + ^{:key name } + [:a {:class "navbar-item" + :on-click (fn [] (re-frame/dispatch [::events/swap-company company])) + } name])]]]] + [:div {:class "navbar-burger burger", :data-target "navMenu"} + [:span] + [:span] + [:span]] + [login-dropdown]]] + [:div {:class "columns", :id "mail-app"} + [:aside {:class "column is-narrow aside menu hero is-fullheight"} + [:div.main + [:p.menu-label "General"] + [:p.menu-item + [:a {:href (bidi/path-for routes/routes :index) , :class (str "item" (active-when= ap :index))} + [:span {:class "icon"} + [:i {:class "fa fa-tachometer"}]] + [:span {:class "name"} "Dashboard"]]] + + [:p.menu-label "Accounts Payable"] + [:ul.menu-list + [:li.menu-item + [:a {:href (bidi/path-for routes/routes :import-invoices) , :class (str "item" (active-when= ap :import-invoices))} + [:span {:class "icon"} + [:i {:class "fa fa-star-o"}]] + + [:span {:class "name"} "Upload Invoices"]]] + [:li.menu-item + [:a {:href (bidi/path-for routes/routes :unpaid-invoices), :class (str "item" (active-when= ap :unpaid-invoices))} + [:span {:class "icon"} + [:i {:class "fa fa-envelope-o"}]] + [:span {:class "name"} "Unpaid Invoices"]] + ] + [:li.menu-item + [:a {:href (bidi/path-for routes/routes :paid-invoices), :class (str "item" (active-when= ap :paid-invoices))} + [:span {:class "icon"} + [:i {:class "fa fa-envelope-o"}]] + [:span {:class "name"} "Paid Invoices"]] + ] + [:ul ]]] + + [:div.left-nav + [:div {:class "compose has-text-centered"} + [:a {:class "button is-danger is-block is-bold" :href (bidi/path-for routes/routes :new-invoice)} + [:span {:class "compose"} "New Invoice"]]]]] + [:div {:class "column messages hero is-fullheight", :id "message-feed"} + ^{:key (str "active-page-" (:name @company))} [pages/active-page ap]]] + [:footer {:class "footer"} + [:div {:class "container"} + [:div {:class "content has-text-centered"} + [:p + [:strong "Auto-AP"]"by " + [:a {:href "https://github.com/"} "Integreat"]"."] + [:p + [:a {:class "icon", :href "https://github.com/dansup/bulma-templates"} + [:i {:class "fa fa-github"}]]]]]] + [:div#dz-hidden]])) + +(defmethod layout :blank [ap] + [:div + ^{:key ap} [pages/active-page ap]]) + +(defn page [] + (let [ap (re-frame/subscribe [::subs/active-page])] + [:div + ^{:key @ap} [layout @ap]])) diff --git a/src/cljs/auto_ap/views/pages.cljs b/src/cljs/auto_ap/views/pages.cljs new file mode 100644 index 00000000..ca967dbf --- /dev/null +++ b/src/cljs/auto_ap/views/pages.cljs @@ -0,0 +1,56 @@ +(ns auto-ap.views.pages + (: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 :as events] + [auto-ap.views.pages.login :refer [login-page]] + [auto-ap.views.pages.index :refer [index-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]] + [auto-ap.views.pages.paid-invoices :refer [paid-invoices-page]] + [cljs.reader :as edn] + [cljsjs.dropzone :as dz] + [auto-ap.routes :as routes] + [bidi.bidi :as bidi] + [cljs-http.client :as http] + [cljs.core.async :refer [js {:init (fn [] + (.on (js-this) "success" (fn [_ files] + (re-frame/dispatch [::events/received-invoices :pending (edn/read-string files)])))) + :paramName "file" + :headers {"Authorization" (str "Token " @token)} + :url (str "/pdf-upload" + (when-let [company-name (-> @company :name)] + (str "?company=" company-name))) + :previewsContainer "#dz-hidden" + :previewTemplate "
"})))}))) +(def import-invoices-page + (with-meta + (fn [] + (let [invoices (re-frame/subscribe [::subs/pending-invoices]) + status (re-frame/subscribe [::subs/status])] + [:div {:class "inbox-messages"} + [:h1.title "Upload invoices"] + [dropzone] + + [:div {:class "section"}] + [:div {:class "card found-invoices",} + [:div {:class "card-header"} + [:span {:class "card-header-title"} "Found Invoices"]] + [:div {:class "card-content"} + (if (:loading @status) + [:h1.title + [:i.fa.fa-spin.fa-spinner]] + (if (seq @invoices) + [:table {:class "table", :style {:width "100%"}} + [:thead + [:tr + [:th "Vendor"] + [:th "Customer"] + [:th "Invoice #"] + [:th "Date"] + [:th "Amount"] + [:th]]] + [:tbody (for [{:keys [vendor potential-duplicate company customer-identifier invoice-number date total id] :as i} @invoices] + ^{:key (str company "-" invoice-number "-" date "-" total "-" id)} + [:tr + [:td vendor] + (if company + [:td company] + [:td [:i.icon.fa.fa-warning {:title "potential duplicate"}] + (str "'" customer-identifier "' doesn't match any known company")]) + [:td invoice-number] + [:td date] + [:td total] + [:td (when potential-duplicate + [:i.icon.fa.fa-warning {:title "potential duplicate"}])]])]] + [:span "No pending invoices"]))] + (if (and (seq @invoices) (not (:loading @status))) + [:div.card-footer + [:a.card-footer-item + {:on-click (fn [e] + (.preventDefault e) + (re-frame/dispatch [::events/approve-invoices]))} + "Accept all"] + [:a.card-footer-item + {:on-click (fn [e] + (.preventDefault e) + (re-frame/dispatch [::events/reject-invoices]))} + "Reject all"]])]])) + {:component-will-mount (fn [] + (re-frame/dispatch [::events/view-pending-invoices]))})) diff --git a/src/cljs/auto_ap/views/pages/index.cljs b/src/cljs/auto_ap/views/pages/index.cljs new file mode 100644 index 00000000..8976fad7 --- /dev/null +++ b/src/cljs/auto_ap/views/pages/index.cljs @@ -0,0 +1,12 @@ +(ns auto-ap.views.pages.index + (:require [bidi.bidi :as bidi] + [auto-ap.routes :as routes])) + +(defn index-page [] + [:div {:class "inbox-messages"} + [:div.hero + [:div.hero-body + [:div.container + [:h1.title "Dashboard"] + [:h2.subtitle "To get started, " + [:a {:href (bidi/path-for routes/routes :import-invoices)} "Import some invoices"]]]]]]) diff --git a/src/cljs/auto_ap/views/pages/login.cljs b/src/cljs/auto_ap/views/pages/login.cljs new file mode 100644 index 00000000..b14d1072 --- /dev/null +++ b/src/cljs/auto_ap/views/pages/login.cljs @@ -0,0 +1,19 @@ +(ns auto-ap.views.pages.login + (: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 :as events] + [auto-ap.views.utils :refer [login-url]] + [cljs.reader :as edn] + [auto-ap.routes :as routes] + [bidi.bidi :as bidi])) + +(defn login-page [] + [:div {:class "inbox-messages"} + [:div.hero + [:div.hero-body + [:div.container + [:h1.title "Login"] + [:h2.subtitle "To get started, " + [:a {:href login-url} "Login with Google"]]]]]]) diff --git a/src/cljs/auto_ap/views/pages/new_invoice.cljs b/src/cljs/auto_ap/views/pages/new_invoice.cljs new file mode 100644 index 00000000..06a5c90f --- /dev/null +++ b/src/cljs/auto_ap/views/pages/new_invoice.cljs @@ -0,0 +1,74 @@ +(ns auto-ap.views.pages.new-invoice + (:require [re-frame.core :as re-frame] + [auto-ap.subs :as subs] + [auto-ap.events :as events])) + +(defn new-invoice-page [] + (let [form-data (re-frame/subscribe [::subs/new-invoice-form])] + [:div {:class "inbox-messages"} + [:form + [:h1.title "New Invoice"] + [:div.field + [:label.label "Vendor"] + [:div.control + [:input.input {:type "text" + :placeholder "CINTAS" + :value (:vendor @form-data) + :on-change (fn [e] + (re-frame/dispatch [::events/change-form-state + [:new-invoice :vendor] + (.. e -target -value)]))}]]] + [:div.field + [:label.label "Customer"] + [:div.control + [:input.input {:type "text" + :placeholder "Brown Chicken Brown Cow" + :value (:company @form-data) + :on-change (fn [e] + (re-frame/dispatch [::events/change-form-state + [:new-invoice :company] + (.. e -target -value)]))}]]] + [:div.field + [:label.label "Invoice #"] + [:div.control + [:input.input {:type "text" + :placeholder "12345" + :value (:invoice-number @form-data) + :on-change (fn [e] + (re-frame/dispatch [::events/change-form-state + [:new-invoice :invoice-number] + (.. e -target -value)]))}]]] + [:div.field + [:label.label "Date"] + [:div.control + [:input.input {:type "text" + :placeholder "11/11/2011" + :value (:date @form-data) + :on-change (fn [e] + (re-frame/dispatch [::events/change-form-state + [:new-invoice :date] + (.. e -target -value)]))}]]] + [:div.field + [:label.label "Total"] + [:div.control + [:input.input {:type "text" + :placeholder "$14.50" + :value (:total @form-data) + :on-change (fn [e] + (re-frame/dispatch [::events/change-form-state + [:new-invoice :total] + (.. e -target -value)]))}]]] + [:div.control + [:submit.button.is-large.is-primary { + :disabled (if (and (:total @form-data) (:date @form-data) (:company @form-data) (:invoice-number @form-data) + (:vendor @form-data)) + "" + "disabled") + :on-click + (fn [x] + (.preventDefault x) + (re-frame/dispatch [::events/submit-new-invoice @form-data]))} + [:span + (when (:loading? @form-data) + [:i.fa.fa-spin.fa-spinner]) + "Save"]]]]])) diff --git a/src/cljs/auto_ap/views/pages/paid_invoices.cljs b/src/cljs/auto_ap/views/pages/paid_invoices.cljs new file mode 100644 index 00000000..f62d9e6f --- /dev/null +++ b/src/cljs/auto_ap/views/pages/paid_invoices.cljs @@ -0,0 +1,34 @@ +(ns auto-ap.views.pages.paid-invoices + (:require [re-frame.core :as re-frame] + [auto-ap.subs :as subs] + [auto-ap.events :as events])) + + +(def paid-invoices-page + (with-meta + (fn [] + (let [invoices (re-frame/subscribe [::subs/unpaid-invoices]) + status (re-frame/subscribe [::subs/status])] + [:div {:class "inbox-messages"} + [:h1.title "Paid invoices"] + (if (:loading @status) + [:div {:class "inbox-messages"} + [:h1.title + [:i.fa.fa-spin.fa-spinner]]] + [:table {:class "table", :style {:width "100%"}} + [:thead + [:tr + [:th "Vendor"] + [:th "Customer"] + [:th "Invoice #"] + [:th "Date"] + [:th "Amount"]]] + [:tbody (for [{:keys [company invoice-number date total id vendor] :as i} @invoices] + ^{:key (str company "-" invoice-number "-" date "-" total "-" id)} + [:tr + [:td vendor] + [:td company] + [:td invoice-number] + [:td date] + [:td total]])]])])) + {:component-will-mount #(re-frame/dispatch-sync [::events/view-unpaid-invoices]) })) diff --git a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs new file mode 100644 index 00000000..369e3bab --- /dev/null +++ b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs @@ -0,0 +1,35 @@ +(ns auto-ap.views.pages.unpaid-invoices + (:require [re-frame.core :as re-frame] + [auto-ap.subs :as subs] + [auto-ap.events :as events] + )) + + +(def unpaid-invoices-page + (with-meta + (fn [] + (let [invoices (re-frame/subscribe [::subs/unpaid-invoices]) + status (re-frame/subscribe [::subs/status])] + [:div {:class "inbox-messages"} + [:h1.title "Unpaid invoices"] + (if (:loading @status) + [:div {:class "inbox-messages"} + [:h1.title + [:i.fa.fa-spin.fa-spinner]]] + [:table {:class "table", :style {:width "100%"}} + [:thead + [:tr + [:th "Vendor"] + [:th "Customer"] + [:th "Invoice #"] + [:th "Date"] + [:th "Amount"]]] + [:tbody (for [{:keys [company invoice-number date total id vendor] :as i} @invoices] + ^{:key (str company "-" invoice-number "-" date "-" total "-" id)} + [:tr + [:td vendor] + [:td company] + [:td invoice-number] + [:td date] + [:td total]])]])])) + {:component-will-mount #(re-frame/dispatch-sync [::events/view-unpaid-invoices]) })) diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs new file mode 100644 index 00000000..8cd38747 --- /dev/null +++ b/src/cljs/auto_ap/views/utils.cljs @@ -0,0 +1,9 @@ +(ns auto-ap.views.utils) + +(defn active-when= [active-page candidate] + (when (= active-page candidate) " active")) + +(def login-url + (let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com" + redirect-uri "http%3A%2F%2Flocalhost%3A3449%2Fapi%2Foauth"] + (str "https://accounts.google.com/o/oauth2/auth?access_type=online&client_id=" client-id "&redirect_uri=" redirect-uri "&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile")))