Skip to content

Commit d6189ee

Browse files
authored
Add fuse-backed org search to superadmin org list (#2277)
Closes #2276 Adds a simple search bar to the superadmin interface that allows users to search for orgs by org name, id, users (names and emails), and subscriptions (subscription id and plan id). [Extended search](https://www.fusejs.io/examples.html#extended-search) is enabled, so exact search terms like `=stripe:sub_xxxxxxx` can be used to find a specific org directly. [See the docs](https://www.fusejs.io/examples.html#extended-search) for what operators are available. <img width="897" alt="Screenshot 2025-01-07 at 1 59 27 PM" src="https://github.com/user-attachments/assets/56c22fd0-5a61-4665-b904-d4534079158a" /> <img width="894" alt="Screenshot 2025-01-07 at 1 59 39 PM" src="https://github.com/user-attachments/assets/2a9fcee7-bcd0-4959-854c-e43daddbe7cf" />
1 parent 3b6f63f commit d6189ee

File tree

1 file changed

+53
-2
lines changed

1 file changed

+53
-2
lines changed

frontend/src/components/orgs-list.ts

+53-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import type {
77
SlMenuItem,
88
} from "@shoelace-style/shoelace";
99
import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js";
10-
import { css, html, nothing } from "lit";
10+
import Fuse from "fuse.js";
11+
import { css, html, nothing, type PropertyValues } from "lit";
1112
import { customElement, property, query, state } from "lit/decorators.js";
1213
import { when } from "lit/directives/when.js";
1314

@@ -56,12 +57,62 @@ export class OrgsList extends BtrixElement {
5657
@query("#orgDeleteButton")
5758
private readonly orgDeleteButton?: SlButton | null;
5859

60+
// For fuzzy search:
61+
private readonly fuse = new Fuse(this.orgList ?? [], {
62+
keys: [
63+
"id",
64+
"name",
65+
"slug",
66+
"users.name",
67+
"users.email",
68+
"subscription.subId",
69+
"subscription.planId",
70+
],
71+
useExtendedSearch: true,
72+
});
73+
74+
@state()
75+
private search = "";
76+
77+
protected willUpdate(changedProperties: PropertyValues<this>) {
78+
if (changedProperties.has("orgList")) {
79+
this.fuse.setCollection(this.orgList ?? []);
80+
}
81+
}
82+
83+
protected firstUpdated() {
84+
this.fuse.setCollection(this.orgList ?? []);
85+
}
86+
5987
render() {
6088
if (this.skeleton) {
6189
return this.renderSkeleton();
6290
}
6391

92+
const orgs = this.search
93+
? this.fuse.search(this.search).map(({ item }) => item)
94+
: this.orgList;
95+
6496
return html`
97+
<sl-input
98+
value=${this.search}
99+
clearable
100+
size="small"
101+
class="mb-6"
102+
placeholder=${msg(
103+
"Search all orgs by name, id, slug, users, and subscriptions",
104+
)}
105+
@sl-input=${(e: Event) => {
106+
this.search = (e.target as SlInput).value.trim() || "";
107+
}}
108+
>
109+
<sl-icon
110+
name="search"
111+
slot="prefix"
112+
aria-hidden="true"
113+
library="default"
114+
></sl-icon
115+
></sl-input>
65116
<btrix-table>
66117
<btrix-table-head class="mb-2">
67118
<btrix-table-header-cell>
@@ -84,7 +135,7 @@ export class OrgsList extends BtrixElement {
84135
</btrix-table-header-cell>
85136
</btrix-table-head>
86137
<btrix-table-body class="rounded border">
87-
${this.orgList?.map(this.renderOrg)}
138+
${orgs?.map(this.renderOrg)}
88139
</btrix-table-body>
89140
</btrix-table>
90141

0 commit comments

Comments
 (0)