Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Date filter improvements (#5917) #7196

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5635983
Add new operands, draft relative date picker header
ad-elias Sep 15, 2024
80c608c
Generate query filters for new view filter operands
ad-elias Sep 16, 2024
0953394
Disable relative date picker calendar clicking
ad-elias Sep 16, 2024
fa1cae9
Change date filter value to JSON
ad-elias Sep 17, 2024
74cd021
Resolve variable filter values, pick relative dates
ad-elias Sep 19, 2024
e0d2701
Merge branch 'main' into feat/date-filter-improvements-5917
ad-elias Sep 19, 2024
d2bc804
Temporarily disable all new date operands, fix view filter workspace …
ad-elias Sep 20, 2024
146df05
Past and future date filters work
ad-elias Sep 20, 2024
f5a1b14
Add isToday filter operand to date: and query does not work, otherwis…
ad-elias Sep 20, 2024
7462f63
Fix checkForDeletedAtFilter this binding
ad-elias Sep 20, 2024
09bf125
Simplify filter value resolution
ad-elias Sep 21, 2024
7855ba6
Add 'is' filter operand to date. And query does not work.
ad-elias Sep 21, 2024
031ea27
Rename 'greater than' and 'less than' date filters
ad-elias Sep 21, 2024
5c5b5f0
Enable relative date filtering. And query does not work, date range h…
ad-elias Sep 22, 2024
7e1eafe
Highlight selected relative dates
ad-elias Sep 23, 2024
577f83d
Merge branch 'main' into feat/date-filter-improvements-5917
ad-elias Sep 23, 2024
10f4f87
Remove unused code
ad-elias Sep 23, 2024
3b9c891
Fix broken date selection
ad-elias Sep 23, 2024
3fc73c5
Refactor
ad-elias Sep 23, 2024
b8dc5e6
Remove comments
ad-elias Sep 24, 2024
20d652f
Improve naming
ad-elias Sep 24, 2024
c2dcef5
Default to ViewFilterValueType.STATIC
ad-elias Sep 24, 2024
26d03e7
Improve readability
ad-elias Sep 24, 2024
82c7537
Disable relative date picker header dropdown blur
ad-elias Sep 24, 2024
80adc22
List configurable view filter operands
ad-elias Sep 24, 2024
75f9ddd
Refactor & make 'valueType' required in frontend
ad-elias Sep 24, 2024
6a4789f
Reset value on value type change
ad-elias Sep 25, 2024
0548fc6
Fix number & currency filter operands
ad-elias Sep 26, 2024
c78fdb0
Remove default relative date unit
ad-elias Sep 26, 2024
f915833
Fix date operands test
ad-elias Sep 26, 2024
ef7eba1
Remove filter valueType
ad-elias Sep 27, 2024
36eccfd
Fix date filter default values
ad-elias Sep 27, 2024
9ec65de
Initialize filter value on operand change to immediately show results…
ad-elias Sep 27, 2024
f8d767b
Merge
FelixMalfait Sep 27, 2024
8ad4774
Fix merge
FelixMalfait Sep 27, 2024
34f8e9b
Is this used?
FelixMalfait Sep 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@
"@types/node": "18.19.26",
"@types/passport-google-oauth20": "^2.0.11",
"@types/passport-jwt": "^3.0.8",
"@types/pluralize": "^0.0.33",
"@types/react": "^18.2.39",
"@types/react-datepicker": "^6.2.0",
"@types/react-dom": "^18.2.15",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,20 @@ export const MultipleFiltersDropdownContent = ({
selectedOperandInDropdownState,
);

const isEmptyOperand =
const isConfigurable =
selectedOperandInDropdown &&
[ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty].includes(
selectedOperandInDropdown,
);
[
ViewFilterOperand.Is,
ViewFilterOperand.IsNotNull,
ViewFilterOperand.IsNot,
ViewFilterOperand.LessThan,
ViewFilterOperand.GreaterThan,
ViewFilterOperand.IsBefore,
ViewFilterOperand.IsAfter,
ViewFilterOperand.Contains,
ViewFilterOperand.DoesNotContain,
ViewFilterOperand.IsRelative,
].includes(selectedOperandInDropdown);

return (
<StyledContainer>
Expand All @@ -72,7 +81,7 @@ export const MultipleFiltersDropdownContent = ({
<ObjectFilterDropdownOperandSelect />
</StyledOperandSelectContainer>
)}
{!isEmptyOperand && selectedOperandInDropdown && (
{isConfigurable && selectedOperandInDropdown && (
<>
{[
'TEXT',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@ import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';

import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { computeVariableDateViewFilterValue } from '@/views/utils/view-filter-value/computeVariableDateViewFilterValue';
import {
VariableDateViewFilterValueDirection,
VariableDateViewFilterValueUnit,
} from '@/views/utils/view-filter-value/resolveDateViewFilterValue';
import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue';
import { useState } from 'react';
import { isDefined } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';

export const ObjectFilterDropdownDateInput = () => {
const {
Expand All @@ -23,40 +32,92 @@ export const ObjectFilterDropdownDateInput = () => {
selectedOperandInDropdownState,
);

const selectedFilter = useRecoilValue(selectedFilterState);
const selectedFilter = useRecoilValue(selectedFilterState) as
| (Filter & { definition: { type: 'DATE' | 'DATE_TIME' } })
| null
| undefined;

const initialFilterValue = selectedFilter
? resolveFilterValue(selectedFilter)
: null;
const [internalDate, setInternalDate] = useState<Date | null>(
selectedFilter?.value ? new Date(selectedFilter.value) : new Date(),
initialFilterValue instanceof Date ? initialFilterValue : null,
);

const isDateTimeInput =
filterDefinitionUsedInDropdown?.type === FieldMetadataType.DateTime;

const handleChange = (date: Date | null) => {
setInternalDate(date);
const handleAbsoluteDateChange = (newDate: Date | null) => {
setInternalDate(newDate);

if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;

selectFilter?.({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
value: isDefined(date) ? date.toISOString() : '',
value: newDate?.toISOString() ?? '',
operand: selectedOperandInDropdown,
displayValue: isDefined(date)
displayValue: isDefined(newDate)
? isDateTimeInput
? date.toLocaleString()
: date.toLocaleDateString()
? newDate.toLocaleString()
: newDate.toLocaleDateString()
: '',
definition: filterDefinitionUsedInDropdown,
});

setIsObjectFilterDropdownUnfolded(false);
};

const handleRelativeDateChange = (
relativeDate: {
direction: VariableDateViewFilterValueDirection;
amount?: number;
unit: VariableDateViewFilterValueUnit;
} | null,
) => {
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;

const value = relativeDate
? computeVariableDateViewFilterValue(
relativeDate.direction,
relativeDate.amount,
relativeDate.unit,
)
: '';

selectFilter?.({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
value,
operand: selectedOperandInDropdown,
displayValue: getRelativeDateDisplayValue(relativeDate),
definition: filterDefinitionUsedInDropdown,
});

setIsObjectFilterDropdownUnfolded(false);
};

const isRelativeOperand =
selectedOperandInDropdown === ViewFilterOperand.IsRelative;

const resolvedValue = selectedFilter
? resolveFilterValue(selectedFilter)
: null;

const relativeDate =
resolvedValue && !(resolvedValue instanceof Date)
? resolvedValue
: undefined;

return (
<InternalDatePicker
relativeDate={relativeDate}
highlightedDateRange={relativeDate}
isRelative={isRelativeOperand}
date={internalDate}
onChange={handleChange}
onMouseSelect={handleChange}
onChange={handleAbsoluteDateChange}
onRelativeDateChange={handleRelativeDateChange}
onMouseSelect={handleAbsoluteDateChange}
isDateTimeInput={isDateTimeInput}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';

import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { isDefined } from '~/utils/isDefined';

import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
import { getOperandLabel } from '../utils/getOperandLabel';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';

Expand Down Expand Up @@ -36,22 +36,25 @@ export const ObjectFilterDropdownOperandSelect = () => {
);

const handleOperandChange = (newOperand: ViewFilterOperand) => {
const isEmptyOperand = [
const isValuelessOperand = [
ViewFilterOperand.IsEmpty,
ViewFilterOperand.IsNotEmpty,
ViewFilterOperand.IsInPast,
ViewFilterOperand.IsInFuture,
ViewFilterOperand.IsToday,
].includes(newOperand);

setSelectedOperandInDropdown(newOperand);
setIsObjectFilterDropdownOperandSelectUnfolded(false);

if (isEmptyOperand) {
if (isValuelessOperand && isDefined(filterDefinitionUsedInDropdown)) {
selectFilter?.({
id: v4(),
fieldMetadataId: filterDefinitionUsedInDropdown?.fieldMetadataId ?? '',
displayValue: '',
operand: newOperand,
value: '',
definition: filterDefinitionUsedInDropdown as FilterDefinition,
definition: filterDefinitionUsedInDropdown,
});
return;
}
Expand All @@ -60,12 +63,19 @@ export const ObjectFilterDropdownOperandSelect = () => {
isDefined(filterDefinitionUsedInDropdown) &&
isDefined(selectedFilter)
) {
const { value, displayValue } = getInitialFilterValue(
filterDefinitionUsedInDropdown.type,
newOperand,
selectedFilter.value,
selectedFilter.displayValue,
);

selectFilter?.({
id: selectedFilter.id ? selectedFilter.id : v4(),
fieldMetadataId: selectedFilter.fieldMetadataId,
displayValue: selectedFilter.displayValue,
displayValue,
operand: newOperand,
value: selectedFilter.value,
value,
definition: filterDefinitionUsedInDropdown,
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { v4 } from 'uuid';

type SelectFilterParams = {
filterDefinition: FilterDefinition;
Expand All @@ -13,6 +15,7 @@ export const useSelectFilter = () => {
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setObjectFilterDropdownSearchInput,
selectFilter: filterDropdownSelectFilter,
} = useFilterDropdown();

const setHotkeyScope = useSetHotkeyScope();
Expand All @@ -31,6 +34,22 @@ export const useSelectFilter = () => {
getOperandsForFilterType(filterDefinition.type)?.[0],
);

const { value, displayValue } = getInitialFilterValue(
filterDefinition.type,
getOperandsForFilterType(filterDefinition.type)?.[0],
);

if (value !== '') {
filterDropdownSelectFilter({
id: v4(),
fieldMetadataId: filterDefinition.fieldMetadataId,
displayValue,
operand: getOperandsForFilterType(filterDefinition.type)?.[0],
value,
definition: filterDefinition,
});
}

setObjectFilterDropdownSearchInput('');
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';

import { FilterDefinition } from './FilterDefinition';

export type Filter = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ describe('getOperandsForFilterType', () => {
ViewFilterOperand.LessThan,
];

const dateOperands = [
ViewFilterOperand.Is,
ViewFilterOperand.IsRelative,
ViewFilterOperand.IsInPast,
ViewFilterOperand.IsInFuture,
ViewFilterOperand.IsToday,
ViewFilterOperand.IsBefore,
ViewFilterOperand.IsAfter,
];

const relationOperand = [ViewFilterOperand.Is, ViewFilterOperand.IsNot];

const testCases = [
Expand All @@ -31,7 +41,8 @@ describe('getOperandsForFilterType', () => {
['ACTOR', [...containsOperands, ...emptyOperands]],
['CURRENCY', [...numberOperands, ...emptyOperands]],
['NUMBER', [...numberOperands, ...emptyOperands]],
['DATE_TIME', [...numberOperands, ...emptyOperands]],
['DATE', [...dateOperands, ...emptyOperands]],
['DATE_TIME', [...dateOperands, ...emptyOperands]],
['RELATION', [...relationOperand, ...emptyOperands]],
[undefined, []],
[null, []],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { z } from 'zod';

export const getInitialFilterValue = (
newType: FilterType,
newOperand: ViewFilterOperand,
oldValue?: string,
oldDisplayValue?: string,
): Pick<Filter, 'value' | 'displayValue'> | Record<string, never> => {
switch (newType) {
case 'DATE':
case 'DATE_TIME': {
const activeDatePickerOperands = [
ViewFilterOperand.IsBefore,
ViewFilterOperand.Is,
ViewFilterOperand.IsAfter,
];

if (activeDatePickerOperands.includes(newOperand)) {
const date = z.coerce.date().safeParse(oldValue).data ?? new Date();
const value = date.toISOString();
const displayValue =
newType === 'DATE'
? date.toLocaleString()
: date.toLocaleDateString();

return { value, displayValue };
}

if (newOperand === ViewFilterOperand.IsRelative) {
return { value: '', displayValue: '' };
}
break;
}
}
return {
value: oldValue ?? '',
displayValue: oldDisplayValue ?? '',
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export const getOperandLabel = (
return 'Greater than';
case ViewFilterOperand.LessThan:
return 'Less than';
case ViewFilterOperand.IsBefore:
return 'Is before';
case ViewFilterOperand.IsAfter:
return 'Is after';
case ViewFilterOperand.Is:
return 'Is';
case ViewFilterOperand.IsNot:
Expand All @@ -22,6 +26,14 @@ export const getOperandLabel = (
return 'Is empty';
case ViewFilterOperand.IsNotEmpty:
return 'Is not empty';
case ViewFilterOperand.IsRelative:
return 'Is relative';
case ViewFilterOperand.IsInPast:
return 'Is in past';
case ViewFilterOperand.IsInFuture:
return 'Is in future';
case ViewFilterOperand.IsToday:
return 'Is today';
default:
return '';
}
Expand All @@ -47,6 +59,16 @@ export const getOperandLabelShort = (
return '\u00A0> ';
case ViewFilterOperand.LessThan:
return '\u00A0< ';
case ViewFilterOperand.IsBefore:
return '\u00A0< ';
case ViewFilterOperand.IsAfter:
return '\u00A0> ';
ad-elias marked this conversation as resolved.
Show resolved Hide resolved
case ViewFilterOperand.IsInPast:
return ': Past';
case ViewFilterOperand.IsInFuture:
return ': Future';
case ViewFilterOperand.IsToday:
return ': Today';
default:
return ': ';
}
Expand Down
Loading
Loading