diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuRecordFilterOperandSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuRecordFilterOperandSelect.tsx index 9f27de297c..82b57581ac 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuRecordFilterOperandSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuRecordFilterOperandSelect.tsx @@ -2,7 +2,6 @@ import { AdvancedFilterRecordFilterOperandSelectContent } from '@/object-record/ import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext'; import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { SelectControl } from '@/ui/input/components/SelectControl'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -33,7 +32,7 @@ export const AdvancedFilterCommandMenuRecordFilterOperandSelect = ({ ? getRecordFilterOperands({ filterType, subFieldName: filter?.subFieldName, - }).filter((operand) => operand !== RecordFilterOperand.IS_RELATIVE) + }) : []; if (isDisabled === true) { diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuValueFormInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuValueFormInput.tsx index ec97e88574..2816f58731 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuValueFormInput.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuValueFormInput.tsx @@ -9,6 +9,7 @@ import { configurableViewFilterOperands } from '@/object-record/object-filter-dr import { FormFieldInput } from '@/object-record/record-field/ui/components/FormFieldInput'; import { FormBooleanFieldInput } from '@/object-record/record-field/ui/form-types/components/FormBooleanFieldInput'; 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 { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; import { type FieldMetadata, @@ -16,11 +17,13 @@ import { type FieldSelectMetadata, } from '@/object-record/record-field/ui/types/FieldMetadata'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { stringifyRelativeDateFilter } from '@/views/view-filter-value/utils/stringifyRelativeDateFilter'; import { isObject, isString } from '@sniptt/guards'; import { useContext } from 'react'; import { FieldMetadataType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; +import { isDefined, type RelativeDateFilter } from 'twenty-shared/utils'; import { parseBooleanFromStringValue } from 'twenty-shared/workflow'; import { type JsonValue } from 'type-fest'; @@ -65,6 +68,10 @@ export const AdvancedFilterCommandMenuValueFormInput = ({ } }; + const handleRelativeDateFilterChange = (newValue: RelativeDateFilter) => { + applyObjectFilterDropdownFilterValue(stringifyRelativeDateFilter(newValue)); + }; + const fieldMetadataItemUsedInDropdown = useRecoilComponentValue( fieldMetadataItemUsedInDropdownComponentSelector, ); @@ -93,10 +100,24 @@ export const AdvancedFilterCommandMenuValueFormInput = ({ recordFilter.type === FieldMetadataType.DATE || recordFilter.type === FieldMetadataType.DATE_TIME; + const isRelativeDateFilter = + isFilterableByDateValue && + recordFilter.operand === RecordFilterOperand.IS_RELATIVE; + if (isDisabled || operandHasNoInput) { return null; } + if (isRelativeDateFilter) { + return ( + + ); + } + if (isFilterableByTextValue) { return ( { const objectFilterDropdownCurrentRecordFilter = useRecoilComponentValue( @@ -42,7 +42,8 @@ export const useApplyObjectFilterDropdownOperand = () => { const { getInitialFilterValue } = useGetInitialFilterValue(); - const { userTimezone } = useUserTimezone(); + const { getRelativeDateFilterWithUserTimezone } = + useGetRelativeDateFilterWithUserTimezone(); const applyObjectFilterDropdownOperand = ( newOperand: RecordFilterOperand, @@ -103,16 +104,17 @@ export const useApplyObjectFilterDropdownOperand = () => { recordFilterToUpsert.displayValue = displayValue; } else if (newOperand === RecordFilterOperand.IS_RELATIVE) { - const defaultRelativeDate: RelativeDateFilter = { - ...DEFAULT_RELATIVE_DATE_FILTER_VALUE, - timezone: userTimezone, - }; + const newRelativeDateFilter = getRelativeDateFilterWithUserTimezone( + DEFAULT_RELATIVE_DATE_FILTER_VALUE, + ); - recordFilterToUpsert.value = - stringifyRelativeDateFilter(defaultRelativeDate); + recordFilterToUpsert.value = stringifyRelativeDateFilter( + newRelativeDateFilter, + ); - recordFilterToUpsert.displayValue = - getRelativeDateDisplayValue(defaultRelativeDate); + recordFilterToUpsert.displayValue = getRelativeDateDisplayValue( + newRelativeDateFilter, + ); } else { recordFilterToUpsert.value = ''; recordFilterToUpsert.displayValue = ''; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormRelativeDatePicker.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormRelativeDatePicker.tsx index d054cc4c9f..903979c341 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormRelativeDatePicker.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormRelativeDatePicker.tsx @@ -1,19 +1,19 @@ +import { useGetRelativeDateFilterWithUserTimezone } from '@/object-record/record-filter/hooks/useGetRelativeDateFilterWithUserTimezone'; import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader'; -import { isNonEmptyString, isString } from '@sniptt/guards'; +import { isNonEmptyString } from '@sniptt/guards'; import { useId } from 'react'; import { DEFAULT_RELATIVE_DATE_FILTER_VALUE } from 'twenty-shared/constants'; import { type RelativeDateFilter, - safeParseRelativeDateFilterJSONStringified, + resolveRelativeDateFilterStringified, } from 'twenty-shared/utils'; -import { type JsonValue } from 'type-fest'; export type FormRelativeDatePickerProps = { label?: string; defaultValue?: string; - onChange: (value: JsonValue) => void; + onChange: (value: RelativeDateFilter) => void; readonly?: boolean; }; @@ -24,22 +24,27 @@ export const FormRelativeDatePicker = ({ }: FormRelativeDatePickerProps) => { const instanceId = useId(); - const value = - isString(defaultValue) && isNonEmptyString(defaultValue) - ? safeParseRelativeDateFilterJSONStringified(defaultValue) - : DEFAULT_RELATIVE_DATE_FILTER_VALUE; + const { getRelativeDateFilterWithUserTimezone } = + useGetRelativeDateFilterWithUserTimezone(); + + const valueParsed = isNonEmptyString(defaultValue) + ? resolveRelativeDateFilterStringified(defaultValue) + : DEFAULT_RELATIVE_DATE_FILTER_VALUE; const handleValueChange = (newValue: RelativeDateFilter) => { - onChange(JSON.stringify(newValue)); + const newValueWithTimezone = + getRelativeDateFilterWithUserTimezone(newValue); + + onChange(newValueWithTimezone); }; return ( { + const { userTimezone } = useUserTimezone(); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + + const getRelativeDateFilterWithUserTimezone = ( + relativeDateFilter: RelativeDateFilter, + ): RelativeDateFilter => { + const userDefinedCalendarStartDay = + CalendarStartDay[ + currentWorkspaceMember?.calendarStartDay ?? CalendarStartDay.SYSTEM + ]; + const defaultSystemCalendarStartDay = detectCalendarStartDay(); + + const resolvedCalendarStartDay = ( + userDefinedCalendarStartDay === CalendarStartDay[CalendarStartDay.SYSTEM] + ? defaultSystemCalendarStartDay + : userDefinedCalendarStartDay + ) as FirstDayOfTheWeek; + + return { + ...relativeDateFilter, + timezone: userTimezone, + firstDayOfTheWeek: resolvedCalendarStartDay, + }; + }; + + return { + getRelativeDateFilterWithUserTimezone, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts index 2ea165036e..ae9da12e4e 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts @@ -9,7 +9,7 @@ export type RecordFilter = { id: string; fieldMetadataId: string; value: string; - /** @deprecated We shouldn't implement new features with this field and instead try to create utils to obtain the displayValue of a filter type at runtime */ + /** @deprecated We shouldn't implement new features with this field and instead try to create utils to obtain the displayValue at runtime */ displayValue: string; type: FilterableAndTSVectorFieldType; recordFilterGroupId?: string; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativeDatePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativeDatePickerHeader.tsx index d52648a660..d4330a41d5 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativeDatePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativeDatePickerHeader.tsx @@ -4,6 +4,7 @@ import { RELATIVE_DATE_DIRECTION_SELECT_OPTIONS } from '@/ui/input/components/in import { RELATIVE_DATE_UNITS_SELECT_OPTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateUnitSelectOptions'; import styled from '@emotion/styled'; +import { useState } from 'react'; import { type Nullable } from 'twenty-shared/types'; import { relativeDateFilterSchema, @@ -43,8 +44,10 @@ export const RelativeDatePickerHeader = ({ }: RelativeDatePickerHeaderProps) => { const amountString = amount?.toString() ?? ''; - const textInputValue = direction === 'THIS' ? '' : amountString; - const textInputPlaceholder = direction === 'THIS' ? '-' : 'Number'; + const amountTextValue = direction === 'THIS' ? '' : amountString; + const amountInputPlaceholder = direction === 'THIS' ? '-' : 'Number'; + + const [draftAmountValue, setDraftAmountValue] = useState(amountTextValue); const isUnitPlural = amount && amount > 1 && direction !== 'THIS'; const unitSelectOptions = RELATIVE_DATE_UNITS_SELECT_OPTIONS.map((unit) => ({ @@ -62,6 +65,14 @@ export const RelativeDatePickerHeader = ({ return; } + if (draftAmountValue === '') { + setDraftAmountValue('1'); + } + + if (newDirection === 'THIS') { + setDraftAmountValue(''); + } + onChange?.({ direction: newDirection, amount: amount, @@ -75,9 +86,11 @@ export const RelativeDatePickerHeader = ({ { const amountString = text.replace(/[^0-9]|^0+/g, ''); + setDraftAmountValue(amountString); + const amount = parseInt(amountString); const valueParts = { @@ -90,7 +103,7 @@ export const RelativeDatePickerHeader = ({ onChange?.(valueParts); } }} - placeholder={textInputPlaceholder} + placeholder={amountInputPlaceholder} disabled={direction === 'THIS' || readonly} />