@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 +24 -0
- package/package.json +4 -4
- package/src/Collection.tsx +6 -1
- package/src/Filters.tsx +29 -4
- package/src/Record.tsx +22 -0
- package/src/RecordSidebar.tsx +3 -0
- package/src/SidebarFilters.tsx +59 -0
- package/src/providers/FiltersProvider.tsx +17 -1
- package/src/utils/getFilterDisjunctions.ts +18 -0
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.
|
|
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.
|
|
55
|
-
"@stoker-platform/utils": "0.5.
|
|
56
|
-
"@stoker-platform/web-client": "0.5.
|
|
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",
|
package/src/Collection.tsx
CHANGED
|
@@ -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 =
|
|
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({
|
|
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.
|
|
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.
|
|
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.
|
|
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}
|
package/src/RecordSidebar.tsx
CHANGED
|
@@ -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, "
|
|
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
|
}
|