big updates for re-org.
This commit is contained in:
@@ -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))
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
(fn [db]
|
||||
(:menu db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::token
|
||||
(fn [db]
|
||||
(:user db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::user
|
||||
(fn [db]
|
||||
|
||||
@@ -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 [<!]]))
|
||||
(defn 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")))
|
||||
|
||||
(defn active-when= [active-page candidate]
|
||||
(when (= active-page candidate) " active"))
|
||||
|
||||
(def dropzone
|
||||
(let [company (re-frame/subscribe [::subs/company])]
|
||||
(with-meta
|
||||
(fn []
|
||||
[:form.dz {:action "/pdf-upload"}
|
||||
[:div.tile.notification
|
||||
[:div.has-text-centered {:style {:padding "80px 0px" :width "100%"}}
|
||||
[:span
|
||||
[:span {:class "icon"}
|
||||
[:i {:class "fa fa-cloud-download"}]]
|
||||
"Drop any invoices you want to process here"]]]])
|
||||
{:component-did-mount (fn [this]
|
||||
(js/Dropzone. (reagent/dom-node this)
|
||||
(clj->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 "<div class='dz-hidden-preview'></div>"})))})))
|
||||
|
||||
|
||||
(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 (<! (http/get "/api/invoices")))])))})])
|
||||
|
||||
(defmethod active-page :import-invoices []
|
||||
[(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]))})])
|
||||
|
||||
|
||||
(defmethod active-page :new-invoice []
|
||||
(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"]]]]]))
|
||||
|
||||
|
||||
|
||||
(defn login []
|
||||
(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"])]]))
|
||||
|
||||
{"" :index
|
||||
"login/" :login
|
||||
"invoices/" {"" :invoices
|
||||
"import" :import-invoices
|
||||
"unpaid" :unpaid-invoices
|
||||
"paid" :paid-invoices
|
||||
"new" :new-invoice}}
|
||||
(defmulti main-layout (fn [x] ({:login :blank
|
||||
:index :left-panel
|
||||
:invoices :left-panel
|
||||
:import-invoices :left-panel
|
||||
:unpaid-invoices :left-panel
|
||||
:paid-invoices :left-panel
|
||||
:new-invoice :blank} x)))
|
||||
|
||||
(defmethod main-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]]]
|
||||
[: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))} [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 main-layout :blank [ap]
|
||||
[:div
|
||||
[active-page ap]])
|
||||
|
||||
(defn page []
|
||||
(let [ap (re-frame/subscribe [::subs/active-page])]
|
||||
[:div
|
||||
[main-layout @ap]]))
|
||||
|
||||
123
src/cljs/auto_ap/views/main.cljs
Normal file
123
src/cljs/auto_ap/views/main.cljs
Normal file
@@ -0,0 +1,123 @@
|
||||
(ns auto-ap.views.main
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[bidi.bidi :as bidi]
|
||||
[auto-ap.routes :as routes]
|
||||
[auto-ap.subs :as subs]
|
||||
[auto-ap.events :as events]
|
||||
[auto-ap.views.utils :refer [active-when= login-url]]
|
||||
[auto-ap.views.pages :as pages]))
|
||||
|
||||
(defn page->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]]))
|
||||
56
src/cljs/auto_ap/views/pages.cljs
Normal file
56
src/cljs/auto_ap/views/pages.cljs
Normal file
@@ -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 [<!]]))
|
||||
|
||||
|
||||
|
||||
(defmulti active-page identity)
|
||||
|
||||
(defmethod active-page :login []
|
||||
login-page)
|
||||
|
||||
(defmethod active-page :index []
|
||||
index-page)
|
||||
|
||||
(defmethod active-page :unpaid-invoices []
|
||||
[unpaid-invoices-page])
|
||||
|
||||
(defmethod active-page :paid-invoices []
|
||||
paid-invoices-page
|
||||
)
|
||||
|
||||
(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 (<! (http/get "/api/invoices")))])))})])
|
||||
|
||||
(defmethod active-page :import-invoices []
|
||||
[import-invoices-page])
|
||||
|
||||
|
||||
(defmethod active-page :new-invoice [] new-invoice-page)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
86
src/cljs/auto_ap/views/pages/import_invoices.cljs
Normal file
86
src/cljs/auto_ap/views/pages/import_invoices.cljs
Normal file
@@ -0,0 +1,86 @@
|
||||
(ns auto-ap.views.pages.import-invoices
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[auto-ap.events :as events]
|
||||
[auto-ap.subs :as subs]
|
||||
[cljsjs.dropzone :as dropzone]
|
||||
[cljs.reader :as edn]))
|
||||
(def dropzone
|
||||
(let [company (re-frame/subscribe [::subs/company])
|
||||
token (re-frame/subscribe [::subs/token])]
|
||||
(with-meta
|
||||
(fn []
|
||||
[:form.dz {:action "/pdf-upload"}
|
||||
[:div.tile.notification
|
||||
[:div.has-text-centered {:style {:padding "80px 0px" :width "100%"}}
|
||||
[:span
|
||||
[:span {:class "icon"}
|
||||
[:i {:class "fa fa-cloud-download"}]]
|
||||
"Drop any invoices you want to process here"]]]])
|
||||
{:component-did-mount (fn [this]
|
||||
(js/Dropzone. (reagent/dom-node this)
|
||||
(clj->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 "<div class='dz-hidden-preview'></div>"})))})))
|
||||
(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]))}))
|
||||
12
src/cljs/auto_ap/views/pages/index.cljs
Normal file
12
src/cljs/auto_ap/views/pages/index.cljs
Normal file
@@ -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"]]]]]])
|
||||
19
src/cljs/auto_ap/views/pages/login.cljs
Normal file
19
src/cljs/auto_ap/views/pages/login.cljs
Normal file
@@ -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"]]]]]])
|
||||
74
src/cljs/auto_ap/views/pages/new_invoice.cljs
Normal file
74
src/cljs/auto_ap/views/pages/new_invoice.cljs
Normal file
@@ -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"]]]]]))
|
||||
34
src/cljs/auto_ap/views/pages/paid_invoices.cljs
Normal file
34
src/cljs/auto_ap/views/pages/paid_invoices.cljs
Normal file
@@ -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]) }))
|
||||
35
src/cljs/auto_ap/views/pages/unpaid_invoices.cljs
Normal file
35
src/cljs/auto_ap/views/pages/unpaid_invoices.cljs
Normal file
@@ -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]) }))
|
||||
9
src/cljs/auto_ap/views/utils.cljs
Normal file
9
src/cljs/auto_ap/views/utils.cljs
Normal file
@@ -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")))
|
||||
Reference in New Issue
Block a user