From ff2bf4c2b3fa6c184e9a10efd340557e8a3bfad2 Mon Sep 17 00:00:00 2001 From: Bryce Date: Tue, 9 Apr 2024 23:27:35 -0700 Subject: [PATCH 01/11] makes client selection not contanimate other tabs --- resources/public/js/alpine-vals.js | 9 + src/clj/auto_ap/handler.clj | 86 +++++----- src/clj/auto_ap/routes/ezcater_xls.clj | 3 +- src/clj/auto_ap/ssr/admin.clj | 2 +- src/clj/auto_ap/ssr/admin/excel_invoice.clj | 2 +- src/clj/auto_ap/ssr/admin/history.clj | 3 +- src/clj/auto_ap/ssr/company.clj | 3 +- src/clj/auto_ap/ssr/company_dropdown.clj | 25 ++- .../auto_ap/ssr/components/user_dropdown.clj | 3 +- src/clj/auto_ap/ssr/grid_page_helper.clj | 2 +- src/clj/auto_ap/ssr/invoice/glimpse.clj | 3 +- src/clj/auto_ap/ssr/invoices.clj | 12 +- src/clj/auto_ap/ssr/outgoing_invoice/new.clj | 2 +- src/clj/auto_ap/ssr/transaction/insights.clj | 3 +- src/clj/auto_ap/ssr/ui.clj | 7 +- src/clj/auto_ap/ssr/utils.clj | 2 +- src/cljs/auto_ap/client_selection.cljs | 30 ++++ src/cljs/auto_ap/effects.cljs | 38 ++--- src/cljs/auto_ap/events.cljs | 161 +++++++++--------- src/cljs/auto_ap/subs.cljs | 15 +- 20 files changed, 228 insertions(+), 183 deletions(-) create mode 100644 src/cljs/auto_ap/client_selection.cljs diff --git a/resources/public/js/alpine-vals.js b/resources/public/js/alpine-vals.js index 55af6cbf..4dcd57d0 100644 --- a/resources/public/js/alpine-vals.js +++ b/resources/public/js/alpine-vals.js @@ -8,6 +8,15 @@ document.addEventListener('alpine:init', () => { el.removeEventListener('htmx:configRequest', config); }) }) +Alpine.directive('hx-header', (el, { value, expression }, { evaluateLater, effect, cleanup, evaluate }) => { + var config = function(evt) { + evt.detail.headers[value] = evaluate(expression); // add a new parameter into the request + } + el.addEventListener('htmx:configRequest', config); + cleanup(() => { + el.removeEventListener('htmx:configRequest', config); + }) + }) Alpine.directive('dispatch', (el, { value, expression }, { evaluateLater, effect, cleanup, evaluate }) => { let dependent_properties = evaluateLater(expression) diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj index 864e082a..ace8ffea 100644 --- a/src/clj/auto_ap/handler.clj +++ b/src/clj/auto_ap/handler.clj @@ -2,7 +2,6 @@ (:require [amazonica.core :refer [defcredential]] [auto-ap.client-routes :as client-routes] [auto-ap.datomic :refer [conn pull-many]] - [auto-ap.datomic.clients :as d-clients] [auto-ap.graphql.utils :refer [assert-can-see-client limited-clients]] [auto-ap.logging :as alog] @@ -16,6 +15,7 @@ [auto-ap.routes.yodlee2 :as yodlee2] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.core :as ssr] + [auto-ap.ssr.utils :refer [entity-id main-transformer]] [bidi.bidi :as bidi] [bidi.ring :refer [->ResourcesMaybe make-handler]] [buddy.auth.backends.session :refer [session-backend]] @@ -26,13 +26,14 @@ [cheshire.core :as cheshire] [clj-time.coerce :as coerce] [clj-time.core :as time] - [clojure.edn :as edn] + [clojure.data.json :as json] [clojure.set :as set] [clojure.string :as str] [com.brunobonacci.mulog :as mu] [config.core :refer [env]] [datomic.api :as dc] [hiccup2.core :as hiccup] + [malli.core :as mc] [ring.middleware.edn :refer [wrap-edn-params]] [ring.middleware.multipart-params :as mp] [ring.middleware.params :refer [wrap-params]] @@ -110,10 +111,10 @@ uri :request-method request-method)) matched-hx-current-url-route (some->> (get headers "hx-current-url") - url/url - :path - (bidi/match-route ssr-routes/only-routes) - :handler)] + url/url + :path + (bidi/match-route ssr-routes/only-routes) + :handler)] (handler (assoc request :matched-route matched-route @@ -135,6 +136,7 @@ (:uri request) :request-method (:request-method request))) + :client-selection (:client-selection request) :source "request" :query (:uri request) :request-method (:request-method request) @@ -185,10 +187,17 @@ request (assoc request :hx-query-params query-params)] (handler request)))) +(def client-selection-schema + (mc/schema + [:orn + [:global [:enum :all :mine]] + [:group-name [:map [:group :string]]] + [:specific [:map [:selected [:vector entity-id]]]]])) + (defn wrap-hydrate-clients [handler] (fn [request] - (let [x-clients (-> request :session :client-selection) + (let [x-clients (-> request :client-selection) identity (or (-> request :identity) (-> request :session :identity)) ideal-ids (set (cond @@ -202,28 +211,21 @@ (= :mine x-clients) (map :db/id (:user/clients identity)) - (= :group (first x-clients)) + (:group x-clients) (->> (dc/q '[:find ?c :in $ ?g :where [?c :client/groups ?g]] (dc/db conn) - (str/upper-case (or (second x-clients) "INVALID"))) + (str/upper-case (or (:group x-clients) "INVALID"))) (map first) set) - (seq x-clients) + (seq (:selected x-clients)) (->> x-clients - (map (fn [c] - (if (string? c) - (try - (Long/parseLong c) - (catch Exception e - nil)) - c))) + :selected (filter #(not (nil? %))) set))) - limited-clients (some->> (limited-clients identity) (map :db/id) set) @@ -236,12 +238,11 @@ (pull-many (dc/db conn) '[:db/id :client/name :client/code :client/locations :client/matches :client/feature-flags - {:client/bank-accounts [:db/id - {:bank-account/type [:db/ident]} + {:client/bank-accounts [:db/id + {:bank-account/type [:db/ident]} :bank-account/number :bank-account/name :bank-account/code]}]))] - (mu/with-context {:clients (take 10 (map :client/code clients))} (handler (assoc request :clients clients @@ -251,33 +252,22 @@ (defn wrap-store-client-in-session [handler] (fn [{:keys [headers identity] :as request}] - (let [x-clients (edn/read-string (get headers "x-clients")) - x-clients (try (if-let [client-id (and x-clients - (sequential? x-clients) - (first x-clients) - (not= :group (first x-clients)) - (first x-clients))] - (do - (assert-can-see-client identity (cond-> client-id - (string? client-id) (Long/parseLong))) - [(if (string? client-id) - (Long/parseLong client-id) - client-id)]) - x-clients) - (catch Exception e - (alog/warn ::cant-access :error e - :identity identity - :x-clients (pr-str x-clients)) - :all)) - new-request (if x-clients - (assoc-in request [:session :client-selection] x-clients) - request)] + (let [client-selection (try (mc/decode client-selection-schema (some-> (get headers "x-clients") not-empty json/read-str) main-transformer) + (catch Exception e + (alog/warn ::cant-access :error e + :identity identity + :x-clients (pr-str (get headers "x-clients"))) + nil)) + + new-request (if client-selection + (assoc-in request [:client-selection] client-selection) + (assoc-in request [:client-selection] (get-in request [:session :client-selection] :all)))] (cond-> (handler new-request) - x-clients (update :session - (fn [new-session] - (-> (:session request) - (into new-session) - (assoc :client-selection x-clients)))))))) + client-selection (update :session + (fn [new-session] + (-> (:session request) + (into new-session) + (assoc :client-selection client-selection)))))))) (defn wrap-gunzip-jwt [handler] @@ -317,9 +307,9 @@ (-> route-handler (wrap-hx-current-url-params) (wrap-guess-route) + (wrap-logging) (wrap-hydrate-clients) (wrap-store-client-in-session) - (wrap-logging) (wrap-gunzip-jwt) (wrap-authorization auth-backend) (wrap-authentication auth-backend diff --git a/src/clj/auto_ap/routes/ezcater_xls.clj b/src/clj/auto_ap/routes/ezcater_xls.clj index ec591ec4..d9af6e75 100644 --- a/src/clj/auto_ap/routes/ezcater_xls.clj +++ b/src/clj/auto_ap/routes/ezcater_xls.clj @@ -193,8 +193,9 @@ (base-page request (com/page {:nav com/admin-aside-nav - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :client (:client request) + :clients (:clients request) :identity (:identity request) :request request :app-params {:hx-get (bidi/path-for ssr-routes/only-routes diff --git a/src/clj/auto_ap/ssr/admin.clj b/src/clj/auto_ap/ssr/admin.clj index 0477acc8..b412afcc 100644 --- a/src/clj/auto_ap/ssr/admin.clj +++ b/src/clj/auto_ap/ssr/admin.clj @@ -45,7 +45,7 @@ (base-page request (com/page {:nav com/admin-aside-nav - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :clients (:clients request) :client (:client request) :identity (:identity request)} diff --git a/src/clj/auto_ap/ssr/admin/excel_invoice.clj b/src/clj/auto_ap/ssr/admin/excel_invoice.clj index 98ae86b0..1149bf44 100644 --- a/src/clj/auto_ap/ssr/admin/excel_invoice.clj +++ b/src/clj/auto_ap/ssr/admin/excel_invoice.clj @@ -243,7 +243,7 @@ (base-page request (com/page {:nav com/admin-aside-nav - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :clients (:clients request) :client (:client request) :identity (:identity request) diff --git a/src/clj/auto_ap/ssr/admin/history.clj b/src/clj/auto_ap/ssr/admin/history.clj index fea7e388..d04ee768 100644 --- a/src/clj/auto_ap/ssr/admin/history.clj +++ b/src/clj/auto_ap/ssr/admin/history.clj @@ -166,8 +166,9 @@ (some-> route-params (get :entity-id) Long/parseLong))] (base-page request (com/page {:nav com/admin-aside-nav - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :client (:client request) + :clients (:clients request) :identity (:identity request) :request request :app-params {:hx-get (bidi/path-for ssr-routes/only-routes diff --git a/src/clj/auto_ap/ssr/company.clj b/src/clj/auto_ap/ssr/company.clj index 17fbb8dd..eba9fa59 100644 --- a/src/clj/auto_ap/ssr/company.clj +++ b/src/clj/auto_ap/ssr/company.clj @@ -131,8 +131,9 @@ (base-page request (com/page {:nav com/company-aside-nav - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :client (:client request) + :clients (:clients request) :identity (:identity request) :app-params {:hx-get (bidi/path-for ssr-routes/only-routes :company) diff --git a/src/clj/auto_ap/ssr/company_dropdown.clj b/src/clj/auto_ap/ssr/company_dropdown.clj index 1d527d08..2a8c3e0f 100644 --- a/src/clj/auto_ap/ssr/company_dropdown.clj +++ b/src/clj/auto_ap/ssr/company_dropdown.clj @@ -1,6 +1,7 @@ (ns auto-ap.ssr.company-dropdown - (:require [auto-ap.datomic :refer [conn pull-attr pull-many]] + (:require [auto-ap.datomic :refer [conn pull-many]] [auto-ap.graphql.utils :refer [cleanse-query]] + [auto-ap.logging :as alog] [auto-ap.solr :as solr] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.hx :as hx] @@ -10,7 +11,9 @@ [clojure.string :as str] [datomic.api :as dc] [hiccup2.core :as hiccup] - [iol-ion.query :refer [can-see-client?]])) + [iol-ion.query :refer [can-see-client?]] + [clojure.data.json :as json])) + (defn dropdown-search-results* [{:keys [options]}] [:ul @@ -25,6 +28,7 @@ :request-method :put) :hx-target "#company-dropdown" :hx-headers (hx/json {"x-clients" (pr-str [:group group])}) + "@click" (format "globalClientSelection={group: %s}" (hx/json group)) :hx-swap "outerHTML" :hx-trigger "click"} name] @@ -34,6 +38,7 @@ :request-method :put) :hx-target "#company-dropdown" :hx-headers (format "{\"x-clients\": \"[%d]\"}" id) + "@click" (format "globalClientSelection={selected: [%d]}" id) :hx-swap "outerHTML" :hx-trigger "click"} name])]])]) @@ -64,11 +69,18 @@ (dropdown-search-results* {:options (get-clients identity (get (:query-params request) "search-text"))}))) (defn dropdown [{:keys [client-selection client identity clients]}] + (alog/peek ::clients clients) [:div#company-dropdown [:script (hiccup/raw "localStorage.setItem(\"last-client-id\", \"" (:db/id client) "\")" "\n" - "localStorage.setItem(\"last-selected-clients\", " (pr-str (pr-str client-selection)) ")")] + "localStorage.setItem(\"last-selected-clients\", " (json/write-str (json/write-str client-selection)) + #_(cond (:group client-selection) + (:group client-selection) + (:selected client-selection) + (:selected client-selection) + :else + client-selection) ")")] [:div [:button#company-dropdown-button {:class "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" :type "button"} @@ -80,7 +92,7 @@ (and client (= 1 (count clients))) - ( :client/name client) + (:client/name client) :else (str (count clients) " Companies")) @@ -116,6 +128,8 @@ :active-client :request-method :put) :hx-target "#company-dropdown" + + "@click" "globalClientSelection=\"mine\"" :hx-headers "{\"x-clients\": \":mine\"}" :hx-swap "outerHTML" :hx-trigger "click"} @@ -127,6 +141,7 @@ :active-client :request-method :put) :hx-target "#company-dropdown" + "@click" "globalClientSelection=\"all\"" :hx-headers "{\"x-clients\": \":all\"}" :hx-swap "outerHTML" :hx-trigger "click"} @@ -161,7 +176,7 @@ function initCompanyDropdown() { (defn active-client [{:keys [identity params] :as request}] (assoc (html-response - (dropdown {:client-selection (:client-selection (:session request)) + (dropdown {:client-selection (:client-selection request) :clients (:clients request) :client (:client request) :identity identity})) diff --git a/src/clj/auto_ap/ssr/components/user_dropdown.clj b/src/clj/auto_ap/ssr/components/user_dropdown.clj index a30cdb71..d244f7fd 100644 --- a/src/clj/auto_ap/ssr/components/user_dropdown.clj +++ b/src/clj/auto_ap/ssr/components/user_dropdown.clj @@ -27,7 +27,8 @@ [:li [:a {:href (bidi/path-for ssr-routes/only-routes :company), :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "My Company"]] (when (= "admin" (:user/role identity)) - [:a {:href (bidi/path-for ssr-routes/only-routes :auto-ap.routes.admin/page), :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "Admin"]) + [:a {:href (bidi/path-for ssr-routes/only-routes :auto-ap.routes.admin/page), + :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem"} "Admin"]) [:li [:a {:href "#", :class "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white", :role "menuitem" "_" (hiccup/raw "on click toggle .dark on ")} diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 91104757..def70ad0 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -245,7 +245,7 @@ (com/page {:nav (:nav grid-spec) :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] [:div#page-specific-nav (page-specific-nav request)]) - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :clients (:clients request) :client (:client request) :identity (:identity request) diff --git a/src/clj/auto_ap/ssr/invoice/glimpse.clj b/src/clj/auto_ap/ssr/invoice/glimpse.clj index 87f5580e..19850803 100644 --- a/src/clj/auto_ap/ssr/invoice/glimpse.clj +++ b/src/clj/auto_ap/ssr/invoice/glimpse.clj @@ -421,8 +421,9 @@ invoice_dropzone = new Dropzone(\"#invoice\", { (base-page request (com/page {:nav com/admin-aside-nav - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :client (:client request) + :clients (:clients request) :identity (:identity request) :app-params {:hx-get (bidi/path-for ssr-routes/only-routes :invoice-glimpse) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 1f87c5fd..4700254a 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -120,8 +120,6 @@ (exact-match-id* request)]]) - - (defn fetch-ids [db {:keys [query-params route-params] :as request}] (let [valid-clients (extract-client-ids (:clients request) (:client-id request) @@ -556,10 +554,10 @@ _ (audit-transact tx identity)] (alog/info ::unvoiding-invoice :transaction :tx) (html-response - (row* identity (dc/pull (dc/db conn) default-read id) {:flash? true - :request request}) - :headers (cond-> {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" id) - "hx-reswap" "outerHTML"})))) + (row* identity (dc/pull (dc/db conn) default-read id) {:flash? true + :request request}) + :headers (cond-> {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" id) + "hx-reswap" "outerHTML"})))) (defn delete [{invoice :entity :as request identity :identity}] (exception->notification @@ -1137,7 +1135,7 @@ (->> (dc/q '[:find ?i :in $ [?i ...] :where [?i :invoice/status :invoice-status/unpaid] - [?i :invoice/client ?c] ] + [?i :invoice/client ?c]] (dc/db conn) ids) (map first))) diff --git a/src/clj/auto_ap/ssr/outgoing_invoice/new.clj b/src/clj/auto_ap/ssr/outgoing_invoice/new.clj index 790c1372..16e8249b 100644 --- a/src/clj/auto_ap/ssr/outgoing_invoice/new.clj +++ b/src/clj/auto_ap/ssr/outgoing_invoice/new.clj @@ -251,7 +251,7 @@ (base-page request (com/page {:nav com/main-aside-nav - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :clients (:clients request) :client (:client request) :identity (:identity request) diff --git a/src/clj/auto_ap/ssr/transaction/insights.clj b/src/clj/auto_ap/ssr/transaction/insights.clj index 18f9f1a1..545df6a3 100644 --- a/src/clj/auto_ap/ssr/transaction/insights.clj +++ b/src/clj/auto_ap/ssr/transaction/insights.clj @@ -318,8 +318,9 @@ (base-page request (com/page {:nav com/main-aside-nav - :client-selection (:client-selection (:session request)) + :client-selection (:client-selection request) :client (:client request) + :clients (:clients request) :identity (:identity request) :app-params {:hx-get (bidi/path-for ssr-routes/only-routes :transaction-insights) diff --git a/src/clj/auto_ap/ssr/ui.clj b/src/clj/auto_ap/ssr/ui.clj index 89f83ca8..e4021556 100644 --- a/src/clj/auto_ap/ssr/ui.clj +++ b/src/clj/auto_ap/ssr/ui.clj @@ -69,9 +69,10 @@ input::-webkit-inner-spin-button { input[type=number] { -moz-appearance:textfield; /* Firefox */ } "] - - - [:body {:hx-ext "disable-submit, class-tools"} + [:body {:hx-ext "disable-submit, class-tools" + :x-data (hx/json {:globalClientSelection (or (:client-selection request) + :all )}) ;; TODO remove once session is used + :x-hx-header:x-clients "JSON.stringify(globalClientSelection)"} contents [:script {:src "/js/flowbite.min.js"}] diff --git a/src/clj/auto_ap/ssr/utils.clj b/src/clj/auto_ap/ssr/utils.clj index cf7b5df9..56fb3bbd 100644 --- a/src/clj/auto_ap/ssr/utils.clj +++ b/src/clj/auto_ap/ssr/utils.clj @@ -107,7 +107,7 @@ (mt2/transformer {:decoders {:map (fn [m] - (if (not (seq (filter identity (vals m)))) + (if (and (map? m) (not (seq (filter identity (vals m))))) nil m)) :string empty->nil diff --git a/src/cljs/auto_ap/client_selection.cljs b/src/cljs/auto_ap/client_selection.cljs new file mode 100644 index 00000000..bfbbcb0f --- /dev/null +++ b/src/cljs/auto_ap/client_selection.cljs @@ -0,0 +1,30 @@ +(ns auto-ap.client-selection + (:require [clojure.string :as str] + [malli.core :as mc] + [malli.transform :as mt2])) + +;; TODO remove this eventuall +(defn str->keyword [s] + (if (string? s) + (let [[ns k] (str/split s #"/")] + (if (and ns k) + (keyword ns k) + (keyword s))) + s)) + +(defn keyword->str [k] + (subs (str k) 1)) +(def client-selection-schema + (mc/schema + [:orn + [:global [:enum :all :mine]] + [:group-name [:map [:group :string]]] + [:specific [:map [:selected [:vector nat-int?]]]]])) + +(def client-selection-transformer + (mt2/transformer + mt2/json-transformer + mt2/string-transformer + (mt2/key-transformer {:encode keyword->str :decode str->keyword}))) + +;; END TODO \ No newline at end of file diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs index ea49046e..5f0ae121 100644 --- a/src/cljs/auto_ap/effects.cljs +++ b/src/cljs/auto_ap/effects.cljs @@ -1,27 +1,25 @@ (ns auto-ap.effects (:require-macros [cljs.core.async.macros :refer [go]]) - (:require - [auto-ap.history :as p] - [auto-ap.status :as status] - [auto-ap.views.utils :refer [date->str standard]] - [cemerick.url :as url] - [cljs-http.client :as http] - [cljs-time.coerce :as c] - [cljs-time.core :as time] - [cljs-time.format :as format] - [cljs.core.async :refer [str standard]] + [cemerick.url :as url] + [cljs-http.client :as http] + [cljs-time.coerce :as c] + [cljs-time.core :as time] + [cljs-time.format :as format] + [cljs.core.async :refer [js (:selected-clients @re-frame.db/app-db)))) headers)) (re-frame/reg-fx diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index 8a3e08ce..d85f0291 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -1,24 +1,24 @@ (ns auto-ap.events - (:require - [auto-ap.db :as db] - [auto-ap.routes :as routes] - [auto-ap.utils :refer [by]] - [auto-ap.views.pages.data-page :as data-page] - [auto-ap.views.utils :refer [parse-jwt with-user gunzip]] - [bidi.bidi :as bidi] - [clojure.string :as str] - [clojure.edn :as edn] - [goog.crypt.base64 :as b64] - [re-frame.core :as re-frame] - [auto-ap.ssr-routes :as ssr-routes] - [cemerick.url :as url] - [auto-ap.subs :as subs] - [pako])) + (:require [auto-ap.client-selection :refer [client-selection-schema + client-selection-transformer]] + [auto-ap.db :as db] + [auto-ap.routes :as routes] + [auto-ap.ssr-routes :as ssr-routes] + [auto-ap.utils :refer [by]] + [auto-ap.views.pages.data-page :as data-page] + [auto-ap.views.utils :refer [gunzip parse-jwt with-user]] + [bidi.bidi :as bidi] + [cemerick.url :as url] + [clojure.string :as str] + [goog.crypt.base64 :as b64] + [malli.core :as mc] + [pako] + [re-frame.core :as re-frame])) (defn jwt->data [token] - (let [raw (js->clj (.parse js/JSON (b64/decodeString (second (str/split token #"\." ))))) + (let [raw (js->clj (.parse js/JSON (b64/decodeString (second (str/split token #"\."))))) gz-clients (or (:gz-clients raw) (get raw "gz-clients"))] (cond-> raw @@ -29,7 +29,7 @@ (defn client-query [] (cond-> [:id :name :code :email :locations :feature-flags :groups [:emails [:id :email :description]] - [:bank-accounts [:id :code :bank-name :name :type :visible + [:bank-accounts [:id :code :bank-name :name :type :visible :locations :include-in-reports :current-balance :sort-order]]])) @@ -46,18 +46,22 @@ [:plaid-account [:name :id :number]] [:intuit-bank-account [:name :id :external-id]] :use-date-instead-of-post-date - :locations :include-in-reports :current-balance :yodlee-balance-old] ] + :locations :include-in-reports :current-balance :yodlee-balance-old]] [:address [:id :street1 :street2 :city :state :zip]] [:forecasted-transactions [:id :amount :identifier :day-of-month]]] - (= "admin" (or (get (jwt->data token) "role") (get (jwt->data token) "user/role")) ) (into [[:yodlee-provider-accounts [:id [:accounts [:id :name :number :available-balance]]]] - [:plaid-items [:id [:accounts [:id :name :number :balance]]]]]))) + (= "admin" (or (get (jwt->data token) "role") (get (jwt->data token) "user/role"))) (into [[:yodlee-provider-accounts [:id [:accounts [:id :name :number :available-balance]]]] + [:plaid-items [:id [:accounts [:id :name :number :balance]]]]]))) + + + (re-frame/reg-event-fx ::initialize-db (fn [{:keys [_]} [_ token]] (let [handler (:handler (bidi/match-route routes/routes (.. js/window -location -pathname))) last-client-id (.getItem js/localStorage "last-client-id") - last-selected-clients (edn/read-string (.getItem js/localStorage "last-selected-clients")) + last-selected-clients (js->clj (.parse js/JSON (.getItem js/localStorage "last-selected-clients"))) + last-selected-clients (mc/decode client-selection-schema last-selected-clients client-selection-transformer) jwt-data (some-> token jwt->data) selected-client-assignment (cond (and token (= "admin" (get jwt-data "user/role")) @@ -70,9 +74,9 @@ [(js/parseInt last-client-id)] :else - nil)] + nil) ] + - (cond (= :login handler) {:db (cond-> (assoc db/default-db @@ -89,7 +93,7 @@ :selected-clients last-selected-clients :user token)} - (and token (= "none" (or (get jwt-data "role") (get jwt-data "user/role")) )) + (and token (= "none" (or (get jwt-data "role") (get jwt-data "user/role")))) {:redirect "/needs-activation" :db (assoc db/default-db :active-route :needs-activation @@ -112,7 +116,7 @@ :on-success [::received-initial] :on-error [::failed-initial]}} selected-client-assignment - (assoc :set-local-storage ["last-selected-clients" selected-client-assignment])))))) + (assoc :set-local-storage ["last-selected-clients" (.stringify js/JSON selected-client-assignment)])))))) (re-frame/reg-event-db @@ -125,13 +129,13 @@ ::received-initial (fn [{:keys [db]} [_ {clients :client}]] (let [only-one-client (when (= 1 (count clients)) - (->> clients first :id ))] + (->> clients first :id))] (when only-one-client (.setItem js/localStorage "last-client-id" only-one-client) (.setItem js/localStorage "last-selected-clients" (pr-str [(js/parseInt only-one-client)]))) - {:db (cond-> (-> db - (assoc :clients (by :id clients) ) + {:db (cond-> (-> db + (assoc :clients (by :id clients)) (assoc :is-initial-loading? false) (assoc :client (or only-one-client (->> clients @@ -167,30 +171,31 @@ :active-route :initial-error))) (re-frame/reg-event-fx - ::swapped-client - (fn [{:keys [db]} [_ client client-identifier]] - (when (:id client) - (.setItem js/localStorage "last-client-id" (:id client))) - (.setItem js/localStorage "last-selected-clients" - (condp = client-identifier - :all - :all + ::swapped-client + (fn [{:keys [db]} [_ client client-identifier]] + (when (:id client) + (.setItem js/localStorage "last-client-id" (:id client))) + (.setItem js/localStorage "last-selected-clients" + (.stringify js/JSON + (clj->js (condp = client-identifier + :all + :all - :mine - :mine + :mine + :mine - (pr-str [(js/parseInt (:id client))]))) + {:selected [(js/parseInt (:id client))]})))) - {:db (assoc db :client (:id client) - :selected-clients - (condp = client-identifier - :all - :all + {:db (assoc db :client (:id client) + :selected-clients + (condp = client-identifier + :all + :all - :mine - :mine + :mine + :mine - [(js/parseInt (:id client))]))})) + {:selected [(js/parseInt (:id client))]}))})) (re-frame/reg-event-fx ::swap-client @@ -210,7 +215,7 @@ (re-frame/reg-event-fx ::set-active-route (fn [{:keys [db]} [_ handler params route-params]] - (cond + (cond (and (not= :login handler) (not (:user db))) {:redirect (bidi/path-for routes/routes :login) :db (assoc db :active-route :login @@ -256,36 +261,36 @@ (fn [{:keys [db]} _] {:graphql {:token (:user db) :query-obj {:venia/queries [[:yodlee-merchants - [:name :yodlee-id :id]]]} + [:name :yodlee-id :id]]]} :on-success [::yodlee-merchants-received]}})) (re-frame/reg-event-fx - ::vendor-preferences-requested - [with-user] - (fn [{:keys [user]} [_ {:keys [ client-id vendor-id on-success on-failure owns-state]}]] - {:graphql {:token user - :query-obj {:venia/queries [[:vendor-by-id - {:id vendor-id} - [[:automatically-paid-when-due [:id]] - [:schedule-payment-dom [[:client [:id]] :dom]] - [:default-account [:id]]]] - [:account-for-vendor - {:vendor-id vendor-id - :client-id client-id} - [:name :id :numeric-code :location]]]} - :owns-state owns-state - :on-success (fn [r] - (let [schedule-payment-dom (->> r - :vendor-by-id - :schedule-payment-dom - (filter (fn [spd] - (= (-> spd :client :id) - client-id))) - first - :dom) - automatically-paid-when-due (boolean ((->> r :vendor-by-id :automatically-paid-when-due (map :id) set) client-id))] - (conj on-success {:default-account (:account-for-vendor r) - :schedule-payment-dom schedule-payment-dom - :automatically-paid-when-due automatically-paid-when-due - :vendor-autopay? (or automatically-paid-when-due (boolean schedule-payment-dom))}))) - :on-failure on-failure}})) + ::vendor-preferences-requested + [with-user] + (fn [{:keys [user]} [_ {:keys [client-id vendor-id on-success on-failure owns-state]}]] + {:graphql {:token user + :query-obj {:venia/queries [[:vendor-by-id + {:id vendor-id} + [[:automatically-paid-when-due [:id]] + [:schedule-payment-dom [[:client [:id]] :dom]] + [:default-account [:id]]]] + [:account-for-vendor + {:vendor-id vendor-id + :client-id client-id} + [:name :id :numeric-code :location]]]} + :owns-state owns-state + :on-success (fn [r] + (let [schedule-payment-dom (->> r + :vendor-by-id + :schedule-payment-dom + (filter (fn [spd] + (= (-> spd :client :id) + client-id))) + first + :dom) + automatically-paid-when-due (boolean ((->> r :vendor-by-id :automatically-paid-when-due (map :id) set) client-id))] + (conj on-success {:default-account (:account-for-vendor r) + :schedule-payment-dom schedule-payment-dom + :automatically-paid-when-due automatically-paid-when-due + :vendor-autopay? (or automatically-paid-when-due (boolean schedule-payment-dom))}))) + :on-failure on-failure}})) diff --git a/src/cljs/auto_ap/subs.cljs b/src/cljs/auto_ap/subs.cljs index e16f2c39..e9d90ada 100644 --- a/src/cljs/auto_ap/subs.cljs +++ b/src/cljs/auto_ap/subs.cljs @@ -17,7 +17,6 @@ ::client :<- [::selected-clients] (fn [selected-clients] - (println "SELECTED CLIENTS ARE" selected-clients) (when (= 1 (count selected-clients)) (first selected-clients)))) @@ -44,10 +43,6 @@ :<- [::user] :<- [::clients] (fn [[selected-clients user clients]] - (println "SELECTED" selected-clients - "USER" user - "CLIENTS" (count clients)) - (cond (= :mine selected-clients) (sort-by :name (:user/clients user)) @@ -58,17 +53,15 @@ (nil? selected-clients)) clients - (= :group (and (sequential? selected-clients) - (first selected-clients))) - (let [group (second selected-clients)] + (:group selected-clients) + (let [group (:group selected-clients)] (filterv (fn [c] - (println "GROUP" group (:groups c)) ((set (:groups c)) group)) clients)) - (sequential? selected-clients) - (filter (comp (set (map coerce-string-version selected-clients)) coerce-string-version :id) + (:selected selected-clients) + (filter (comp (set (:selected selected-clients)) coerce-string-version :id) clients) :else From f12f8e14c247d6295f0419540c7911a0b796b31c Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 10 Apr 2024 00:36:41 -0700 Subject: [PATCH 02/11] Forces users to re login when there's a major update --- src/clj/auto_ap/handler.clj | 10 +++++--- src/clj/auto_ap/routes/auth.clj | 6 +++-- src/clj/auto_ap/session_version.clj | 36 +++++++++++++++++++++++++++++ src/clj/auto_ap/ssr/auth.clj | 6 +++-- src/cljs/auto_ap/effects.cljs | 10 ++++---- src/cljs/auto_ap/events.cljs | 10 ++++++-- 6 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 src/clj/auto_ap/session_version.clj diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj index ace8ffea..6629c598 100644 --- a/src/clj/auto_ap/handler.clj +++ b/src/clj/auto_ap/handler.clj @@ -2,8 +2,7 @@ (:require [amazonica.core :refer [defcredential]] [auto-ap.client-routes :as client-routes] [auto-ap.datomic :refer [conn pull-many]] - [auto-ap.graphql.utils :refer [assert-can-see-client - limited-clients]] + [auto-ap.graphql.utils :refer [limited-clients]] [auto-ap.logging :as alog] [auto-ap.routes.auth :as auth] [auto-ap.routes.exports :as exports] @@ -13,6 +12,7 @@ [auto-ap.routes.invoices :as invoices] [auto-ap.routes.queries :as queries] [auto-ap.routes.yodlee2 :as yodlee2] + [auto-ap.session-version :as session-version] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.core :as ssr] [auto-ap.ssr.utils :refer [entity-id main-transformer]] @@ -160,10 +160,12 @@ :exception e) (throw e))))))) + + (defn wrap-idle-session-timeout [handler] (fn [request] - (let [session (:session request {}) + (let [session (:session request {:version session-version/current-session-version}) end-time (coerce/to-date-time (::idle-timeout session))] (if (and end-time (time/before? end-time (time/now))) (if (get (:headers request) "hx-request") @@ -317,6 +319,8 @@ (dissoc auth :exp))})) #_(wrap-pprint-session) + + (session-version/wrap-session-version) (wrap-idle-session-timeout) (wrap-session {:store (cookie-store {:key diff --git a/src/clj/auto_ap/routes/auth.clj b/src/clj/auto_ap/routes/auth.clj index 9925f338..517e12f0 100644 --- a/src/clj/auto_ap/routes/auth.clj +++ b/src/clj/auto_ap/routes/auth.clj @@ -8,7 +8,8 @@ [config.core :refer [env]] [com.brunobonacci.mulog :as mu] [clojure.java.io :as io] - [clojure.edn :as edn])) + [clojure.edn :as edn] + [auto-ap.session-version :as session-version])) (def google-client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com") (def google-client-secret "OC-WemHurPXYpuIw5cT-B90g") @@ -94,7 +95,8 @@ (jwt/sign jwt (:jwt-secret env) {:alg :hs512}))} - :session {:identity (dissoc jwt :exp)}} + :session {:identity (dissoc jwt :exp) + :version session-version/current-session-version}} {:status 401 :body "Couldn't authenticate"})) (catch Exception e diff --git a/src/clj/auto_ap/session_version.clj b/src/clj/auto_ap/session_version.clj new file mode 100644 index 00000000..f623c3fa --- /dev/null +++ b/src/clj/auto_ap/session_version.clj @@ -0,0 +1,36 @@ +(ns auto-ap.session-version + (:require [bidi.bidi :as bidi])) + +;; TODO this should only be done until SSR is complete +;; once it is, it should just use redirects based on headers +;; no header=use default, mismatch header=redirect to login +(def current-session-version 1) +(defn wrap-session-version + [handler] + (fn [request] + (let [session (:session request) + route (bidi/match-route @(resolve 'auto-ap.handler/all-routes) + (:uri request) + :request-method (:request-method request)) + is-normal-route? (or (keyword? route) + (keyword? (:handler route)))] ;; TODO SSR icky + (if (and (not= (:version session) current-session-version) + (not= :login route) + (not= :oauth route) + (not= :oauth (:handler route)) + (not= :login (:handler route)) + is-normal-route?) + (cond + (or (= :graphql (:handler route)) + (= :graphql route)) + {:status 401} + + (get (:headers request) "hx-request") + {:session nil + :status 200 + :headers {"hx-redirect" "/login"}} + :else + {:session nil + :status 302 + :headers {"Location" "/login"}}) + (handler request))))) \ No newline at end of file diff --git a/src/clj/auto_ap/ssr/auth.clj b/src/clj/auto_ap/ssr/auth.clj index 6f863dc4..84fde14a 100644 --- a/src/clj/auto_ap/ssr/auth.clj +++ b/src/clj/auto_ap/ssr/auth.clj @@ -1,5 +1,6 @@ (ns auto-ap.ssr.auth - (:require [buddy.sign.jwt :as jwt] + (:require [auto-ap.session-version :as session-version] + [buddy.sign.jwt :as jwt] [config.core :refer [env]])) (defn logout [request] @@ -13,4 +14,5 @@ :session {:identity (dissoc (jwt/unsign (get-in request [:query-params "jwt"]) (:jwt-secret env) {:alg :hs512}) - :exp)}}) + :exp) + :version session-version/current-session-version}}) diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs index 5f0ae121..293e0f71 100644 --- a/src/cljs/auto_ap/effects.cljs +++ b/src/cljs/auto_ap/effects.cljs @@ -197,27 +197,27 @@ "&variables=" (pr-str (or variables {})))}))] (cond + (= (:status response) 401) (re-frame/dispatch [:auto-ap.events/logout "Your session has expired. Please log in again."]) - + (>= (:status response) 400) (let [error (->> response :body :errors (dates->date-times) - (map #(assoc % :status (:status response))) - )] + (map #(assoc % :status (:status response))))] (when (:multi owns-state) (re-frame/dispatch [::status/error-multi (:multi owns-state) (:which owns-state) error])) (when (:single owns-state) (re-frame/dispatch [::status/error (:single owns-state) error])) (when on-error - (->> error + (->> error (conj on-error) (re-frame/dispatch)))) :else - (do + (do (when (:multi owns-state) (re-frame/dispatch [::status/completed-multi (:multi owns-state) (:which owns-state)])) (when (:single owns-state) diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index d85f0291..20107835 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -60,7 +60,13 @@ (fn [{:keys [_]} [_ token]] (let [handler (:handler (bidi/match-route routes/routes (.. js/window -location -pathname))) last-client-id (.getItem js/localStorage "last-client-id") - last-selected-clients (js->clj (.parse js/JSON (.getItem js/localStorage "last-selected-clients"))) + last-selected-clients (try (some->> "last-selected-clients" + (.getItem js/localStorage) + not-empty + (.parse js/JSON) + js->clj) + (catch js/Error e + :all)) last-selected-clients (mc/decode client-selection-schema last-selected-clients client-selection-transformer) jwt-data (some-> token jwt->data) selected-client-assignment (cond (and token @@ -74,7 +80,7 @@ [(js/parseInt last-client-id)] :else - nil) ] + nil)] (cond From 162d24c47034894ea89d114f383aff936ce68158 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 10 Apr 2024 01:03:05 -0700 Subject: [PATCH 03/11] broaden whitelist --- src/clj/auto_ap/session_version.clj | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/clj/auto_ap/session_version.clj b/src/clj/auto_ap/session_version.clj index f623c3fa..20cdcbfa 100644 --- a/src/clj/auto_ap/session_version.clj +++ b/src/clj/auto_ap/session_version.clj @@ -13,12 +13,28 @@ (:uri request) :request-method (:request-method request)) is-normal-route? (or (keyword? route) - (keyword? (:handler route)))] ;; TODO SSR icky + (keyword? (:handler route))) + whitelist #{:fastlink :oauth :login :health :raw-query :results-csv-query :results-json-query + :export-expected-deposits + :export-trial-balance + :export-sales + :export-transactions + :export-company-vendors + :export-payments + :export-ntg-sales-snapshot + :export-vendors + :export-transactions2 + :aggregated-sales-export + :export-raw + :export-invoices + :export-clients + :export-accounts + :export-ntg-account-snapshot + :export-ledger}] ;; TODO SSR icky (if (and (not= (:version session) current-session-version) - (not= :login route) - (not= :oauth route) - (not= :oauth (:handler route)) - (not= :login (:handler route)) + + (not (whitelist route) ) + (not (whitelist (:handler route)) ) is-normal-route?) (cond (or (= :graphql (:handler route)) From 8da7eaaf46fd134f5c54be90e90abcbd3d9d4e94 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 10 Apr 2024 09:30:33 -0700 Subject: [PATCH 04/11] More reliability with company dropdown --- src/clj/auto_ap/handler.clj | 2 +- src/clj/auto_ap/session_version.clj | 28 +++++-------------------- src/clj/auto_ap/ssr/payments.clj | 3 ++- src/cljs/auto_ap/effects.cljs | 3 ++- src/cljs/auto_ap/events.cljs | 13 +++++++++--- src/cljs/auto_ap/views/pages/login.cljs | 2 +- 6 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj index 6629c598..e1cc0603 100644 --- a/src/clj/auto_ap/handler.clj +++ b/src/clj/auto_ap/handler.clj @@ -325,7 +325,7 @@ (wrap-session {:store (cookie-store {:key (byte-array - [42, 52, -31, 105, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])})}) + [42, 52, -31, 101, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])})}) #_(wrap-reload) (wrap-params) diff --git a/src/clj/auto_ap/session_version.clj b/src/clj/auto_ap/session_version.clj index 20cdcbfa..30026ebf 100644 --- a/src/clj/auto_ap/session_version.clj +++ b/src/clj/auto_ap/session_version.clj @@ -1,10 +1,11 @@ (ns auto-ap.session-version - (:require [bidi.bidi :as bidi])) + (:require [bidi.bidi :as bidi] + [auto-ap.logging :as alog])) ;; TODO this should only be done until SSR is complete ;; once it is, it should just use redirects based on headers ;; no header=use default, mismatch header=redirect to login -(def current-session-version 1) +(def current-session-version 2) (defn wrap-session-version [handler] (fn [request] @@ -13,28 +14,9 @@ (:uri request) :request-method (:request-method request)) is-normal-route? (or (keyword? route) - (keyword? (:handler route))) - whitelist #{:fastlink :oauth :login :health :raw-query :results-csv-query :results-json-query - :export-expected-deposits - :export-trial-balance - :export-sales - :export-transactions - :export-company-vendors - :export-payments - :export-ntg-sales-snapshot - :export-vendors - :export-transactions2 - :aggregated-sales-export - :export-raw - :export-invoices - :export-clients - :export-accounts - :export-ntg-account-snapshot - :export-ledger}] ;; TODO SSR icky - (if (and (not= (:version session) current-session-version) + (keyword? (:handler route)))] ;; TODO SSR icky + (if (and (not= (:version session current-session-version) current-session-version) - (not (whitelist route) ) - (not (whitelist (:handler route)) ) is-normal-route?) (cond (or (= :graphql (:handler route)) diff --git a/src/clj/auto_ap/ssr/payments.clj b/src/clj/auto_ap/ssr/payments.clj index 12082e50..14778f31 100644 --- a/src/clj/auto_ap/ssr/payments.clj +++ b/src/clj/auto_ap/ssr/payments.clj @@ -419,7 +419,8 @@ (audit-transact (conj removing-payments updated-payment) identity) - (html-response (row* (:identity request) updated-payment {:delete-after-settle? true :class "live-removed"}) + (html-response (row* (:identity request) updated-payment {:delete-after-settle? true :class "live-removed" + :request request}) :headers {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id check))}))) ;; TODO use decoding here diff --git a/src/cljs/auto_ap/effects.cljs b/src/cljs/auto_ap/effects.cljs index 293e0f71..4b1bd50f 100644 --- a/src/cljs/auto_ap/effects.cljs +++ b/src/cljs/auto_ap/effects.cljs @@ -18,7 +18,8 @@ [venia.core :as v])) (defn maybe-add-x-clients [headers] - (if (mc/validate client-selection-schema (:selected-clients @re-frame.db/app-db)) + (if (and (mc/validate client-selection-schema (:selected-clients @re-frame.db/app-db)) + (not (get headers "x-clients"))) (assoc headers "x-clients" (.stringify js/JSON (clj->js (:selected-clients @re-frame.db/app-db)))) headers)) diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index 20107835..f2c34ba9 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -64,10 +64,10 @@ (.getItem js/localStorage) not-empty (.parse js/JSON) - js->clj) + js->clj + ( #(mc/decode client-selection-schema % client-selection-transformer))) (catch js/Error e :all)) - last-selected-clients (mc/decode client-selection-schema last-selected-clients client-selection-transformer) jwt-data (some-> token jwt->data) selected-client-assignment (cond (and token (= "admin" (get jwt-data "user/role")) @@ -208,7 +208,6 @@ [with-user] (fn [{:keys [db user]} [_ client]] (let [client-identifier (or (:id client) client)] - {:http {:token user :method :put :uri (str (bidi/path-for ssr-routes/only-routes @@ -216,6 +215,14 @@ :request-method :put) "?" (url/map->query {:search-client client-identifier})) + :headers {"x-clients" + (.stringify js/JSON + (clj->js (cond (= :all client-identifier) + "all" + (= :mine client-identifier) + "mine" + :else + {:selected [client-identifier]})))} :on-success [::swapped-client client client-identifier]}}))) (re-frame/reg-event-fx diff --git a/src/cljs/auto_ap/views/pages/login.cljs b/src/cljs/auto_ap/views/pages/login.cljs index 76b63982..41d0b84a 100644 --- a/src/cljs/auto_ap/views/pages/login.cljs +++ b/src/cljs/auto_ap/views/pages/login.cljs @@ -21,6 +21,6 @@ [:img {:src "/img/logo-big.png"}] [:div - [:a.button.is-large.is-primary {:href (doto (login-url (get (:query (url/url (.-location js/window))) "redirect-to")) println)} "Login with Google"]]] + [:a.button.is-large.is-primary {:href (login-url (get (:query (url/url (.-location js/window))) "redirect-to"))} "Login with Google"]]] [:p.has-text-gray "Copyright Integreat 2018"]]]]]]) From 894f7af16e5525edd031bc378dc9f609c2bce823 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 10 Apr 2024 09:58:05 -0700 Subject: [PATCH 05/11] Date default --- src/clj/auto_ap/ssr/grid_page_helper.clj | 9 +++++---- src/clj/auto_ap/ssr/invoices.clj | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index def70ad0..506be5f0 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -208,6 +208,7 @@ (defn table-route [grid-spec] (-> (fn table [{:keys [identity] :as request}] + (alog/peek ::TABLE-QP (:parsed-query-params request)) (let [unparse-query-params (or (:unparse-query grid-spec) default-unparse-query-params)] (html-response (table* @@ -224,10 +225,10 @@ main-transformer)) "sort" sort->query))) (update (filter-vals #(not (nil? %)) - (m/encode (:query-schema grid-spec) - (:query-params request) - main-transformer)) - "sort" sort->query)) + (m/encode (:query-schema grid-spec) + (:query-params request) + main-transformer)) + "sort" sort->query)) (unparse-query-params (:parsed-query-params request))) "selected" "all-selected")))} ;; TODO seems hacky to special case selected and all-selected here :oob (when-let [oob-render (:oob-render grid-spec)] diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 4700254a..16b499ad 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -390,7 +390,8 @@ (assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)]) :query-schema query-schema :parse-query-params (fn [p] - (mc/decode query-schema p main-transformer)) + (alog/peek ::PARSE + (mc/decode query-schema p main-transformer))) :action-buttons (fn [request] [(when (can? (:identity request) {:subject :invoice :activity :bulk-delete}) (com/button {:hx-get (str (bidi/path-for ssr-routes/only-routes ::route/bulk-delete)) @@ -1216,6 +1217,7 @@ (-> h (wrap-status-from-source) (wrap-apply-sort grid-page) - (wrap-schema-enforce :query-schema query-schema) (wrap-merge-prior-hx) + (wrap-schema-enforce :query-schema query-schema) + (wrap-schema-enforce :hx-schema query-schema) (wrap-client-redirect-unauthenticated))))) \ No newline at end of file From 1493b03ba31d9f6701603a0d3adb57705d34b468 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 10 Apr 2024 10:27:08 -0700 Subject: [PATCH 06/11] makes it possible to add new vendors. --- resources/public/output.css | 2 +- .../ssr/invoice/new_invoice_wizard.clj | 9 +++ src/cljc/auto_ap/client_routes.cljc | 3 +- src/cljs/auto_ap/views/main.cljs | 6 +- src/cljs/auto_ap/views/pages/home.cljs | 75 +++++++++++-------- 5 files changed, 59 insertions(+), 36 deletions(-) diff --git a/resources/public/output.css b/resources/public/output.css index 440bac81..443f03e3 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -1 +1 @@ -/*! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Calibri,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#007dbb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#007dbb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}select:not([size]){background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#007dbb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#007dbb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark [type=checkbox]:checked,.dark [type=radio]:checked,[type=checkbox]:checked,[type=radio]:checked{border-color:#0000;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:indeterminate,[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:#0000;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto inherit}input[type=file]::file-selector-button{color:#fff;background:#1f2937;border:0;font-weight:500;font-size:.875rem;cursor:pointer;padding:.625rem 1rem .625rem 2rem;-webkit-margin-start:-1rem;margin-inline-start:-1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}input[type=file]::file-selector-button:hover{background:#374151}.dark input[type=file]::file-selector-button{color:#fff;background:#4b5563}.dark input[type=file]::file-selector-button:hover{background:#6b7280}input[type=range]::-webkit-slider-thumb{height:1.25rem;width:1.25rem;background:#007dbb;border-radius:9999px;border:0;appearance:none;-moz-appearance:none;-webkit-appearance:none;cursor:pointer}input[type=range]:disabled::-webkit-slider-thumb{background:#9ca3af}.dark input[type=range]:disabled::-webkit-slider-thumb{background:#6b7280}input[type=range]:focus::-webkit-slider-thumb{outline:2px solid #0000;outline-offset:2px;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-opacity:1px;--tw-ring-color:rgb(164 202 254/var(--tw-ring-opacity))}input[type=range]::-moz-range-thumb{height:1.25rem;width:1.25rem;background:#007dbb;border-radius:9999px;border:0;appearance:none;-moz-appearance:none;-webkit-appearance:none;cursor:pointer}input[type=range]:disabled::-moz-range-thumb{background:#9ca3af}.dark input[type=range]:disabled::-moz-range-thumb{background:#6b7280}input[type=range]::-moz-range-progress{background:#009cea}input[type=range]::-ms-fill-lower{background:#009cea}.toggle-bg:after{content:"";position:absolute;top:.125rem;left:.125rem;background:#fff;border-color:#d1d5db;border-width:1px;border-radius:9999px;height:1.25rem;width:1.25rem;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.15s;box-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}input:checked+.toggle-bg:after{transform:translateX(100%);;border-color:#fff}input:checked+.toggle-bg{background:#007dbb;border-color:#007dbb}.tooltip-arrow,.tooltip-arrow:before{position:absolute;width:8px;height:8px;background:inherit}.tooltip-arrow{visibility:hidden}.tooltip-arrow:before{content:"";visibility:visible;transform:rotate(45deg)}[data-tooltip-style^=light]+.tooltip>.tooltip-arrow:before{border-style:solid;border-color:#e5e7eb}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=top]>.tooltip-arrow:before{border-bottom-width:1px;border-right-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=right]>.tooltip-arrow:before{border-bottom-width:1px;border-left-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=bottom]>.tooltip-arrow:before{border-top-width:1px;border-left-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=left]>.tooltip-arrow:before{border-top-width:1px;border-right-width:1px}.tooltip[data-popper-placement^=top]>.tooltip-arrow{bottom:-4px}.tooltip[data-popper-placement^=bottom]>.tooltip-arrow{top:-4px}.tooltip[data-popper-placement^=left]>.tooltip-arrow{right:-4px}.tooltip[data-popper-placement^=right]>.tooltip-arrow{left:-4px}.tooltip.invisible>.tooltip-arrow:before{visibility:hidden}[data-popper-arrow],[data-popper-arrow]:before{position:absolute;width:8px;height:8px;background:inherit}[data-popper-arrow]{visibility:hidden}[data-popper-arrow]:after,[data-popper-arrow]:before{content:"";visibility:visible;transform:rotate(45deg)}[data-popper-arrow]:after{position:absolute;width:9px;height:9px;background:inherit}[role=tooltip]>[data-popper-arrow]:before{border-style:solid;border-color:#e5e7eb}.dark [role=tooltip]>[data-popper-arrow]:before{border-style:solid;border-color:#4b5563}[role=tooltip]>[data-popper-arrow]:after{border-style:solid;border-color:#e5e7eb}.dark [role=tooltip]>[data-popper-arrow]:after{border-style:solid;border-color:#4b5563}[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]:after,[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]:before{border-bottom-width:1px;border-right-width:1px}[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]:after,[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]:before{border-bottom-width:1px;border-left-width:1px}[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]:after,[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]:before{border-top-width:1px;border-left-width:1px}[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]:after,[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]:before{border-top-width:1px;border-right-width:1px}[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]{bottom:-5px}[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]{top:-5px}[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]{right:-5px}[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]{left:-5px}[role=tooltip].invisible>[data-popper-arrow]:after,[role=tooltip].invisible>[data-popper-arrow]:before{visibility:hidden}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#009cea80;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.-right-2{right:-.5rem}.-top-2{top:-.5rem}.bottom-0{bottom:0}.bottom-\[60px\]{bottom:60px}.left-0{left:0}.left-1\/2{left:50%}.right-0{right:0}.right-2{right:.5rem}.top-0{top:0}.top-2{top:.5rem}.top-2\/4{top:50%}.top-5{top:1.25rem}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[99\]{z-index:99}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-6{grid-column:span 6/span 6}.col-start-1{grid-column-start:1}.m-1{margin:.25rem}.m-2{margin:.5rem}.m-4{margin:1rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-0{margin-top:0;margin-bottom:0}.my-4{margin-top:1rem;margin-bottom:1rem}.-mb-1{margin-bottom:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-10{margin-right:2.5rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-8{margin-right:2rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-10{height:2.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-96{height:24rem}.h-\[350px\]{height:350px}.h-\[600px\]{height:600px}.h-full{height:100%}.h-screen{height:100vh}.max-h-0{max-height:0}.max-h-96{max-height:24rem}.max-h-screen{max-height:100vh}.w-1\/2{width:50%}.w-1\/4{width:25%}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-36{width:9rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-72{width:18rem}.w-8{width:2rem}.w-96{width:24rem}.w-\[10em\]{width:10em}.w-\[20em\]{width:20em}.w-\[30em\]{width:30em}.w-\[5em\]{width:5em}.w-\[600px\]{width:600px}.w-\[748px\]{width:748px}.w-\[7em\]{width:7em}.w-\[850px\]{width:850px}.w-\[8em\]{width:8em}.w-auto{width:auto}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.w-screen{width:100vw}.max-w-2xl{max-width:42rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-screen-2xl{max-width:1536px}.max-w-screen-lg{max-width:1024px}.flex-1{flex:1 1 0%}.flex-initial{flex:0 1 auto}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-1\/4{flex-basis:25%}.\!translate-y-0{--tw-translate-y:0px!important}.\!translate-y-0,.\!translate-y-32{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.\!translate-y-32{--tw-translate-y:8rem!important}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-x-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-full{--tw-translate-x:-100%}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.-translate-y-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-full{--tw-translate-y:-100%}.translate-x-0{--tw-translate-x:0px}.translate-x-0,.translate-x-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-full{--tw-translate-x:100%}.translate-y-full{--tw-translate-y:100%}.rotate-180,.translate-y-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate:180deg}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.scale-100,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform-none{transform:none}@keyframes gentleGrow{0%{transform:scale(1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:scale(1.1);animation-timing-function:cubic-bezier(0,0,.2,1)}to{transform:scale(1);animation-timing-function:cubic-bezier(.8,0,1,1)}}.animate-gg{animation:gentleGrow 1s infinite}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes shake{0%{transform:translateX(0)}12.5%{transform:translateX(-5px)}25%{transform:translateX(0)}37.5%{transform:translateX(5px)}50%{transform:translateX(0)}62.5%{transform:translateX(-5px)}75%{transform:translateX(5px)}87.5%{transform:translateX(5px)}to{transform:translateX(0)}}.animate-shake{animation:shake .5s ease-out 1}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-move{cursor:move}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-stretch{justify-content:stretch}.justify-items-stretch{justify-items:stretch}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-2{row-gap:.5rem}.-space-x-px>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(-1px*var(--tw-space-x-reverse));margin-left:calc(-1px*(1 - var(--tw-space-x-reverse)))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.place-self-end{place-self:end}.self-center{align-self:center}.self-stretch{align-self:stretch}.justify-self-end{justify-self:end}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-scroll{overflow-y:scroll}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-b-lg{border-bottom-right-radius:.5rem}.rounded-b-lg,.rounded-l-lg{border-bottom-left-radius:.5rem}.rounded-l-lg{border-top-left-radius:.5rem}.rounded-r-lg{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-0{border-width:0}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-dotted{border-style:dotted}.border-blue-300{--tw-border-opacity:1;border-color:rgb(102 196 242/var(--tw-border-opacity))}.border-blue-600{--tw-border-opacity:1;border-color:rgb(0 125 187/var(--tw-border-opacity))}.border-blue-700{--tw-border-opacity:1;border-color:rgb(0 94 140/var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.border-primary-300{--tw-border-opacity:1;border-color:rgb(175 211 130/var(--tw-border-opacity))}.border-primary-600{--tw-border-opacity:1;border-color:rgb(97 145 37/var(--tw-border-opacity))}.border-red-300{--tw-border-opacity:1;border-color:rgb(255 104 104/var(--tw-border-opacity))}.border-white{--tw-border-opacity:1;border-color:rgb(255 255 255/var(--tw-border-opacity))}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(204 235 251/var(--tw-bg-opacity))}.bg-blue-200{--tw-bg-opacity:1;background-color:rgb(153 215 247/var(--tw-bg-opacity))}.bg-blue-300{--tw-bg-opacity:1;background-color:rgb(102 196 242/var(--tw-bg-opacity))}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(51 176 238/var(--tw-bg-opacity))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(230 245 253/var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(0 156 234/var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(0 125 187/var(--tw-bg-opacity))}.bg-blue-700{--tw-bg-opacity:1;background-color:rgb(0 94 140/var(--tw-bg-opacity))}.bg-blue-800{--tw-bg-opacity:1;background-color:rgb(0 62 94/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(228 240 213/var(--tw-bg-opacity))}.bg-green-200{--tw-bg-opacity:1;background-color:rgb(201 225 171/var(--tw-bg-opacity))}.bg-green-300{--tw-bg-opacity:1;background-color:rgb(175 211 130/var(--tw-bg-opacity))}.bg-green-400{--tw-bg-opacity:1;background-color:rgb(148 196 88/var(--tw-bg-opacity))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(242 248 234/var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity:1;background-color:rgb(121 181 46/var(--tw-bg-opacity))}.bg-green-600{--tw-bg-opacity:1;background-color:rgb(97 145 37/var(--tw-bg-opacity))}.bg-green-700{--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}.bg-green-800{--tw-bg-opacity:1;background-color:rgb(48 72 18/var(--tw-bg-opacity))}.bg-primary-200{--tw-bg-opacity:1;background-color:rgb(201 225 171/var(--tw-bg-opacity))}.bg-primary-50{--tw-bg-opacity:1;background-color:rgb(242 248 234/var(--tw-bg-opacity))}.bg-purple-50{--tw-bg-opacity:1;background-color:rgb(246 245 255/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(255 205 205/var(--tw-bg-opacity))}.bg-red-200{--tw-bg-opacity:1;background-color:rgb(255 154 154/var(--tw-bg-opacity))}.bg-red-300{--tw-bg-opacity:1;background-color:rgb(255 104 104/var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(255 230 230/var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(255 3 3/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-white\/50{background-color:#ffffff80}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(253 246 178/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(252 233 106/var(--tw-bg-opacity))}.\!bg-opacity-0{--tw-bg-opacity:0!important}.\!bg-opacity-100{--tw-bg-opacity:1!important}.\!bg-opacity-50{--tw-bg-opacity:0.5!important}.bg-opacity-50{--tw-bg-opacity:0.5}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pl-10{padding-left:2.5rem}.pl-11{padding-left:2.75rem}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.pr-2{padding-right:.5rem}.pr-2\.5{padding-right:.625rem}.pr-6{padding-right:1.5rem}.pt-16{padding-top:4rem}.pt-2{padding-top:.5rem}.pt-5{padding-top:1.25rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-baseline{vertical-align:initial}.align-top{vertical-align:top}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[0\.6rem\]{font-size:.6rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-light{font-weight:300}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-6{line-height:1.5rem}.leading-9{line-height:2.25rem}.leading-none{line-height:1}.leading-tight{line-height:1.25}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-blue-400{--tw-text-opacity:1;color:rgb(51 176 238/var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity:1;color:rgb(0 125 187/var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity:1;color:rgb(0 62 94/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-green-300{--tw-text-opacity:1;color:rgb(175 211 130/var(--tw-text-opacity))}.text-green-500{--tw-text-opacity:1;color:rgb(121 181 46/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(97 145 37/var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgb(73 109 28/var(--tw-text-opacity))}.text-green-800{--tw-text-opacity:1;color:rgb(48 72 18/var(--tw-text-opacity))}.text-primary-300{--tw-text-opacity:1;color:rgb(175 211 130/var(--tw-text-opacity))}.text-primary-600{--tw-text-opacity:1;color:rgb(97 145 37/var(--tw-text-opacity))}.text-purple-600{--tw-text-opacity:1;color:rgb(126 58 242/var(--tw-text-opacity))}.text-red-300{--tw-text-opacity:1;color:rgb(255 104 104/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(255 3 3/var(--tw-text-opacity))}.text-red-600{--tw-text-opacity:1;color:rgb(204 2 2/var(--tw-text-opacity))}.text-red-800{--tw-text-opacity:1;color:rgb(102 1 1/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgb(114 59 19/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.\!opacity-0{opacity:0!important}.\!opacity-100{opacity:1!important}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-70{opacity:.7}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-2xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px #00000040;--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.outline-0{outline-width:0}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-75{transition-duration:75ms}.ease-\[cubic-bezier\(\.3\2c 2\.3\2c \.6\2c 1\)\]{transition-timing-function:cubic-bezier(.3,2.3,.6,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.htmx-added .fade-in,.htmx-added.fade-in{opacity:0!important}.fade-in{opacity:1}.htmx-settling .fade-in-settle,.htmx-settling.fade-in-settle{opacity:0!important}.fade-in-settle{opacity:1}.htmx-added .swipe-left-swap,.htmx-added.swipe-left-swap{opacity:1!important;--tw-scale-x:1!important;--tw-scale-y:1!important;--tw-translate-x:-50%!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.swipe-left-swap{opacity:1;--tw-scale-x:1;--tw-scale-y:1;--tw-translate-x:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-settling.htmx-added .swipe-left-swap,.htmx-settling.htmx-added.swipe-left-swap{opacity:0!important;--tw-scale-x:.75!important;--tw-scale-y:.75!important;--tw-translate-x:50%!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.htmx-settling .slide-up-settle,.htmx-settling.slide-up-settle{--tw-translate-y:1.25rem!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.slide-up-settle{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hidden .slide-up,.htmx-added .slide-up{--tw-translate-y:1.25rem!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.slide-up{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.live-added{animation:pulse-green .3s 2;animation-direction:alternate;animation-timing-function:ease-in-out}.dark .live-added{animation:pulse-dark-green .3s 2!important;animation-direction:alternate;animation-timing-function:ease-in-out}.live-removed{animation:pulse-red .3s 2;animation-direction:alternate;animation-timing-function:ease-in-out}.dark .live-removed{animation:pulse-dark-red .3s 2!important;animation-direction:alternate;animation-timing-function:ease-in-out}@keyframes pulse-green{0%{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(175 211 130/var(--tw-bg-opacity))}:is(.dark to){--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}}@keyframes pulse-dark-green{:is(.dark 0%){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}}@keyframes pulse-red{0%{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(255 104 104/var(--tw-bg-opacity))}:is(.dark to){--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}}@keyframes pulse-dark-red{:is(.dark 0%){--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}}.htmx-request .htmx-indicator,.htmx-request.htmx-indicator{display:inherit!important}.htmx-indicator{display:none}.htmx-request .htmx-indicator-hidden{display:none!important}.htmx-indicator-hidden{display:inherit}.htmx-request .htmx-indicator-invisible{visibility:hidden!important}.htmx-indicator-invisible{display:inherit}.htmx-swapping .fade-out{opacity:0!important}.fade-out{opacity:1}.min-h-content{min-height:calc(100vh - 4em)}.choices{margin-bottom:0!important;border-width:0!important}.choices__inner{display:block!important;width:100%!important;border-radius:.5rem!important;border-width:1px!important;--tw-border-opacity:1!important;border-color:rgb(209 213 219/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(249 250 251/var(--tw-bg-opacity))!important;padding:.25rem!important;font-size:.875rem!important;line-height:1.25rem!important;--tw-text-opacity:1!important;color:rgb(17 24 39/var(--tw-text-opacity))!important}.choices__inner:focus{--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.group.has-error .choices__inner{--tw-border-opacity:1!important;border-color:rgb(255 3 3/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(255 230 230/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(51 1 1/var(--tw-text-opacity))!important}.group.has-error .choices__inner::-moz-placeholder{--tw-placeholder-opacity:1!important;color:rgb(153 2 2/var(--tw-placeholder-opacity))!important}.group.has-error .choices__inner::placeholder{--tw-placeholder-opacity:1!important;color:rgb(153 2 2/var(--tw-placeholder-opacity))!important}.group.has-error .choices__inner:focus{--tw-border-opacity:1!important;border-color:rgb(255 3 3/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(255 3 3/var(--tw-ring-opacity))!important}:is(.dark .choices__inner){--tw-border-opacity:1!important;border-color:rgb(75 85 99/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(55 65 81/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}:is(.dark .choices__inner)::-moz-placeholder{--tw-placeholder-opacity:1!important;color:rgb(156 163 175/var(--tw-placeholder-opacity))!important}:is(.dark .choices__inner)::placeholder{--tw-placeholder-opacity:1!important;color:rgb(156 163 175/var(--tw-placeholder-opacity))!important}:is(.dark .choices__inner:focus){--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.group.has-error :is(.dark .choices__inner){--tw-border-opacity:1!important;border-color:rgb(255 3 3/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(55 65 81/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(255 3 3/var(--tw-text-opacity))!important}.group.has-error :is(.dark .choices__inner)::-moz-placeholder{--tw-placeholder-opacity:1!important;color:rgb(255 3 3/var(--tw-placeholder-opacity))!important}.group.has-error :is(.dark .choices__inner)::placeholder{--tw-placeholder-opacity:1!important;color:rgb(255 3 3/var(--tw-placeholder-opacity))!important}.choices:focus-within .choices__inner,:is(.dark .choices:focus-within .choices__inner){--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.choices:focus-within .choices__inner{outline:2px solid #0000!important;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#007dbb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#007dbb}.choices__inner .choices__input{margin:0!important;--tw-bg-opacity:1!important;background-color:rgb(249 250 251/var(--tw-bg-opacity))!important}:is(.dark .choices__inner .choices__input){--tw-bg-opacity:1!important;background-color:rgb(55 65 81/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}.choices__inner .choices__item{white-space:nowrap!important;border-radius:.25rem!important;--tw-border-opacity:1!important;border-color:rgb(156 163 175/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(175 211 130/var(--tw-bg-opacity))!important;padding:.125rem .5rem!important;font-size:.75rem!important;line-height:1rem!important;font-weight:500!important;--tw-text-opacity:1!important;color:rgb(48 72 18/var(--tw-text-opacity))!important}:is(.dark .choices__inner .choices__item){--tw-bg-opacity:1!important;background-color:rgb(24 36 9/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(175 211 130/var(--tw-text-opacity))!important}.choices__list--dropdown{border-radius:.5rem!important;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a!important;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}:is(.dark .choices__list--dropdown){--tw-bg-opacity:1!important;background-color:rgb(55 65 81/var(--tw-bg-opacity))!important}.choices__list--dropdown .choices__item--selectable.is-highlighted{--tw-bg-opacity:1!important;background-color:rgb(175 211 130/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(48 72 18/var(--tw-text-opacity))!important}:is(.dark .choices__list--dropdown .choices__item--selectable.is-highlighted){--tw-bg-opacity:1!important;background-color:rgb(24 36 9/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(175 211 130/var(--tw-text-opacity))!important}.choices[data-type*=select-multiple] .choices__button{--tw-border-opacity:1!important;border-color:rgb(107 114 128/var(--tw-border-opacity))!important}.choices[data-type*=select-multiple] .choices__button:focus{--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.choices__inner .choices__item:focus-within{--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(121 181 46/var(--tw-bg-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.choices__list--single .choices__item{display:flex!important;width:auto!important}.choices__list--single{width:auto!important}.choices__list--single button{position:relative!important;margin:0!important;display:block!important;height:auto!important}.choices[data-type*=select-one] .choices__button{right:auto!important}.arrow,.arrow:before{position:absolute;width:24px;height:24px;background:inherit}.arrow{visibility:hidden}.arrow:before{visibility:visible;content:"";transform:rotate(45deg)}.arrow{bottom:-4px}.ct-series-a .ct-bar{stroke:#79b52e;fill:#79b52e;stroke-width:20px}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:scale-105:hover,.hover\:scale-110:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.hover\:border-green-300:hover{--tw-border-opacity:1;border-color:rgb(175 211 130/var(--tw-border-opacity))}.hover\:bg-blue-300:hover{--tw-bg-opacity:1;background-color:rgb(102 196 242/var(--tw-bg-opacity))}.hover\:bg-blue-600:hover{--tw-bg-opacity:1;background-color:rgb(0 125 187/var(--tw-bg-opacity))}.hover\:bg-blue-800:hover{--tw-bg-opacity:1;background-color:rgb(0 62 94/var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:bg-green-100:hover{--tw-bg-opacity:1;background-color:rgb(228 240 213/var(--tw-bg-opacity))}.hover\:bg-green-200:hover{--tw-bg-opacity:1;background-color:rgb(201 225 171/var(--tw-bg-opacity))}.hover\:bg-green-300:hover{--tw-bg-opacity:1;background-color:rgb(175 211 130/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(97 145 37/var(--tw-bg-opacity))}.hover\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}.hover\:bg-neutral-100:hover{--tw-bg-opacity:1;background-color:rgb(245 245 245/var(--tw-bg-opacity))}.hover\:bg-primary-100:hover{--tw-bg-opacity:1;background-color:rgb(228 240 213/var(--tw-bg-opacity))}.hover\:bg-red-300:hover{--tw-bg-opacity:1;background-color:rgb(255 104 104/var(--tw-bg-opacity))}.hover\:bg-white:hover{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity:1;color:rgb(0 125 187/var(--tw-text-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.hover\:text-primary-700:hover{--tw-text-opacity:1;color:rgb(73 109 28/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:z-10:focus{z-index:10}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(0 156 234/var(--tw-border-opacity))}.focus\:border-primary-500:focus{--tw-border-opacity:1;border-color:rgb(121 181 46/var(--tw-border-opacity))}.focus\:bg-neutral-100:focus{--tw-bg-opacity:1;background-color:rgb(245 245 245/var(--tw-bg-opacity))}.focus\:text-green-700:focus{--tw-text-opacity:1;color:rgb(73 109 28/var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-2:focus,.focus\:ring-4:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-4:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-blue-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(153 215 247/var(--tw-ring-opacity))}.focus\:ring-blue-300:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(102 196 242/var(--tw-ring-opacity))}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))}.focus\:ring-gray-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(229 231 235/var(--tw-ring-opacity))}.focus\:ring-gray-300:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.focus\:ring-green-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(201 225 171/var(--tw-ring-opacity))}.focus\:ring-green-300:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(175 211 130/var(--tw-ring-opacity))}.focus\:ring-green-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(148 196 88/var(--tw-ring-opacity))}.focus\:ring-green-700:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(73 109 28/var(--tw-ring-opacity))}.focus\:ring-primary-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(121 181 46/var(--tw-ring-opacity))}.focus\:ring-red-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 154 154/var(--tw-ring-opacity))}.group:hover .group-hover\:scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:text-blue-500{--tw-text-opacity:1;color:rgb(0 156 234/var(--tw-text-opacity))}.group.has-error .group-\[\.has-error\]\:border-red-500{--tw-border-opacity:1;border-color:rgb(255 3 3/var(--tw-border-opacity))}.group.has-error .group-\[\.has-error\]\:bg-red-50{--tw-bg-opacity:1;background-color:rgb(255 230 230/var(--tw-bg-opacity))}.group.has-error .group-\[\.has-error\]\:text-red-900{--tw-text-opacity:1;color:rgb(51 1 1/var(--tw-text-opacity))}.group.has-error .group-\[\.has-error\]\:placeholder-red-700::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(153 2 2/var(--tw-placeholder-opacity))}.group.has-error .group-\[\.has-error\]\:placeholder-red-700::placeholder{--tw-placeholder-opacity:1;color:rgb(153 2 2/var(--tw-placeholder-opacity))}.group.has-error .group-\[\.has-error\]\:focus\:border-red-500:focus{--tw-border-opacity:1;border-color:rgb(255 3 3/var(--tw-border-opacity))}.group.has-error .group-\[\.has-error\]\:focus\:ring-red-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 3 3/var(--tw-ring-opacity))}.peer:hover~.peer-hover\:block{display:block}.htmx-swapping\:-translate-x-2\/3.htmx-swapping{--tw-translate-x:-66.666667%}.htmx-swapping\:-translate-x-2\/3.htmx-swapping,.htmx-swapping\:translate-x-2\/3.htmx-swapping{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-swapping\:translate-x-2\/3.htmx-swapping{--tw-translate-x:66.666667%}.htmx-swapping\:scale-0.htmx-swapping{--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-swapping\:opacity-0.htmx-swapping{opacity:0}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-swapping\:translate-x-1\/4.htmx-swapping{--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-swapping\:-translate-x-1\/4.htmx-swapping{--tw-translate-x:-25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-swapping\:scale-75.htmx-swapping,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-swapping\:scale-75.htmx-swapping{--tw-scale-x:.75;--tw-scale-y:.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-swapping\:opacity-0.htmx-swapping,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-swapping\:opacity-0.htmx-swapping{opacity:0}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-swapping\:ease-in.htmx-swapping,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-swapping\:ease-in.htmx-swapping{transition-timing-function:cubic-bezier(.4,0,1,1)}.htmx-swapping .htmx-swapping\:-translate-x-2\/3{--tw-translate-x:-66.666667%}.htmx-swapping .htmx-swapping\:-translate-x-2\/3,.htmx-swapping .htmx-swapping\:translate-x-2\/3{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-swapping .htmx-swapping\:translate-x-2\/3{--tw-translate-x:66.666667%}.htmx-swapping .htmx-swapping\:scale-0{--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-swapping .htmx-swapping\:opacity-0{opacity:0}.group\/transition.backward .htmx-swapping .group-\[\.backward\]\/transition\:htmx-swapping\:translate-x-1\/4{--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.forward .htmx-swapping .group-\[\.forward\]\/transition\:htmx-swapping\:-translate-x-1\/4{--tw-translate-x:-25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .htmx-swapping .group-\[\.backward\]\/transition\:htmx-swapping\:scale-75,.group\/transition.forward .htmx-swapping .group-\[\.forward\]\/transition\:htmx-swapping\:scale-75{--tw-scale-x:.75;--tw-scale-y:.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .htmx-swapping .group-\[\.backward\]\/transition\:htmx-swapping\:opacity-0,.group\/transition.forward .htmx-swapping .group-\[\.forward\]\/transition\:htmx-swapping\:opacity-0{opacity:0}.group\/transition.backward .htmx-swapping .group-\[\.backward\]\/transition\:htmx-swapping\:ease-in,.group\/transition.forward .htmx-swapping .group-\[\.forward\]\/transition\:htmx-swapping\:ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.htmx-added\:-translate-x-2\/3.htmx-added{--tw-translate-x:-66.666667%}.htmx-added\:-translate-x-2\/3.htmx-added,.htmx-added\:translate-x-2\/3.htmx-added{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-added\:translate-x-2\/3.htmx-added{--tw-translate-x:66.666667%}.htmx-added\:scale-0.htmx-added{--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-added\:opacity-0.htmx-added{opacity:0}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-added\:-translate-x-1\/4.htmx-added{--tw-translate-x:-25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-added\:translate-x-1\/4.htmx-added{--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-added\:scale-75.htmx-added,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-added\:scale-75.htmx-added{--tw-scale-x:.75;--tw-scale-y:.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-added\:opacity-0.htmx-added,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-added\:opacity-0.htmx-added{opacity:0}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-added\:ease-out.htmx-added,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-added\:ease-out.htmx-added{transition-timing-function:cubic-bezier(0,0,.2,1)}.htmx-added .htmx-added\:-translate-x-2\/3{--tw-translate-x:-66.666667%}.htmx-added .htmx-added\:-translate-x-2\/3,.htmx-added .htmx-added\:translate-x-2\/3{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-added .htmx-added\:translate-x-2\/3{--tw-translate-x:66.666667%}.htmx-added .htmx-added\:scale-0{--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-added .htmx-added\:opacity-0{opacity:0}.group\/transition.backward .htmx-added .group-\[\.backward\]\/transition\:htmx-added\:-translate-x-1\/4{--tw-translate-x:-25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.forward .htmx-added .group-\[\.forward\]\/transition\:htmx-added\:translate-x-1\/4{--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .htmx-added .group-\[\.backward\]\/transition\:htmx-added\:scale-75,.group\/transition.forward .htmx-added .group-\[\.forward\]\/transition\:htmx-added\:scale-75{--tw-scale-x:.75;--tw-scale-y:.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .htmx-added .group-\[\.backward\]\/transition\:htmx-added\:opacity-0,.group\/transition.forward .htmx-added .group-\[\.forward\]\/transition\:htmx-added\:opacity-0{opacity:0}.group\/transition.backward .htmx-added .group-\[\.backward\]\/transition\:htmx-added\:ease-out,.group\/transition.forward .htmx-added .group-\[\.forward\]\/transition\:htmx-added\:ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}:is(.dark .dark\:block){display:block}:is(.dark .dark\:hidden){display:none}:is(.dark .dark\:divide-gray-600)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(75 85 99/var(--tw-divide-opacity))}:is(.dark .dark\:border-blue-500){--tw-border-opacity:1;border-color:rgb(0 156 234/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-400){--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-500){--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-600){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-700){--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-900){--tw-border-opacity:1;border-color:rgb(17 24 39/var(--tw-border-opacity))}:is(.dark .dark\:border-green-800){--tw-border-opacity:1;border-color:rgb(48 72 18/var(--tw-border-opacity))}:is(.dark .dark\:border-primary-500){--tw-border-opacity:1;border-color:rgb(121 181 46/var(--tw-border-opacity))}:is(.dark .dark\:border-transparent){border-color:#0000}:is(.dark .dark\:bg-blue-600){--tw-bg-opacity:1;background-color:rgb(0 125 187/var(--tw-bg-opacity))}:is(.dark .dark\:bg-blue-700){--tw-bg-opacity:1;background-color:rgb(0 94 140/var(--tw-bg-opacity))}:is(.dark .dark\:bg-blue-900){--tw-bg-opacity:1;background-color:rgb(0 31 47/var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-600){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-700){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800\/50){background-color:#1f293780}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}:is(.dark .dark\:bg-green-600){--tw-bg-opacity:1;background-color:rgb(97 145 37/var(--tw-bg-opacity))}:is(.dark .dark\:bg-green-700){--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}:is(.dark .dark\:bg-green-900){--tw-bg-opacity:1;background-color:rgb(24 36 9/var(--tw-bg-opacity))}:is(.dark .dark\:bg-red-700){--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}:is(.dark .dark\:bg-red-900){--tw-bg-opacity:1;background-color:rgb(51 1 1/var(--tw-bg-opacity))}:is(.dark .dark\:bg-yellow-900){--tw-bg-opacity:1;background-color:rgb(99 49 18/var(--tw-bg-opacity))}:is(.dark .dark\:bg-opacity-80){--tw-bg-opacity:0.8}:is(.dark .dark\:text-blue-100){--tw-text-opacity:1;color:rgb(204 235 251/var(--tw-text-opacity))}:is(.dark .dark\:text-blue-200){--tw-text-opacity:1;color:rgb(153 215 247/var(--tw-text-opacity))}:is(.dark .dark\:text-blue-300){--tw-text-opacity:1;color:rgb(102 196 242/var(--tw-text-opacity))}:is(.dark .dark\:text-blue-400){--tw-text-opacity:1;color:rgb(51 176 238/var(--tw-text-opacity))}:is(.dark .dark\:text-blue-500){--tw-text-opacity:1;color:rgb(0 156 234/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-100){--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-200){--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-300){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-400){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-50){--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-500){--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}:is(.dark .dark\:text-green-300){--tw-text-opacity:1;color:rgb(175 211 130/var(--tw-text-opacity))}:is(.dark .dark\:text-green-400){--tw-text-opacity:1;color:rgb(148 196 88/var(--tw-text-opacity))}:is(.dark .dark\:text-primary-500){--tw-text-opacity:1;color:rgb(121 181 46/var(--tw-text-opacity))}:is(.dark .dark\:text-red-300){--tw-text-opacity:1;color:rgb(255 104 104/var(--tw-text-opacity))}:is(.dark .dark\:text-red-400){--tw-text-opacity:1;color:rgb(255 53 53/var(--tw-text-opacity))}:is(.dark .dark\:text-red-500){--tw-text-opacity:1;color:rgb(255 3 3/var(--tw-text-opacity))}:is(.dark .dark\:text-white){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark .dark\:text-yellow-300){--tw-text-opacity:1;color:rgb(250 202 21/var(--tw-text-opacity))}:is(.dark .dark\:placeholder-gray-400)::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity))}:is(.dark .dark\:placeholder-gray-400)::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity))}:is(.dark .dark\:ring-offset-gray-700){--tw-ring-offset-color:#374151}:is(.dark .dark\:ring-offset-gray-800){--tw-ring-offset-color:#1f2937}:is(.dark .hover\:dark\:border-green-800):hover{--tw-border-opacity:1;border-color:rgb(48 72 18/var(--tw-border-opacity))}:is(.dark .dark\:hover\:bg-blue-600:hover){--tw-bg-opacity:1;background-color:rgb(0 125 187/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-blue-700:hover){--tw-bg-opacity:1;background-color:rgb(0 94 140/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-600:hover){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-700:hover){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-800:hover){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-green-600:hover){--tw-bg-opacity:1;background-color:rgb(97 145 37/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-green-700:hover){--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-red-600:hover){--tw-bg-opacity:1;background-color:rgb(204 2 2/var(--tw-bg-opacity))}:is(.dark .hover\:dark\:bg-gray-800):hover{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:text-blue-500:hover){--tw-text-opacity:1;color:rgb(0 156 234/var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-100:hover){--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-300:hover){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-white:hover){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark .hover\:dark\:text-green-400):hover{--tw-text-opacity:1;color:rgb(148 196 88/var(--tw-text-opacity))}:is(.dark .dark\:focus\:border-blue-500:focus){--tw-border-opacity:1;border-color:rgb(0 156 234/var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-500:focus){--tw-border-opacity:1;border-color:rgb(121 181 46/var(--tw-border-opacity))}:is(.dark .dark\:focus\:text-white:focus){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark .dark\:focus\:ring-blue-500:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-blue-600:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(0 125 187/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-blue-800:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(0 62 94/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-gray-600:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(75 85 99/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-green-500:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(121 181 46/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-green-800:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(48 72 18/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-500:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(121 181 46/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-600:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(97 145 37/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-offset-gray-700:focus){--tw-ring-offset-color:#374151}:is(.dark .group:hover .dark\:group-hover\:text-white){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:border-red-500){--tw-border-opacity:1;border-color:rgb(255 3 3/var(--tw-border-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:bg-gray-700){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:text-red-500){--tw-text-opacity:1;color:rgb(255 3 3/var(--tw-text-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:placeholder-red-500)::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(255 3 3/var(--tw-placeholder-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:placeholder-red-500)::placeholder{--tw-placeholder-opacity:1;color:rgb(255 3 3/var(--tw-placeholder-opacity))}@media (min-width:640px){.sm\:ml-4{margin-left:1rem}.sm\:block{display:block}.sm\:inline{display:inline}.sm\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.sm\:space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.sm\:rounded-lg{border-radius:.5rem}.sm\:p-6{padding:1.5rem}.sm\:py-5{padding-top:1.25rem;padding-bottom:1.25rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:768px){.md\:ml-2{margin-left:.5rem}.md\:mr-24{margin-right:6rem}.md\:block{display:block}.md\:table-cell{display:table-cell}.md\:h-\[600px\]{height:600px}.md\:w-\[750px\]{width:750px}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-center{justify-content:center}.md\:space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.md\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.md\:p-12{padding:3rem}}@media (min-width:1024px){.lg\:block{display:block}.lg\:table-cell{display:table-cell}.lg\:hidden{display:none}.lg\:w-96{width:24rem}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:justify-end{justify-content:flex-end}.lg\:justify-between{justify-content:space-between}.lg\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.lg\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.lg\:px-5{padding-left:1.25rem;padding-right:1.25rem}.lg\:pl-3{padding-left:.75rem}.lg\:pl-64{padding-left:16rem}}.\[\&\.active\]\:bg-primary-300.active{--tw-bg-opacity:1;background-color:rgb(175 211 130/var(--tw-bg-opacity))}.\[\&\.active\]\:bg-primary-500.active{--tw-bg-opacity:1;background-color:rgb(121 181 46/var(--tw-bg-opacity))}:is(.dark .\[\&\.active\]\:dark\:bg-primary-700).active{--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))} \ No newline at end of file +/*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Calibri,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}.tooltip-arrow,.tooltip-arrow:before{position:absolute;width:8px;height:8px;background:inherit}.tooltip-arrow{visibility:hidden}.tooltip-arrow:before{content:"";visibility:visible;transform:rotate(45deg)}[data-tooltip-style^=light]+.tooltip>.tooltip-arrow:before{border-style:solid;border-color:#e5e7eb}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=top]>.tooltip-arrow:before{border-bottom-width:1px;border-right-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=right]>.tooltip-arrow:before{border-bottom-width:1px;border-left-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=bottom]>.tooltip-arrow:before{border-top-width:1px;border-left-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=left]>.tooltip-arrow:before{border-top-width:1px;border-right-width:1px}.tooltip[data-popper-placement^=top]>.tooltip-arrow{bottom:-4px}.tooltip[data-popper-placement^=bottom]>.tooltip-arrow{top:-4px}.tooltip[data-popper-placement^=left]>.tooltip-arrow{right:-4px}.tooltip[data-popper-placement^=right]>.tooltip-arrow{left:-4px}.tooltip.invisible>.tooltip-arrow:before{visibility:hidden}[data-popper-arrow],[data-popper-arrow]:before{position:absolute;width:8px;height:8px;background:inherit}[data-popper-arrow]{visibility:hidden}[data-popper-arrow]:after,[data-popper-arrow]:before{content:"";visibility:visible;transform:rotate(45deg)}[data-popper-arrow]:after{position:absolute;width:9px;height:9px;background:inherit}[role=tooltip]>[data-popper-arrow]:before{border-style:solid;border-color:#e5e7eb}.dark [role=tooltip]>[data-popper-arrow]:before{border-style:solid;border-color:#4b5563}[role=tooltip]>[data-popper-arrow]:after{border-style:solid;border-color:#e5e7eb}.dark [role=tooltip]>[data-popper-arrow]:after{border-style:solid;border-color:#4b5563}[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]:after,[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]:before{border-bottom-width:1px;border-right-width:1px}[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]:after,[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]:before{border-bottom-width:1px;border-left-width:1px}[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]:after,[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]:before{border-top-width:1px;border-left-width:1px}[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]:after,[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]:before{border-top-width:1px;border-right-width:1px}[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]{bottom:-5px}[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]{top:-5px}[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]{right:-5px}[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]{left:-5px}[role=tooltip].invisible>[data-popper-arrow]:after,[role=tooltip].invisible>[data-popper-arrow]:before{visibility:hidden}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#007dbb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#007dbb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}select:not([size]){background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' aria-hidden='true' viewBox='0 0 10 6'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 1 4 4 4-4'/%3E%3C/svg%3E");background-position:right .75rem center;background-repeat:no-repeat;background-size:.75em .75em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#007dbb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#007dbb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark [type=checkbox]:checked,.dark [type=radio]:checked,[type=checkbox]:checked,[type=radio]:checked{border-color:#0000;background-color:currentColor;background-size:.55em .55em;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' aria-hidden='true' viewBox='0 0 16 12'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M1 5.917 5.724 10.5 15 1.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-size:.55em .55em;-webkit-print-color-adjust:exact;print-color-adjust:exact}.dark [type=radio]:checked,[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E");background-size:1em 1em}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' aria-hidden='true' viewBox='0 0 16 12'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M1 5.917 5.724 10.5 15 1.5'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:.55em .55em;-webkit-print-color-adjust:exact;print-color-adjust:exact}[type=checkbox]:indeterminate,[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto inherit}input[type=file]::file-selector-button{color:#fff;background:#1f2937;border:0;font-weight:500;font-size:.875rem;cursor:pointer;padding:.625rem 1rem .625rem 2rem;margin-inline-start:-1rem;margin-inline-end:1rem}input[type=file]::file-selector-button:hover{background:#374151}.dark input[type=file]::file-selector-button{color:#fff;background:#4b5563}.dark input[type=file]::file-selector-button:hover{background:#6b7280}input[type=range]::-webkit-slider-thumb{height:1.25rem;width:1.25rem;background:#007dbb;border-radius:9999px;border:0;appearance:none;-moz-appearance:none;-webkit-appearance:none;cursor:pointer}input[type=range]:disabled::-webkit-slider-thumb{background:#9ca3af}.dark input[type=range]:disabled::-webkit-slider-thumb{background:#6b7280}input[type=range]:focus::-webkit-slider-thumb{outline:2px solid #0000;outline-offset:2px;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-opacity:1px;--tw-ring-color:rgb(164 202 254/var(--tw-ring-opacity))}input[type=range]::-moz-range-thumb{height:1.25rem;width:1.25rem;background:#007dbb;border-radius:9999px;border:0;appearance:none;-moz-appearance:none;-webkit-appearance:none;cursor:pointer}input[type=range]:disabled::-moz-range-thumb{background:#9ca3af}.dark input[type=range]:disabled::-moz-range-thumb{background:#6b7280}input[type=range]::-moz-range-progress{background:#009cea}input[type=range]::-ms-fill-lower{background:#009cea}.toggle-bg:after{content:"";position:absolute;top:.125rem;left:.125rem;background:#fff;border-color:#d1d5db;border-width:1px;border-radius:9999px;height:1.25rem;width:1.25rem;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.15s;box-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}input:checked+.toggle-bg:after{transform:translateX(100%);;border-color:#fff}input:checked+.toggle-bg{background:#007dbb;border-color:#007dbb}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#009cea80;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.-right-2{right:-.5rem}.-top-2{top:-.5rem}.bottom-0{bottom:0}.bottom-\[60px\]{bottom:60px}.left-0{left:0}.left-1\/2{left:50%}.right-0{right:0}.right-2{right:.5rem}.top-0{top:0}.top-2{top:.5rem}.top-2\/4{top:50%}.top-5{top:1.25rem}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[99\]{z-index:99}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-6{grid-column:span 6/span 6}.col-start-1{grid-column-start:1}.m-1{margin:.25rem}.m-2{margin:.5rem}.m-4{margin:1rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-0{margin-top:0;margin-bottom:0}.my-4{margin-top:1rem;margin-bottom:1rem}.-mb-1{margin-bottom:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-10{margin-right:2.5rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-8{margin-right:2rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-10{height:2.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-96{height:24rem}.h-\[350px\]{height:350px}.h-\[600px\]{height:600px}.h-full{height:100%}.h-screen{height:100vh}.max-h-0{max-height:0}.max-h-96{max-height:24rem}.max-h-screen{max-height:100vh}.w-1\/2{width:50%}.w-1\/4{width:25%}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-36{width:9rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-72{width:18rem}.w-8{width:2rem}.w-96{width:24rem}.w-\[10em\]{width:10em}.w-\[20em\]{width:20em}.w-\[30em\]{width:30em}.w-\[5em\]{width:5em}.w-\[600px\]{width:600px}.w-\[748px\]{width:748px}.w-\[7em\]{width:7em}.w-\[850px\]{width:850px}.w-\[8em\]{width:8em}.w-auto{width:auto}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.w-screen{width:100vw}.max-w-24{max-width:6rem}.max-w-2xl{max-width:42rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-screen-2xl{max-width:1536px}.max-w-screen-lg{max-width:1024px}.flex-1{flex:1 1 0%}.flex-initial{flex:0 1 auto}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-1\/4{flex-basis:25%}.\!translate-y-0{--tw-translate-y:0px!important}.\!translate-y-0,.\!translate-y-32{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.\!translate-y-32{--tw-translate-y:8rem!important}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-x-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-full{--tw-translate-x:-100%}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.-translate-y-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-full{--tw-translate-y:-100%}.translate-x-0{--tw-translate-x:0px}.translate-x-0,.translate-x-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-full{--tw-translate-x:100%}.translate-y-full{--tw-translate-y:100%}.rotate-180,.translate-y-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate:180deg}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.scale-100,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform-none{transform:none}@keyframes gentleGrow{0%{transform:scale(1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:scale(1.1);animation-timing-function:cubic-bezier(0,0,.2,1)}to{transform:scale(1);animation-timing-function:cubic-bezier(.8,0,1,1)}}.animate-gg{animation:gentleGrow 1s infinite}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes shake{0%{transform:translateX(0)}12.5%{transform:translateX(-5px)}25%{transform:translateX(0)}37.5%{transform:translateX(5px)}50%{transform:translateX(0)}62.5%{transform:translateX(-5px)}75%{transform:translateX(5px)}87.5%{transform:translateX(5px)}to{transform:translateX(0)}}.animate-shake{animation:shake .5s ease-out 1}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-move{cursor:move}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-stretch{justify-content:stretch}.justify-items-stretch{justify-items:stretch}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-2{row-gap:.5rem}.-space-x-px>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(-1px*var(--tw-space-x-reverse));margin-left:calc(-1px*(1 - var(--tw-space-x-reverse)))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.place-self-end{place-self:end}.self-center{align-self:center}.self-stretch{align-self:stretch}.justify-self-end{justify-self:end}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-scroll{overflow-y:scroll}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-b-lg{border-bottom-right-radius:.5rem}.rounded-b-lg,.rounded-l-lg{border-bottom-left-radius:.5rem}.rounded-l-lg{border-top-left-radius:.5rem}.rounded-r-lg{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-0{border-width:0}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-dotted{border-style:dotted}.border-blue-300{--tw-border-opacity:1;border-color:rgb(102 196 242/var(--tw-border-opacity))}.border-blue-600{--tw-border-opacity:1;border-color:rgb(0 125 187/var(--tw-border-opacity))}.border-blue-700{--tw-border-opacity:1;border-color:rgb(0 94 140/var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.border-primary-300{--tw-border-opacity:1;border-color:rgb(175 211 130/var(--tw-border-opacity))}.border-primary-600{--tw-border-opacity:1;border-color:rgb(97 145 37/var(--tw-border-opacity))}.border-red-300{--tw-border-opacity:1;border-color:rgb(255 104 104/var(--tw-border-opacity))}.border-white{--tw-border-opacity:1;border-color:rgb(255 255 255/var(--tw-border-opacity))}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(204 235 251/var(--tw-bg-opacity))}.bg-blue-200{--tw-bg-opacity:1;background-color:rgb(153 215 247/var(--tw-bg-opacity))}.bg-blue-300{--tw-bg-opacity:1;background-color:rgb(102 196 242/var(--tw-bg-opacity))}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(51 176 238/var(--tw-bg-opacity))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(230 245 253/var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(0 156 234/var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(0 125 187/var(--tw-bg-opacity))}.bg-blue-700{--tw-bg-opacity:1;background-color:rgb(0 94 140/var(--tw-bg-opacity))}.bg-blue-800{--tw-bg-opacity:1;background-color:rgb(0 62 94/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(228 240 213/var(--tw-bg-opacity))}.bg-green-200{--tw-bg-opacity:1;background-color:rgb(201 225 171/var(--tw-bg-opacity))}.bg-green-300{--tw-bg-opacity:1;background-color:rgb(175 211 130/var(--tw-bg-opacity))}.bg-green-400{--tw-bg-opacity:1;background-color:rgb(148 196 88/var(--tw-bg-opacity))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(242 248 234/var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity:1;background-color:rgb(121 181 46/var(--tw-bg-opacity))}.bg-green-600{--tw-bg-opacity:1;background-color:rgb(97 145 37/var(--tw-bg-opacity))}.bg-green-700{--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}.bg-green-800{--tw-bg-opacity:1;background-color:rgb(48 72 18/var(--tw-bg-opacity))}.bg-primary-200{--tw-bg-opacity:1;background-color:rgb(201 225 171/var(--tw-bg-opacity))}.bg-primary-50{--tw-bg-opacity:1;background-color:rgb(242 248 234/var(--tw-bg-opacity))}.bg-purple-50{--tw-bg-opacity:1;background-color:rgb(246 245 255/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(255 205 205/var(--tw-bg-opacity))}.bg-red-200{--tw-bg-opacity:1;background-color:rgb(255 154 154/var(--tw-bg-opacity))}.bg-red-300{--tw-bg-opacity:1;background-color:rgb(255 104 104/var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(255 230 230/var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(255 3 3/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-white\/50{background-color:#ffffff80}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(253 246 178/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(252 233 106/var(--tw-bg-opacity))}.\!bg-opacity-0{--tw-bg-opacity:0!important}.\!bg-opacity-100{--tw-bg-opacity:1!important}.\!bg-opacity-50{--tw-bg-opacity:0.5!important}.bg-opacity-50{--tw-bg-opacity:0.5}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pl-10{padding-left:2.5rem}.pl-11{padding-left:2.75rem}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.pr-2{padding-right:.5rem}.pr-2\.5{padding-right:.625rem}.pr-6{padding-right:1.5rem}.pt-16{padding-top:4rem}.pt-2{padding-top:.5rem}.pt-5{padding-top:1.25rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-baseline{vertical-align:initial}.align-top{vertical-align:top}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[0\.6rem\]{font-size:.6rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-light{font-weight:300}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-6{line-height:1.5rem}.leading-9{line-height:2.25rem}.leading-none{line-height:1}.leading-tight{line-height:1.25}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-blue-400{--tw-text-opacity:1;color:rgb(51 176 238/var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity:1;color:rgb(0 125 187/var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity:1;color:rgb(0 62 94/var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-green-300{--tw-text-opacity:1;color:rgb(175 211 130/var(--tw-text-opacity))}.text-green-500{--tw-text-opacity:1;color:rgb(121 181 46/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(97 145 37/var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgb(73 109 28/var(--tw-text-opacity))}.text-green-800{--tw-text-opacity:1;color:rgb(48 72 18/var(--tw-text-opacity))}.text-primary-300{--tw-text-opacity:1;color:rgb(175 211 130/var(--tw-text-opacity))}.text-primary-600{--tw-text-opacity:1;color:rgb(97 145 37/var(--tw-text-opacity))}.text-purple-600{--tw-text-opacity:1;color:rgb(126 58 242/var(--tw-text-opacity))}.text-red-300{--tw-text-opacity:1;color:rgb(255 104 104/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(255 3 3/var(--tw-text-opacity))}.text-red-600{--tw-text-opacity:1;color:rgb(204 2 2/var(--tw-text-opacity))}.text-red-800{--tw-text-opacity:1;color:rgb(102 1 1/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgb(114 59 19/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.\!opacity-0{opacity:0!important}.\!opacity-100{opacity:1!important}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-70{opacity:.7}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-2xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px #00000040;--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.outline-0{outline-width:0}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-75{transition-duration:75ms}.ease-\[cubic-bezier\(\.3\2c 2\.3\2c \.6\2c 1\)\]{transition-timing-function:cubic-bezier(.3,2.3,.6,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.htmx-added .fade-in,.htmx-added.fade-in{opacity:0!important}.fade-in{opacity:1}.htmx-settling .fade-in-settle,.htmx-settling.fade-in-settle{opacity:0!important}.fade-in-settle{opacity:1}.htmx-added .swipe-left-swap,.htmx-added.swipe-left-swap{opacity:1!important;--tw-scale-x:1!important;--tw-scale-y:1!important;--tw-translate-x:-50%!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.swipe-left-swap{opacity:1;--tw-scale-x:1;--tw-scale-y:1;--tw-translate-x:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-settling.htmx-added .swipe-left-swap,.htmx-settling.htmx-added.swipe-left-swap{opacity:0!important;--tw-scale-x:.75!important;--tw-scale-y:.75!important;--tw-translate-x:50%!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.htmx-settling .slide-up-settle,.htmx-settling.slide-up-settle{--tw-translate-y:1.25rem!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.slide-up-settle{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hidden .slide-up,.htmx-added .slide-up{--tw-translate-y:1.25rem!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.slide-up{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.live-added{animation:pulse-green .3s 2;animation-direction:alternate;animation-timing-function:ease-in-out}.dark .live-added{animation:pulse-dark-green .3s 2!important;animation-direction:alternate;animation-timing-function:ease-in-out}.live-removed{animation:pulse-red .3s 2;animation-direction:alternate;animation-timing-function:ease-in-out}.dark .live-removed{animation:pulse-dark-red .3s 2!important;animation-direction:alternate;animation-timing-function:ease-in-out}@keyframes pulse-green{0%{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(175 211 130/var(--tw-bg-opacity))}:is(.dark to){--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}}@keyframes pulse-dark-green{:is(.dark 0%){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}}@keyframes pulse-red{0%{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(255 104 104/var(--tw-bg-opacity))}:is(.dark to){--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}}@keyframes pulse-dark-red{:is(.dark 0%){--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}}.htmx-request .htmx-indicator,.htmx-request.htmx-indicator{display:inherit!important}.htmx-indicator{display:none}.htmx-request .htmx-indicator-hidden{display:none!important}.htmx-indicator-hidden{display:inherit}.htmx-request .htmx-indicator-invisible{visibility:hidden!important}.htmx-indicator-invisible{display:inherit}.htmx-swapping .fade-out{opacity:0!important}.fade-out{opacity:1}.min-h-content{min-height:calc(100vh - 4em)}.choices{margin-bottom:0!important;border-width:0!important}.choices__inner{display:block!important;width:100%!important;border-radius:.5rem!important;border-width:1px!important;--tw-border-opacity:1!important;border-color:rgb(209 213 219/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(249 250 251/var(--tw-bg-opacity))!important;padding:.25rem!important;font-size:.875rem!important;line-height:1.25rem!important;--tw-text-opacity:1!important;color:rgb(17 24 39/var(--tw-text-opacity))!important}.choices__inner:focus{--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.group.has-error .choices__inner{--tw-border-opacity:1!important;border-color:rgb(255 3 3/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(255 230 230/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(51 1 1/var(--tw-text-opacity))!important}.group.has-error .choices__inner::-moz-placeholder{--tw-placeholder-opacity:1!important;color:rgb(153 2 2/var(--tw-placeholder-opacity))!important}.group.has-error .choices__inner::placeholder{--tw-placeholder-opacity:1!important;color:rgb(153 2 2/var(--tw-placeholder-opacity))!important}.group.has-error .choices__inner:focus{--tw-border-opacity:1!important;border-color:rgb(255 3 3/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(255 3 3/var(--tw-ring-opacity))!important}:is(.dark .choices__inner){--tw-border-opacity:1!important;border-color:rgb(75 85 99/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(55 65 81/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}:is(.dark .choices__inner)::-moz-placeholder{--tw-placeholder-opacity:1!important;color:rgb(156 163 175/var(--tw-placeholder-opacity))!important}:is(.dark .choices__inner)::placeholder{--tw-placeholder-opacity:1!important;color:rgb(156 163 175/var(--tw-placeholder-opacity))!important}:is(.dark .choices__inner:focus){--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.group.has-error :is(.dark .choices__inner){--tw-border-opacity:1!important;border-color:rgb(255 3 3/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(55 65 81/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(255 3 3/var(--tw-text-opacity))!important}.group.has-error :is(.dark .choices__inner)::-moz-placeholder{--tw-placeholder-opacity:1!important;color:rgb(255 3 3/var(--tw-placeholder-opacity))!important}.group.has-error :is(.dark .choices__inner)::placeholder{--tw-placeholder-opacity:1!important;color:rgb(255 3 3/var(--tw-placeholder-opacity))!important}.choices:focus-within .choices__inner,:is(.dark .choices:focus-within .choices__inner){--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.choices:focus-within .choices__inner{outline:2px solid #0000!important;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#007dbb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#007dbb}.choices__inner .choices__input{margin:0!important;--tw-bg-opacity:1!important;background-color:rgb(249 250 251/var(--tw-bg-opacity))!important}:is(.dark .choices__inner .choices__input){--tw-bg-opacity:1!important;background-color:rgb(55 65 81/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}.choices__inner .choices__item{white-space:nowrap!important;border-radius:.25rem!important;--tw-border-opacity:1!important;border-color:rgb(156 163 175/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(175 211 130/var(--tw-bg-opacity))!important;padding:.125rem .5rem!important;font-size:.75rem!important;line-height:1rem!important;font-weight:500!important;--tw-text-opacity:1!important;color:rgb(48 72 18/var(--tw-text-opacity))!important}:is(.dark .choices__inner .choices__item){--tw-bg-opacity:1!important;background-color:rgb(24 36 9/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(175 211 130/var(--tw-text-opacity))!important}.choices__list--dropdown{border-radius:.5rem!important;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a!important;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}:is(.dark .choices__list--dropdown){--tw-bg-opacity:1!important;background-color:rgb(55 65 81/var(--tw-bg-opacity))!important}.choices__list--dropdown .choices__item--selectable.is-highlighted{--tw-bg-opacity:1!important;background-color:rgb(175 211 130/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(48 72 18/var(--tw-text-opacity))!important}:is(.dark .choices__list--dropdown .choices__item--selectable.is-highlighted){--tw-bg-opacity:1!important;background-color:rgb(24 36 9/var(--tw-bg-opacity))!important;--tw-text-opacity:1!important;color:rgb(175 211 130/var(--tw-text-opacity))!important}.choices[data-type*=select-multiple] .choices__button{--tw-border-opacity:1!important;border-color:rgb(107 114 128/var(--tw-border-opacity))!important}.choices[data-type*=select-multiple] .choices__button:focus{--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.choices__inner .choices__item:focus-within{--tw-border-opacity:1!important;border-color:rgb(0 156 234/var(--tw-border-opacity))!important;--tw-bg-opacity:1!important;background-color:rgb(121 181 46/var(--tw-bg-opacity))!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))!important}.choices__list--single .choices__item{display:flex!important;width:auto!important}.choices__list--single{width:auto!important}.choices__list--single button{position:relative!important;margin:0!important;display:block!important;height:auto!important}.choices[data-type*=select-one] .choices__button{right:auto!important}.arrow,.arrow:before{position:absolute;width:24px;height:24px;background:inherit}.arrow{visibility:hidden}.arrow:before{visibility:visible;content:"";transform:rotate(45deg)}.arrow{bottom:-4px}.ct-series-a .ct-bar{stroke:#79b52e;fill:#79b52e;stroke-width:20px}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:scale-105:hover,.hover\:scale-110:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.hover\:border-green-300:hover{--tw-border-opacity:1;border-color:rgb(175 211 130/var(--tw-border-opacity))}.hover\:bg-blue-300:hover{--tw-bg-opacity:1;background-color:rgb(102 196 242/var(--tw-bg-opacity))}.hover\:bg-blue-600:hover{--tw-bg-opacity:1;background-color:rgb(0 125 187/var(--tw-bg-opacity))}.hover\:bg-blue-800:hover{--tw-bg-opacity:1;background-color:rgb(0 62 94/var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:bg-green-100:hover{--tw-bg-opacity:1;background-color:rgb(228 240 213/var(--tw-bg-opacity))}.hover\:bg-green-200:hover{--tw-bg-opacity:1;background-color:rgb(201 225 171/var(--tw-bg-opacity))}.hover\:bg-green-300:hover{--tw-bg-opacity:1;background-color:rgb(175 211 130/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(97 145 37/var(--tw-bg-opacity))}.hover\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}.hover\:bg-neutral-100:hover{--tw-bg-opacity:1;background-color:rgb(245 245 245/var(--tw-bg-opacity))}.hover\:bg-primary-100:hover{--tw-bg-opacity:1;background-color:rgb(228 240 213/var(--tw-bg-opacity))}.hover\:bg-red-300:hover{--tw-bg-opacity:1;background-color:rgb(255 104 104/var(--tw-bg-opacity))}.hover\:bg-white:hover{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity:1;color:rgb(0 125 187/var(--tw-text-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.hover\:text-primary-700:hover{--tw-text-opacity:1;color:rgb(73 109 28/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:z-10:focus{z-index:10}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(0 156 234/var(--tw-border-opacity))}.focus\:border-primary-500:focus{--tw-border-opacity:1;border-color:rgb(121 181 46/var(--tw-border-opacity))}.focus\:bg-neutral-100:focus{--tw-bg-opacity:1;background-color:rgb(245 245 245/var(--tw-bg-opacity))}.focus\:text-green-700:focus{--tw-text-opacity:1;color:rgb(73 109 28/var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-2:focus,.focus\:ring-4:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-4:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-blue-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(153 215 247/var(--tw-ring-opacity))}.focus\:ring-blue-300:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(102 196 242/var(--tw-ring-opacity))}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))}.focus\:ring-gray-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(229 231 235/var(--tw-ring-opacity))}.focus\:ring-gray-300:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.focus\:ring-green-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(201 225 171/var(--tw-ring-opacity))}.focus\:ring-green-300:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(175 211 130/var(--tw-ring-opacity))}.focus\:ring-green-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(148 196 88/var(--tw-ring-opacity))}.focus\:ring-green-700:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(73 109 28/var(--tw-ring-opacity))}.focus\:ring-primary-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(121 181 46/var(--tw-ring-opacity))}.focus\:ring-red-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 154 154/var(--tw-ring-opacity))}.group:hover .group-hover\:scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:text-blue-500{--tw-text-opacity:1;color:rgb(0 156 234/var(--tw-text-opacity))}.group.has-error .group-\[\.has-error\]\:border-red-500{--tw-border-opacity:1;border-color:rgb(255 3 3/var(--tw-border-opacity))}.group.has-error .group-\[\.has-error\]\:bg-red-50{--tw-bg-opacity:1;background-color:rgb(255 230 230/var(--tw-bg-opacity))}.group.has-error .group-\[\.has-error\]\:text-red-900{--tw-text-opacity:1;color:rgb(51 1 1/var(--tw-text-opacity))}.group.has-error .group-\[\.has-error\]\:placeholder-red-700::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(153 2 2/var(--tw-placeholder-opacity))}.group.has-error .group-\[\.has-error\]\:placeholder-red-700::placeholder{--tw-placeholder-opacity:1;color:rgb(153 2 2/var(--tw-placeholder-opacity))}.group.has-error .group-\[\.has-error\]\:focus\:border-red-500:focus{--tw-border-opacity:1;border-color:rgb(255 3 3/var(--tw-border-opacity))}.group.has-error .group-\[\.has-error\]\:focus\:ring-red-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 3 3/var(--tw-ring-opacity))}.peer:hover~.peer-hover\:block{display:block}.htmx-swapping\:-translate-x-2\/3.htmx-swapping{--tw-translate-x:-66.666667%}.htmx-swapping\:-translate-x-2\/3.htmx-swapping,.htmx-swapping\:translate-x-2\/3.htmx-swapping{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-swapping\:translate-x-2\/3.htmx-swapping{--tw-translate-x:66.666667%}.htmx-swapping\:scale-0.htmx-swapping{--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-swapping\:opacity-0.htmx-swapping{opacity:0}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-swapping\:translate-x-1\/4.htmx-swapping{--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-swapping\:-translate-x-1\/4.htmx-swapping{--tw-translate-x:-25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-swapping\:scale-75.htmx-swapping,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-swapping\:scale-75.htmx-swapping{--tw-scale-x:.75;--tw-scale-y:.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-swapping\:opacity-0.htmx-swapping,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-swapping\:opacity-0.htmx-swapping{opacity:0}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-swapping\:ease-in.htmx-swapping,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-swapping\:ease-in.htmx-swapping{transition-timing-function:cubic-bezier(.4,0,1,1)}.htmx-swapping .htmx-swapping\:-translate-x-2\/3{--tw-translate-x:-66.666667%}.htmx-swapping .htmx-swapping\:-translate-x-2\/3,.htmx-swapping .htmx-swapping\:translate-x-2\/3{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-swapping .htmx-swapping\:translate-x-2\/3{--tw-translate-x:66.666667%}.htmx-swapping .htmx-swapping\:scale-0{--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-swapping .htmx-swapping\:opacity-0{opacity:0}.group\/transition.backward .htmx-swapping .group-\[\.backward\]\/transition\:htmx-swapping\:translate-x-1\/4{--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.forward .htmx-swapping .group-\[\.forward\]\/transition\:htmx-swapping\:-translate-x-1\/4{--tw-translate-x:-25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .htmx-swapping .group-\[\.backward\]\/transition\:htmx-swapping\:scale-75,.group\/transition.forward .htmx-swapping .group-\[\.forward\]\/transition\:htmx-swapping\:scale-75{--tw-scale-x:.75;--tw-scale-y:.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .htmx-swapping .group-\[\.backward\]\/transition\:htmx-swapping\:opacity-0,.group\/transition.forward .htmx-swapping .group-\[\.forward\]\/transition\:htmx-swapping\:opacity-0{opacity:0}.group\/transition.backward .htmx-swapping .group-\[\.backward\]\/transition\:htmx-swapping\:ease-in,.group\/transition.forward .htmx-swapping .group-\[\.forward\]\/transition\:htmx-swapping\:ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.htmx-added\:-translate-x-2\/3.htmx-added{--tw-translate-x:-66.666667%}.htmx-added\:-translate-x-2\/3.htmx-added,.htmx-added\:translate-x-2\/3.htmx-added{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-added\:translate-x-2\/3.htmx-added{--tw-translate-x:66.666667%}.htmx-added\:scale-0.htmx-added{--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-added\:opacity-0.htmx-added{opacity:0}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-added\:-translate-x-1\/4.htmx-added{--tw-translate-x:-25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-added\:translate-x-1\/4.htmx-added{--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-added\:scale-75.htmx-added,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-added\:scale-75.htmx-added{--tw-scale-x:.75;--tw-scale-y:.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-added\:opacity-0.htmx-added,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-added\:opacity-0.htmx-added{opacity:0}.group\/transition.backward .group-\[\.backward\]\/transition\:htmx-added\:ease-out.htmx-added,.group\/transition.forward .group-\[\.forward\]\/transition\:htmx-added\:ease-out.htmx-added{transition-timing-function:cubic-bezier(0,0,.2,1)}.htmx-added .htmx-added\:-translate-x-2\/3{--tw-translate-x:-66.666667%}.htmx-added .htmx-added\:-translate-x-2\/3,.htmx-added .htmx-added\:translate-x-2\/3{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-added .htmx-added\:translate-x-2\/3{--tw-translate-x:66.666667%}.htmx-added .htmx-added\:scale-0{--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.htmx-added .htmx-added\:opacity-0{opacity:0}.group\/transition.backward .htmx-added .group-\[\.backward\]\/transition\:htmx-added\:-translate-x-1\/4{--tw-translate-x:-25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.forward .htmx-added .group-\[\.forward\]\/transition\:htmx-added\:translate-x-1\/4{--tw-translate-x:25%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .htmx-added .group-\[\.backward\]\/transition\:htmx-added\:scale-75,.group\/transition.forward .htmx-added .group-\[\.forward\]\/transition\:htmx-added\:scale-75{--tw-scale-x:.75;--tw-scale-y:.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/transition.backward .htmx-added .group-\[\.backward\]\/transition\:htmx-added\:opacity-0,.group\/transition.forward .htmx-added .group-\[\.forward\]\/transition\:htmx-added\:opacity-0{opacity:0}.group\/transition.backward .htmx-added .group-\[\.backward\]\/transition\:htmx-added\:ease-out,.group\/transition.forward .htmx-added .group-\[\.forward\]\/transition\:htmx-added\:ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}:is(.dark .dark\:block){display:block}:is(.dark .dark\:hidden){display:none}:is(.dark .dark\:divide-gray-600)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(75 85 99/var(--tw-divide-opacity))}:is(.dark .dark\:border-blue-500){--tw-border-opacity:1;border-color:rgb(0 156 234/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-400){--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-500){--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-600){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-700){--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-900){--tw-border-opacity:1;border-color:rgb(17 24 39/var(--tw-border-opacity))}:is(.dark .dark\:border-green-800){--tw-border-opacity:1;border-color:rgb(48 72 18/var(--tw-border-opacity))}:is(.dark .dark\:border-primary-500){--tw-border-opacity:1;border-color:rgb(121 181 46/var(--tw-border-opacity))}:is(.dark .dark\:border-transparent){border-color:#0000}:is(.dark .dark\:bg-blue-600){--tw-bg-opacity:1;background-color:rgb(0 125 187/var(--tw-bg-opacity))}:is(.dark .dark\:bg-blue-700){--tw-bg-opacity:1;background-color:rgb(0 94 140/var(--tw-bg-opacity))}:is(.dark .dark\:bg-blue-900){--tw-bg-opacity:1;background-color:rgb(0 31 47/var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-600){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-700){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800\/50){background-color:#1f293780}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}:is(.dark .dark\:bg-green-600){--tw-bg-opacity:1;background-color:rgb(97 145 37/var(--tw-bg-opacity))}:is(.dark .dark\:bg-green-700){--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}:is(.dark .dark\:bg-green-900){--tw-bg-opacity:1;background-color:rgb(24 36 9/var(--tw-bg-opacity))}:is(.dark .dark\:bg-red-700){--tw-bg-opacity:1;background-color:rgb(153 2 2/var(--tw-bg-opacity))}:is(.dark .dark\:bg-red-900){--tw-bg-opacity:1;background-color:rgb(51 1 1/var(--tw-bg-opacity))}:is(.dark .dark\:bg-yellow-900){--tw-bg-opacity:1;background-color:rgb(99 49 18/var(--tw-bg-opacity))}:is(.dark .dark\:bg-opacity-80){--tw-bg-opacity:0.8}:is(.dark .dark\:text-blue-100){--tw-text-opacity:1;color:rgb(204 235 251/var(--tw-text-opacity))}:is(.dark .dark\:text-blue-200){--tw-text-opacity:1;color:rgb(153 215 247/var(--tw-text-opacity))}:is(.dark .dark\:text-blue-300){--tw-text-opacity:1;color:rgb(102 196 242/var(--tw-text-opacity))}:is(.dark .dark\:text-blue-400){--tw-text-opacity:1;color:rgb(51 176 238/var(--tw-text-opacity))}:is(.dark .dark\:text-blue-500){--tw-text-opacity:1;color:rgb(0 156 234/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-100){--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-200){--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-300){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-400){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-50){--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-500){--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}:is(.dark .dark\:text-green-300){--tw-text-opacity:1;color:rgb(175 211 130/var(--tw-text-opacity))}:is(.dark .dark\:text-green-400){--tw-text-opacity:1;color:rgb(148 196 88/var(--tw-text-opacity))}:is(.dark .dark\:text-primary-500){--tw-text-opacity:1;color:rgb(121 181 46/var(--tw-text-opacity))}:is(.dark .dark\:text-red-300){--tw-text-opacity:1;color:rgb(255 104 104/var(--tw-text-opacity))}:is(.dark .dark\:text-red-400){--tw-text-opacity:1;color:rgb(255 53 53/var(--tw-text-opacity))}:is(.dark .dark\:text-red-500){--tw-text-opacity:1;color:rgb(255 3 3/var(--tw-text-opacity))}:is(.dark .dark\:text-white){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark .dark\:text-yellow-300){--tw-text-opacity:1;color:rgb(250 202 21/var(--tw-text-opacity))}:is(.dark .dark\:placeholder-gray-400)::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity))}:is(.dark .dark\:placeholder-gray-400)::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity))}:is(.dark .dark\:ring-offset-gray-700){--tw-ring-offset-color:#374151}:is(.dark .dark\:ring-offset-gray-800){--tw-ring-offset-color:#1f2937}:is(.dark .hover\:dark\:border-green-800):hover{--tw-border-opacity:1;border-color:rgb(48 72 18/var(--tw-border-opacity))}:is(.dark .dark\:hover\:bg-blue-600:hover){--tw-bg-opacity:1;background-color:rgb(0 125 187/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-blue-700:hover){--tw-bg-opacity:1;background-color:rgb(0 94 140/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-600:hover){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-700:hover){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-800:hover){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-green-600:hover){--tw-bg-opacity:1;background-color:rgb(97 145 37/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-green-700:hover){--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-red-600:hover){--tw-bg-opacity:1;background-color:rgb(204 2 2/var(--tw-bg-opacity))}:is(.dark .hover\:dark\:bg-gray-800):hover{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}:is(.dark .dark\:hover\:text-blue-500:hover){--tw-text-opacity:1;color:rgb(0 156 234/var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-100:hover){--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-300:hover){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-white:hover){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark .hover\:dark\:text-green-400):hover{--tw-text-opacity:1;color:rgb(148 196 88/var(--tw-text-opacity))}:is(.dark .dark\:focus\:border-blue-500:focus){--tw-border-opacity:1;border-color:rgb(0 156 234/var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-500:focus){--tw-border-opacity:1;border-color:rgb(121 181 46/var(--tw-border-opacity))}:is(.dark .dark\:focus\:text-white:focus){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark .dark\:focus\:ring-blue-500:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(0 156 234/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-blue-600:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(0 125 187/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-blue-800:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(0 62 94/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-gray-600:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(75 85 99/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-green-500:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(121 181 46/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-green-800:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(48 72 18/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-500:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(121 181 46/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-600:focus){--tw-ring-opacity:1;--tw-ring-color:rgb(97 145 37/var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-offset-gray-700:focus){--tw-ring-offset-color:#374151}:is(.dark .group:hover .dark\:group-hover\:text-white){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:border-red-500){--tw-border-opacity:1;border-color:rgb(255 3 3/var(--tw-border-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:bg-gray-700){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:text-red-500){--tw-text-opacity:1;color:rgb(255 3 3/var(--tw-text-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:placeholder-red-500)::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(255 3 3/var(--tw-placeholder-opacity))}.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:placeholder-red-500)::placeholder{--tw-placeholder-opacity:1;color:rgb(255 3 3/var(--tw-placeholder-opacity))}@media (min-width:640px){.sm\:ml-4{margin-left:1rem}.sm\:block{display:block}.sm\:inline{display:inline}.sm\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.sm\:space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.sm\:rounded-lg{border-radius:.5rem}.sm\:p-6{padding:1.5rem}.sm\:py-5{padding-top:1.25rem;padding-bottom:1.25rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:768px){.md\:ml-2{margin-left:.5rem}.md\:mr-24{margin-right:6rem}.md\:block{display:block}.md\:table-cell{display:table-cell}.md\:h-\[600px\]{height:600px}.md\:w-\[750px\]{width:750px}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-center{justify-content:center}.md\:space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.md\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.md\:p-12{padding:3rem}}@media (min-width:1024px){.lg\:block{display:block}.lg\:table-cell{display:table-cell}.lg\:hidden{display:none}.lg\:w-96{width:24rem}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:justify-end{justify-content:flex-end}.lg\:justify-between{justify-content:space-between}.lg\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.lg\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.lg\:px-5{padding-left:1.25rem;padding-right:1.25rem}.lg\:pl-3{padding-left:.75rem}.lg\:pl-64{padding-left:16rem}}.\[\&\.active\]\:bg-primary-300.active{--tw-bg-opacity:1;background-color:rgb(175 211 130/var(--tw-bg-opacity))}.\[\&\.active\]\:bg-primary-500.active{--tw-bg-opacity:1;background-color:rgb(121 181 46/var(--tw-bg-opacity))}:is(.dark .\[\&\.active\]\:dark\:bg-primary-700).active{--tw-bg-opacity:1;background-color:rgb(73 109 28/var(--tw-bg-opacity))} \ No newline at end of file diff --git a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj index 1de76014..1cf681b6 100644 --- a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj +++ b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj @@ -10,6 +10,7 @@ [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated]] [auto-ap.rule-matching :as rm] + [auto-ap.client-routes :as client-routes] [auto-ap.solr :as solr] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.common-handlers :refer [add-new-entity-handler]] @@ -229,6 +230,14 @@ :value (fc/field-value) :content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c)) :x-model "vendorId"})])) + [:div.mb-4 + [:span.text-sm.text-gray-500 "Can't find the vendor? " + (com/link {:href (bidi.bidi/path-for + client-routes/routes + :new-vendor) + :target "new"} + "Add new vendor") + " in a new window, then return here."]] [:div.flex.items-center.gap-2 diff --git a/src/cljc/auto_ap/client_routes.cljc b/src/cljc/auto_ap/client_routes.cljc index 4e58345d..0a40e437 100644 --- a/src/cljc/auto_ap/client_routes.cljc +++ b/src/cljc/auto_ap/client_routes.cljc @@ -6,7 +6,8 @@ "needs-activation/" :needs-activation "needs-activation" :needs-activation "payments/" :payments - "admin/" { "vendors" :admin-vendors} + "admin/" {"vendors" :admin-vendors} + "vendor/" {"new" :new-vendor} "invoices/" {"" :invoices "import" :import-invoices "unpaid" :unpaid-invoices diff --git a/src/cljs/auto_ap/views/main.cljs b/src/cljs/auto_ap/views/main.cljs index 5ff99762..35bd1c17 100644 --- a/src/cljs/auto_ap/views/main.cljs +++ b/src/cljs/auto_ap/views/main.cljs @@ -18,7 +18,7 @@ [auto-ap.views.pages.ledger.profit-and-loss-detail :refer [profit-and-loss-detail-page]] [auto-ap.views.pages.login :refer [login-page]] [auto-ap.views.pages.payments :refer [payments-page]] - [auto-ap.views.pages.home :refer [home-page]])) + [auto-ap.views.pages.home :refer [home-page home-page-with-vendor]])) (defmulti page (fn [active-page] active-page)) (defmethod page :unpaid-invoices [_] @@ -94,6 +94,10 @@ (defmethod page :index [_] (home-page)) +(defmethod page :new-vendor [_] + (home-page-with-vendor)) + + (defmethod page :login [_] [login-page]) diff --git a/src/cljs/auto_ap/views/pages/home.cljs b/src/cljs/auto_ap/views/pages/home.cljs index 131cd81f..b9d3eb3c 100644 --- a/src/cljs/auto_ap/views/pages/home.cljs +++ b/src/cljs/auto_ap/views/pages/home.cljs @@ -2,7 +2,9 @@ (:require [auto-ap.routes :as routes] [auto-ap.subs :as subs] [auto-ap.views.components.grid :as grid] + [auto-ap.permissions :as p] [auto-ap.views.components.layouts :refer [side-bar-layout]] + [auto-ap.views.components.vendor-dialog :as vendor-dialog] [auto-ap.history :refer [history]] [cemerick.url :as url] [auto-ap.views.utils @@ -35,15 +37,14 @@ (defn make-pie-chart [{:keys [width height data]}] [pie-chart {:width width - :height height} + :height height} [pie {:fill "#82ca9d" :data data :dataKey "value" :inner-radius 20} (map (fn [_ y] ^{:key y} - [cell {:key y :fill (colors y)}]) data (range)) - ] + [cell {:key y :fill (colors y)}]) data (range))] [tool-tip] [legend]]) @@ -56,10 +57,9 @@ [y-axis] [legend]]) -(defn make-cash-flow-chart [{:keys [width height data] }] +(defn make-cash-flow-chart [{:keys [width height data]}] (let [redirect-fn (fn [x] - (pushy/set-token! history (str (bidi/path-for routes/routes :unpaid-invoices) "?" (get (js->clj x) "query-params"))) - )] + (pushy/set-token! history (str (bidi/path-for routes/routes :unpaid-invoices) "?" (get (js->clj x) "query-params"))))] [bar-chart {:width width :height height :data data :fill "#FFFFFF" :stackOffset "sign"} [tool-tip] [bar {:dataKey "effective-balance" :fill (get colors 1) :stackId "a" :name "Effective Balance" @@ -69,13 +69,12 @@ [bar {:dataKey "invoices" :fill (get colors 3) :stackId "a" :name "Invoices" :on-click redirect-fn}] [bar {:dataKey "credits" :fill (get colors 2) :stackId "a" :name "Upcoming Credits" - :on-click redirect-fn}] + :on-click redirect-fn}] [bar {:dataKey "debits" :fill (get colors 4) :stackId "a" :name "Upcoming Debits" :on-click redirect-fn}] [x-axis {:dataKey "name"}] [y-axis] - [legend]]) - ) + [legend]])) (re-frame/reg-event-db ::received @@ -120,7 +119,7 @@ (::top-expense-categories db))) (defn sum-by-date [pairs] - (reduce + (reduce (fn [result [date amount]] (let [due (if (t/before? date (local-now)) (local-now) @@ -156,10 +155,10 @@ upcoming-debits (sum-by-date (map (fn [i] [(:date i) (:amount i)]) upcoming-debits)) start-date (local-now) effective-balance (- beginning-balance outstanding-payments (invoices-due-soon (date->str start-date) 0.0))] - - (reverse + + (reverse (reduce - (fn [[{:keys [effective-balance credits-yesterday] } :as acc] day] + (fn [[{:keys [effective-balance credits-yesterday]} :as acc] day] (let [invoices-due-today (invoices-due-soon (date->str (t/plus start-date (t/days day))) 0.0) credits-due-today (upcoming-credits (date->str (t/plus start-date (t/days day))) 0.0) debits-due-today (upcoming-debits (date->str (t/plus start-date (t/days day))) 0.0) @@ -167,7 +166,7 @@ (conj acc {:name (date->str today) :date today - :effective-balance (+ (- effective-balance invoices-due-today ) + :effective-balance (+ (- effective-balance invoices-due-today) debits-due-today credits-yesterday) :credits-yesterday credits-due-today @@ -175,7 +174,7 @@ :debits debits-due-today :invoices (- invoices-due-today) :query-params (url/map->query {:due-range {:start (date->str today standard) - :end (date->str today standard)}})}))) + :end (date->str today standard)}})}))) (list {:name (date->str start-date) :date start-date :effective-balance effective-balance @@ -212,7 +211,7 @@ :<- [::cash-flow-table-params] :<- [::cash-flow-data] (fn [[params cash-flow-data]] - (let [ {:keys [invoices-due-soon upcoming-credits upcoming-debits]} cash-flow-data + (let [{:keys [invoices-due-soon upcoming-credits upcoming-debits]} cash-flow-data rows (concat (map (fn [c] {:date (:date c) :days-until (days-until (:date c)) @@ -233,7 +232,7 @@ :name (str (:name (:vendor c)) " (" (:invoice-number c) ")") :type "Invoice"}) invoices-due-soon))] - (assoc (grid/virtual-paginate-controls (:start params ) (:per-page params) rows) + (assoc (grid/virtual-paginate-controls (:start params) (:per-page params) rows) :data (grid/virtual-paginate (:start params) (:per-page params) (sort-by (comp coerce/to-date :date) rows)))))) @@ -243,19 +242,19 @@ [(re-frame/inject-cofx ::inject/sub [::subs/client])] (fn [{:keys [db] ::subs/keys [client]} _] (cond-> - {:db (assoc db ::top-expense-categories nil - ::cash-flow nil - ::invoice-stats nil)} + {:db (assoc db ::top-expense-categories nil + ::cash-flow nil + ::invoice-stats nil)} client (assoc :graphql {:token (-> db :user) :owns-state {:single ::page} :query-obj {:venia/queries [[:expense_account_stats - {:client-id (:id client)} + {:client-id (:id client)} [[:account [:id :name]] :total]] [:invoice_stats - {:client-id (:id client)} + {:client-id (:id client)} [:name :paid :unpaid]] [:cash-flow - {:client-id (:id client)} + {:client-id (:id client)} [:beginning-balance :outstanding-payments [:invoices-due-soon [:due :outstanding-balance [:vendor [:id :name]] :invoice-number]] @@ -287,42 +286,40 @@ [grid/header-cell {} "Name"] [grid/header-cell {:class "has-text-right"} "Amount"]]] [grid/body - (for [[i {:keys [date days-until type name amount] }] (map vector (range) (:data page))] + (for [[i {:keys [date days-until type name amount]}] (map vector (range) (:data page))] ^{:key i} [grid/row {} [grid/cell {} (if (> days-until 0) [:span.has-text-success days-until " days"] [:span.has-text-danger days-until " days"]) - [:i.is-size-7 " (" (date->str date) ")"] ] + [:i.is-size-7 " (" (date->str date) ")"]] [grid/cell {} (if (> date 0) "Upcoming " "Due ") type] [grid/cell {} name] - [grid/cell {:class "has-text-right"} (->$ amount)] - ])]]])) + [grid/cell {:class "has-text-right"} (->$ amount)]])]]])) (defn home-content [] (let [client-id (-> @(re-frame/subscribe [::subs/client]) :id) chart-options @(re-frame/subscribe [::chart-options]) state @(re-frame/subscribe [::status/single ::page])] ^{:key client-id} - [side-bar-layout {:side-bar [:div - ] + [side-bar-layout {:side-bar [:div] :main [:div [:h1.title "Home"] - (if client-id + (if client-id (if (= :loading (:state state)) [:div.loader.is-loading.big.is-centered] - [:<> + [:<> [:h1.title.is-4 "Top expense categories"] (let [expense-categories @(re-frame/subscribe [::top-expense-categories])] (make-pie-chart {:width 800 :height 500 :data (clj->js - (map (fn [x] {:name (:name (:account x)) :value (:total x)}) expense-categories))})) + (map (fn [x] {:name (:name (:account x)) :value (:total x)}) expense-categories))})) [:h1.title.is-4 "Upcoming Bills"] (make-bar-chart {:width 800 :height 500 :data (clj->js - @(re-frame/subscribe [::invoice-stats]))}) + @(re-frame/subscribe [::invoice-stats]))}) [:h1.title.is-4 "Cash Flow"] [:div.buttons.has-addons @@ -360,4 +357,16 @@ (defn home-page [] (let [client-id (-> @(re-frame/subscribe [::subs/client]) :id)] (re-frame/dispatch [::mounted]) + ^{:key client-id} [home-content])) + +(defn home-page-with-vendor [] + (let [client-id (-> @(re-frame/subscribe [::subs/client]) :id) + user @(re-frame/subscribe [::subs/user])] + (re-frame/dispatch [::mounted]) + (when (p/can? user {:subject :vendor + :activity :create}) + (re-frame/dispatch [::vendor-dialog/started {}])) + + + ^{:key client-id} [home-content])) From 65be50cf9f1dadf1220f16f4bcd7430e66182f20 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 10 Apr 2024 14:48:47 -0700 Subject: [PATCH 07/11] simplify sorting --- src/clj/auto_ap/datomic.clj | 8 ++- src/clj/auto_ap/query_params.clj | 33 +++++---- src/clj/auto_ap/ssr/admin/clients.clj | 18 +++-- src/clj/auto_ap/ssr/grid_page_helper.clj | 90 +++++++++++++----------- src/clj/auto_ap/ssr/invoices.clj | 14 ++-- src/clj/auto_ap/ssr/payments.clj | 20 +++--- src/clj/auto_ap/ssr/utils.clj | 2 +- 7 files changed, 104 insertions(+), 81 deletions(-) diff --git a/src/clj/auto_ap/datomic.clj b/src/clj/auto_ap/datomic.clj index d2e38c2e..4729db20 100644 --- a/src/clj/auto_ap/datomic.clj +++ b/src/clj/auto_ap/datomic.clj @@ -601,7 +601,8 @@ (:sort args))) (defn apply-sort-3 [args results] - (let [sort-bys (conj (:sort args) + + (let [sort-bys (conj (into [] (:sort args)) {:sort-key "default" :asc (if (contains? args :default-asc?) (:default-asc? args) true)}) @@ -609,16 +610,17 @@ comparator (fn [xs ys] (reduce (fn [_ i] + (let [comparison (if (:asc (nth sort-bys i)) (compare (nth xs i) (nth ys i)) (compare (nth ys i) (nth xs i)))] - + (if (not= 0 comparison) (reduced comparison) 0))) 0 (range length)))] - (sort comparator results ))) + (sort comparator results))) (defn apply-pagination-raw [args results] {:entries (->> results diff --git a/src/clj/auto_ap/query_params.clj b/src/clj/auto_ap/query_params.clj index c269e7cf..9e3453db 100644 --- a/src/clj/auto_ap/query_params.clj +++ b/src/clj/auto_ap/query_params.clj @@ -4,6 +4,9 @@ [clj-time.core :as time] [clojure.string :as str])) +(defn wrap-copy-qp-pqp [handler] + (fn [request] + (handler (assoc request :parsed-query-params (:query-params request))))) (defn wrap-parse-query-params [handler parser] (fn parsed-handler [request] @@ -42,13 +45,13 @@ [])) (defn parse-long [l] - (try + (try (Long/parseLong l) (catch Exception e nil))) (defn parse-double [l] - (try + (try (Double/parseDouble l) (catch Exception e nil))) @@ -74,7 +77,7 @@ "all" (assoc query-params - start-date-key (time/plus (time/now) (time/years -3)) + start-date-key (time/plus (time/now) (time/years -6)) end-date-key (time/now)) query-params) @@ -88,18 +91,18 @@ presently-sorted? ((set (map :sort-key current-sort)) key-to-toggle) new-sort (if presently-sorted? (mapv - (fn [s] - (if (= (:sort-key s) - key-to-toggle) - (-> s - (update :asc - #(boolean (not %))) - (update :sort-icon (fn [x] - (if (= x svg/sort-down) - svg/sort-up - svg/sort-down)))) - s)) - current-sort) + (fn [s] + (if (= (:sort-key s) + key-to-toggle) + (-> s + (update :asc + #(boolean (not %))) + (update :sort-icon (fn [x] + (if (= x svg/sort-down) + svg/sort-up + svg/sort-down)))) + s)) + current-sort) (conj current-sort {:sort-key key-to-toggle :asc true :name (:name (first (filter #(= (str key-to-toggle) (:sort-key %)) (:headers grid-spec)))) diff --git a/src/clj/auto_ap/ssr/admin/clients.clj b/src/clj/auto_ap/ssr/admin/clients.clj index 1ff2b337..6054c981 100644 --- a/src/clj/auto_ap/ssr/admin/clients.clj +++ b/src/clj/auto_ap/ssr/admin/clients.clj @@ -5,6 +5,7 @@ pull-many query2]] [auto-ap.graphql.utils :refer [extract-client-ids]] [auto-ap.logging :as alog] + [auto-ap.query-params :refer [wrap-copy-qp-pqp]] [auto-ap.routes.admin.clients :as route] [auto-ap.routes.indicators :as indicators] [auto-ap.routes.queries :as q] @@ -27,7 +28,7 @@ :refer [apply-middleware-to-all-handlers entity-id form-validation-error html-response main-transformer many-entity modal-response ref->enum-schema strip temp-id - wrap-entity wrap-schema-enforce]] + wrap-entity wrap-schema-enforce wrap-merge-prior-hx]] [auto-ap.time :as atime] [bidi.bidi :as bidi] [cheshire.core :as cheshire] @@ -77,7 +78,7 @@ (com/text-input {:name "name" :id "name" :class "hot-filter" - :value (:name (:parsed-query-params request)) + :value (:name (:query-params request)) :placeholder "Best Restaurant LLC" :size :small})) @@ -85,14 +86,14 @@ (com/text-input {:name "code" :id "code" :class "hot-filter" - :value (:code (:parsed-query-params request)) + :value (:code (:query-params request)) :placeholder "BRLC" :size :small})) (com/field {:label "Group"} (com/text-input {:name "group" :id "group" :class "hot-filter" - :value (:group (:parsed-query-params request)) + :value (:group (:query-params request)) :placeholder "NTG" :size :small})) (com/field {:label "Select"} @@ -150,7 +151,7 @@ :client/location-matches [:location-match/matches :location-match/location :db/id]}]) (defn fetch-ids [db request] - (let [query-params (:parsed-query-params request) + (let [query-params (:query-params request) valid-clients (extract-client-ids #_(:clients request) (map first (dc/q '[:find ?c :where [?c :client/code]] (dc/db conn))) (:client-id query-params) @@ -1842,8 +1843,8 @@ (def key->handler (apply-middleware-to-all-handlers - {::route/page (helper/page-route grid-page) - ::route/table (helper/table-route grid-page) + {::route/page (helper/page-route grid-page :parse-query-params? false) + ::route/table (helper/table-route grid-page :parse-query-params? false) ::route/new-location (add-new-primitive-handler [:step-params :client/locations] "" location-row) @@ -1904,7 +1905,10 @@ (mm/wrap-wizard client-wizard))} (fn [h] (-> h + (wrap-copy-qp-pqp) (wrap-apply-sort grid-page) + (wrap-merge-prior-hx) (wrap-schema-enforce :query-schema query-schema) + (wrap-schema-enforce :hx-schema query-schema) (wrap-admin) (wrap-client-redirect-unauthenticated))))) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 506be5f0..3fb9e12c 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -21,6 +21,8 @@ [malli.transform :as mt] [taoensso.encore :refer [filter-vals]])) + + (defn row* [gridspec user entity {:keys [flash? delete-after-settle? request class] :as options}] (let [cells (if (:check-boxes? gridspec) [(com/data-grid-cell {} (com/checkbox {:name "id" :value ((:id-fn gridspec) entity) @@ -75,6 +77,10 @@ "default sort")) (defn table* [grid-spec user {{:keys [start per-page flash-id sort]} :parsed-query-params :as request}] + (alog/info ::TABLE-QP + :qp (:query-params request) + :pqp (:parsed-query-params request) + :sort sort) (let [start (or start 0) per-page (or per-page 25) [entities total] ((:fetch-page grid-spec) @@ -206,9 +212,9 @@ set)] (handler (assoc request :trimmed-clients valid-clients))))) -(defn table-route [grid-spec] - (-> (fn table [{:keys [identity] :as request}] - (alog/peek ::TABLE-QP (:parsed-query-params request)) +(defn table-route [grid-spec & {:keys [parse-query-params?] :or {parse-query-params? true}}] + (cond-> (fn table [{:keys [identity] :as request}] + (let [unparse-query-params (or (:unparse-query grid-spec) default-unparse-query-params)] (html-response (table* @@ -233,41 +239,45 @@ "selected" "all-selected")))} ;; TODO seems hacky to special case selected and all-selected here :oob (when-let [oob-render (:oob-render grid-spec)] (oob-render request))))) - (wrap-trim-client-ids) - (query-params/wrap-parse-query-params (or (:parse-query-params grid-spec) + true (wrap-trim-client-ids) + parse-query-params? (query-params/wrap-parse-query-params (or (:parse-query-params grid-spec) (default-parse-query-params grid-spec))) - (wrap-secure) - (wrap-client-redirect-unauthenticated))) + true (wrap-secure) + true (wrap-client-redirect-unauthenticated))) -(defn page-route [grid-spec] - (-> (fn page [{:keys [identity] :as request}] - (base-page - request - (com/page {:nav (:nav grid-spec) - :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] - [:div#page-specific-nav (page-specific-nav request)]) - :client-selection (:client-selection request) - :clients (:clients request) - :client (:client request) - :identity (:identity request) - :request request} - (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) - [:div {:x-data (hx/json {:selected [] :all_selected false}) - "x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})" - :x-init "$watch('selected', s=> $dispatch('selectedChanged', {selected: s, all_selected: all_selected}) ); +(defn page-route [grid-spec & {:keys [parse-query-params?] :or {parse-query-params? true}}] + + (cond-> (fn page [{:keys [identity] :as request}] + (alog/info ::page-route + :pqp (:parsed-query-params request) + :qp (:query-params request)) + (base-page + request + (com/page {:nav (:nav grid-spec) + :page-specific (when-let [page-specific-nav (:page-specific-nav grid-spec)] + [:div#page-specific-nav (page-specific-nav request)]) + :client-selection (:client-selection request) + :clients (:clients request) + :client (:client request) + :identity (:identity request) + :request request} + (apply com/breadcrumbs {} (:breadcrumbs grid-spec)) + [:div {:x-data (hx/json {:selected [] :all_selected false}) + "x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})" + :x-init "$watch('selected', s=> $dispatch('selectedChanged', {selected: s, all_selected: all_selected}) ); $watch('all_selected', a=>$dispatch('selectedChanged', {selected: selected, all_selected: a}))"} - - (table* grid-spec - identity - request)]) - (if (string? (:title grid-spec)) - (:title grid-spec) - ((:title grid-spec) request)))) - (wrap-trim-client-ids) - (query-params/wrap-parse-query-params (or (:parse-query-params grid-spec) - (default-parse-query-params grid-spec))) - (wrap-secure) - (wrap-client-redirect-unauthenticated))) + + (table* grid-spec + identity + request)]) + (if (string? (:title grid-spec)) + (:title grid-spec) + ((:title grid-spec) request)))) + true (wrap-trim-client-ids) + parse-query-params? (query-params/wrap-parse-query-params (or (:parse-query-params grid-spec) + (default-parse-query-params grid-spec))) + true (wrap-secure) + true (wrap-client-redirect-unauthenticated))) (def request-spec (m/schema [:map])) (def entity-spec (m/schema [:map])) @@ -281,8 +291,8 @@ (def grid-spec (m/schema [:map [:id :string] [:nav [:=> - [:cat request-spec] - vector?]] + [:cat request-spec] + vector?]] [:page-specific-nav {:optional true :default (fn [request])} @@ -339,8 +349,8 @@ (handler (update request :query-params (fn [qp] ((comp - (query-params/apply-remove-sort) - (query-params/apply-toggle-sort grid-spec) - (query-params/parse-key :sort #(query-params/parse-sort grid-spec %))) + (query-params/apply-remove-sort) + (query-params/apply-toggle-sort grid-spec) + (query-params/parse-key :sort #(query-params/parse-sort grid-spec %))) qp)))))) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 16b499ad..2e9369e5 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -7,6 +7,7 @@ [auto-ap.datomic.accounts :as d-accounts] [auto-ap.datomic.bank-accounts :as d-bank-accounts] [auto-ap.datomic.invoices :as d-invoices] + [auto-ap.query-params :refer [wrap-copy-qp-pqp]] [auto-ap.graphql.checks :as gq-checks :refer [base-payment invoice-payments print-checks-internal @@ -236,6 +237,7 @@ query-params) true (merge-query {:query {:find ['?sort-default '?e]}})))] + (->> (observable-query query) (apply-sort-3 (assoc query-params :default-asc? false)) (apply-pagination query-params)))) @@ -715,7 +717,6 @@ (map :invoice-id invoices)) (into {}))] (every? (fn [%] - (println "TEST" (:amount %) (outstanding-balances (:invoice-id %))) (not (does-amount-exceed-outstanding? (:amount %) (outstanding-balances (:invoice-id %))))) invoices)))]]] [:has-warning? :boolean] @@ -1176,13 +1177,13 @@ (def key->handler (apply-middleware-to-all-handlers (-> - {::route/all-page (-> (helper/page-route grid-page) + {::route/all-page (-> (helper/page-route grid-page :parse-query-params? false) (wrap-implied-route-param :status nil)) - ::route/paid-page (-> (helper/page-route grid-page) + ::route/paid-page (-> (helper/page-route grid-page :parse-query-params? false) (wrap-implied-route-param :status :invoice-status/paid)) - ::route/unpaid-page (-> (helper/page-route grid-page) + ::route/unpaid-page (-> (helper/page-route grid-page :parse-query-params? false) (wrap-implied-route-param :status :invoice-status/unpaid)) - ::route/voided-page (-> (helper/page-route grid-page) + ::route/voided-page (-> (helper/page-route grid-page :parse-query-params? false) (wrap-implied-route-param :status :invoice-status/voided)) ::route/unvoid (-> unvoid-invoice (wrap-entity [:route-params :db/id] default-read) @@ -1211,10 +1212,11 @@ (mm/wrap-wizard pay-wizard) (mm/wrap-decode-multi-form-state)) - ::route/table (helper/table-route grid-page)} + ::route/table (helper/table-route grid-page :parse-query-params? false)} (merge new-invoice-wizard/key->handler)) (fn [h] (-> h + (wrap-copy-qp-pqp) (wrap-status-from-source) (wrap-apply-sort grid-page) (wrap-merge-prior-hx) diff --git a/src/clj/auto_ap/ssr/payments.clj b/src/clj/auto_ap/ssr/payments.clj index 14778f31..aa6ba9d4 100644 --- a/src/clj/auto_ap/ssr/payments.clj +++ b/src/clj/auto_ap/ssr/payments.clj @@ -7,6 +7,7 @@ [auto-ap.graphql.utils :refer [assert-can-see-client exception->notification extract-client-ids notify-if-locked]] + [auto-ap.query-params :refer [wrap-copy-qp-pqp]] [auto-ap.logging :as alog] [auto-ap.permissions :refer [can?]] [auto-ap.routes.invoice :as invoice-route] @@ -38,8 +39,8 @@ [malli.transform :as mt])) (defn exact-match-id* [request] - (if (nat-int? (:exact-match-id (:parsed-query-params request))) - [:div {:x-data (hx/json {:exact_match (:exact-match-id (:parsed-query-params request))}) :id "exact-match-id-tag"} + (if (nat-int? (:exact-match-id (:query-params request))) + [:div {:x-data (hx/json {:exact_match (:exact-match-id (:query-params request))}) :id "exact-match-id-tag"} (com/hidden {:name "exact-match-id" "x-model" "exact_match"}) (com/pill {:color :primary} @@ -68,7 +69,7 @@ :value (:vendor (:query-params request)) :value-fn :db/id :content-fn :vendor/name})) - (date-range-field* request) + (date-range-field* (assoc request :parsed-query-params (:query-params request))) (com/field {:label "Check #"} (com/text-input {:name "check-number" :id "check-number" @@ -130,7 +131,7 @@ {:transaction/_payment [:db/id :transaction/date]}]) (defn fetch-ids [db {:keys [query-params route-params] :as request}] - (let [ valid-clients (extract-client-ids (:clients request) + (let [valid-clients (extract-client-ids (:clients request) (:client request) (:client-id query-params) (when (:client-code query-params) @@ -530,13 +531,13 @@ (def key->handler (apply-middleware-to-all-handlers - {::route/cleared-page (-> (helper/page-route grid-page) + {::route/cleared-page (-> (helper/page-route grid-page :parse-query-params? false) (wrap-implied-route-param :status :payment-status/cleared)) - ::route/pending-page (-> (helper/page-route grid-page) + ::route/pending-page (-> (helper/page-route grid-page :parse-query-params? false) (wrap-implied-route-param :status :payment-status/pending)) - ::route/voided-page (-> (helper/page-route grid-page) + ::route/voided-page (-> (helper/page-route grid-page :parse-query-params? false) (wrap-implied-route-param :status :payment-status/voided)) - ::route/all-page (-> (helper/page-route grid-page) + ::route/all-page (-> (helper/page-route grid-page :parse-query-params? false) (wrap-implied-route-param :status nil)) ::route/delete (-> delete @@ -549,9 +550,10 @@ (wrap-admin)) - ::route/table (helper/table-route grid-page)} + ::route/table (helper/table-route grid-page :parse-query-params? false)} (fn [h] (-> h + (wrap-copy-qp-pqp) (wrap-apply-sort grid-page) (wrap-merge-prior-hx) (wrap-status-from-source) diff --git a/src/clj/auto_ap/ssr/utils.clj b/src/clj/auto_ap/ssr/utils.clj index 56fb3bbd..56885f6a 100644 --- a/src/clj/auto_ap/ssr/utils.clj +++ b/src/clj/auto_ap/ssr/utils.clj @@ -255,7 +255,7 @@ end-date-key (time/now)) "all" - (assoc m start-date-key (time/plus (time/now) (time/years -3)) + (assoc m start-date-key (time/plus (time/now) (time/years -6)) end-date-key (time/now)) m) From 2fb49d3331e799e8013c1a958320768334e5dea0 Mon Sep 17 00:00:00 2001 From: Bryce Date: Thu, 11 Apr 2024 11:42:44 -0700 Subject: [PATCH 08/11] QOL items --- .../auto_ap/ssr/components/link_dropdown.clj | 2 +- src/clj/auto_ap/ssr/components/tags.clj | 7 ++- .../ssr/invoice/new_invoice_wizard.clj | 53 +++++++++++++++---- src/cljc/auto_ap/routes/invoice.cljc | 3 +- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/clj/auto_ap/ssr/components/link_dropdown.clj b/src/clj/auto_ap/ssr/components/link_dropdown.clj index a4ad3d96..43a63f1c 100644 --- a/src/clj/auto_ap/ssr/components/link_dropdown.clj +++ b/src/clj/auto_ap/ssr/components/link_dropdown.clj @@ -14,7 +14,7 @@ (com/a-icon-button {:x-ref "link" "@click.prevent" "show=!show; $nextTick(() => popper.update());" :class "relative"} svg/paperclip - (com/badge {} (count links))) + (com/badge {:color "blue"} (count links))) [:div.divide-y.divide-gray-200.bg-white.rounded-lg.shadow.z-50 (hx/alpine-appear {:x-ref "tooltip" :x-show "show" :data-key "show"}) [:div {:class "p-3 overflow-y-auto text-sm text-gray-700 dark:text-gray-200"} [:div.flex.flex-col.gap-y-2 diff --git a/src/clj/auto_ap/ssr/components/tags.clj b/src/clj/auto_ap/ssr/components/tags.clj index da9ceba5..76bc4e7c 100644 --- a/src/clj/auto_ap/ssr/components/tags.clj +++ b/src/clj/auto_ap/ssr/components/tags.clj @@ -21,4 +21,9 @@ children)) (defn badge- [params & children] - [:div {:class (hh/add-class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-black text-white bg-red-300 border-3 border-white rounded-full -top-2 -right-2 dark:border-gray-900" (:class params))} children]) + [:div {:class (-> (hh/add-class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-black text-white + border-3 border-white rounded-full -top-2 -right-2 dark:border-gray-900" + (:class params) + ) + (hh/add-class (or (some-> (:color params) (#(str "bg-" % "-300"))) + "bg-red-300")))} children]) diff --git a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj index 1cf681b6..e07a4994 100644 --- a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj +++ b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj @@ -430,17 +430,36 @@ (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x)))) (defn invoice-expense-account-total* [request] - (format "$%,.2f" (->> (-> request - :multi-form-state - :step-params - :invoice/expense-accounts) - (map (fnil :invoice-expense-account/amount 0.0)) - (filter number?) - (reduce + 0.0)))) + (let [total (->> (-> request + :multi-form-state + :step-params + :invoice/expense-accounts) + (map (fnil :invoice-expense-account/amount 0.0)) + (filter number?) + (reduce + 0.0))] + (format "$%,.2f" total))) + +(defn invoice-expense-account-balance* [request] + (let [total (->> (-> request + :multi-form-state + :step-params + :invoice/expense-accounts) + (map (fnil :invoice-expense-account/amount 0.0)) + (filter number?) + (reduce + 0.0)) + balance (- + (-> request :multi-form-state :snapshot :invoice/total) + total)] + [:span {:class (when-not (dollars= 0.0 balance) + "text-red-300")} + (format "$%,.2f" balance)])) (defn invoice-expense-account-total [request] (html-response (invoice-expense-account-total* request))) +(defn invoice-expense-account-balance [request] + (html-response (invoice-expense-account-balance* request))) + (defrecord AccountsStep [linear-wizard] mm/ModalWizardStep (step-name [_] @@ -490,6 +509,19 @@ :hx-swap "innerHTML"} (invoice-expense-account-total* request)) (com/data-grid-cell {})) + + (com/data-grid-row {} + (com/data-grid-cell {}) + (com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "BALANCE"]) + (com/data-grid-cell {:id "total" + :class "text-right" + :hx-trigger "change from:closest form target:.amount-field" + :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-balance) + :hx-target "this" + :hx-swap "innerHTML"} + (invoice-expense-account-balance* request)) + (com/data-grid-cell {})) + (com/data-grid-row {} (com/data-grid-cell {}) @@ -601,12 +633,12 @@ (when (seq eas) (let [leftover (- invoice-total (reduce + 0 (map :invoice-expense-account/amount eas))) leftover-beyond-a-single-cent? (or (< leftover -1) - (> leftover 1)) + (> leftover 1)) leftover (if leftover-beyond-a-single-cent? 0 leftover) [first-eas & rest] eas] - (cons + (cons (update first-eas :invoice-expense-account/amount #(+ % leftover)) rest)))) @@ -822,6 +854,9 @@ ::route/expense-account-total (-> invoice-expense-account-total (mm/wrap-wizard new-wizard) (mm/wrap-decode-multi-form-state)) + ::route/expense-account-balance (-> invoice-expense-account-balance + (mm/wrap-wizard new-wizard) + (mm/wrap-decode-multi-form-state)) ::route/location-select (-> location-select (wrap-schema-enforce :query-schema [:map [:name :string] diff --git a/src/cljc/auto_ap/routes/invoice.cljc b/src/cljc/auto_ap/routes/invoice.cljc index b6b19552..b1b0d6d2 100644 --- a/src/cljc/auto_ap/routes/invoice.cljc +++ b/src/cljc/auto_ap/routes/invoice.cljc @@ -12,7 +12,8 @@ "/account/new" ::new-wizard-new-account "/account/location-select" ::location-select "/account/prediction" ::account-prediction - "/total" ::expense-account-total} + "/total" ::expense-account-total + "/balance" ::expense-account-balance} "/pay-button" ::pay-button "/pay" {:get ::pay-wizard From 6031f15246515b35592c2ce6eb48eb03f094d20d Mon Sep 17 00:00:00 2001 From: Bryce Date: Thu, 11 Apr 2024 13:18:45 -0700 Subject: [PATCH 09/11] Adds totals --- src/clj/auto_ap/datomic.clj | 3 +- src/clj/auto_ap/ssr/grid_page_helper.clj | 5 +- src/clj/auto_ap/ssr/invoices.clj | 65 ++++++++++++++++++------ 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/clj/auto_ap/datomic.clj b/src/clj/auto_ap/datomic.clj index 4729db20..e5516ac0 100644 --- a/src/clj/auto_ap/datomic.clj +++ b/src/clj/auto_ap/datomic.clj @@ -635,7 +635,8 @@ (:per-page args) default-pagination-size)) (map last)) - :count (count results)}) + :count (count results) + :all-ids (map last results)}) (defn audit-transact-batch [txes id] (let [batch-id (.toString (java.util.UUID/randomUUID))] diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 3fb9e12c..fa36881f 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -83,8 +83,9 @@ :sort sort) (let [start (or start 0) per-page (or per-page 25) - [entities total] ((:fetch-page grid-spec) - request)] + [entities total :as page-results] ((:fetch-page grid-spec) + request) + request (assoc request :page-results page-results)] (com/data-grid-card {:id (:id grid-spec) :title (if (string? (:title grid-spec)) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 2e9369e5..8edd2207 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -251,12 +251,42 @@ (map first))] refunds)) +(defn sum-outstanding [ids] + + (->> + (dc/q {:find ['?id '?o] + :in ['$ '[?id ...]] + :where ['[?id :invoice/outstanding-balance ?o]]} + (dc/db conn) + ids) + (map last) + (reduce + + + 0.0))) + +(defn sum-total-amount [ids] + + (->> + (dc/q {:find ['?id '?o] + :in ['$ '[?id ...]] + :where ['[?id :invoice/total ?o]] + } + (dc/db conn) + ids) + (map last) + (reduce + + + 0.0))) + (defn fetch-page [request] (let [db (dc/db conn) - {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] + {ids-to-retrieve :ids matching-count :count + all-ids :all-ids} (fetch-ids db request)] [(->> (hydrate-results ids-to-retrieve db request)) - matching-count])) + matching-count + (sum-outstanding all-ids) + (sum-total-amount all-ids)])) (def query-schema (mc/schema [:maybe [:map {:date-range [:date-range :start-date :end-date]} @@ -378,7 +408,6 @@ (:query-params request))}))) - ;; TODO test as a real user (def grid-page (helper/build {:id "entity-table" @@ -395,18 +424,24 @@ (alog/peek ::PARSE (mc/decode query-schema p main-transformer))) :action-buttons (fn [request] - [(when (can? (:identity request) {:subject :invoice :activity :bulk-delete}) - (com/button {:hx-get (str (bidi/path-for ssr-routes/only-routes ::route/bulk-delete)) - "x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})" - "hx-include" "#invoice-filters" - :color :red} - "Void selected")) - (when (can? (:identity request) {:subject :invoice :activity :pay}) - (pay-button* {:ids (selected->ids request - (:query-params request))})) - (when (can? (:identity request) {:subject :invoice :activity :create}) - (com/button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard)} - "New invoice"))]) + (let [[_ _ outstanding total] (:page-results request)] + [(com/pill {:color :primary} "Outstanding: " + (format "$%,.2f" outstanding)) + (com/pill {:color :secondary} "Total: " + (format "$%,.2f" total)) + + (when (can? (:identity request) {:subject :invoice :activity :bulk-delete}) + (com/button {:hx-get (str (bidi/path-for ssr-routes/only-routes ::route/bulk-delete)) + "x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})" + "hx-include" "#invoice-filters" + :color :red} + "Void selected")) + (when (can? (:identity request) {:subject :invoice :activity :pay}) + (pay-button* {:ids (selected->ids request + (:query-params request))})) + (when (can? (:identity request) {:subject :invoice :activity :create}) + (com/button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard)} + "New invoice"))])) :row-buttons (fn [request entity] [(when (and (= :invoice-status/unpaid (:invoice/status entity)) (can? (:identity request) {:subject :invoice :activity :delete})) From 71b5b9864ca35e772d0d90f2a0d25e568b222ae1 Mon Sep 17 00:00:00 2001 From: Bryce Date: Sun, 14 Apr 2024 22:29:01 -0700 Subject: [PATCH 10/11] Applies all of the feedback for the new page --- src/clj/auto_ap/query_params.clj | 7 +-- src/clj/auto_ap/ssr/components/aside.clj | 8 +-- src/clj/auto_ap/ssr/components/date_range.clj | 16 +++--- .../auto_ap/ssr/components/link_dropdown.clj | 2 +- .../auto_ap/ssr/components/multi_modal.clj | 15 +++--- src/clj/auto_ap/ssr/invoices.clj | 3 +- src/clj/auto_ap/ssr/users.clj | 53 ++++++++++--------- src/clj/auto_ap/ssr/utils.clj | 7 +-- src/clj/auto_ap/time.clj | 14 ++++- terraform/connect-ports-cloud-staging.sh | 2 +- 10 files changed, 74 insertions(+), 53 deletions(-) diff --git a/src/clj/auto_ap/query_params.clj b/src/clj/auto_ap/query_params.clj index 9e3453db..c36837aa 100644 --- a/src/clj/auto_ap/query_params.clj +++ b/src/clj/auto_ap/query_params.clj @@ -61,9 +61,10 @@ (dissoc (condp = (source-key query-params) "week" - (assoc query-params - start-date-key (time/plus (time/now) (time/days -7)) - end-date-key (time/now)) + (let [last-monday (atime/last-monday)] + (assoc query-params + start-date-key (time/plus last-monday (time/days -7)) + end-date-key last-monday)) "month" (assoc query-params diff --git a/src/clj/auto_ap/ssr/components/aside.clj b/src/clj/auto_ap/ssr/components/aside.clj index f860cac3..1d57ff60 100644 --- a/src/clj/auto_ap/ssr/components/aside.clj +++ b/src/clj/auto_ap/ssr/components/aside.clj @@ -111,26 +111,26 @@ :active? (= "invoices" selected)} (menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes ::invoice-route/all-page) - {:date-range "month"}) + {:date-range "year"}) :active? (= ::invoice-route/all-page (:matched-route request)) :hx-boost "true"} "All") (menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes ::invoice-route/paid-page) - {:date-range "month"}) + {:date-range "year"}) :active? (= ::invoice-route/paid-page (:matched-route request)) :hx-boost "true"} "Paid") (menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes ::invoice-route/unpaid-page) - {:date-range "month"}) + {:date-range "year"}) :active? (= ::invoice-route/unpaid-page (:matched-route request)) :hx-boost "true"} "Unpaid") (menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes ::invoice-route/voided-page) - {:date-range "month"}) + {:date-range "year"}) :active? (= ::invoice-route/voided-page (:matched-route request)) :hx-boost "true"} "Voided") diff --git a/src/clj/auto_ap/ssr/components/date_range.clj b/src/clj/auto_ap/ssr/components/date_range.clj index 6f740e1a..ff1306f1 100644 --- a/src/clj/auto_ap/ssr/components/date_range.clj +++ b/src/clj/auto_ap/ssr/components/date_range.clj @@ -1,26 +1,28 @@ (ns auto-ap.ssr.components.date-range (:require [auto-ap.ssr.components :as com] - [auto-ap.time :as atime])) + [auto-ap.time :as atime] + [clj-time.coerce :as c] + [clj-time.core :as t] + [clj-time.periodic :as per])) -(defn date-range-field [{:keys [value id] }] +(defn date-range-field [{:keys [value id]}] [:div {:id id} (com/field {:label "Date Range"} - [:div.space-y-4 + [:div.space-y-4 [:div (com/button-group {:name "date-range"} (com/button-group-button {:size :small :value "all" :hx-trigger "click"} "All") (com/button-group-button {:size :small :value "week" :hx-trigger "click"} "Week") (com/button-group-button {:size :small :value "month" :hx-trigger "click"} "Month") - (com/button-group-button {:size :small :value "year" :hx-trigger "click"} "Year")) - ] + (com/button-group-button {:size :small :value "year" :hx-trigger "click"} "Year"))] [:div.flex.space-x-1.items-baseline.w-full.justify-start (com/date-input {:name "start-date" :value (some-> (:start value) - (atime/unparse-local atime/normal-date)) + (atime/unparse-local atime/normal-date)) :placeholder "Date" :size :small :class "shrink"}) - + (com/date-input {:name "end-date" :value (some-> (:end value) (atime/unparse-local atime/normal-date)) diff --git a/src/clj/auto_ap/ssr/components/link_dropdown.clj b/src/clj/auto_ap/ssr/components/link_dropdown.clj index 43a63f1c..a63f1523 100644 --- a/src/clj/auto_ap/ssr/components/link_dropdown.clj +++ b/src/clj/auto_ap/ssr/components/link_dropdown.clj @@ -20,6 +20,6 @@ [:div.flex.flex-col.gap-y-2 (for [l links] [:div.flex-initial - [:a {:href (:link l)} + [:a {:href (:link l) :target "_blank"} (com/pill {:color (or (:color l) :primary) :class "truncate block shrink grow-0"} (:content l))]])]]]])) \ No newline at end of file diff --git a/src/clj/auto_ap/ssr/components/multi_modal.clj b/src/clj/auto_ap/ssr/components/multi_modal.clj index 74c664c5..86a6fb49 100644 --- a/src/clj/auto_ap/ssr/components/multi_modal.clj +++ b/src/clj/auto_ap/ssr/components/multi_modal.clj @@ -114,7 +114,7 @@ :class "dark:text-blue-500"} "Back"]) -(defn default-next-button [linear-wizard step validation-route] +(defn default-next-button [linear-wizard step validation-route & {:keys [next-button-content]}] (let [steps (steps linear-wizard) last? (= (step-key step) (last steps)) next-step (when-not last? (->> steps @@ -131,9 +131,10 @@ {:from (encode-step-key (step-key step)) :to (encode-step-key (step-key next-step))}))) - (if next-step - (step-name next-step) - "Save") + (or next-button-content + (if next-step + (step-name next-step) + "Save")) (when-not last? [:div.w-5.h-5 svg/arrow-right])))) @@ -143,7 +144,8 @@ (defn default-step-footer [linear-wizard step & {:keys [validation-route discard-button - next-button]}] + next-button + next-button-content]}] [:div.flex.justify-end [:div.flex.items-baseline.gap-x-4 (com/form-errors {:errors (:errors (:step-params fc/*form-errors*))}) @@ -157,7 +159,8 @@ next-button validation-route - (default-next-button linear-wizard step validation-route) + (default-next-button linear-wizard step validation-route + :next-button-content next-button-content) :else [:div "No action possible."])]]) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 8edd2207..582c3a20 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -1003,7 +1003,8 @@ :name (fc/field-name) :error? (fc/error?)}))))))))))]]) :footer - (mm/default-step-footer linear-wizard this :validation-route ::route/pay-wizard-navigate) + (mm/default-step-footer linear-wizard this :validation-route ::route/pay-wizard-navigate + :next-button-content "Pay") :validation-route ::route/pay-wizard-navigate))) (defn add-handwritten-check [request wizard snapshot] diff --git a/src/clj/auto_ap/ssr/users.clj b/src/clj/auto_ap/ssr/users.clj index 4c8e7d6d..8f5bcdc0 100644 --- a/src/clj/auto_ap/ssr/users.clj +++ b/src/clj/auto_ap/ssr/users.clj @@ -48,7 +48,7 @@ "hx-target" "#user-table" "hx-indicator" "#user-table"} - [:fieldset.space-y-6 + [:fieldset.space-y-6 (com/field {:label "Name"} (com/text-input {:name "name" :id "name" @@ -57,6 +57,16 @@ :placeholder "Johnny Testerson" :size :small})) + (com/field {:label "Client"} + (com/typeahead {:name "client" + :placeholder "Search..." + :url (bidi/path-for ssr-routes/only-routes + :company-search) + :id (str "client-search") + :value (:client (:parsed-query-params request)) + :value-fn :db/id + :content-fn :client/name})) + (com/field {:label "Email"} (com/text-input {:name "email" :id "email" @@ -66,31 +76,22 @@ :size :small})) (com/field {:label "Role"} - (com/radio-card {:size :small - :name "role" - :options [{:value "" - :content "All"} - {:value "admin" - :content "Admin"} - {:value "power-user" - :content "Power user"} - {:value "manager" - :content "Manager"} - {:value "user" - :content "User"} - {:value "read-only" - :content "Read Only"} - {:value "none" - :content "None"}]})) - (com/field {:label "Client"} - (com/typeahead {:name "client" - :placeholder "Search..." - :url (bidi/path-for ssr-routes/only-routes - :company-search) - :id (str "client-search") - :value (:client (:parsed-query-params request)) - :value-fn :db/id - :content-fn :client/name}))]]) + (com/radio-card {:size :small + :name "role" + :options [{:value "" + :content "All"} + {:value "admin" + :content "Admin"} + {:value "power-user" + :content "Power user"} + {:value "manager" + :content "Manager"} + {:value "user" + :content "User"} + {:value "read-only" + :content "Read Only"} + {:value "none" + :content "None"}]}))]]) (def default-read '[:db/id :user/name diff --git a/src/clj/auto_ap/ssr/utils.clj b/src/clj/auto_ap/ssr/utils.clj index 56885f6a..ef21be4c 100644 --- a/src/clj/auto_ap/ssr/utils.clj +++ b/src/clj/auto_ap/ssr/utils.clj @@ -240,9 +240,10 @@ (if date-range-value (-> (condp = date-range-value "week" - (assoc m - start-date-key (time/plus (time/now) (time/days -7)) - end-date-key (time/now)) + (let [last-monday (atime/last-monday)] + (assoc m + start-date-key (time/plus last-monday (time/days -7)) + end-date-key last-monday)) "month" (assoc m diff --git a/src/clj/auto_ap/time.clj b/src/clj/auto_ap/time.clj index 1a3c7360..389e14d0 100644 --- a/src/clj/auto_ap/time.clj +++ b/src/clj/auto_ap/time.clj @@ -1,5 +1,6 @@ (ns auto-ap.time (:require [clj-time.core :as time] + [clj-time.coerce :as coerce] [clj-time.format :as f] [auto-ap.logging :as alog])) @@ -40,7 +41,7 @@ (defn unparse-local [v format] (try - + (f/unparse (f/with-zone (f/formatter format) (time/time-zone-for-id "America/Los_Angeles")) v) (catch Exception _ nil))) @@ -52,3 +53,14 @@ d (recur (time/plus d (time/days 1)))))] (iterate #(time/plus % (time/days 7)) next-day))) + +(defn local-today [] + (coerce/in-time-zone (time/now) (time/time-zone-for-id "America/Los_Angeles"))) + + +(defn last-monday [] + (loop [current (local-now)] + (if (= 1 (time/day-of-week current)) + current + (recur (time/minus current (time/days 1)))))) + \ No newline at end of file diff --git a/terraform/connect-ports-cloud-staging.sh b/terraform/connect-ports-cloud-staging.sh index 77db7143..9bcffd4a 100755 --- a/terraform/connect-ports-cloud-staging.sh +++ b/terraform/connect-ports-cloud-staging.sh @@ -1,2 +1,2 @@ #!/bin/sh -ssh -L 2049:172.31.32.90:2049 3.213.115.86 -L 8983:solr-staging.local:8983 -L 4334:integreat-datomic.local:4334 -L9001:integreat-app-staging.local:9000 +ssh -L 2049:172.31.32.90:2049 3.213.115.86 -L 8984:solr-staging.local:8983 -L 4334:integreat-datomic.local:4334 -L9001:integreat-app-staging.local:9000 From 38030637ba928fe27291fdbf8825af6057b946bb Mon Sep 17 00:00:00 2001 From: Bryce Date: Sun, 14 Apr 2024 22:35:39 -0700 Subject: [PATCH 11/11] should prevent bad error message from uploaded invoices. --- src/clj/auto_ap/jobs/import_uploaded_invoices.clj | 8 +++----- src/clj/auto_ap/jobs/ntg.clj | 2 +- src/clj/auto_ap/jobs/sysco.clj | 2 +- src/clj/auto_ap/pdf/ledger.clj | 8 ++++---- src/clj/auto_ap/routes/invoices.clj | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/clj/auto_ap/jobs/import_uploaded_invoices.clj b/src/clj/auto_ap/jobs/import_uploaded_invoices.clj index 680debfa..44d4d828 100644 --- a/src/clj/auto_ap/jobs/import_uploaded_invoices.clj +++ b/src/clj/auto_ap/jobs/import_uploaded_invoices.clj @@ -20,9 +20,7 @@ (defn send-email-about-failed-message [mail-bucket mail-key message] (let [target-key (str "failed-emails/" mail-key ".eml") - target-url (str "http://" (:data-bucket env) - ".s3-website-us-east-1.amazonaws.com/" - target-key)] + target-url (str "https://" (:data-bucket env) "/" target-key)] (alog/info ::sending-failure-email :who (:import-failure-destination-emails env)) (s3/copy-object mail-bucket mail-key (:data-bucket env) target-key) (ses/send-email {:destination {:to-addresses (:import-failure-destination-emails env)} @@ -66,8 +64,8 @@ :content-length (.length (io/file filename))}) (let [imports (->> (parse/parse-file filename filename) (map #(assoc % - :source-url (str "http://" (:data-bucket env) - ".s3-website-us-east-1.amazonaws.com/" + :source-url (str "https://" (:data-bucket env) + "/" s3-location) :import-status :import-status/imported)))] (alog/info ::found-imports :imports imports) diff --git a/src/clj/auto_ap/jobs/ntg.clj b/src/clj/auto_ap/jobs/ntg.clj index 6c21fce3..9b2cabee 100644 --- a/src/clj/auto_ap/jobs/ntg.clj +++ b/src/clj/auto_ap/jobs/ntg.clj @@ -273,7 +273,7 @@ (mapcat (fn [k] (try (let [invoice-key (copy-readable-version k) - invoice-url (str "http://" bucket-name ".s3-website-us-east-1.amazonaws.com/" invoice-key)] + invoice-url (str "https://" bucket-name "/" invoice-key)] (with-open [is (-> (s3/get-object {:bucket-name bucket-name :key k}) :input-stream)] diff --git a/src/clj/auto_ap/jobs/sysco.clj b/src/clj/auto_ap/jobs/sysco.clj index 2fb53f35..b7da2288 100644 --- a/src/clj/auto_ap/jobs/sysco.clj +++ b/src/clj/auto_ap/jobs/sysco.clj @@ -134,7 +134,7 @@ (mapcat (fn [k] (try (let [invoice-key (str "invoice-files/" (UUID/randomUUID) ".csv") ; - invoice-url (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/" invoice-key)] + invoice-url (str "https://" (:data-bucket env) "/" invoice-key)] (s3/copy-object {:source-bucket-name (:data-bucket env) :destination-bucket-name (:data-bucket env) :source-key k diff --git a/src/clj/auto_ap/pdf/ledger.clj b/src/clj/auto_ap/pdf/ledger.clj index c58b02a3..c1d01857 100644 --- a/src/clj/auto_ap/pdf/ledger.clj +++ b/src/clj/auto_ap/pdf/ledger.clj @@ -338,7 +338,7 @@ pdf-data (make-pnl args data) name (pnl-args->name args) key (str "reports/pnl/" uuid "/" name ".pdf") - url (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/" key)] + url (str "https://" (:data-bucket env) "/" key)] (s3/put-object :bucket-name (:data-bucket env) :key key :input-stream (io/make-input-stream pdf-data {}) @@ -359,7 +359,7 @@ pdf-data (make-cash-flows args data) name (cash-flows-args->name args) key (str "reports/cash-flows/" uuid "/" name ".pdf") - url (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/" key)] + url (str "https://" (:data-bucket env) "/" key)] (s3/put-object :bucket-name (:data-bucket env) :key key :input-stream (io/make-input-stream pdf-data {}) @@ -380,7 +380,7 @@ pdf-data (make-balance-sheet args data) name (balance-sheet-args->name args) key (str "reports/balance-sheet/" uuid "/" name ".pdf") - url (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/" key)] + url (str "https://" (:data-bucket env) "/" key)] (s3/put-object :bucket-name (:data-bucket env) :key key :input-stream (io/make-input-stream pdf-data {}) @@ -401,7 +401,7 @@ pdf-data (make-journal-detail-report args data) name (journal-detail-args->name args) key (str "reports/journal-detail/" uuid "/" name ".pdf") - url (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/" key)] + url (str "https://" (:data-bucket env) "/" key)] (s3/put-object :bucket-name (:data-bucket env) :key key :input-stream (io/make-input-stream pdf-data {}) diff --git a/src/clj/auto_ap/routes/invoices.clj b/src/clj/auto_ap/routes/invoices.clj index cbc682a5..b4f2b840 100644 --- a/src/clj/auto_ap/routes/invoices.clj +++ b/src/clj/auto_ap/routes/invoices.clj @@ -277,8 +277,8 @@ :client-override client :location-override location :vendor-override vendor - :source-url (str "http://" (:data-bucket env) - ".s3-website-us-east-1.amazonaws.com/" + :source-url (str "https://" (:data-bucket env) + "/" s3-location))))] (import-uploaded-invoice user imports)) {:status 200