@stoker-platform/web-app 0.5.202 → 0.5.205

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,29 @@
1
1
  # @stoker-platform/web-app
2
2
 
3
+ ## 0.5.205
4
+
5
+ ### Patch Changes
6
+
7
+ - fix: fix filter clearing issue
8
+
9
+ ## 0.5.204
10
+
11
+ ### Patch Changes
12
+
13
+ - feat: add record sidebar filters
14
+ - @stoker-platform/node-client@0.5.80
15
+ - @stoker-platform/utils@0.5.71
16
+ - @stoker-platform/web-client@0.5.84
17
+
18
+ ## 0.5.203
19
+
20
+ ### Patch Changes
21
+
22
+ - feat: add filter condition method
23
+ - @stoker-platform/node-client@0.5.79
24
+ - @stoker-platform/utils@0.5.70
25
+ - @stoker-platform/web-client@0.5.83
26
+
3
27
  ## 0.5.202
4
28
 
5
29
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/web-app",
3
- "version": "0.5.202",
3
+ "version": "0.5.205",
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.78",
55
- "@stoker-platform/utils": "0.5.69",
56
- "@stoker-platform/web-client": "0.5.82",
54
+ "@stoker-platform/node-client": "0.5.80",
55
+ "@stoker-platform/utils": "0.5.71",
56
+ "@stoker-platform/web-client": "0.5.84",
57
57
  "@tanstack/react-table": "^8.21.3",
58
58
  "@types/react": "18.3.13",
59
59
  "@types/react-dom": "18.3.1",
@@ -529,7 +529,7 @@ function Collection({
529
529
  : Math.min(itemsPerPage || 10, Math.max(1, Math.floor(30 / disjunctions)))
530
530
  let hitsPerPage = 0
531
531
  if (exactPhrase && !(hasEntityRestrictions.length > 0 || hasEntityParentFilters.length > 0)) {
532
- hitsPerPage = 1000
532
+ hitsPerPage = 500
533
533
  } else {
534
534
  hitsPerPage = batchSize
535
535
  }
@@ -1359,6 +1359,9 @@ function Collection({
1359
1359
  }
1360
1360
  }
1361
1361
  })
1362
+ if (relationList.showFilters) {
1363
+ excluded.push(...relationList.showFilters)
1364
+ }
1362
1365
  }
1363
1366
  return excluded
1364
1367
  }, [isPreloadCacheEnabled, cardsConfig, statusField, tab, filters])
@@ -2285,6 +2288,8 @@ function Collection({
2285
2288
  collection={collection}
2286
2289
  excluded={excludedFilters}
2287
2290
  relationList={relationList}
2291
+ relationCollection={relationCollection}
2292
+ relationParent={relationParent}
2288
2293
  assignable={assignable}
2289
2294
  isAssigning={displayIsAssigning}
2290
2295
  />
package/src/Filters.tsx CHANGED
@@ -54,11 +54,23 @@ interface FiltersProps {
54
54
  collection: CollectionSchema
55
55
  excluded: string[]
56
56
  relationList?: RelationList
57
+ relationCollection?: CollectionSchema
58
+ relationParent?: StokerRecord
57
59
  assignable?: Assignable
58
60
  isAssigning?: boolean
61
+ isSidebar?: boolean
59
62
  }
60
63
 
61
- export function Filters({ collection, excluded, relationList, assignable, isAssigning }: FiltersProps) {
64
+ export function Filters({
65
+ collection,
66
+ excluded,
67
+ relationList,
68
+ relationCollection,
69
+ relationParent,
70
+ assignable,
71
+ isAssigning,
72
+ isSidebar,
73
+ }: FiltersProps) {
62
74
  const { labels, fields } = collection
63
75
  const location = useLocation()
64
76
  const schema = getSchema()
@@ -446,6 +458,12 @@ export function Filters({ collection, excluded, relationList, assignable, isAssi
446
458
  return (
447
459
  <div className="flex flex-col gap-6">
448
460
  {filters.map((filter: Filter) => {
461
+ if (
462
+ "condition" in filter &&
463
+ filter.condition &&
464
+ filter.condition(relationCollection, relationParent, isAssigning) === false
465
+ )
466
+ return null
449
467
  if (filter.type === "relation" && !hasRelationFilterAccess(filter)) return
450
468
  if (filter.type === "status" || filter.type === "range") return
451
469
  const field = getField(fields, filter.field)
@@ -474,7 +492,7 @@ export function Filters({ collection, excluded, relationList, assignable, isAssi
474
492
  <Label htmlFor={title}>{title}:</Label>
475
493
  <RadioGroup defaultValue="no_selection" className="mt-2">
476
494
  {values.map((value: string) => {
477
- if (filter.condition && filter.condition(value) === false) return null
495
+ if (filter.filterValues && filter.filterValues(value) === false) return null
478
496
  return (
479
497
  <div key={value} className="flex items-center space-x-2">
480
498
  <RadioGroupItem
@@ -525,7 +543,7 @@ export function Filters({ collection, excluded, relationList, assignable, isAssi
525
543
  <Label htmlFor={title}>{title}:</Label>
526
544
  <div className="mt-2 flex flex-col gap-2">
527
545
  {values.map((value: string) => {
528
- if (filter.condition && filter.condition(value) === false) return null
546
+ if (filter.filterValues && filter.filterValues(value) === false) return null
529
547
  return (
530
548
  <Button
531
549
  key={value}
@@ -600,7 +618,7 @@ export function Filters({ collection, excluded, relationList, assignable, isAssi
600
618
  <SelectContent>
601
619
  <SelectItem value="no_selection">----</SelectItem>
602
620
  {values.map((value: string) => {
603
- if (filter.condition && filter.condition(value) === false) return
621
+ if (filter.filterValues && filter.filterValues(value) === false) return
604
622
  return (
605
623
  <SelectItem key={value} value={value}>
606
624
  {value}
@@ -814,6 +832,13 @@ export function Filters({ collection, excluded, relationList, assignable, isAssi
814
832
  filters.forEach((filter) => {
815
833
  if (filter.type === "status" || filter.type === "range") return
816
834
  if (relationList && relationList.field === filter.field) return
835
+ if (
836
+ "condition" in filter &&
837
+ filter.condition &&
838
+ !filter.condition(relationCollection, relationParent, isAssigning)
839
+ )
840
+ return
841
+ if (isSidebar && !relationList?.showFilters?.includes(filter.field)) return
817
842
  const field = getField(fields, filter.field)
818
843
  if (!field) return
819
844
  if (filter.type === "select") {
package/src/Record.tsx CHANGED
@@ -30,6 +30,7 @@ import { serverReadOnly } from "./utils/serverReadOnly"
30
30
  import { useRouteLoading } from "./providers/LoadingProvider"
31
31
  import { getRelationFields } from "./utils/getRelationFields"
32
32
  import { FiltersProvider } from "./providers/FiltersProvider"
33
+ import { SidebarFilters } from "./SidebarFilters"
33
34
  import Collection from "./Collection"
34
35
  import { Breadcrumbs } from "./Breadcrumbs"
35
36
  import { Separator } from "./components/ui/separator"
@@ -82,6 +83,7 @@ export const Record = ({ collection }: { collection: CollectionSchema }) => {
82
83
 
83
84
  const [isAssigning, setIsAssigning] = useState<Record<string, boolean>>({})
84
85
  const [assignable, setAssignable] = useState<Assignable[] | undefined>(undefined)
86
+ const [sidebarFiltersContainer, setSidebarFiltersContainer] = useState<HTMLDivElement | null>(null)
85
87
 
86
88
  useEffect(() => {
87
89
  if (id && record && record.id !== id) {
@@ -213,6 +215,7 @@ export const Record = ({ collection }: { collection: CollectionSchema }) => {
213
215
  customRecordPages={customRecordPages}
214
216
  isAssigning={isAssigning}
215
217
  setIsAssigning={setIsAssigning}
218
+ filtersContainerRef={setSidebarFiltersContainer}
216
219
  />
217
220
  <Routes>
218
221
  <Route
@@ -277,6 +280,25 @@ export const Record = ({ collection }: { collection: CollectionSchema }) => {
277
280
  </div>
278
281
  </>
279
282
  )}
283
+ {sidebarFiltersContainer && (
284
+ <SidebarFilters
285
+ key={`${relationList.collection}-sidebar-filters`}
286
+ container={sidebarFiltersContainer}
287
+ collection={relationCollection}
288
+ relationList={relationList}
289
+ relationCollection={collection}
290
+ relationParent={record}
291
+ assignable={assignable?.find(
292
+ (item: Assignable) =>
293
+ item.collection === relationList.collection,
294
+ )}
295
+ isAssigning={
296
+ isAssigning?.[
297
+ relationList.collection.toLowerCase()
298
+ ]
299
+ }
300
+ />
301
+ )}
280
302
  <Collection
281
303
  key={`${relationList.collection}-collection`}
282
304
  collection={relationCollection}
@@ -35,12 +35,14 @@ export const RecordSidebar = ({
35
35
  customRecordPages,
36
36
  isAssigning,
37
37
  setIsAssigning,
38
+ filtersContainerRef,
38
39
  }: {
39
40
  record: StokerRecord
40
41
  collection: CollectionSchema
41
42
  customRecordPages?: CustomRecordPage[]
42
43
  isAssigning: Record<string, boolean>
43
44
  setIsAssigning: (isAssigning: Record<string, boolean>) => void
45
+ filtersContainerRef?: (element: HTMLDivElement | null) => void
44
46
  }) => {
45
47
  const { labels } = collection
46
48
  const { path, id } = useParams()
@@ -308,6 +310,7 @@ export const RecordSidebar = ({
308
310
  )}
309
311
  </div>
310
312
  </SidebarMenu>
313
+ <div ref={filtersContainerRef} />
311
314
  </SidebarGroupContent>
312
315
  </SidebarGroup>
313
316
  </SidebarContent>
@@ -0,0 +1,59 @@
1
+ import { Assignable, CollectionSchema, RelationList, StokerRecord } from "@stoker-platform/types"
2
+ import { useMemo } from "react"
3
+ import { createPortal } from "react-dom"
4
+ import { Filters } from "./Filters"
5
+ import { useFilters } from "./providers/FiltersProvider"
6
+ import { Separator } from "./components/ui/separator"
7
+
8
+ export const SidebarFilters = ({
9
+ container,
10
+ collection,
11
+ relationList,
12
+ relationCollection,
13
+ relationParent,
14
+ assignable,
15
+ isAssigning,
16
+ }: {
17
+ container: HTMLElement
18
+ collection: CollectionSchema
19
+ relationList: RelationList
20
+ relationCollection: CollectionSchema
21
+ relationParent: StokerRecord
22
+ assignable?: Assignable
23
+ isAssigning?: boolean
24
+ }) => {
25
+ const { filters } = useFilters()
26
+ const showFilters = relationList.showFilters
27
+
28
+ const excluded = useMemo(
29
+ () =>
30
+ filters
31
+ .filter((filter) => filter.type !== "status" && filter.type !== "range")
32
+ .map((filter) => filter.field)
33
+ .filter((field) => !showFilters?.includes(field)),
34
+ [filters, showFilters],
35
+ )
36
+
37
+ if (!showFilters?.length) return null
38
+ const hasFiltersToShow = filters.some(
39
+ (filter) => filter.type !== "status" && filter.type !== "range" && showFilters.includes(filter.field),
40
+ )
41
+ if (!hasFiltersToShow) return null
42
+
43
+ return createPortal(
44
+ <div className="flex flex-col gap-4 px-2 pt-2">
45
+ <Separator />
46
+ <Filters
47
+ collection={collection}
48
+ excluded={excluded}
49
+ relationList={relationList}
50
+ relationCollection={relationCollection}
51
+ relationParent={relationParent}
52
+ assignable={assignable}
53
+ isAssigning={isAssigning}
54
+ isSidebar={true}
55
+ />
56
+ </div>,
57
+ container,
58
+ )
59
+ }
@@ -135,6 +135,9 @@ export const FiltersProvider: React.FC<FiltersProviderProps> = ({
135
135
  }
136
136
  if (filter.type === "select") {
137
137
  const field = getField(fields, filter.field)
138
+ const includeValueInFilters = assignable?.includeValueInFilters?.find(
139
+ (include) => include.field === filter.field && include.values.includes(filter.value),
140
+ )
138
141
  if (field.type === "Boolean") {
139
142
  const fieldCustomization = getFieldCustomization(field, customization)
140
143
  const label = tryFunction(fieldCustomization.admin?.label) || field.name
@@ -145,13 +148,21 @@ export const FiltersProvider: React.FC<FiltersProviderProps> = ({
145
148
  } else if (field.type === "Number") {
146
149
  constraints.push(where(filter.field, "==", Number(filter.value)))
147
150
  } else if (includesAssigned(relationList, relationParent, assignable, isAssigning, filter)) {
151
+ const filterValues = [filter.value]
152
+ if (includeValueInFilters) {
153
+ filterValues.push(includeValueInFilters.includeValue as string | number)
154
+ }
148
155
  constraints.push(
149
156
  or(
150
- where(filter.field, "==", filter.value),
157
+ where(filter.field, "in", filterValues),
151
158
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
152
159
  where(`${relationList!.field}_Array`, "array-contains", relationParent!.id),
153
160
  ) as unknown as QueryConstraint,
154
161
  )
162
+ } else if (isAssigning && includeValueInFilters) {
163
+ constraints.push(
164
+ where(filter.field, "in", [filter.value, includeValueInFilters.includeValue]),
165
+ )
155
166
  } else {
156
167
  constraints.push(where(filter.field, "==", filter.value))
157
168
  }
@@ -312,6 +323,9 @@ export const FiltersProvider: React.FC<FiltersProviderProps> = ({
312
323
  }
313
324
  if (filter.type === "select") {
314
325
  const field = getField(fields, filter.field)
326
+ const includeValueInFilters = assignable?.includeValueInFilters?.find(
327
+ (include) => include.field === filter.field && include.values.includes(filter.value),
328
+ )
315
329
  if (field.type === "Boolean") {
316
330
  const fieldCustomization = getFieldCustomization(field, customization)
317
331
  const label = tryFunction(fieldCustomization.admin?.label) || field.name
@@ -327,6 +341,8 @@ export const FiltersProvider: React.FC<FiltersProviderProps> = ({
327
341
  (record[filter.field] === filter.value ||
328
342
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
329
343
  record[`${relationList!.field}_Array`]?.includes(relationParent!.id))
344
+ } else if (isAssigning && includeValueInFilters) {
345
+ show = show && [filter.value, includeValueInFilters.includeValue].includes(record[filter.field])
330
346
  } else {
331
347
  show = show && record[filter.field] === filter.value
332
348
  }
@@ -51,5 +51,23 @@ export const getFilterDisjunctions = (collection: CollectionSchema, options?: Fi
51
51
  incrementDisjunctions(2)
52
52
  }
53
53
  }
54
+ if (
55
+ options?.isAssigning &&
56
+ options.relationList &&
57
+ options.relationParent?.id &&
58
+ options.assignable?.includeValueInFilters?.length &&
59
+ options.filters
60
+ ) {
61
+ const activeOrCount = options.filters.filter(
62
+ (filter) =>
63
+ filter.type === "select" &&
64
+ filter.value &&
65
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
66
+ options.assignable!.includeValueInFilters!.some((item) => item.field === filter.field),
67
+ ).length
68
+ for (let i = 0; i < activeOrCount; i++) {
69
+ incrementDisjunctions(2)
70
+ }
71
+ }
54
72
  return disjunctions
55
73
  }