Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

import { toString } from 'lodash'
import { DatePickerSettingValue, type DateValue } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/components/dynamic-type-field-filter-date-component'

export enum DateFilterOperator {
On = 'on',
From = 'from',
To = 'to'
}

export interface DateFilterCondition {
operator: DateFilterOperator
value: string
}

/**
* Transforms a date field-filter value into the concrete date conditions it implies:
* - before → [to]
* - after → [from]
* - between → [from, to]
* - on → [on]
*/
export const transformDateFilter = (value: DateValue): DateFilterCondition[] => {
const { setting, on, from, to } = value

switch (setting) {
case DatePickerSettingValue.BEFORE:
return [{ operator: DateFilterOperator.To, value: toString(to) }]
case DatePickerSettingValue.AFTER:
return [{ operator: DateFilterOperator.From, value: toString(from) }]
case DatePickerSettingValue.BETWEEN:
return [
{ operator: DateFilterOperator.From, value: toString(from) },
{ operator: DateFilterOperator.To, value: toString(to) }
]
default:
return [{ operator: DateFilterOperator.On, value: toString(on) }]
}
}
26 changes: 8 additions & 18 deletions assets/js/src/core/modules/notes-and-events/filters/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,15 @@
* @license Pimcore Open Core License (POCL)
*/

import { toString } from 'lodash'
import { NotesFilterOperator, type OperatorValue } from '@Pimcore/modules/notes-and-events/filters/types'
import { DatePickerSettingValue, type DateValue } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/components/dynamic-type-field-filter-date-component'
import { DateFilterOperator, transformDateFilter } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/utils/date-filter-transform'
import { type DateValue } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/components/dynamic-type-field-filter-date-component'

export const getDateOperatorValues = (value: DateValue): OperatorValue[] => {
const { setting, on, from, to } = value

switch (setting) {
case DatePickerSettingValue.BEFORE:
return [{ operator: NotesFilterOperator.LessThan, value: toString(to) }]
case DatePickerSettingValue.AFTER:
return [{ operator: NotesFilterOperator.GreaterThan, value: toString(from) }]
case DatePickerSettingValue.BETWEEN:
return [
{ operator: NotesFilterOperator.GreaterThan, value: toString(from) },
{ operator: NotesFilterOperator.LessThan, value: toString(to) }
]
default:
return [{ operator: NotesFilterOperator.Equal, value: toString(on) }]
}
const OPERATOR_MAP: Record<DateFilterOperator, NotesFilterOperator> = {
[DateFilterOperator.On]: NotesFilterOperator.Equal,
[DateFilterOperator.From]: NotesFilterOperator.GreaterThan,
[DateFilterOperator.To]: NotesFilterOperator.LessThan
}

export const getDateOperatorValues = (value: DateValue): OperatorValue[] =>
transformDateFilter(value).map(({ operator, value }) => ({ operator: OPERATOR_MAP[operator], value }))
131 changes: 131 additions & 0 deletions assets/js/src/core/modules/notifications/filters/filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

import {
type AnyFilterDescriptor,
type FilterHostAdapter,
createFiltersStore,
defineFilter
} from '@Pimcore/components/filters'
import { useDynamicTypeResolver } from '@Pimcore/modules/element/dynamic-types/resolver/hooks/use-dynamic-type-resolver'
import { DynamicTypeFieldFilterAbstract } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/dynamic-type-field-filter-abstract'
import { DynamicTypeFieldFilterDate } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/types/date/dynamic-type-field-filter-date'
import { type DateValue } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/components/dynamic-type-field-filter-date-component'
import { transformDateFilter } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/utils/date-filter-transform'
import { type FieldFilter } from '@Pimcore/modules/element/listing/decorators/general-filters/context-layer/provider/field-filters/field-filters-provider'
import {
type NotificationColumnFilter,
type NotificationFilterColumn,
type NotificationFilterContext,
type NotificationFilterContribution,
type NotificationFilterQuery
} from '@Pimcore/modules/notifications/filters/types'

export const NOTIFICATION_FILTERABLE_FIELDS: NotificationFilterColumn[] = [
{ key: 'title', translationKey: 'notifications.columns.title', type: 'like', frontendType: 'string' },
{ key: 'creationDate', translationKey: 'notifications.columns.date', type: 'date', frontendType: 'date' }
]

export const {
FiltersStoreProvider: NotificationsAppliedFiltersProvider,
useFiltersStore: useNotificationsAppliedFilters
} = createFiltersStore()

export const {
FiltersStoreProvider: NotificationsDraftFiltersProvider,
useFiltersStore: useNotificationsDraftFilters
} = createFiltersStore()

export const useNotificationsFilterContext = (): NotificationFilterContext => {
const { getType } = useDynamicTypeResolver()

return { columns: NOTIFICATION_FILTERABLE_FIELDS, getType }
}

const prepareFieldFilters = (filters: FieldFilter[], context: NotificationFilterContext): NotificationColumnFilter[] => {
const { columns, getType } = context
const preparedFilters: NotificationColumnFilter[] = []

filters.forEach((filter) => {
const column = columns.find((col) => col.key === filter.key)

if (column === undefined) {
return
}

let type = getType({ target: 'FIELD_FILTER', dynamicTypeIds: [column.type, column.frontendType] })

if (type === null) {
return
}

if (!(type instanceof DynamicTypeFieldFilterAbstract)) {
if ('dynamicTypeFieldFilterType' in type) {
type = type.dynamicTypeFieldFilterType as DynamicTypeFieldFilterAbstract
} else {
return
}
}

const dynamicTypeFieldFilter = type as DynamicTypeFieldFilterAbstract

if (!dynamicTypeFieldFilter.shouldApply(filter)) {
return
}

if (dynamicTypeFieldFilter instanceof DynamicTypeFieldFilterDate) {
transformDateFilter(filter.filterValue as DateValue).forEach((filterValue) => {
preparedFilters.push({ key: column.key, type: column.type, filterValue })
})
} else {
preparedFilters.push({ key: column.key, type: column.type, filterValue: String(filter.filterValue) })
}
})

return preparedFilters
}

const fieldFiltersDescriptor = defineFilter<FieldFilter[], NotificationFilterContribution, NotificationFilterContext>({
key: 'fieldFilters',
defaultValue: [],
section: 'fields',
order: 10,
isEnabled: () => true,
toQuery: (value, context) => {
if (value.length === 0) {
return undefined
}

return { kind: 'columnFilters', filters: prepareFieldFilters(value, context) }
}
})

export const notificationsFilterDescriptors: ReadonlyArray<AnyFilterDescriptor<NotificationFilterContribution, NotificationFilterContext>> = [
fieldFiltersDescriptor
]

export const notificationsFilterAdapter: FilterHostAdapter<NotificationFilterContribution, NotificationFilterContext, NotificationFilterQuery> = {
descriptors: notificationsFilterDescriptors,
useBuildContext: useNotificationsFilterContext,
composeIntoQuery: (contributions, baseQuery) => {
const next: NotificationFilterQuery = { ...baseQuery }
const columnFilters: NotificationColumnFilter[] = []

for (const contribution of contributions) {
columnFilters.push(...contribution.filters)
}

if (columnFilters.length > 0) {
next.columnFilters = columnFilters
}

return next
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDynamicTypeResolver } from '@Pimcore/modules/element/dynamic-types/resolver/hooks/use-dynamic-type-resolver'
import { DynamicTypeFieldFilterAbstract } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/dynamic-type-field-filter-abstract'
import { type FieldFilter } from '@Pimcore/modules/element/listing/decorators/general-filters/context-layer/provider/field-filters/field-filters-provider'
import { type FieldFiltersProps } from '@Pimcore/components/field-filters/field-filters'
import { type ColumnPickerGroup } from '@Pimcore/components/column-picker/column-picker.types'
import { NOTIFICATION_FILTERABLE_FIELDS, useNotificationsDraftFilters } from '@Pimcore/modules/notifications/filters/filters'
import { type NotificationFilterColumn } from '@Pimcore/modules/notifications/filters/types'

export interface UseNotificationsFieldFilterEditorReturn {
filters: FieldFiltersProps['data']
onFilterChange: NonNullable<FieldFiltersProps['onChange']>
columnGroups: Array<ColumnPickerGroup<NotificationFilterColumn>>
handleColumnClick: (column: NotificationFilterColumn) => void
}

export const useNotificationsFieldFilterEditor = (): UseNotificationsFieldFilterEditorReturn => {
const { t } = useTranslation()

const { getType } = useDynamicTypeResolver()
const { values, setValue } = useNotificationsDraftFilters()

const fieldFilters = (values.fieldFilters ?? []) as FieldFilter[]

const initialFilters: FieldFiltersProps['data'] = useMemo(() => {
const _initialFilters: FieldFiltersProps['data'] = []

for (const filter of fieldFilters) {
const column = NOTIFICATION_FILTERABLE_FIELDS.find((col) => col.key === filter.key)

if (column === undefined) {
continue
}

_initialFilters.push({
id: filter.key,
translationKey: filter.meta?.translationKey ?? t(column.translationKey),
data: filter.filterValue,
type: filter.type,
frontendType: column.frontendType,
config: filter.meta ?? {}
})
}

return _initialFilters
}, [fieldFilters, t])

const [filters, setFilters] = useState<FieldFiltersProps['data']>(initialFilters)

useEffect(() => {
setFilters(initialFilters)
}, [initialFilters])

const onFilterChange: UseNotificationsFieldFilterEditorReturn['onFilterChange'] = (data) => {
setFilters(data)
setValue('fieldFilters', data.map((filter) => ({
key: filter.id,
filterValue: filter.data,
type: filter.type,
locale: filter.locale,
meta: {
translationKey: filter.translationKey,
...filter.config ?? {}

Check warning on line 74 in assets/js/src/core/modules/notifications/filters/hooks/use-notifications-field-filter-editor.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The empty object is useless.

See more on https://sonarcloud.io/project/issues?id=pimcore_studio-ui-bundle&issues=AZ8UNhVWEmJQFMF_7VAZ&open=AZ8UNhVWEmJQFMF_7VAZ&pullRequest=3830
}
})))
}

const handleColumnClick = (column: NotificationFilterColumn): void => {
setFilters((prevFilters) => [
...prevFilters,
{
data: undefined,
id: column.key,
translationKey: t(column.translationKey),
type: column.type,
frontendType: column.frontendType,
config: {}
}
])
}

const availableColumns = useMemo(() => NOTIFICATION_FILTERABLE_FIELDS.filter((column) => {
if (filters.some((filter) => filter.id === column.key)) {
return false
}

let dynamicType = getType({ target: 'FIELD_FILTER', dynamicTypeIds: [column.type, column.frontendType] })

if (dynamicType === null) {
return false
}

if (!(dynamicType instanceof DynamicTypeFieldFilterAbstract)) {
if ('dynamicTypeFieldFilterType' in dynamicType) {
dynamicType = dynamicType.dynamicTypeFieldFilterType as DynamicTypeFieldFilterAbstract
} else {
return false
}
}

return (dynamicType as DynamicTypeFieldFilterAbstract).isFilterAvailable(column.frontendType)
}), [filters])

const columnGroups = useMemo<Array<ColumnPickerGroup<NotificationFilterColumn>>>(() => {
if (availableColumns.length === 0) {
return []
}

return [{
key: 'notification-columns',
label: '',
items: availableColumns.map((column) => ({
key: column.key,
label: t(column.translationKey),
meta: column
}))
}]
}, [availableColumns, t])

return { filters, onFilterChange, columnGroups, handleColumnClick }
}
42 changes: 42 additions & 0 deletions assets/js/src/core/modules/notifications/filters/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

import type { UseDynamicTypeResolverReturnType } from '@Pimcore/modules/element/dynamic-types/resolver/hooks/use-dynamic-type-resolver'
import type { DateFilterOperator } from '@Pimcore/modules/element/dynamic-types/definitions/field-filters/utils/date-filter-transform'

export interface NotificationDateFilterValue {
operator: DateFilterOperator
value: string
}

export interface NotificationColumnFilter {
key: string
type: string
filterValue: string | NotificationDateFilterValue
}

export interface NotificationFilterColumn {
key: string
translationKey: string
type: string
frontendType: string
}

export type NotificationFilterContribution =
| { kind: 'columnFilters', filters: NotificationColumnFilter[] }

export interface NotificationFilterContext {
columns: NotificationFilterColumn[]
getType: UseDynamicTypeResolverReturnType['getType']
}

export interface NotificationFilterQuery {
columnFilters?: NotificationColumnFilter[]
}
Loading