Handle relative date step filter (#13930)

https://github.com/user-attachments/assets/2fbb02dd-2170-4807-a1dd-faa3f374bd5a



- move relative date types to twenty-shared
- use and adapt existing relative date picker to be used in workflow
forms
- add backend logic to support relative dates in filters
This commit is contained in:
Thomas Trompette
2025-08-18 14:12:22 +02:00
committed by GitHub
parent 483c1e2214
commit 578a2f4e6b
21 changed files with 1366 additions and 36 deletions
@@ -6,13 +6,13 @@ import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropd
import { DateTimePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue';
import { resolveDateViewFilterValue } from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { useState } from 'react';
import {
resolveDateViewFilterValue,
ViewFilterOperand,
type VariableDateViewFilterValueDirection,
type VariableDateViewFilterValueUnit,
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { useState } from 'react';
import { ViewFilterOperand } from 'twenty-shared/types';
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@@ -14,7 +14,7 @@ import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/ut
import {
type VariableDateViewFilterValueDirection,
type VariableDateViewFilterValueUnit,
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const useApplyObjectFilterDropdownOperand = () => {
@@ -1,8 +1,8 @@
import { plural } from 'pluralize';
import {
type VariableDateViewFilterValueDirection,
type VariableDateViewFilterValueUnit,
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { plural } from 'pluralize';
} from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
export const getRelativeDateDisplayValue = (
@@ -0,0 +1,43 @@
import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader';
import { isNonEmptyString, isString } from '@sniptt/guards';
import {
DEFAULT_RELATIVE_DATE_VALUE,
type VariableDateViewFilterValue,
} from 'twenty-shared/types';
import { safeParseRelativeDateFilterValue } from 'twenty-shared/utils';
import { type JsonValue } from 'type-fest';
export type FormRelativeDatePickerProps = {
label?: string;
defaultValue?: string;
onChange: (value: JsonValue) => void;
readonly?: boolean;
};
export const FormRelativeDatePicker = ({
defaultValue,
onChange,
readonly,
}: FormRelativeDatePickerProps) => {
const value =
isString(defaultValue) && isNonEmptyString(defaultValue)
? safeParseRelativeDateFilterValue(defaultValue)
: DEFAULT_RELATIVE_DATE_VALUE;
const handleValueChange = (newValue: VariableDateViewFilterValue) => {
onChange(JSON.stringify(newValue));
};
return (
<RelativeDatePickerHeader
onChange={handleValueChange}
direction={value?.direction ?? 'THIS'}
unit={value?.unit ?? 'DAY'}
amount={value?.amount}
isFormField={true}
readonly={readonly}
unitDropdownWidth={150}
/>
);
};
@@ -14,14 +14,14 @@ import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/co
import { getHighlightedDates } from '@/ui/input/components/internal/date/utils/getHighlightedDates';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { UserContext } from '@/users/contexts/UserContext';
import {
type VariableDateViewFilterValueDirection,
type VariableDateViewFilterValueUnit,
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import 'react-datepicker/dist/react-datepicker.css';
import { useRecoilValue } from 'recoil';
import {
type VariableDateViewFilterValueDirection,
type VariableDateViewFilterValueUnit,
} from 'twenty-shared/types';
import { IconCalendarX } from 'twenty-ui/display';
import {
MenuItemLeftContent,
@@ -2,20 +2,20 @@ import { Select } from '@/ui/input/components/Select';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { RELATIVE_DATE_DIRECTION_SELECT_OPTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateDirectionSelectOptions';
import { RELATIVE_DATE_UNITS_SELECT_OPTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateUnitSelectOptions';
import { variableDateViewFilterValuePartsSchema } from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import {
type VariableDateViewFilterValueDirection,
type VariableDateViewFilterValueUnit,
variableDateViewFilterValuePartsSchema,
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
} from 'twenty-shared/types';
import styled from '@emotion/styled';
import { useEffect, useState } from 'react';
const StyledContainer = styled.div`
const StyledContainer = styled.div<{ noPadding: boolean }>`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme, noPadding }) => (noPadding ? '0' : theme.spacing(2))};
padding-bottom: 0;
`;
@@ -28,6 +28,9 @@ type RelativeDatePickerHeaderProps = {
amount?: number;
unit: VariableDateViewFilterValueUnit;
}) => void;
isFormField?: boolean;
readonly?: boolean;
unitDropdownWidth?: number;
};
export const RelativeDatePickerHeader = (
@@ -55,7 +58,7 @@ export const RelativeDatePickerHeader = (
}));
return (
<StyledContainer>
<StyledContainer noPadding={props.isFormField ?? false}>
<Select
dropdownId="direction-select"
value={direction}
@@ -70,6 +73,7 @@ export const RelativeDatePickerHeader = (
}}
options={RELATIVE_DATE_DIRECTION_SELECT_OPTIONS}
fullWidth
disabled={props.readonly}
/>
<SettingsTextInput
instanceId="relative-date-picker-amount"
@@ -94,7 +98,7 @@ export const RelativeDatePickerHeader = (
}
}}
placeholder={textInputPlaceholder}
disabled={direction === 'THIS'}
disabled={direction === 'THIS' || props.readonly}
/>
<Select
dropdownId="unit-select"
@@ -108,8 +112,10 @@ export const RelativeDatePickerHeader = (
unit: newUnit,
});
}}
options={unitSelectOptions}
fullWidth
options={unitSelectOptions}
disabled={props.readonly}
dropdownWidth={props.unitDropdownWidth}
/>
</StyledContainer>
);
@@ -1,4 +1,4 @@
import { type VariableDateViewFilterValueDirection } from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { type VariableDateViewFilterValueDirection } from 'twenty-shared/types';
type RelativeDateDirectionOption = {
value: VariableDateViewFilterValueDirection;
@@ -1,4 +1,4 @@
import { type VariableDateViewFilterValueUnit } from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { type VariableDateViewFilterValueUnit } from 'twenty-shared/types';
type RelativeDateUnit = {
value: VariableDateViewFilterValueUnit;
@@ -1,7 +1,7 @@
import {
type VariableDateViewFilterValueDirection,
type VariableDateViewFilterValueUnit,
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
} from 'twenty-shared/types';
export const computeVariableDateViewFilterValue = (
direction: VariableDateViewFilterValueDirection,
@@ -19,7 +19,6 @@ import {
subYears,
} from 'date-fns';
import { ViewFilterOperand } from 'twenty-shared/types';
import { z } from 'zod';
const variableDateViewFilterValueDirectionSchema = z.enum([
@@ -33,6 +33,7 @@ export const WorkflowStepFilterOperandSelect = ({
stepFilterToUpsert: {
...stepFilter,
operand,
value: '',
},
});
};
@@ -1,9 +1,11 @@
import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
import { FormFieldInput } from '@/object-record/record-field/ui/components/FormFieldInput';
import { FormMultiSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormMultiSelectFieldInput';
import { FormRelativeDatePicker } from '@/object-record/record-field/ui/form-types/components/FormRelativeDatePicker';
import { FormSingleRecordPicker } from '@/object-record/record-field/ui/form-types/components/FormSingleRecordPicker';
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { WorkflowStepFilterValueCompositeInput } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueCompositeInput';
import { useGetFilterFieldMetadataItem } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useGetFilterFieldMetadataItem';
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
@@ -12,7 +14,11 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components
import { useLingui } from '@lingui/react/macro';
import { isObject, isString } from '@sniptt/guards';
import { useContext } from 'react';
import { FieldMetadataType, type StepFilter } from 'twenty-shared/types';
import {
FieldMetadataType,
ViewFilterOperand,
type StepFilter,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type JsonValue } from 'type-fest';
@@ -105,6 +111,10 @@ export const WorkflowStepFilterValueInput = ({
selectedFieldMetadataItem?.name === 'id' &&
isDefined(objectMetadataItem?.nameSingular);
const isDateField =
variableType === FieldMetadataType.DATE_TIME ||
variableType === FieldMetadataType.DATE;
if (isFullRecord) {
return (
<FormSingleRecordPicker
@@ -157,6 +167,16 @@ export const WorkflowStepFilterValueInput = ({
);
}
if (isDateField && stepFilter.operand === ViewFilterOperand.IsRelative) {
return (
<FormRelativeDatePicker
defaultValue={stepFilter.value}
onChange={handleValueChange}
readonly={readonly}
/>
);
}
const field = {
type: variableType as FieldMetadataType,
label: '',
@@ -43,15 +43,7 @@ export const FILTER_OPERANDS_MAP = {
ViewFilterOperand.IsToday,
ViewFilterOperand.IsBefore,
ViewFilterOperand.IsAfter,
...emptyOperands,
],
DATE: [
ViewFilterOperand.Is,
ViewFilterOperand.IsInPast,
ViewFilterOperand.IsInFuture,
ViewFilterOperand.IsToday,
ViewFilterOperand.IsBefore,
ViewFilterOperand.IsAfter,
ViewFilterOperand.IsRelative,
...emptyOperands,
],
RATING: [ViewFilterOperand.Is, ViewFilterOperand.IsNot, ...emptyOperands],