Skip to content

Commit 1d4134c

Browse files
[Translations] Add filters (#3839)
* added the search & filter tab * Automatic frontend build --------- Co-authored-by: ValeriaMaltseva <11871778+ValeriaMaltseva@users.noreply.github.com>
1 parent e5d8634 commit 1d4134c

749 files changed

Lines changed: 30945 additions & 70 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* This source file is available under the terms of the
3+
* Pimcore Open Core License (POCL)
4+
* Full copyright and license information is available in
5+
* LICENSE.md which is distributed with this source code.
6+
*
7+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
8+
* @license Pimcore Open Core License (POCL)
9+
*/
10+
11+
import React, { type FC } from 'react'
12+
import {
13+
type AnyFilterDescriptor,
14+
type FilterControlProps,
15+
type FilterHostAdapter,
16+
createFiltersStore,
17+
defineFilter
18+
} from '@Pimcore/components/filters'
19+
import { SearchInput } from '@Pimcore/components/search-input/search-input'
20+
import { useDynamicTypeResolver } from '@Pimcore/modules/element/dynamic-types/resolver/hooks/use-dynamic-type-resolver'
21+
import { DynamicTypeFieldFilterAbstract } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/dynamic-type-field-filter-abstract'
22+
import { type FieldFilter } from '@Pimcore/modules/element/listing/decorators/general-filters/context-layer/provider/field-filters/field-filters-provider'
23+
import { useTranslationFilterColumns } from '@Pimcore/modules/translations/filters/hooks/use-translation-filter-columns'
24+
import {
25+
type TranslationColumnFilter,
26+
type TranslationFilterContext,
27+
type TranslationFilterContribution,
28+
type TranslationFilterQuery
29+
} from '@Pimcore/modules/translations/filters/types'
30+
31+
export const {
32+
FiltersStoreProvider: TranslationsAppliedFiltersProvider,
33+
useFiltersStore: useTranslationsAppliedFilters
34+
} = createFiltersStore()
35+
36+
export const {
37+
FiltersStoreProvider: TranslationsDraftFiltersProvider,
38+
useFiltersStore: useTranslationsDraftFilters
39+
} = createFiltersStore()
40+
41+
export const useTranslationsFilterContext = (): TranslationFilterContext => {
42+
const { getType } = useDynamicTypeResolver()
43+
const columns = useTranslationFilterColumns()
44+
45+
return { columns, getType }
46+
}
47+
48+
const prepareFieldFilters = (filters: FieldFilter[], context: TranslationFilterContext): TranslationColumnFilter[] => {
49+
const { columns, getType } = context
50+
const preparedFilters: TranslationColumnFilter[] = []
51+
52+
filters.forEach((filter) => {
53+
const column = columns.find((col) => col.key === filter.key)
54+
55+
if (column === undefined) {
56+
return
57+
}
58+
59+
let type = getType({ target: 'FIELD_FILTER', dynamicTypeIds: [column.type, column.frontendType] })
60+
61+
if (type === null) {
62+
return
63+
}
64+
65+
if (!(type instanceof DynamicTypeFieldFilterAbstract)) {
66+
if ('dynamicTypeFieldFilterType' in type) {
67+
type = type.dynamicTypeFieldFilterType as DynamicTypeFieldFilterAbstract
68+
} else {
69+
return
70+
}
71+
}
72+
73+
const dynamicTypeFieldFilter = type as DynamicTypeFieldFilterAbstract
74+
75+
if (!dynamicTypeFieldFilter.shouldApply(filter)) {
76+
return
77+
}
78+
79+
preparedFilters.push({ key: column.key, type: column.type, filterValue: String(filter.filterValue) })
80+
})
81+
82+
return preparedFilters
83+
}
84+
85+
const SearchTermControl: FC<FilterControlProps<string>> = ({ value, onChange }) => (
86+
<SearchInput
87+
className='w-full'
88+
data-testid='translations-search-input'
89+
maxWidth={ '100%' }
90+
onChange={ (event) => { onChange(event.target.value) } }
91+
placeholder='Search'
92+
value={ value }
93+
/>
94+
)
95+
96+
const searchTermDescriptor = defineFilter<string, TranslationFilterContribution, TranslationFilterContext>({
97+
key: 'searchTerm',
98+
defaultValue: '',
99+
section: 'search',
100+
order: 0,
101+
isEnabled: () => true,
102+
Control: SearchTermControl,
103+
toQuery: (value) => value !== '' ? { kind: 'columnFilters', filters: [{ type: 'search', filterValue: value }] } : undefined
104+
})
105+
106+
const fieldFiltersDescriptor = defineFilter<FieldFilter[], TranslationFilterContribution, TranslationFilterContext>({
107+
key: 'fieldFilters',
108+
defaultValue: [],
109+
section: 'fields',
110+
order: 10,
111+
isEnabled: () => true,
112+
toQuery: (value, context) => {
113+
if (value.length === 0) {
114+
return undefined
115+
}
116+
117+
return { kind: 'columnFilters', filters: prepareFieldFilters(value, context) }
118+
}
119+
})
120+
121+
export const translationsFilterDescriptors: ReadonlyArray<AnyFilterDescriptor<TranslationFilterContribution, TranslationFilterContext>> = [
122+
searchTermDescriptor,
123+
fieldFiltersDescriptor
124+
]
125+
126+
export const translationsFilterAdapter: FilterHostAdapter<TranslationFilterContribution, TranslationFilterContext, TranslationFilterQuery> = {
127+
descriptors: translationsFilterDescriptors,
128+
useBuildContext: useTranslationsFilterContext,
129+
composeIntoQuery: (contributions, baseQuery) => {
130+
const next: TranslationFilterQuery = { ...baseQuery }
131+
const columnFilters: TranslationColumnFilter[] = []
132+
133+
for (const contribution of contributions) {
134+
columnFilters.push(...contribution.filters)
135+
}
136+
137+
if (columnFilters.length > 0) {
138+
next.columnFilters = columnFilters
139+
}
140+
141+
return next
142+
}
143+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* This source file is available under the terms of the
3+
* Pimcore Open Core License (POCL)
4+
* Full copyright and license information is available in
5+
* LICENSE.md which is distributed with this source code.
6+
*
7+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
8+
* @license Pimcore Open Core License (POCL)
9+
*/
10+
11+
import { useMemo } from 'react'
12+
import { useTranslationGetDomainsQuery } from '@Pimcore/modules/app/translations/translations-api-slice-enhanced'
13+
import { useTranslationDomain } from '@Pimcore/modules/translations/hooks/translation-domain-provider'
14+
import { useWebsiteTranslationLanguages, useAdminTranslationLanguages } from '@Pimcore/modules/translations/hooks/use-translation-languages'
15+
import { type TranslationFilterColumn } from '@Pimcore/modules/translations/filters/types'
16+
17+
export const LANGUAGE_COLUMN_FILTER_TYPE = 'translationLike'
18+
19+
export const useTranslationFilterColumns = (): TranslationFilterColumn[] => {
20+
const { domain } = useTranslationDomain()
21+
const { data: domainsData } = useTranslationGetDomainsQuery()
22+
23+
const isFrontendDomain = (domainsData ?? []).find((d) => d.domain === domain)?.isFrontendDomain ?? false
24+
25+
const websiteLanguages = useWebsiteTranslationLanguages()
26+
const adminLanguages = useAdminTranslationLanguages()
27+
const { languages } = isFrontendDomain ? websiteLanguages : adminLanguages
28+
29+
const viewableLanguages = languages.filter((language) => language.canView)
30+
const signature = viewableLanguages.map((language) => `${language.locale}:${language.displayName}`).join('|')
31+
32+
return useMemo(() => viewableLanguages.map((language) => ({
33+
key: language.locale,
34+
label: language.displayName,
35+
type: LANGUAGE_COLUMN_FILTER_TYPE,
36+
frontendType: 'string'
37+
})),
38+
[signature])
39+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* This source file is available under the terms of the
3+
* Pimcore Open Core License (POCL)
4+
* Full copyright and license information is available in
5+
* LICENSE.md which is distributed with this source code.
6+
*
7+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
8+
* @license Pimcore Open Core License (POCL)
9+
*/
10+
11+
import { useEffect, useMemo, useState } from 'react'
12+
import { useDynamicTypeResolver } from '@Pimcore/modules/element/dynamic-types/resolver/hooks/use-dynamic-type-resolver'
13+
import { DynamicTypeFieldFilterAbstract } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/dynamic-type-field-filter-abstract'
14+
import { type FieldFilter } from '@Pimcore/modules/element/listing/decorators/general-filters/context-layer/provider/field-filters/field-filters-provider'
15+
import { type FieldFiltersProps } from '@Pimcore/components/field-filters/field-filters'
16+
import { type ColumnPickerGroup } from '@Pimcore/components/column-picker/column-picker.types'
17+
import { useTranslationsDraftFilters } from '@Pimcore/modules/translations/filters/filters'
18+
import { useTranslationFilterColumns } from '@Pimcore/modules/translations/filters/hooks/use-translation-filter-columns'
19+
import { type TranslationFilterColumn } from '@Pimcore/modules/translations/filters/types'
20+
21+
export interface UseTranslationsFieldFilterEditorReturn {
22+
filters: FieldFiltersProps['data']
23+
onFilterChange: NonNullable<FieldFiltersProps['onChange']>
24+
columnGroups: Array<ColumnPickerGroup<TranslationFilterColumn>>
25+
handleColumnClick: (column: TranslationFilterColumn) => void
26+
}
27+
28+
export const useTranslationsFieldFilterEditor = (): UseTranslationsFieldFilterEditorReturn => {
29+
const { getType } = useDynamicTypeResolver()
30+
const { values, setValue } = useTranslationsDraftFilters()
31+
const columns = useTranslationFilterColumns()
32+
33+
const fieldFilters = (values.fieldFilters ?? []) as FieldFilter[]
34+
35+
const initialFilters: FieldFiltersProps['data'] = useMemo(() => {
36+
const _initialFilters: FieldFiltersProps['data'] = []
37+
38+
for (const filter of fieldFilters) {
39+
const column = columns.find((col) => col.key === filter.key)
40+
41+
if (column === undefined) {
42+
continue
43+
}
44+
45+
_initialFilters.push({
46+
id: filter.key,
47+
translationKey: filter.meta?.translationKey ?? column.label,
48+
data: filter.filterValue,
49+
type: filter.type,
50+
frontendType: column.frontendType,
51+
config: filter.meta ?? {}
52+
})
53+
}
54+
55+
return _initialFilters
56+
}, [fieldFilters, columns])
57+
58+
const [filters, setFilters] = useState<FieldFiltersProps['data']>(initialFilters)
59+
60+
useEffect(() => {
61+
setFilters(initialFilters)
62+
}, [initialFilters])
63+
64+
const onFilterChange: UseTranslationsFieldFilterEditorReturn['onFilterChange'] = (data) => {
65+
setFilters(data)
66+
setValue('fieldFilters', data.map((filter) => ({
67+
key: filter.id,
68+
filterValue: filter.data,
69+
type: filter.type,
70+
locale: filter.locale,
71+
meta: {
72+
translationKey: filter.translationKey,
73+
...filter.config ?? {}
74+
}
75+
})))
76+
}
77+
78+
const handleColumnClick = (column: TranslationFilterColumn): void => {
79+
setFilters((prevFilters) => [
80+
...prevFilters,
81+
{
82+
data: undefined,
83+
id: column.key,
84+
translationKey: column.label,
85+
type: column.type,
86+
frontendType: column.frontendType,
87+
config: {}
88+
}
89+
])
90+
}
91+
92+
const availableColumns = useMemo(() => columns.filter((column) => {
93+
if (filters.some((filter) => filter.id === column.key)) {
94+
return false
95+
}
96+
97+
let dynamicType = getType({ target: 'FIELD_FILTER', dynamicTypeIds: [column.type, column.frontendType] })
98+
99+
if (dynamicType === null) {
100+
return false
101+
}
102+
103+
if (!(dynamicType instanceof DynamicTypeFieldFilterAbstract)) {
104+
if ('dynamicTypeFieldFilterType' in dynamicType) {
105+
dynamicType = dynamicType.dynamicTypeFieldFilterType as DynamicTypeFieldFilterAbstract
106+
} else {
107+
return false
108+
}
109+
}
110+
111+
return (dynamicType as DynamicTypeFieldFilterAbstract).isFilterAvailable(column.frontendType)
112+
}), [filters, columns, getType])
113+
114+
const columnGroups = useMemo<Array<ColumnPickerGroup<TranslationFilterColumn>>>(() => {
115+
if (availableColumns.length === 0) {
116+
return []
117+
}
118+
119+
return [{
120+
key: 'translation-columns',
121+
label: '',
122+
items: availableColumns.map((column) => ({
123+
key: column.key,
124+
label: column.label,
125+
meta: column
126+
}))
127+
}]
128+
}, [availableColumns])
129+
130+
return { filters, onFilterChange, columnGroups, handleColumnClick }
131+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* This source file is available under the terms of the
3+
* Pimcore Open Core License (POCL)
4+
* Full copyright and license information is available in
5+
* LICENSE.md which is distributed with this source code.
6+
*
7+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
8+
* @license Pimcore Open Core License (POCL)
9+
*/
10+
11+
import type { UseDynamicTypeResolverReturnType } from '@Pimcore/modules/element/dynamic-types/resolver/hooks/use-dynamic-type-resolver'
12+
13+
export interface TranslationColumnFilter {
14+
key?: string
15+
type: string
16+
filterValue: string
17+
}
18+
19+
export interface TranslationFilterColumn {
20+
key: string
21+
label: string
22+
type: string
23+
frontendType: string
24+
}
25+
26+
export type TranslationFilterContribution =
27+
| { kind: 'columnFilters', filters: TranslationColumnFilter[] }
28+
29+
export interface TranslationFilterContext {
30+
columns: TranslationFilterColumn[]
31+
getType: UseDynamicTypeResolverReturnType['getType']
32+
}
33+
34+
export interface TranslationFilterQuery {
35+
columnFilters?: TranslationColumnFilter[]
36+
}

0 commit comments

Comments
 (0)