diff --git a/libs/ui/src/components/Explorer/Explorer.test.tsx b/libs/ui/src/components/Explorer/Explorer.test.tsx
index 225d8302a..37f515cae 100644
--- a/libs/ui/src/components/Explorer/Explorer.test.tsx
+++ b/libs/ui/src/components/Explorer/Explorer.test.tsx
@@ -461,13 +461,11 @@ describe('Explorer', () => {
expect(screen.getByText(record2.whoAmI.label)).toBeInTheDocument();
});
- test('Should hide table header when no data provided', async () => {
+ test('Should display message on empty data', async () => {
spyUseExplorerQuery.mockReturnValueOnce(mockEmptyExplorerQueryResult);
render();
- expect(screen.getByRole('table')).toBeVisible();
- expect(screen.getByRole('row')).toBeVisible();
- expect(within(screen.getByRole('row')).getByText('Aucune donnée')).toBeVisible();
+ expect(screen.getByText(/empty-data/)).toBeVisible();
});
test('Should display the list of records in a table with attributes values', async () => {
diff --git a/libs/ui/src/components/Explorer/Explorer.tsx b/libs/ui/src/components/Explorer/Explorer.tsx
index 3d21d7a35..35b9ea99e 100644
--- a/libs/ui/src/components/Explorer/Explorer.tsx
+++ b/libs/ui/src/components/Explorer/Explorer.tsx
@@ -3,7 +3,7 @@
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
import {ComponentProps, FunctionComponent, useReducer} from 'react';
import {createPortal} from 'react-dom';
-import {KitSpace, KitTypography} from 'aristid-ds';
+import {KitEmpty, KitSpace, KitTypography} from 'aristid-ds';
import styled from 'styled-components';
import {IItemAction, IPrimaryAction} from './_types';
import {useExplorerData} from './_queries/useExplorerData';
@@ -26,6 +26,8 @@ import {
import {useSearchInput} from './useSearchInput';
import {usePagination} from './usePagination';
import {Loading} from '../Loading';
+import {ExplorerFilterBar} from './display-view-filters/ExplorerFilterBar';
+import {useSharedTranslation} from '_ui/hooks/useSharedTranslation';
const isNotEmpty = (union: T): union is Exclude => union.length > 0;
@@ -64,6 +66,8 @@ export const Explorer: FunctionComponent = ({
defaultPrimaryActions = ['create'],
defaultViewSettings
}) => {
+ const {t} = useSharedTranslation();
+
const {panelElement} = useEditSettings();
const [view, dispatch] = useReducer(viewSettingsReducer, {
@@ -79,7 +83,8 @@ export const Explorer: FunctionComponent = ({
attributeIds: view.attributesIds,
fulltextSearch: view.fulltextSearch,
pagination: noPagination ? null : {limit: view.pageSize, offset: view.pageSize * (currentPage - 1)},
- sorts: view.sort
+ sorts: view.sort,
+ filters: view.filters
}); // TODO: refresh when go back on page
const {deactivateAction} = useDeactivateAction({
@@ -128,13 +133,16 @@ export const Explorer: FunctionComponent = ({
{primaryButton}
+
{loading ? (
+ ) : data === null ? (
+
) : (
diff --git a/libs/ui/src/components/Explorer/_queries/useExplorerData.ts b/libs/ui/src/components/Explorer/_queries/useExplorerData.ts
index 74361b1c6..34caadf8c 100644
--- a/libs/ui/src/components/Explorer/_queries/useExplorerData.ts
+++ b/libs/ui/src/components/Explorer/_queries/useExplorerData.ts
@@ -2,8 +2,8 @@
// This file is released under LGPL V3
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
import {localizedTranslation} from '@leav/utils';
-import {IExplorerData} from '../_types';
-import {ExplorerQuery, SortOrder, useExplorerQuery} from '_ui/_gqlTypes';
+import {IExplorerData, IExplorerFilter} from '../_types';
+import {ExplorerQuery, RecordFilterCondition, SortOrder, useExplorerQuery} from '_ui/_gqlTypes';
import {useLang} from '_ui/hooks';
const _mapping = (data: ExplorerQuery, libraryId: string, availableLangs: string[]): IExplorerData => {
@@ -44,7 +44,8 @@ export const useExplorerData = ({
attributeIds,
fulltextSearch,
sorts,
- pagination
+ pagination,
+ filters
}: {
libraryId: string;
attributeIds: string[];
@@ -54,6 +55,7 @@ export const useExplorerData = ({
order: SortOrder;
}>;
pagination: null | {limit: number; offset: number};
+ filters: IExplorerFilter[];
}) => {
const {lang: availableLangs} = useLang();
const {data, loading, refetch} = useExplorerQuery({
@@ -65,12 +67,23 @@ export const useExplorerData = ({
multipleSort: sorts.map(({order, attributeId}) => ({
field: attributeId,
order
- }))
+ })),
+ filters: filters
+ .filter(
+ ({value, condition}) =>
+ value !== null ||
+ [RecordFilterCondition.IS_EMPTY, RecordFilterCondition.IS_NOT_EMPTY].includes(condition)
+ )
+ .map(({field, condition, value}) => ({
+ field,
+ condition,
+ value
+ }))
}
});
return {
- data: data !== undefined ? _mapping(data, libraryId, availableLangs) : null,
+ data: data !== undefined && data.records.list.length > 0 ? _mapping(data, libraryId, availableLangs) : null,
loading,
refetch
};
diff --git a/libs/ui/src/components/Explorer/_types.ts b/libs/ui/src/components/Explorer/_types.ts
index 3f2a4af1f..bfd415062 100644
--- a/libs/ui/src/components/Explorer/_types.ts
+++ b/libs/ui/src/components/Explorer/_types.ts
@@ -2,7 +2,13 @@
// This file is released under LGPL V3
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
import {Override} from '@leav/utils';
-import {AttributePropertiesFragment, PropertyValueFragment, RecordIdentityFragment} from '_ui/_gqlTypes';
+import {
+ AttributeFormat,
+ AttributePropertiesFragment,
+ PropertyValueFragment,
+ RecordFilterCondition,
+ RecordIdentityFragment
+} from '_ui/_gqlTypes';
import {ReactElement} from 'react';
export interface IExplorerData {
@@ -37,3 +43,18 @@ export interface IPrimaryAction {
}
export type ActionHook = {isEnabled: boolean} & T;
+
+export interface IExplorerFilter {
+ id: string;
+ attribute: {
+ format: AttributeFormat;
+ label: string;
+ };
+ field: string;
+ condition: RecordFilterCondition;
+ value: string | null;
+}
+
+export interface IFilterDropDownProps {
+ filter: IExplorerFilter;
+}
diff --git a/libs/ui/src/components/Explorer/display-view-filters/ExplorerFilterBar.tsx b/libs/ui/src/components/Explorer/display-view-filters/ExplorerFilterBar.tsx
new file mode 100644
index 000000000..6f5f9407f
--- /dev/null
+++ b/libs/ui/src/components/Explorer/display-view-filters/ExplorerFilterBar.tsx
@@ -0,0 +1,70 @@
+// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06
+// This file is released under LGPL V3
+// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
+import styled from 'styled-components';
+import {useViewSettingsContext} from '../manage-view-settings/store-view-settings/useViewSettingsContext';
+import {KitButton, KitDivider, KitFilter, KitSpace} from 'aristid-ds';
+import {FunctionComponent} from 'react';
+import {FilterDropDown} from '../manage-view-settings/filter-items/filter-type/FilterDropDown';
+import {FaTrash} from 'react-icons/fa';
+import {useSharedTranslation} from '_ui/hooks/useSharedTranslation';
+
+const FilterStyled = styled(KitFilter)`
+ flex: 0 0 auto;
+`;
+
+const ExplorerFilterBarStyledDiv = styled.div`
+ overflow: auto;
+ padding: 0 calc(var(--general-spacing-xxs) * 1px);
+ padding-bottom: calc(var(--general-spacing-m) * 1px);
+`;
+
+const ExplorerBarItemsListDiv = styled.div`
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ gap: 0;
+ white-space: nowrap;
+ padding-top: calc(var(--general-spacing-xs) * 1px);
+`;
+
+const DividerStyled = styled(KitDivider)`
+ height: 2em;
+`;
+
+export const ExplorerFilterBar: FunctionComponent = () => {
+ const {t} = useSharedTranslation();
+
+ const {
+ view: {filters}
+ } = useViewSettingsContext();
+
+ if (filters.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+ {filters.map(filter => (
+
+ }}
+ />
+ ))}
+
+
+ } disabled>
+ {t('explorer.delete-filters')}
+
+
+
+ );
+};
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/FilterItems.tsx b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/FilterItems.tsx
index 63cbf0aea..0fef4a810 100644
--- a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/FilterItems.tsx
+++ b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/FilterItems.tsx
@@ -6,7 +6,7 @@ import {FaEye, FaEyeSlash, FaSearch} from 'react-icons/fa';
import {KitInput, KitTypography} from 'aristid-ds';
import styled from 'styled-components';
import {useSharedTranslation} from '_ui/hooks/useSharedTranslation';
-import {AttributeType, SortOrder} from '_ui/_gqlTypes';
+import {AttributeFormat, AttributeType, RecordFilterCondition} from '_ui/_gqlTypes';
import {
closestCenter,
DndContext,
@@ -21,7 +21,7 @@ import {useAttributeDetailsData} from '../_shared/useAttributeDetailsData';
import {ViewSettingsActionTypes} from '../store-view-settings/viewSettingsReducer';
import {useViewSettingsContext} from '../store-view-settings/useViewSettingsContext';
import {FilterListItem} from './FilterListItem';
-import {SimpleFilterDropdown} from './filter-type/SimpleFilterDropDown';
+import {FilterDropDown} from './filter-type/FilterDropDown';
const StyledList = styled.ul`
padding: calc(var(--general-spacing-s) * 1px) 0;
@@ -54,27 +54,28 @@ export const FilterItems: FunctionComponent<{libraryId: string}> = ({libraryId})
})
);
- const _toggleColumnVisibility = (attributeId: string) => () => {
- const isAttributeAlreadyFiltering = filters.find(filterItem => filterItem.field === attributeId);
- if (isAttributeAlreadyFiltering) {
- dispatch({
- type: ViewSettingsActionTypes.REMOVE_FILTER,
- payload: {
- id: isAttributeAlreadyFiltering.id
- }
- });
- } else {
- if (canAddFilter) {
- dispatch({
- type: ViewSettingsActionTypes.ADD_FILTER,
- payload: {
- field: attributeId,
- condition: '',
- values: []
- }
- });
+ const addFilter = (attributeId: string) => () => {
+ dispatch({
+ type: ViewSettingsActionTypes.ADD_FILTER,
+ payload: {
+ field: attributeId,
+ attribute: {
+ label: attributeDetailsById[attributeId].label,
+ format: attributeDetailsById[attributeId].format ?? AttributeFormat.text
+ },
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
- }
+ });
+ };
+
+ const removeFilter = (filterId: string) => () => {
+ dispatch({
+ type: ViewSettingsActionTypes.REMOVE_FILTER,
+ payload: {
+ id: filterId
+ }
+ });
};
const _handleDragEnd = ({active: draggedElement, over: dropTarget}: DragEndEvent) => {
@@ -110,22 +111,17 @@ export const FilterItems: FunctionComponent<{libraryId: string}> = ({libraryId})
attributeId={activeFilter.field}
isDraggable
filterChipProps={{
- label: attributeDetailsById[activeFilter.field].label,
+ label: activeFilter.attribute.label,
expandable: true,
- values: activeFilter.values,
+ values: activeFilter.value === null ? [] : [activeFilter.value],
dropDownProps: {
- dropdownRender: () => (
-
- )
+ dropdownRender: () =>
}
}}
visibilityButtonProps={{
icon: ,
title: String(t('explorer.hide')),
- onClick: _toggleColumnVisibility(activeFilter.field)
+ onClick: removeFilter(activeFilter.id)
}}
/>
))}
@@ -154,7 +150,7 @@ export const FilterItems: FunctionComponent<{libraryId: string}> = ({libraryId})
? {
icon: ,
title: String(t('explorer.show')),
- onClick: _toggleColumnVisibility(attributeId)
+ onClick: addFilter(attributeId)
}
: undefined
}
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/FilterDropDown.tsx b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/FilterDropDown.tsx
new file mode 100644
index 000000000..6c97367b7
--- /dev/null
+++ b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/FilterDropDown.tsx
@@ -0,0 +1,21 @@
+// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06
+// This file is released under LGPL V3
+// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
+import {IFilterDropDownProps} from '_ui/components/Explorer/_types';
+import {AttributeFormat} from '_ui/_gqlTypes';
+import {FunctionComponent} from 'react';
+import {SimpleFilterDropdown} from './SimpleFilterDropDown';
+import {TextAttributeDropDown} from './TextAttributeDropDown';
+import {NumericAttributeDropDown} from './NumericAttributeDropDown';
+
+export const FilterDropDown: FunctionComponent = ({filter}) => {
+ switch (filter.attribute.format) {
+ case AttributeFormat.numeric:
+ return ;
+ case AttributeFormat.text:
+ case AttributeFormat.rich_text:
+ return ;
+ default:
+ return ;
+ }
+};
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/FilterValueList.tsx b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/FilterValueList.tsx
new file mode 100644
index 000000000..0f0efcfe0
--- /dev/null
+++ b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/FilterValueList.tsx
@@ -0,0 +1,120 @@
+// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06
+// This file is released under LGPL V3
+// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
+import {useDebouncedValue} from '_ui/hooks/useDebouncedValue';
+import {useSharedTranslation} from '_ui/hooks/useSharedTranslation';
+import {KitInput, KitTypography} from 'aristid-ds';
+import {ChangeEvent, FunctionComponent, useMemo, useState} from 'react';
+import {FaCheck, FaSearch} from 'react-icons/fa';
+import styled from 'styled-components';
+
+interface IFilterValueListProps {
+ values: string[];
+ selectedValues: string[];
+ multiple: boolean;
+ freeEntry: boolean;
+ onSelectionChanged: (value: string[]) => void;
+}
+
+const FilterValueListStyledUl = styled.ul`
+ padding: 0;
+ margin: 0;
+ list-style: none;
+`;
+
+const ValueListItemValueLi = styled.li`
+ display: flex;
+ align-items: center;
+ min-height: 32px;
+ height: 32px;
+ border-radius: calc(var(--general-border-radius-s) * 1px);
+ cursor: pointer;
+
+ &.selected,
+ &:hover {
+ color: var(--components-Icon-colors-icon-on, var(--general-utilities-main-default));
+ background-color: var(--components-Icon-colors-background-on, var(--general-utilities-main-light));
+ }
+
+ .label {
+ flex: 1;
+ padding: 0 calc(var(--general-spacing-xs) * 1px);
+ }
+
+ .check {
+ flex: 0;
+ padding: 0 calc(var(--general-spacing-xs) * 1px);
+ }
+`;
+
+export const FilterValueList: FunctionComponent = ({
+ values,
+ selectedValues,
+ multiple,
+ onSelectionChanged
+}) => {
+ const {t} = useSharedTranslation();
+ const [searchInput, setSearchInput] = useState('');
+ const debouncedSearchInput = useDebouncedValue(searchInput, 300);
+
+ const searchFilteredValues = useMemo(() => {
+ if (debouncedSearchInput === '' || debouncedSearchInput.length < 3) {
+ return values;
+ }
+
+ return values.filter(value => value.includes(debouncedSearchInput));
+ }, [debouncedSearchInput, values]);
+
+ const onSearchChanged = (event: ChangeEvent) => {
+ const shouldIgnoreInputChange = event.target.value.length < 3 && debouncedSearchInput.length < 3;
+ if (shouldIgnoreInputChange) {
+ return;
+ }
+ setSearchInput(() => {
+ if (event.target.value.length > 2) {
+ return event.target.value;
+ }
+ return '';
+ });
+ };
+
+ const onClickItem = (value: string) => {
+ if (selectedValues.includes(value)) {
+ onSelectionChanged(selectedValues.filter(selectedValue => selectedValue !== value));
+ } else {
+ onSelectionChanged(multiple ? [...selectedValues, value] : [value]);
+ }
+ };
+
+ return (
+ <>
+ }
+ />
+
+ {searchFilteredValues.map(value => {
+ const isSelected = selectedValues.includes(value);
+ return (
+ onClickItem(value)}
+ >
+
+ {value}
+
+ {isSelected && (
+
+
+
+ )}
+
+ );
+ })}
+
+ >
+ );
+};
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/NumericAttributeDropDown.tsx b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/NumericAttributeDropDown.tsx
new file mode 100644
index 000000000..691a3ad7b
--- /dev/null
+++ b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/NumericAttributeDropDown.tsx
@@ -0,0 +1,66 @@
+// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06
+// This file is released under LGPL V3
+// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
+import {useSharedTranslation} from '_ui/hooks/useSharedTranslation';
+import {KitButton, KitDivider, KitInput, KitInputNumber, KitSelect, KitSpace} from 'aristid-ds';
+import {ComponentProps, FunctionComponent} from 'react';
+import {FaClock, FaTrash} from 'react-icons/fa';
+import {AttributeConditionFilter} from '_ui/types';
+import {IExplorerFilter, IFilterDropDownProps} from '_ui/components/Explorer/_types';
+import {useViewSettingsContext} from '../../store-view-settings/useViewSettingsContext';
+import {ViewSettingsActionTypes} from '../../store-view-settings/viewSettingsReducer';
+import {useConditionsOptionsByType} from './useConditionOptionsByType';
+
+export const NumericAttributeDropDown: FunctionComponent = ({filter}) => {
+ const {t} = useSharedTranslation();
+ const {dispatch} = useViewSettingsContext();
+
+ const {conditionOptionsByType} = useConditionsOptionsByType(filter);
+
+ const _updateFilter = (filterData: IExplorerFilter) => {
+ dispatch({
+ type: ViewSettingsActionTypes.CHANGE_FILTER_CONFIG,
+ payload: filterData
+ });
+ };
+
+ const _onConditionChanged: ComponentProps['onChange'] = condition =>
+ _updateFilter({...filter, condition});
+
+ const _onInputChanged: ComponentProps['onChange'] = value =>
+ _updateFilter({...filter, value: value === null ? null : String(value)});
+
+ const _onResetFilter: ComponentProps['onClick'] = () => _updateFilter({...filter, value: null});
+
+ const _onDeleteFilter: ComponentProps['onClick'] = () =>
+ dispatch({
+ type: ViewSettingsActionTypes.REMOVE_FILTER,
+ payload: {
+ id: filter.id
+ }
+ });
+
+ const showInput =
+ filter.condition !== AttributeConditionFilter.IS_EMPTY &&
+ filter.condition !== AttributeConditionFilter.IS_NOT_EMPTY;
+
+ return (
+
+
+ {showInput && (
+
+ )}
+
+ } onClick={_onResetFilter}>
+ {t('explorer.reset-filter')}
+
+ } onClick={_onDeleteFilter}>
+ {t('global.delete')}
+
+
+ );
+};
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/SimpleFilterDropDown.tsx b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/SimpleFilterDropDown.tsx
index 6c06c9793..a1b955e33 100644
--- a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/SimpleFilterDropDown.tsx
+++ b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/SimpleFilterDropDown.tsx
@@ -2,12 +2,13 @@
// This file is released under LGPL V3
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
import {useSharedTranslation} from '_ui/hooks/useSharedTranslation';
-import {KitButton, KitDivider, KitInput, KitSelect, KitSpace} from 'aristid-ds';
-import {FunctionComponent} from 'react';
-import {FaCheck, FaClock, FaTrash} from 'react-icons/fa';
+import {KitButton, KitDivider, KitSelect, KitSpace} from 'aristid-ds';
+import {ComponentProps, FunctionComponent} from 'react';
+import {FaClock, FaTrash} from 'react-icons/fa';
import {useViewSettingsContext} from '../../store-view-settings/useViewSettingsContext';
-import {IExplorerFilter, ViewSettingsActionTypes} from '../../store-view-settings/viewSettingsReducer';
-import styled from 'styled-components';
+import {ViewSettingsActionTypes} from '../../store-view-settings/viewSettingsReducer';
+import {IFilterDropDownProps} from '_ui/components/Explorer/_types';
+import {FilterValueList} from './FilterValueList';
// TODO : This is an exemple file showing ho to customize dropdown Panel content. Don't mind the content of the file, missing types,... it's just an example.
@@ -42,36 +43,9 @@ const conditions = [
}
];
-const attributeValuesList = ['Value 1', 'Value 2', 'Value 3', 'Value 4', 'Value 5'];
+const attributeValuesList = ['toto', 'tata', 'Value 3', 'Value 4', 'Value 5'];
-const ValueListItemValueLi = styled.li`
- display: flex;
- align-items: center;
- min-height: 32px;
- height: 32px;
- border-radius: calc(var(--general-border-radius-s) * 1px);
- cursor: pointer;
-
- &.selected {
- color: var(--components-Icon-colors-icon-on, var(--general-utilities-main-default));
- background-color: var(--components-Icon-colors-background-on, var(--general-utilities-main-light));
- }
-
- .label {
- flex: 1;
- padding: 0 calc(var(--general-spacing-xs) * 1px);
- }
-
- .check {
- flex: 0;
- padding: 0 calc(var(--general-spacing-xs) * 1px);
- }
-`;
-
-export const SimpleFilterDropdown: FunctionComponent<{
- filter: IExplorerFilter;
- attribute: {multiple_values: boolean};
-}> = ({filter, attribute}) => {
+export const SimpleFilterDropdown: FunctionComponent = ({filter}) => {
const {t} = useSharedTranslation();
const {dispatch} = useViewSettingsContext();
@@ -80,57 +54,43 @@ export const SimpleFilterDropdown: FunctionComponent<{
type: ViewSettingsActionTypes.CHANGE_FILTER_CONFIG,
payload: {
id: filter.id,
- field: filter.field,
- condition: data.condition,
- values: data.values
+ ...data
}
});
};
- const onconditionChanged = condition => {
- updateFilter({...filter, condition});
+ const onConditionChanged = operator => {
+ updateFilter({...filter, operator});
};
const onValueClick = value => {
- let newValues = [...filter.values];
- if (filter.values.includes(value)) {
- newValues = filter.values.filter(v => v !== value);
- } else {
- if (attribute.multiple_values) {
- newValues = [...filter.values, value];
- } else {
- newValues = [value];
- }
- }
- updateFilter({...filter, values: newValues});
+ updateFilter({...filter, value});
};
+ const _onDeleteFilter: ComponentProps['onClick'] = () =>
+ dispatch({
+ type: ViewSettingsActionTypes.REMOVE_FILTER,
+ payload: {
+ id: filter.id
+ }
+ });
+
return (
-
-
-
- {attributeValuesList.map(value => (
- onValueClick(value)}
- className={filter.values.includes(value) ? 'selected' : ''}
- >
- {value}
- {filter.values.includes(value) && (
-
-
-
- )}
-
- ))}
-
+
+
- }>
- Réinitialiser le filtre
+ } disabled>
+ {t('explorer.reset-filter')}
- }>
- Supprimer
+ } onClick={_onDeleteFilter}>
+ {t('global.delete')}
);
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/TextAttributeDropDown.tsx b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/TextAttributeDropDown.tsx
new file mode 100644
index 000000000..fbf82f285
--- /dev/null
+++ b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/TextAttributeDropDown.tsx
@@ -0,0 +1,72 @@
+// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06
+// This file is released under LGPL V3
+// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
+import {useSharedTranslation} from '_ui/hooks/useSharedTranslation';
+import {KitButton, KitDivider, KitInput, KitSelect, KitSpace} from 'aristid-ds';
+import {ChangeEvent, ComponentProps, FunctionComponent} from 'react';
+import {FaClock, FaTrash} from 'react-icons/fa';
+import {useViewSettingsContext} from '../../store-view-settings/useViewSettingsContext';
+import {ViewSettingsActionTypes} from '../../store-view-settings/viewSettingsReducer';
+import {IExplorerFilter, IFilterDropDownProps} from '_ui/components/Explorer/_types';
+import {useConditionsOptionsByType} from './useConditionOptionsByType';
+import {AttributeConditionFilter} from '_ui/types';
+
+export const TextAttributeDropDown: FunctionComponent = ({filter}) => {
+ const {t} = useSharedTranslation();
+ const {dispatch} = useViewSettingsContext();
+
+ const {conditionOptionsByType} = useConditionsOptionsByType(filter);
+
+ const _updateFilter = (filterData: IExplorerFilter) => {
+ dispatch({
+ type: ViewSettingsActionTypes.CHANGE_FILTER_CONFIG,
+ payload: filterData
+ });
+ };
+
+ const _onConditionChanged: ComponentProps['onChange'] = condition => {
+ _updateFilter({...filter, condition});
+ };
+
+ // TODO debounce ?
+ const _onInputChanged: ComponentProps['onChange'] = event => {
+ const shouldIgnoreInputChange =
+ event.target.value.length < 3 && (filter.value?.length ?? 0) <= event.target.value.length;
+ if (shouldIgnoreInputChange) {
+ return;
+ }
+ _updateFilter({...filter, value: event.target.value.length === 0 ? null : event.target.value});
+ };
+
+ const _onDeleteFilter: ComponentProps['onClick'] = () =>
+ dispatch({
+ type: ViewSettingsActionTypes.REMOVE_FILTER,
+ payload: {
+ id: filter.id
+ }
+ });
+
+ const showSearch =
+ filter.condition !== AttributeConditionFilter.IS_EMPTY &&
+ filter.condition !== AttributeConditionFilter.IS_NOT_EMPTY;
+
+ return (
+
+
+ {showSearch && (
+
+ )}
+
+ } disabled>
+ {t('explorer.reset-filter')}
+
+ } onClick={_onDeleteFilter}>
+ {t('global.delete')}
+
+
+ );
+};
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/useConditionOptionsByType.ts b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/useConditionOptionsByType.ts
new file mode 100644
index 000000000..6af0f0b29
--- /dev/null
+++ b/libs/ui/src/components/Explorer/manage-view-settings/filter-items/filter-type/useConditionOptionsByType.ts
@@ -0,0 +1,96 @@
+// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06
+// This file is released under LGPL V3
+// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
+import {AttributeFormat} from '_ui/_gqlTypes';
+import {IExplorerFilter} from '_ui/components/Explorer/_types';
+import {AttributeConditionFilter, AttributeConditionType} from '_ui/types';
+import {TFunction} from 'i18next';
+import {useSharedTranslation} from '_ui/hooks/useSharedTranslation';
+
+const conditionsByFormat: Record = {
+ [AttributeFormat.text]: [
+ AttributeConditionFilter.CONTAINS,
+ AttributeConditionFilter.NOT_CONTAINS,
+ AttributeConditionFilter.EQUAL,
+ AttributeConditionFilter.NOT_EQUAL,
+ AttributeConditionFilter.BEGIN_WITH,
+ AttributeConditionFilter.END_WITH,
+ AttributeConditionFilter.IS_EMPTY,
+ AttributeConditionFilter.IS_NOT_EMPTY
+ ],
+ [AttributeFormat.rich_text]: [
+ AttributeConditionFilter.CONTAINS,
+ AttributeConditionFilter.NOT_CONTAINS,
+ AttributeConditionFilter.IS_EMPTY,
+ AttributeConditionFilter.IS_NOT_EMPTY
+ ],
+ [AttributeFormat.boolean]: [],
+ [AttributeFormat.color]: [],
+ [AttributeFormat.date]: [],
+ [AttributeFormat.date_range]: [],
+ [AttributeFormat.encrypted]: [],
+ [AttributeFormat.extended]: [],
+ [AttributeFormat.numeric]: [
+ AttributeConditionFilter.EQUAL,
+ AttributeConditionFilter.NOT_EQUAL,
+ AttributeConditionFilter.IS_EMPTY,
+ AttributeConditionFilter.IS_NOT_EMPTY,
+ AttributeConditionFilter.LESS_THAN,
+ AttributeConditionFilter.GREATER_THAN
+ ]
+};
+
+interface IExplorerFilterConditionOption {
+ label: string;
+ value: T;
+ textByFormat?: {[key in AttributeFormat]?: string};
+}
+
+const _getAttributeConditionOptions = (t: TFunction): Array> => [
+ {label: t('filters.contains'), value: AttributeConditionFilter.CONTAINS},
+ {label: t('filters.not-contains'), value: AttributeConditionFilter.NOT_CONTAINS},
+ {label: t('filters.equal'), value: AttributeConditionFilter.EQUAL},
+ {label: t('filters.not-equal'), value: AttributeConditionFilter.NOT_EQUAL},
+ {label: t('filters.begin-with'), value: AttributeConditionFilter.BEGIN_WITH},
+ {label: t('filters.end-with'), value: AttributeConditionFilter.END_WITH},
+ {
+ label: t('filters.less-than'),
+ textByFormat: {[AttributeFormat.date]: String(t('filters.before'))},
+ value: AttributeConditionFilter.LESS_THAN
+ },
+ {
+ label: t('filters.greater-than'),
+ textByFormat: {[AttributeFormat.date]: String(t('filters.after'))},
+ value: AttributeConditionFilter.GREATER_THAN
+ },
+ {label: t('filters.today'), value: AttributeConditionFilter.TODAY},
+ {label: t('filters.tomorrow'), value: AttributeConditionFilter.TOMORROW},
+ {label: t('filters.yesterday'), value: AttributeConditionFilter.YESTERDAY},
+ {label: t('filters.last-month'), value: AttributeConditionFilter.LAST_MONTH},
+ {label: t('filters.next-month'), value: AttributeConditionFilter.NEXT_MONTH},
+ {label: t('filters.between'), value: AttributeConditionFilter.BETWEEN},
+ {label: t('filters.start-on'), value: AttributeConditionFilter.START_ON},
+ {label: t('filters.start-after'), value: AttributeConditionFilter.START_AFTER},
+ {label: t('filters.start-before'), value: AttributeConditionFilter.START_BEFORE},
+ {label: t('filters.end-on'), value: AttributeConditionFilter.END_ON},
+ {label: t('filters.end-after'), value: AttributeConditionFilter.END_AFTER},
+ {label: t('filters.end-before'), value: AttributeConditionFilter.END_BEFORE},
+ {label: t('filters.is-empty'), value: AttributeConditionFilter.IS_EMPTY},
+ {label: t('filters.is-not-empty'), value: AttributeConditionFilter.IS_NOT_EMPTY},
+ {label: t('filters.values-count-equal'), value: AttributeConditionFilter.VALUES_COUNT_EQUAL},
+ {label: t('filters.values-count-greater-than'), value: AttributeConditionFilter.VALUES_COUNT_GREATER_THAN},
+ {label: t('filters.values-count-lower-than'), value: AttributeConditionFilter.VALUES_COUNT_LOWER_THAN},
+ {label: t('filters.through'), value: AttributeConditionFilter.THROUGH}
+];
+
+export const useConditionsOptionsByType = (filter: IExplorerFilter) => {
+ const {t} = useSharedTranslation();
+
+ const attributeConditionOptions = _getAttributeConditionOptions(t);
+
+ return {
+ conditionOptionsByType: attributeConditionOptions.filter(({value}) =>
+ conditionsByFormat[filter.attribute.format].includes(value)
+ )
+ };
+};
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/store-view-settings/viewSettingsReducer.test.ts b/libs/ui/src/components/Explorer/manage-view-settings/store-view-settings/viewSettingsReducer.test.ts
index b5e1b2c8a..63c94b8c2 100644
--- a/libs/ui/src/components/Explorer/manage-view-settings/store-view-settings/viewSettingsReducer.test.ts
+++ b/libs/ui/src/components/Explorer/manage-view-settings/store-view-settings/viewSettingsReducer.test.ts
@@ -3,7 +3,12 @@
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
import {IViewSettingsState, ViewSettingsActionTypes, viewSettingsReducer, ViewType} from './viewSettingsReducer';
import {defaultPageSizeOptions, viewSettingsInitialState} from './viewSettingsInitialState';
-import {SortOrder} from '_ui/_gqlTypes';
+import {AttributeFormat, RecordFilterCondition, SortOrder} from '_ui/_gqlTypes';
+
+const attributeData = {
+ label: 'first',
+ format: AttributeFormat.text
+};
describe('ViewSettings Reducer', () => {
describe(`Action ${ViewSettingsActionTypes.CHANGE_PAGE_SIZE}`, () => {
@@ -275,18 +280,20 @@ describe('ViewSettings Reducer', () => {
filters: [
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]
},
{
type: ViewSettingsActionTypes.ADD_FILTER,
payload: {
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: 'test'
}
}
);
@@ -294,15 +301,17 @@ describe('ViewSettings Reducer', () => {
expect(state.filters).toEqual([
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: expect.any(String),
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: 'test'
}
]);
});
@@ -315,24 +324,27 @@ describe('ViewSettings Reducer', () => {
filters: [
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'second-id',
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: 'test'
}
]
},
{
type: ViewSettingsActionTypes.ADD_FILTER,
payload: {
+ attribute: attributeData,
field: 'third',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
}
);
@@ -340,15 +352,17 @@ describe('ViewSettings Reducer', () => {
expect(state.filters).toEqual([
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'second-id',
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: 'test'
}
]);
});
@@ -363,18 +377,20 @@ describe('ViewSettings Reducer', () => {
filters: [
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]
},
{
type: ViewSettingsActionTypes.ADD_FILTER,
payload: {
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: 'test'
}
}
);
@@ -390,15 +406,17 @@ describe('ViewSettings Reducer', () => {
filters: [
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'second-id',
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: 'test'
}
]
},
@@ -421,21 +439,24 @@ describe('ViewSettings Reducer', () => {
filters: [
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'second-id',
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'third-id',
+ attribute: attributeData,
field: 'third',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]
},
@@ -450,15 +471,17 @@ describe('ViewSettings Reducer', () => {
expect(state.filters).toEqual([
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'third-id',
+ attribute: attributeData,
field: 'third',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]);
});
@@ -470,15 +493,17 @@ describe('ViewSettings Reducer', () => {
filters: [
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'second-id',
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]
},
@@ -486,9 +511,10 @@ describe('ViewSettings Reducer', () => {
type: ViewSettingsActionTypes.CHANGE_FILTER_CONFIG,
payload: {
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'less',
- values: []
+ condition: RecordFilterCondition.LESS_THAN,
+ value: null
}
}
);
@@ -496,15 +522,17 @@ describe('ViewSettings Reducer', () => {
expect(state.filters).toEqual([
{
id: 'id',
+ attribute: attributeData,
field: 'first',
- condition: 'less',
- values: []
+ condition: RecordFilterCondition.LESS_THAN,
+ value: null
},
{
id: 'second-id',
+ attribute: attributeData,
field: 'second',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]);
});
@@ -515,25 +543,25 @@ describe('ViewSettings Reducer', () => {
filters: [
{
id: 'id',
+ attribute: attributeData,
field: 'test',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'active-id',
+ attribute: attributeData,
field: 'active',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'created_at-id',
+ attribute: attributeData,
field: 'created_at',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
- // {order: SortOrder.desc, attributeId: 'test'},
- // {order: SortOrder.asc, attributeId: 'active'},
- // {order: SortOrder.asc, attributeId: 'created_at'}
]
};
@@ -544,21 +572,24 @@ describe('ViewSettings Reducer', () => {
expected: [
{
id: 'active-id',
+ attribute: attributeData,
field: 'active',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'created_at-id',
+ attribute: attributeData,
field: 'created_at',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'id',
+ attribute: attributeData,
field: 'test',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]
},
@@ -568,21 +599,24 @@ describe('ViewSettings Reducer', () => {
expected: [
{
id: 'created_at-id',
+ attribute: attributeData,
field: 'created_at',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'id',
+ attribute: attributeData,
field: 'test',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'active-id',
+ attribute: attributeData,
field: 'active',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]
},
@@ -592,21 +626,24 @@ describe('ViewSettings Reducer', () => {
expected: [
{
id: 'id',
+ attribute: attributeData,
field: 'test',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'created_at-id',
+ attribute: attributeData,
field: 'created_at',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
},
{
id: 'active-id',
+ attribute: attributeData,
field: 'active',
- condition: 'eq',
- values: []
+ condition: RecordFilterCondition.EQUAL,
+ value: null
}
]
},
diff --git a/libs/ui/src/components/Explorer/manage-view-settings/store-view-settings/viewSettingsReducer.ts b/libs/ui/src/components/Explorer/manage-view-settings/store-view-settings/viewSettingsReducer.ts
index e922bbac9..3ce0b3578 100644
--- a/libs/ui/src/components/Explorer/manage-view-settings/store-view-settings/viewSettingsReducer.ts
+++ b/libs/ui/src/components/Explorer/manage-view-settings/store-view-settings/viewSettingsReducer.ts
@@ -1,7 +1,8 @@
// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06
// This file is released under LGPL V3
// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt
-import {SortOrder} from '_ui/_gqlTypes';
+import {AttributeDetailsFragment, SortOrder} from '_ui/_gqlTypes';
+import {IExplorerFilter} from '../../_types';
export type ViewType = 'table' | 'list' | 'timeline' | 'mosaic';
@@ -24,13 +25,6 @@ export const ViewSettingsActionTypes = {
CHANGE_FILTER_CONFIG: 'CHANGE_FILTER_CONFIG'
} as const;
-export interface IExplorerFilter {
- id: string;
- field: string;
- condition: string;
- values: string[];
-}
-
export interface IViewSettingsState {
viewType: ViewType;
attributesIds: string[];
@@ -231,9 +225,7 @@ const removeFilter: Reducer = (state
const changeFilterConfig: Reducer = (state, payload) => ({
...state,
- filters: state.filters.map(filter =>
- filter.id === payload.id ? {...filter, condition: payload.condition, values: payload.values} : filter
- )
+ filters: state.filters.map(filter => (filter.id === payload.id ? {...filter, ...payload} : filter))
});
const moveFilter: Reducer = (state, payload) => {
@@ -259,7 +251,6 @@ export type IViewSettingsAction =
| IViewSettingsActionChangePageSize
| IViewSettingsActionChangeFulltextSearch
| IViewSettingsActionClearFulltextSearch
- | IViewSettingsActionMoveSort
| IViewSettingsActionAddFilter
| IViewSettingsActionRemoveFilter
| IViewSettingsActionChangeFilterConfig
diff --git a/libs/ui/src/locales/en/shared.json b/libs/ui/src/locales/en/shared.json
index 71b32a4d9..d5d769c02 100644
--- a/libs/ui/src/locales/en/shared.json
+++ b/libs/ui/src/locales/en/shared.json
@@ -639,6 +639,9 @@
"active": "Active filters",
"inactive": "Inactive filters"
},
+ "reset-filter": "Reset filter",
+ "delete-filters": "Delete all filters",
+ "empty-data": "No items found.",
"available-attributes": "Other available attributes",
"coming-soon": "Coming soon",
"show": "Show",
@@ -649,6 +652,7 @@
"pagination-total-number": "{{from, number}} - {{to, number}} of {{total, number}} items",
"active-items-number": "{{count, number}} active",
"active-items-number_zero": "None",
- "invalid-value": "Invalid value"
+ "invalid-value": "Invalid value",
+ "type-a-value": "Type a value"
}
}
diff --git a/libs/ui/src/locales/fr/shared.json b/libs/ui/src/locales/fr/shared.json
index 81d8382ce..16f577945 100644
--- a/libs/ui/src/locales/fr/shared.json
+++ b/libs/ui/src/locales/fr/shared.json
@@ -637,6 +637,9 @@
"active": "Filtres actifs",
"inactive": "Filtres inactifs"
},
+ "reset-filter": "Réinitialiser le filtre",
+ "delete-filters": "Supprimer tous les filtres",
+ "empty-data": "Aucun élément trouvé.",
"sort-ascending": "Ascendant",
"sort-descending": "Descendant",
"available-attributes": "Autres propriétés disponibles",
@@ -649,6 +652,7 @@
"invisible-columns": "Colonnes invisibles",
"active-items-number": "{{count, number}} actifs",
"active-items-number_zero": "Aucun",
- "invalid-value": "Valeur invalide"
+ "invalid-value": "Valeur invalide",
+ "type-a-value": "Saisissez une valeur"
}
}