Follow-up high fixes on date refactor (#15553)

This PR fixes important bugs on date filter handling following-up date
refactor.

Fixes : https://github.com/twentyhq/core-team-issues/issues/1814
This commit is contained in:
Lucas Bordeau
2025-11-03 17:51:42 +01:00
committed by GitHub
parent bafc0496b1
commit b33b38cb02
16 changed files with 119 additions and 213 deletions
@@ -8,6 +8,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import styled from '@emotion/styled';
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { useGetRecordFilterDisplayValue } from '@/object-record/record-filter/hooks/useGetRecordFilterDisplayValue';
import { isDefined } from 'twenty-shared/utils';
// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752
@@ -38,6 +39,8 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({
currentRecordFiltersComponentState,
);
const { getRecordFilterDisplayValue } = useGetRecordFilterDisplayValue();
const recordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === recordFilterId,
);
@@ -56,9 +59,11 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({
? getAdvancedFilterInputPlaceholderText(fieldMetadataItem)
: 'Enter filter';
const recordFilterDisplayValue = getRecordFilterDisplayValue(recordFilter);
const advancedFilterInputText = shouldUsePlaceholder
? placeholderText
: (recordFilter?.displayValue ?? '');
: (recordFilterDisplayValue ?? '');
const isDateTimeType =
recordFilter?.type === 'DATE' || recordFilter?.type === 'DATE_TIME';
@@ -6,7 +6,6 @@ import {
type NonSystemCalendarStartDay,
} from '@/localization/utils/detection/detectCalendarStartDay';
import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue';
import { useGetNowInUserTimezoneForRelativeFilter } from '@/object-record/object-filter-dropdown/hooks/useGetNowInUserTimezoneForRelativeFilter';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
@@ -34,9 +33,6 @@ export const ObjectFilterDropdownDateInput = () => {
selectedOperandInDropdownComponentState,
);
const { getNowInUserTimezoneForRelativeFilter } =
useGetNowInUserTimezoneForRelativeFilter();
const objectFilterDropdownCurrentRecordFilter = useRecoilComponentValue(
objectFilterDropdownCurrentRecordFilterComponentState,
);
@@ -62,9 +58,6 @@ export const ObjectFilterDropdownDateInput = () => {
const handleRelativeDateChange = (
relativeDate: RelativeDateFilter | null,
) => {
const { dayAsStringInUserTimezone } =
getNowInUserTimezoneForRelativeFilter();
const userDefinedCalendarStartDay =
CalendarStartDay[
currentWorkspaceMember?.calendarStartDay ?? CalendarStartDay.SYSTEM
@@ -81,7 +74,6 @@ export const ObjectFilterDropdownDateInput = () => {
? stringifyRelativeDateFilter({
...relativeDate,
timezone: timeZone,
referenceDayAsString: dayAsStringInUserTimezone,
firstDayOfTheWeek: resolvedCalendarStartDay,
})
: '';
@@ -6,9 +6,7 @@ import {
type NonSystemCalendarStartDay,
} from '@/localization/utils/detection/detectCalendarStartDay';
import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue';
import { useGetNowInUserTimezoneForRelativeFilter } from '@/object-record/object-filter-dropdown/hooks/useGetNowInUserTimezoneForRelativeFilter';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
import { DateTimePicker } from '@/ui/input/components/internal/date/components/DateTimePicker';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -31,10 +29,6 @@ export const ObjectFilterDropdownDateTimeInput = () => {
const dateLocale = useRecoilValue(dateLocaleState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const selectedOperandInDropdown = useRecoilComponentValue(
selectedOperandInDropdownComponentState,
);
const objectFilterDropdownCurrentRecordFilter = useRecoilComponentValue(
objectFilterDropdownCurrentRecordFilterComponentState,
);
@@ -46,9 +40,6 @@ export const ObjectFilterDropdownDateTimeInput = () => {
? resolveDateTimeFilter(objectFilterDropdownCurrentRecordFilter)
: null;
const { getNowInUserTimezoneForRelativeFilter } =
useGetNowInUserTimezoneForRelativeFilter();
const [internalDate, setInternalDate] = useState<Date | null>(
initialFilterValue instanceof Date ? initialFilterValue : null,
);
@@ -74,9 +65,6 @@ export const ObjectFilterDropdownDateTimeInput = () => {
const handleRelativeDateChange = (
relativeDate: RelativeDateFilter | null,
) => {
const { dayAsStringInUserTimezone } =
getNowInUserTimezoneForRelativeFilter();
const userDefinedCalendarStartDay =
CalendarStartDay[
currentWorkspaceMember?.calendarStartDay ?? CalendarStartDay.SYSTEM
@@ -93,7 +81,6 @@ export const ObjectFilterDropdownDateTimeInput = () => {
? stringifyRelativeDateFilter({
...relativeDate,
timezone: timeZone,
referenceDayAsString: dayAsStringInUserTimezone,
firstDayOfTheWeek: resolvedCalendarStartDay,
})
: '';
@@ -106,7 +93,8 @@ export const ObjectFilterDropdownDateTimeInput = () => {
};
const isRelativeOperand =
selectedOperandInDropdown === ViewFilterOperand.IS_RELATIVE;
objectFilterDropdownCurrentRecordFilter?.operand ===
ViewFilterOperand.IS_RELATIVE;
const handleClear = () => {
isRelativeOperand
@@ -1,6 +1,5 @@
import { DATE_OPERANDS_THAT_SHOULD_BE_INITIALIZED_WITH_NOW } from '@/object-record/object-filter-dropdown/constants/DateOperandsThatShouldBeInitializedWithNow';
import { useGetInitialFilterValue } from '@/object-record/object-filter-dropdown/hooks/useGetInitialFilterValue';
import { useGetNowInUserTimezoneForRelativeFilter } from '@/object-record/object-filter-dropdown/hooks/useGetNowInUserTimezoneForRelativeFilter';
import { useUpsertObjectFilterDropdownCurrentFilter } from '@/object-record/object-filter-dropdown/hooks/useUpsertObjectFilterDropdownCurrentFilter';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
@@ -49,9 +48,6 @@ export const useApplyObjectFilterDropdownOperand = () => {
const { userTimezone } = useUserTimezone();
const { getNowInUserTimezoneForRelativeFilter } =
useGetNowInUserTimezoneForRelativeFilter();
const applyObjectFilterDropdownOperand = (
newOperand: RecordFilterOperand,
) => {
@@ -111,15 +107,11 @@ export const useApplyObjectFilterDropdownOperand = () => {
recordFilterToUpsert.displayValue = displayValue;
} else if (newOperand === RecordFilterOperand.IS_RELATIVE) {
const { dayAsStringInUserTimezone } =
getNowInUserTimezoneForRelativeFilter();
const defaultRelativeDate: RelativeDateFilter = {
direction: 'THIS' as RelativeDateFilterDirection,
amount: 1,
unit: 'DAY' as RelativeDateFilterUnit,
timezone: userTimezone,
referenceDayAsString: dayAsStringInUserTimezone,
};
recordFilterToUpsert.value =
@@ -1,33 +0,0 @@
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
import { TZDate } from '@date-fns/tz';
import { format } from 'date-fns';
import { DATE_TYPE_FORMAT } from 'twenty-shared/constants';
export const useGetNowInUserTimezoneForRelativeFilter = () => {
const { userTimezone } = useUserTimezone();
const getNowInUserTimezoneForRelativeFilter = () => {
const now = new Date();
const nowInUserTimezone = new TZDate(
now.getFullYear(),
now.getMonth(),
now.getDate(),
userTimezone,
);
const dayAsStringInUserTimezone = format(
nowInUserTimezone,
DATE_TYPE_FORMAT,
);
return {
nowInUserTimezone,
dayAsStringInUserTimezone,
};
};
return {
getNowInUserTimezoneForRelativeFilter,
};
};
@@ -0,0 +1,164 @@
import { useGetFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { useGetDateTimeFilterDisplayValue } from '@/object-record/object-filter-dropdown/hooks/useGetDateTimeFilterDisplayValue';
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
import { UserContext } from '@/users/contexts/UserContext';
import { isValid } from 'date-fns';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { type Nullable } from 'twenty-shared/types';
import {
getDateFromPlainDate,
isDefined,
isEmptinessOperand,
parseJson,
relativeDateFilterStringifiedSchema,
shiftPointInTimeFromTimezoneDifferenceInMinutesWithSystemTimezone,
} from 'twenty-shared/utils';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { formatDateString } from '~/utils/string/formatDateString';
// TODO: finish the implementation of this hook to obtain filter display value and remove deprecated display value property
export const useGetRecordFilterDisplayValue = () => {
const { dateFormat, timeZone } = useContext(UserContext);
const dateLocale = useRecoilValue(dateLocaleState);
const { userTimezone } = useUserTimezone();
const { getDateTimeFilterDisplayValue } = useGetDateTimeFilterDisplayValue();
const { getFieldMetadataItemByIdOrThrow } =
useGetFieldMetadataItemByIdOrThrow();
const getRecordFilterDisplayValue = (
recordFilter?: Nullable<RecordFilter>,
) => {
if (!isDefined(recordFilter)) {
return '';
}
const filterType = recordFilter.type;
const operandIsEmptiness = isEmptinessOperand(recordFilter.operand);
const recordFilterIsEmpty = isRecordFilterConsideredEmpty(recordFilter);
if (filterType === 'DATE') {
switch (recordFilter.operand) {
case RecordFilterOperand.IS: {
const date = getDateFromPlainDate(recordFilter.value);
const shiftedDate =
shiftPointInTimeFromTimezoneDifferenceInMinutesWithSystemTimezone(
date,
userTimezone,
'add',
);
if (!isValid(date)) {
return '';
}
const formattedDate = formatDateString({
value: shiftedDate.toISOString(),
timeZone,
dateFormat,
localeCatalog: dateLocale.localeCatalog,
});
return `${formattedDate}`;
}
case RecordFilterOperand.IS_RELATIVE: {
const relativeDateFilter =
relativeDateFilterStringifiedSchema.safeParse(recordFilter.value);
if (!relativeDateFilter.success) {
return ``;
}
const relativeDateDisplayValue = getRelativeDateDisplayValue(
relativeDateFilter.data,
);
return ` ${relativeDateDisplayValue}`;
}
case RecordFilterOperand.IS_TODAY:
case RecordFilterOperand.IS_IN_FUTURE:
case RecordFilterOperand.IS_IN_PAST:
return '';
default:
return ` ${recordFilter.displayValue}`;
}
} else if (recordFilter.type === 'DATE_TIME') {
switch (recordFilter.operand) {
case RecordFilterOperand.IS:
case RecordFilterOperand.IS_AFTER:
case RecordFilterOperand.IS_BEFORE: {
const pointInTime = new Date(recordFilter.value);
const { displayValue } = getDateTimeFilterDisplayValue(pointInTime);
return `${displayValue}`;
}
case RecordFilterOperand.IS_RELATIVE: {
const relativeDateFilter =
relativeDateFilterStringifiedSchema.safeParse(recordFilter.value);
if (!relativeDateFilter.success) {
return ``;
}
const relativeDateDisplayValue = getRelativeDateDisplayValue(
relativeDateFilter.data,
);
return `${relativeDateDisplayValue}`;
}
case RecordFilterOperand.IS_TODAY:
case RecordFilterOperand.IS_IN_FUTURE:
case RecordFilterOperand.IS_IN_PAST:
return '';
default:
return `${recordFilter.displayValue}`;
}
} else if (
recordFilter.type === 'SELECT' ||
recordFilter.type === 'MULTI_SELECT'
) {
const valueArray = parseJson<string[]>(recordFilter.value);
if (!Array.isArray(valueArray)) {
return '';
}
const { fieldMetadataItem } = getFieldMetadataItemByIdOrThrow(
recordFilter.fieldMetadataId,
);
const fieldMetadataItemOptions = fieldMetadataItem.options;
const optionLabels = valueArray.map(
(value) =>
fieldMetadataItemOptions?.find((option) => option.value === value)
?.label,
);
return `${optionLabels.join(', ')}`;
}
if (!operandIsEmptiness && !recordFilterIsEmpty) {
return `${recordFilter.displayValue}`;
}
if (operandIsEmptiness) {
return ``;
}
return recordFilter.displayValue;
};
return {
getRecordFilterDisplayValue,
};
};
@@ -9,6 +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 */
displayValue: string;
type: FilterableAndTSVectorFieldType;
recordFilterGroupId?: string;