(ns auto-ap.views.pages.import-invoices (:require [re-frame.core :as re-frame] [reagent.core :as reagent] [reagent.dom :as rdom] [auto-ap.subs :as subs] [auto-ap.views.components.layouts :refer [side-bar-layout]] [auto-ap.views.components.invoices.side-bar :refer [invoices-side-bar]] [auto-ap.views.utils :refer [dispatch-event with-user ->%]] [auto-ap.utils :refer [by]] [auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table] [cljs.reader :as edn] [auto-ap.status :as status] [vimsical.re-frame.fx.track :as track] #_{:clj-kondo/ignore [:unused-namespace]} [dropzone :as dz] [auto-ap.views.pages.data-page :as data-page] [clojure.set :as set] [auto-ap.effects.forward :as forward] [goog.string :as gstring] [auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]])) (defn dropzone [] (let [client (re-frame/subscribe [::subs/client]) token (re-frame/subscribe [::subs/token]) vendor (reagent/atom nil)] (reagent/create-class {:display-name "dropzone" :component-did-mount (fn [this] (dz. (rdom/dom-node this) (clj->js {:init (fn [] (let [^js/Dropzone t (js-this)] (.on t "addedfiles" (fn [] (re-frame/dispatch [::status/completed ::import]))) (.on t "success" (fn [_ _] (re-frame/dispatch [::invalidated]))) (.on t "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 "
"}))) :reagent-render (fn [] [:form.dz {:action "/api/invoices/upload"} [:div.field.has-addons [:p.control [:a.button.is-static "Force Location"]] [:p.control [:input.input {:name "location" :placeholder "SG" :size "4" :maxlength "2" :style {:display "inline"}}]]] [:div.field.has-addons [:p.control [:a.button.is-static "Force vendor"]] [:div.control {:style {:width "400px"}} [search-backed-typeahead {:search-query (fn [i] [:search_vendor {:query i} [:name :id]]) :type "typeahead-v3" :name "vendor" :on-change (fn [v] (reset! vendor v)) :value @vendor}]]] [: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"]]]])}))) (re-frame/reg-sub ::batch (fn [db] (-> db (::batch 0)))) (re-frame/reg-event-fx ::invalidated (fn [{:keys [db]} _] {:dispatch-n [[::params-change @(re-frame/subscribe [::data-page/params :import-invoices])] [::data-page/reset-checked :import-invoices]] :db (update db ::batch inc)})) (re-frame/reg-event-fx ::mounted (fn [_ _] {::track/register [{:id ::params :subscription [::data-page/params :import-invoices] :event-fn (fn [params] [::params-change params])} ] ::forward/register [{:id ::received :events #{::data-page/received} :event-fn (fn [[_ _ {:keys [data]}]] [::received data])}]})) (re-frame/reg-event-fx ::unmounted (fn [_ _] {::track/dispose {:id ::params} :dispatch [::data-page/dispose :import-invoices]})) (re-frame/reg-event-fx ::params-change [with-user] (fn [{:keys [user] } [_ params]] {:graphql {:token user :owns-state {:single [::data-page/page :import-invoices]} :query-obj (invoice-table/query (assoc params :import-status "pending")) :on-success (fn [result] (let [result (set/rename-keys (first (:invoice-page result)) {:invoices :data})] [::data-page/received :import-invoices result]))}})) (re-frame/reg-event-fx ::received (fn [_ [_ data]] {:dispatch [::data-page/toggle-check :import-invoices (by :id data)]})) (re-frame/reg-event-fx ::reject-invoices-clicked (fn [{:keys [db]} [_ invoices]] {:graphql {:token (-> db :user) :owns-state {:single ::reject} :query-obj {:venia/operation {:operation/type :mutation :operation/name "RejectInvoices"} :venia/queries [[:reject-invoices {:invoices (vec invoices)} []]]} :on-success [::invalidated]}})) (re-frame/reg-event-fx ::approve-invoices-clicked (fn [{:keys [db]} [_ invoices]] {:graphql {:token (-> db :user) :owns-state {:single ::approve} :query-obj {:venia/operation {:operation/type :mutation :operation/name "ApproveInvoices"} :venia/queries [[:approve-invoices {:invoices (vec invoices)} []]]} :on-success [::invalidated]}})) (defn approve-reject-button [checked] [:div.buttons [:button.button.is-primary {:on-click (dispatch-event [::approve-invoices-clicked checked]) :class (status/class-for @(re-frame/subscribe [::status/single ::approve])) :disabled (or (not (boolean (seq checked))) (status/disabled-for @(re-frame/subscribe [::status/single ::approve])))} "Approve " (str (count checked) " invoices") [:span " "]] [:button.button.is-warning {:on-click (dispatch-event [::reject-invoices-clicked checked]) :class (status/class-for @(re-frame/subscribe [::status/single ::reject])) :disabled (or (not (boolean (seq checked))) (status/disabled-for @(re-frame/subscribe [::status/single ::reject])))} "Reject " (str (count checked) " invoices") [:span " "]]]) (def import-invoices-content (with-meta (fn [] (let [page @(re-frame/subscribe [::data-page/page :import-invoices]) batch @(re-frame/subscribe [::batch]) client (:id @(re-frame/subscribe [::subs/client])) checked-set (into #{} (:checked-set page))] ^{:key (str client "-" batch)} [:div [:h1.title "Upload invoices"] [status/status-notification {:statuses [[::status/single ::approve] [::status/single ::reject] [::status/single ::import]]}] ^{:key (str batch)} [dropzone] [:div.mb-4] [:div {:class "card found-invoices",} [:div {:class "card-header"} [:span {:class "card-header-title"} "Found Invoices"]] [:div {:class "card-content"} [approve-reject-button (if (get checked-set "header") (into #{} (map :id (:data (:data page)))) checked-set)] (if (seq (:data (:data page))) [invoice-table {:id :approved :data-page :import-invoices :overrides {:client (fn [row] [:p (:name (:client row)) [:p [:i.is-size-7 (:client-identifier row)] " " [:span {:style {:background-color (gstring/format "rgba(255, 0,0,%.2f)" (- 1.0 (:similarity row))) }} (->% (:similarity row))]]])} :check-boxes true}] [:span "No pending invoices"])]]])) {:component-did-mount (fn [] (re-frame/dispatch-sync [::mounted])) :component-will-unmount (fn [] (re-frame/dispatch-sync [::unmounted]))})) (defn import-invoices-page [] [side-bar-layout {:side-bar [invoices-side-bar {}] :main [import-invoices-content ]}])