Files
integreat/src/clj/auto_ap/ssr/company_dropdown.clj
Bryce 712b2c0cb8 fix: use cleansed-query for Solr client name search
Fixes substring search in company dropdown. The search query was
using raw user input instead of the cleansed version that adds a
wildcard suffix (e.g. 'dough' -> 'dough*'). Without the wildcard,
Solr performs exact token matching, so searching 'dough' would not
match 'Doughballs'.
2026-05-26 13:21:53 -07:00

200 lines
9.3 KiB
Clojure

(ns auto-ap.ssr.company-dropdown
(:require
[auto-ap.datomic :refer [conn pull-many]]
[auto-ap.graphql.utils :refer [cleanse-query strip-special]]
[auto-ap.logging :as alog]
[auto-ap.solr :as solr]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils :refer [html-response]]
[bidi.bidi :as bidi]
[clojure.data.json :as json]
[clojure.string :as str]
[datomic.api :as dc]
[hiccup2.core :as hiccup]
[iol-ion.query :refer [can-see-client?]]))
(defn dropdown-search-results* [{:keys [options]}]
[:ul
(for [{:keys [id name group]} options]
[:li
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
(if group
[:a {:href "#" :class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
:hx-put (bidi/path-for ssr-routes/only-routes
:active-client
: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]
[:a {:href "#" :class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
:hx-put (bidi/path-for ssr-routes/only-routes
:active-client
: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])]])])
(defn get-clients [identity query]
(let [raw-query (not-empty (strip-special query))
cleansed-query (not-empty (cleanse-query query))
cleansed-search-query (str "name:(" cleansed-query ")")
exec-search (fn []
(for [n (pull-many (dc/db conn) [:client/name :db/id]
(for [{:keys [id name]} (solr/query solr/impl "clients" {"query" cleansed-search-query
"fields" "id, name"})
:let [client-id (Long/parseLong id)]
:when (can-see-client? identity client-id)]
client-id))]
{:id (:db/id n)
:name (:client/name n)}))]
(cond (str/starts-with? query "g:")
[{:group (subs query 2)
:name (str "All clients matching " (subs query 2))}]
raw-query
(let [exact-code-matches (for [n (pull-many (dc/db conn) [:client/name :db/id]
(for [client-id (map first (dc/q '[:find ?e
:in $ ?code
:where [?e :client/code ?code]]
(dc/db conn)
query))
:when (can-see-client? identity client-id)]
client-id))]
{:id (:db/id n)
:name (:client/name n)})]
(or (seq exact-code-matches) (exec-search)))
cleansed-query
(exec-search))))
(defn dropdown-search-results [{:keys [identity] :as request}]
(html-response
(dropdown-search-results* {:options (get-clients identity (get (:query-params request) "search-text"))})))
(defn dropdown [{:keys [client-selection client identity clients]}]
[:div#company-dropdown {:x-data (hx/json {})}
[:script
(hiccup/raw
"localStorage.setItem(\"last-client-id\", \"" (:db/id client) "\")" "\n"
"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"
"x-tooltip.on.click" "{content: ()=>$refs.tooltip.innerHTML, theme: 'light', onMount(i) { htmx.process(i.popper); }, allowHTML: true, interactive:true}"
:type "button"}
(cond
(= :mine client-selection)
"My Companies"
(= :all client-selection)
"All Companies"
(and client
(= 1 (count clients)))
(:client/name client)
:else
(str (count clients) " Companies"))
[:div.w-4.h-4.ml-2
svg/drop-down]]
[:template#company-dropdown-list {:x-ref "tooltip"}
[:div {:class "w-[300px]"
"x-init" "$refs.company.focus()"}
[:div {:class "p-3"}
[:label {:for "input-group-search", :class "sr-only"} "Search"]
[:div {:class "relative"}
[:div {:class "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"}
[:div.w-5.h-5.text-gray-500.dark:text-gray-400
svg/search]]
[:input#company-search {:placeholder "Company name"
:x-ref "company"
:name "search-text"
:class "block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
:autoFocus true
:tab-index -1
:hx-trigger "keyup changed delay:500ms, search"
:hx-get (bidi/path-for ssr-routes/only-routes
:company-dropdown-search-results)
:hx-target "#company-search-results"
:hx-swap "innerHTML"}]]
[:input#company-search-value {:type "hidden"
:name "x-clients"}]]
[:div.divide-y.divide-gray-100
[:div#company-search-results {:class "h-48 px-3 pb-3 overflow-y-auto text-sm text-gray-700 dark:text-gray-200"}]
(when (= "admin" (:user/role identity))
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
[:button {:class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
:hx-put (bidi/path-for ssr-routes/only-routes
:active-client
:request-method :put)
:hx-target "#company-dropdown"
"@click" "globalClientSelection=\"mine\""
:hx-headers "{\"x-clients\": \":mine\"}"
:hx-swap "outerHTML"
:hx-trigger "click"}
"Mine"]])
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
[:button {:class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
:hx-put (bidi/path-for ssr-routes/only-routes
:active-client
:request-method :put)
:hx-target "#company-dropdown"
"@click" "globalClientSelection=\"all\""
:hx-headers "{\"x-clients\": \":all\"}"
:hx-swap "outerHTML"
:hx-trigger "click"}
"All"]]]]]
[:script {:lang "text/javascript"}
(hiccup/raw
"
function initCompanyDropdown() {
var $dropdownTargetEl = document.getElementById('company-dropdown-list');
// set the element that trigger the dropdown menu on click
var $dropdownTriggerEl = document.getElementById('company-dropdown-button');
var dropdownOptions = {
placement: 'bottom',
triggerType: 'click',
offsetSkidding: 0,
offsetDistance: 10,
delay: 300,
onHide: () => {
},
onShow: () => {
document.getElementById('company-search').focus()
},
onToggle: () => {
}
};
var companyDrowdown = new Dropdown($dropdownTargetEl, $dropdownTriggerEl, dropdownOptions);
}
")]]])
(defn active-client [{:keys [identity params] :as request}]
(assoc
(html-response
(dropdown {:client-selection (:client-selection request)
:clients (:clients request)
:client (:client request)
:identity identity}))
:headers
{"hx-trigger" "clientSelected"}))