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