@stoker-platform/web-app 0.5.172 → 0.5.174

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @stoker-platform/web-app
2
2
 
3
+ ## 0.5.174
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: add exact match search as default search mode
8
+ - @stoker-platform/node-client@0.5.71
9
+ - @stoker-platform/utils@0.5.62
10
+ - @stoker-platform/web-client@0.5.73
11
+
12
+ ## 0.5.173
13
+
14
+ ### Patch Changes
15
+
16
+ - feat: add props option to custom record pages
17
+ - @stoker-platform/node-client@0.5.70
18
+ - @stoker-platform/utils@0.5.61
19
+ - @stoker-platform/web-client@0.5.72
20
+
3
21
  ## 0.5.172
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/web-app",
3
- "version": "0.5.172",
3
+ "version": "0.5.174",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "scripts": {
@@ -51,9 +51,9 @@
51
51
  "@radix-ui/react-tooltip": "^1.2.8",
52
52
  "@react-google-maps/api": "^2.20.8",
53
53
  "@sentry/react": "^10.56.0",
54
- "@stoker-platform/node-client": "0.5.69",
55
- "@stoker-platform/utils": "0.5.60",
56
- "@stoker-platform/web-client": "0.5.71",
54
+ "@stoker-platform/node-client": "0.5.71",
55
+ "@stoker-platform/utils": "0.5.62",
56
+ "@stoker-platform/web-client": "0.5.73",
57
57
  "@tanstack/react-table": "^8.21.3",
58
58
  "@types/react": "18.3.13",
59
59
  "@types/react-dom": "18.3.1",
package/src/List.tsx CHANGED
@@ -693,6 +693,12 @@ export function List({
693
693
  .filter((record): record is StokerRecord => record !== undefined)
694
694
  }, [rowSelection, searchList])
695
695
 
696
+ const searchOptions = tryFunction(customization.admin?.searchOptions) || {
697
+ fuzzy: false,
698
+ prefix: false,
699
+ }
700
+ const exactPhrase = searchOptions.fuzzy === false && searchOptions.prefix === false
701
+
696
702
  const table = useReactTable<StokerRecord>({
697
703
  data: searchList,
698
704
  columns,
@@ -700,7 +706,7 @@ export function List({
700
706
  getCoreRowModel: getCoreRowModel(),
701
707
  getPaginationRowModel: getPaginationRowModel(),
702
708
  onSortingChange: (sortingUpdater) => {
703
- if (isSearchRelevanceOrder) return
709
+ if (isSearchRelevanceOrder && !exactPhrase) return
704
710
  if (typeof sortingUpdater === "function") {
705
711
  const newSorting = sortingUpdater(sorting)
706
712
  const field = getField(fields, newSorting[0].id)
@@ -740,9 +746,9 @@ export function List({
740
746
  onRowSelectionChange: setRowSelection,
741
747
  pageCount,
742
748
  autoResetPageIndex: false,
743
- enableSorting: !isSearchRelevanceOrder,
749
+ enableSorting: !isSearchRelevanceOrder || exactPhrase,
744
750
  state: {
745
- sorting: isSearchRelevanceOrder ? [] : sorting,
751
+ sorting: isSearchRelevanceOrder && !exactPhrase ? [] : sorting,
746
752
  columnFilters,
747
753
  rowSelection,
748
754
  pagination: {
package/src/Record.tsx CHANGED
@@ -311,6 +311,7 @@ export const Record = ({ collection }: { collection: CollectionSchema }) => {
311
311
  }),
312
312
  hooks: import.meta.glob("./hooks/*.{ts,tsx}", { eager: true }),
313
313
  utils: import.meta.glob("./lib/*.{ts,tsx}", { eager: true }),
314
+ ...page.props,
314
315
  })}
315
316
  </main>
316
317
  }
package/src/Tenant.tsx CHANGED
@@ -591,7 +591,7 @@ function Tenant() {
591
591
  runViewTransition(() => navigate("/"))
592
592
  }}
593
593
  >
594
- <img src={logo || defaultLogo} alt="Logo" className="h-8 mr-2" />
594
+ <img src={logo || defaultLogo} alt="Logo" className="h-8 mr-2 object-contain" />
595
595
  </button>
596
596
  {links}
597
597
  </nav>
@@ -163,8 +163,7 @@ export const getFormattedFieldValue = (
163
163
  variant="outline"
164
164
  size="sm"
165
165
  className={cn(
166
- "w-full max-w-[200px] whitespace-normal break-words h-auto p-3 bg-blue-500 dark:bg-blue-500/50 text-primary-foreground dark:text-primary hover:bg-blue-500 dark:hover:bg-blue-500 border-transparent dark:border-input",
167
- !card && "min-w-[100px]",
166
+ "w-fit min-w-[100px] md:min-w-[200px] max-w-full whitespace-normal break-words h-auto p-3 bg-transparent dark:bg-blue-500/50 text-blue-500 dark:text-primary dark:hover:bg-blue-500 dark:border-input",
168
167
  )}
169
168
  disabled={
170
169
  !relationCollection ||
@@ -1,32 +1,83 @@
1
1
  import { CollectionSchema, StokerRecord } from "@stoker-platform/types"
2
+ import { getCollectionConfigModule } from "@stoker-platform/web-client"
2
3
  import MiniSearch, { Options } from "minisearch"
3
4
 
5
+ const flattenToSearchText = (value: unknown): string => {
6
+ if (value == null) return ""
7
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
8
+ return String(value)
9
+ }
10
+ if (Array.isArray(value)) {
11
+ return value.map(flattenToSearchText).filter(Boolean).join(" ")
12
+ }
13
+ if (typeof value === "object") {
14
+ return Object.values(value).map(flattenToSearchText).filter(Boolean).join(" ")
15
+ }
16
+ return ""
17
+ }
18
+
19
+ const valueContainsPhrase = (value: unknown, phrase: string): boolean => {
20
+ if (value == null) return false
21
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
22
+ return String(value).toLowerCase().includes(phrase)
23
+ }
24
+ if (Array.isArray(value)) {
25
+ return value.some((item) => valueContainsPhrase(item, phrase))
26
+ }
27
+ if (typeof value === "object") {
28
+ return Object.values(value).some((item) => valueContainsPhrase(item, phrase))
29
+ }
30
+ return false
31
+ }
32
+
33
+ const recordMatchesPhrase = (record: StokerRecord, fields: string[], phrase: string) =>
34
+ // eslint-disable-next-line security/detect-object-injection
35
+ fields.some((field) => valueContainsPhrase(record[field], phrase))
36
+
4
37
  export const localFullTextSearch = (
5
38
  collection: CollectionSchema,
6
39
  query: string,
7
40
  list: StokerRecord[],
8
41
  filter?: (result: StokerRecord) => boolean,
9
- tokenize?: boolean,
10
42
  ) => {
11
43
  const { recordTitleField, fullTextSearch } = collection
12
- const miniSearchConfig: Options = {
13
- fields: fullTextSearch || [recordTitleField],
14
- storeFields: fullTextSearch || [recordTitleField],
15
- searchOptions: {
16
- ...(collection.searchOptions || {
17
- fuzzy: 0.2,
18
- prefix: true,
19
- }),
20
- },
44
+ const fields = fullTextSearch || [recordTitleField]
45
+ const customization = getCollectionConfigModule(collection.labels.collection)
46
+ const searchOptions = customization.admin?.searchOptions || {
47
+ fuzzy: false,
48
+ prefix: false,
21
49
  }
50
+
22
51
  if (filter) {
23
52
  list = list.filter((record) => filter(record))
24
53
  }
25
- if (tokenize) {
26
- miniSearchConfig.tokenize = (string) => [string]
54
+
55
+ const phrase = query.trim().toLowerCase()
56
+ const exactPhrase = searchOptions.fuzzy === false && searchOptions.prefix === false
57
+
58
+ if (exactPhrase) {
59
+ if (!phrase) return []
60
+ return list
61
+ .filter((record) => recordMatchesPhrase(record, fields, phrase))
62
+ .map((record) => ({
63
+ id: record.id,
64
+ score: 1,
65
+ terms: [phrase],
66
+ queryTerms: [phrase],
67
+ match: { [phrase]: fields },
68
+ // eslint-disable-next-line security/detect-object-injection
69
+ ...Object.fromEntries(fields.map((field) => [field, record[field]])),
70
+ }))
27
71
  }
72
+
73
+ const miniSearchConfig: Options = {
74
+ fields,
75
+ storeFields: fields,
76
+ searchOptions,
77
+ stringifyField: (fieldValue) => flattenToSearchText(fieldValue),
78
+ }
79
+
28
80
  const miniSearch = new MiniSearch(miniSearchConfig)
29
81
  miniSearch.addAll(list)
30
- const results = miniSearch.search(query)
31
- return results
82
+ return miniSearch.search(query)
32
83
  }