From 5635983c56be27471596afa87d2d98a2520daf50 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Sun, 15 Sep 2024 09:32:15 +0200 Subject: [PATCH 01/33] Add new operands, draft relative date picker header --- .../ObjectFilterDropdownDateInput.tsx | 4 + .../utils/getOperandLabel.ts | 8 ++ .../utils/getOperandsForFilterType.ts | 5 + ...turnObjectDropdownFilterIntoQueryFilter.ts | 14 +- .../date/components/DatePickerHeader.tsx | 122 ++++++++++++++++++ .../date/components/InternalDatePicker.tsx | 106 +++++---------- .../date/components/RelativePickerHeader.tsx | 62 +++++++++ .../modules/views/types/ViewFilterOperand.ts | 4 + 8 files changed, 247 insertions(+), 78 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index 20d1dee8389f..e197b5c09be1 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -3,6 +3,7 @@ import { v4 } from 'uuid'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useState } from 'react'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { isDefined } from '~/utils/isDefined'; @@ -51,9 +52,12 @@ export const ObjectFilterDropdownDateInput = () => { setIsObjectFilterDropdownUnfolded(false); }; + const isRelativeOperand = + selectedOperandInDropdown === ViewFilterOperand.IsRelative; return ( & { @@ -366,9 +367,20 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.definition.type, ); break; + case ViewFilterOperand.Is: { + const date = new Date(rawUIFilter.value); + + objectRecordFilters.push({ + [correspondingField.name]: { + lt: endOfDay(date).toISOString(), + gte: startOfDay(date).toISOString(), + } as DateFilter, + }); + break; + } default: throw new Error( - `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, // ); } break; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx new file mode 100644 index 000000000000..94f98b1b0377 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx @@ -0,0 +1,122 @@ +import styled from '@emotion/styled'; +import { DateTime } from 'luxon'; +import { IconChevronLeft, IconChevronRight } from 'twenty-ui'; + +import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; +import { Select } from '@/ui/input/components/Select'; +import { DateTimeInput } from '@/ui/input/components/internal/date/components/DateTimeInput'; + +import { + MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID, + MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID, +} from './InternalDatePicker'; + +const StyledCustomDatePickerHeader = styled.div` + align-items: center; + display: flex; + justify-content: flex-end; + padding-left: ${({ theme }) => theme.spacing(2)}; + padding-right: ${({ theme }) => theme.spacing(2)}; + padding-top: ${({ theme }) => theme.spacing(2)}; + + gap: ${({ theme }) => theme.spacing(1)}; +`; + +const months = [ + { label: 'January', value: 0 }, + { label: 'February', value: 1 }, + { label: 'March', value: 2 }, + { label: 'April', value: 3 }, + { label: 'May', value: 4 }, + { label: 'June', value: 5 }, + { label: 'July', value: 6 }, + { label: 'August', value: 7 }, + { label: 'September', value: 8 }, + { label: 'October', value: 9 }, + { label: 'November', value: 10 }, + { label: 'December', value: 11 }, +]; + +const years = Array.from( + { length: 200 }, + (_, i) => new Date().getFullYear() + 5 - i, +).map((year) => ({ label: year.toString(), value: year })); + +type DatePickerHeaderProps = { + date: Date; + onChange?: (date: Date | null) => void; + onChangeMonth: (month: number) => void; + onChangeYear: (year: number) => void; + onAddMonth: () => void; + onSubtractMonth: () => void; + prevMonthButtonDisabled: boolean; + nextMonthButtonDisabled: boolean; + isDateTimeInput?: boolean; + timeZone: string; +}; + +export const DatePickerHeader = ({ + date, + onChange, + onChangeMonth, + onChangeYear, + onAddMonth, + onSubtractMonth, + prevMonthButtonDisabled, + nextMonthButtonDisabled, + isDateTimeInput, + timeZone, +}: DatePickerHeaderProps) => { + const endOfDayDateTimeInLocalTimezone = DateTime.now().set({ + day: date.getDate(), + month: date.getMonth() + 1, + year: date.getFullYear(), + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + + const endOfDayInLocalTimezone = endOfDayDateTimeInLocalTimezone.toJSDate(); + + return ( + <> + + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index a3004373d059..22d35ffbf2da 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -2,45 +2,20 @@ import styled from '@emotion/styled'; import { DateTime } from 'luxon'; import ReactDatePicker from 'react-datepicker'; import { Key } from 'ts-key-enum'; -import { - IconCalendarX, - IconChevronLeft, - IconChevronRight, - OVERLAY_BACKGROUND, -} from 'twenty-ui'; - -import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; +import { IconCalendarX, OVERLAY_BACKGROUND } from 'twenty-ui'; + import { DateTimeInput } from '@/ui/input/components/internal/date/components/DateTimeInput'; -import { Select } from '@/ui/input/components/Select'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItemLeftContent } from '@/ui/navigation/menu-item/internals/components/MenuItemLeftContent'; import { StyledHoverableMenuItemBase } from '@/ui/navigation/menu-item/internals/components/StyledMenuItemBase'; import { isDefined } from '~/utils/isDefined'; +import { DatePickerHeader } from '@/ui/input/components/internal/date/components/DatePickerHeader'; +import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativePickerHeader'; import { UserContext } from '@/users/contexts/UserContext'; import { useContext } from 'react'; import 'react-datepicker/dist/react-datepicker.css'; -const months = [ - { label: 'January', value: 0 }, - { label: 'February', value: 1 }, - { label: 'March', value: 2 }, - { label: 'April', value: 3 }, - { label: 'May', value: 4 }, - { label: 'June', value: 5 }, - { label: 'July', value: 6 }, - { label: 'August', value: 7 }, - { label: 'September', value: 8 }, - { label: 'October', value: 9 }, - { label: 'November', value: 10 }, - { label: 'December', value: 11 }, -]; - -const years = Array.from( - { length: 200 }, - (_, i) => new Date().getFullYear() + 5 - i, -).map((year) => ({ label: year.toString(), value: year })); - export const MONTH_AND_YEAR_DROPDOWN_ID = 'date-picker-month-and-year-dropdown'; export const MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID = 'date-picker-month-and-year-dropdown-month-select'; @@ -288,18 +263,8 @@ const StyledButton = styled(MenuItemLeftContent)` justify-content: start; `; -const StyledCustomDatePickerHeader = styled.div` - align-items: center; - display: flex; - justify-content: flex-end; - padding-left: ${({ theme }) => theme.spacing(2)}; - padding-right: ${({ theme }) => theme.spacing(2)}; - padding-top: ${({ theme }) => theme.spacing(2)}; - - gap: ${({ theme }) => theme.spacing(1)}; -`; - type InternalDatePickerProps = { + isRelativeToNow?: boolean; date: Date | null; onMouseSelect?: (date: Date | null) => void; onChange?: (date: Date | null) => void; @@ -321,6 +286,7 @@ export const InternalDatePicker = ({ isDateTimeInput, keyboardEventsDisabled, onClear, + isRelativeToNow, }: InternalDatePickerProps) => { const internalDate = date ?? new Date(); @@ -489,46 +455,32 @@ export const InternalDatePicker = ({ renderCustomHeader={({ prevMonthButtonDisabled, nextMonthButtonDisabled, - }) => ( - <> - + isRelativeToNow ? ( + {}} + onValueChange={() => {}} + onUnitChange={() => {}} + onChange={() => {}} + /> + ) : ( + - - - - - - - )} + ) + } onSelect={handleDateSelect} /> diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx new file mode 100644 index 000000000000..3376a187171a --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx @@ -0,0 +1,62 @@ +import { Select } from '@/ui/input/components/Select'; +import { TextInput } from '@/ui/input/components/TextInput'; + +import styled from '@emotion/styled'; + +const StyledContainer = styled.div` + display: flex; + align-items: center; + gap: ${({ theme }) => theme.spacing(1)}; + padding: ${({ theme }) => theme.spacing(2)}; + padding-bottom: 0; +`; + +type RelativeDatePickerHeaderProps = { + direction: string; + value: string; + unit: string; + onDirectionChange: (value: string) => void; + onValueChange: (value: string) => void; + onUnitChange: (value: string) => void; + onChange: (value: { direction: string; value: string; unit: string }) => void; +}; + +export const RelativeDatePickerHeader = ({ + direction, + value, + unit, + onDirectionChange, + onValueChange, + onUnitChange, +}: RelativeDatePickerHeaderProps) => { + return ( + + + + ); +}; diff --git a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts index 025d0085d49d..f9a41a545337 100644 --- a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts +++ b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts @@ -8,4 +8,8 @@ export enum ViewFilterOperand { DoesNotContain = 'doesNotContain', IsEmpty = 'isEmpty', IsNotEmpty = 'isNotEmpty', + IsRelative = 'isRelative', + IsInPast = 'isInPast', + IsInFuture = 'isInFuture', + IsToday = 'isToday', } From 80c608cf7e1bd385851a5cd92be7a9ef68ae2018 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Mon, 16 Sep 2024 16:52:35 +0200 Subject: [PATCH 02/33] Generate query filters for new view filter operands --- ...turnObjectDropdownFilterIntoQueryFilter.ts | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 91eef2dc936f..3168859cee38 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -26,6 +26,7 @@ import { convertRatingToRatingValue, } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; import { endOfDay, startOfDay } from 'date-fns'; +import { format } from 'date-fns-tz'; import { Filter } from '../../object-filter-dropdown/types/Filter'; export type ObjectDropdownFilter = Omit & { @@ -367,17 +368,57 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.definition.type, ); break; + case ViewFilterOperand.IsRelative: { + const date = new Date(rawUIFilter.value); + const DATE_FORMAT_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + const now = new Date(); + const start = format( + startOfDay(Math.min(+date, +now)), + DATE_FORMAT_WITHOUT_TZ, + ); + const end = format( + endOfDay(Math.max(+date, +now)), + DATE_FORMAT_WITHOUT_TZ, + ); + + // Does not work + objectRecordFilters.push({ + [correspondingField.name]: { + gte: start, + lte: end, + } as DateFilter, + }); + break; + } case ViewFilterOperand.Is: { + const DATE_FORMAT_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS"; const date = new Date(rawUIFilter.value); + const start = format(startOfDay(date), DATE_FORMAT_WITHOUT_TZ); + const end = format(endOfDay(date), DATE_FORMAT_WITHOUT_TZ); + // Does not work objectRecordFilters.push({ [correspondingField.name]: { - lt: endOfDay(date).toISOString(), - gte: startOfDay(date).toISOString(), + gte: start, + lte: end, } as DateFilter, }); break; } + case ViewFilterOperand.IsInPast: + objectRecordFilters.push({ + [correspondingField.name]: { + lt: new Date().toISOString(), + } as DateFilter, + }); + break; + case ViewFilterOperand.IsInFuture: + objectRecordFilters.push({ + [correspondingField.name]: { + gt: new Date().toISOString(), + } as DateFilter, + }); + break; default: throw new Error( `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, // From 0953394c538f64474e41ebe91c7db97477e183ec Mon Sep 17 00:00:00 2001 From: ad-elias Date: Mon, 16 Sep 2024 20:51:18 +0200 Subject: [PATCH 03/33] Disable relative date picker calendar clicking --- .../date/components/InternalDatePicker.tsx | 23 ++++--- .../date/components/RelativePickerHeader.tsx | 67 ++++++++++++------- .../constants/RelativeDateDirectionOptions.ts | 10 +++ .../date/constants/RelativeDateUnits.ts | 9 +++ .../date/types/RelativeDateDirection.ts | 5 ++ .../internal/date/types/RelativeDateUnit.ts | 6 ++ 6 files changed, 86 insertions(+), 34 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnits.ts create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateDirection.ts create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateUnit.ts diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index 22d35ffbf2da..9aa1ecca7a55 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -12,6 +12,8 @@ import { isDefined } from '~/utils/isDefined'; import { DatePickerHeader } from '@/ui/input/components/internal/date/components/DatePickerHeader'; import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativePickerHeader'; +import { RelativeDateDirection } from '@/ui/input/components/internal/date/types/RelativeDateDirection'; +import { RelativeDateUnit } from '@/ui/input/components/internal/date/types/RelativeDateUnit'; import { UserContext } from '@/users/contexts/UserContext'; import { useContext } from 'react'; import 'react-datepicker/dist/react-datepicker.css'; @@ -22,7 +24,7 @@ export const MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID = export const MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID = 'date-picker-month-and-year-dropdown-year-select'; -const StyledContainer = styled.div` +const StyledContainer = styled.div<{ calendarDisabled?: boolean }>` & .react-datepicker { border-color: ${({ theme }) => theme.border.color.light}; background: transparent; @@ -182,6 +184,10 @@ const StyledContainer = styled.div` & .react-datepicker__month { margin-top: 0; + + pointer-events: ${({ calendarDisabled }) => + calendarDisabled ? 'none' : 'auto'}; + opacity: ${({ calendarDisabled }) => (calendarDisabled ? '0.5' : '1')}; } & .react-datepicker__day { @@ -434,9 +440,11 @@ export const InternalDatePicker = ({ const endOfDayInLocalTimezone = endOfDayDateTimeInLocalTimezone.toJSDate(); const dateToUse = isDateTimeInput ? endOfDayInLocalTimezone : dateWithoutTime; - return ( - +
isRelativeToNow ? ( {}} - onValueChange={() => {}} - onUnitChange={() => {}} + direction={RelativeDateDirection.Past} + value={1} + unit={RelativeDateUnit.Day} onChange={() => {}} /> ) : ( diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx index 3376a187171a..f567d34133fe 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx @@ -1,6 +1,10 @@ import { Select } from '@/ui/input/components/Select'; import { TextInput } from '@/ui/input/components/TextInput'; +import { RelativeDateUnit } from '../types/RelativeDateUnit'; +import { RELATIVE_DATE_DIRECTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateDirectionOptions'; +import { RELATIVE_DATE_UNITS } from '@/ui/input/components/internal/date/constants/RelativeDateUnits'; +import { RelativeDateDirection } from '@/ui/input/components/internal/date/types/RelativeDateDirection'; import styled from '@emotion/styled'; const StyledContainer = styled.div` @@ -12,50 +16,63 @@ const StyledContainer = styled.div` `; type RelativeDatePickerHeaderProps = { - direction: string; - value: string; - unit: string; - onDirectionChange: (value: string) => void; - onValueChange: (value: string) => void; - onUnitChange: (value: string) => void; - onChange: (value: { direction: string; value: string; unit: string }) => void; + direction?: RelativeDateDirection; + value?: number; + unit?: RelativeDateUnit; + onChange: (value: { + direction?: RelativeDateDirection; + value?: number; + unit?: RelativeDateUnit; + }) => void; }; export const RelativeDatePickerHeader = ({ direction, value, unit, - onDirectionChange, - onValueChange, - onUnitChange, + onChange, }: RelativeDatePickerHeaderProps) => { return ( + onChange({ + direction: direction, + value: value, + unit: newUnit, + }) + } + options={RELATIVE_DATE_UNITS} /> ); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts new file mode 100644 index 000000000000..93eb2a413289 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts @@ -0,0 +1,10 @@ +import { RelativeDateDirection } from '../types/RelativeDateDirection'; + +export const RELATIVE_DATE_DIRECTIONS: { + value: RelativeDateDirection; + label: string; +}[] = [ + { value: RelativeDateDirection.Past, label: 'Past' }, + { value: RelativeDateDirection.Next, label: 'Next' }, + { value: RelativeDateDirection.This, label: 'This' }, +]; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnits.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnits.ts new file mode 100644 index 000000000000..54b4ab1615d5 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnits.ts @@ -0,0 +1,9 @@ +import { RelativeDateUnit } from '../types/RelativeDateUnit'; + +export const RELATIVE_DATE_UNITS: { value: RelativeDateUnit; label: string }[] = + [ + { value: RelativeDateUnit.Day, label: 'Day' }, + { value: RelativeDateUnit.Week, label: 'Week' }, + { value: RelativeDateUnit.Month, label: 'Month' }, + { value: RelativeDateUnit.Year, label: 'Year' }, + ]; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateDirection.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateDirection.ts new file mode 100644 index 000000000000..068bb21c04ac --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateDirection.ts @@ -0,0 +1,5 @@ +export enum RelativeDateDirection { + Past = 'past', + Next = 'next', + This = 'this', +} diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateUnit.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateUnit.ts new file mode 100644 index 000000000000..43d8e0976328 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateUnit.ts @@ -0,0 +1,6 @@ +export enum RelativeDateUnit { + Day = 'day', + Week = 'week', + Month = 'month', + Year = 'year', +} From fa1cae914fc4b0e2631215bc103e2ed453cfa7e1 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Tue, 17 Sep 2024 16:48:45 +0200 Subject: [PATCH 04/33] Change date filter value to JSON --- .../MultipleFiltersDropdownContent.tsx | 14 +++-- .../ObjectFilterDropdownDateInput.tsx | 42 ++++++++++---- .../types/DateFilterValue.ts | 16 ++++++ .../utils/getDateFromDateFilterValue.ts | 56 +++++++++++++++++++ ...turnObjectDropdownFilterIntoQueryFilter.ts | 53 +++++++++++++----- ...eader.tsx => AbsoluteDatePickerHeader.tsx} | 6 +- .../date/components/InternalDatePicker.tsx | 13 +++-- .../date/components/RelativePickerHeader.tsx | 37 ++++++------ 8 files changed, 181 insertions(+), 56 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/DateFilterValue.ts create mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue.ts rename packages/twenty-front/src/modules/ui/input/components/internal/date/components/{DatePickerHeader.tsx => AbsoluteDatePickerHeader.tsx} (96%) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx index a735dc7fe69d..1a3d08790c0d 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx @@ -38,11 +38,15 @@ export const MultipleFiltersDropdownContent = ({ const selectedOperandInDropdown = useRecoilValue( selectedOperandInDropdownState, ); - const isEmptyOperand = + const isNotConfigurable = selectedOperandInDropdown && - [ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty].includes( - selectedOperandInDropdown, - ); + [ + ViewFilterOperand.IsEmpty, + ViewFilterOperand.IsNotEmpty, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsToday, + ].includes(selectedOperandInDropdown); return ( <> @@ -50,7 +54,7 @@ export const MultipleFiltersDropdownContent = ({ ) : isObjectFilterDropdownOperandSelectUnfolded ? ( - ) : isEmptyOperand ? ( + ) : isNotConfigurable ? ( ) : ( selectedOperandInDropdown && ( diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index e197b5c09be1..f1a43d40bfb8 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -2,11 +2,12 @@ import { useRecoilValue } from 'recoil'; import { v4 } from 'uuid'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; +import { DateFilterValue } from '@/object-record/object-filter-dropdown/types/DateFilterValue'; import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useState } from 'react'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { isDefined } from '~/utils/isDefined'; +import { getDateFromDateFilterValue } from '../utils/getDateFromDateFilterValue'; export const ObjectFilterDropdownDateInput = () => { const { @@ -25,27 +26,37 @@ export const ObjectFilterDropdownDateInput = () => { ); const selectedFilter = useRecoilValue(selectedFilterState); + const dateFilterValue = selectedFilter?.value + ? JSON.parse(selectedFilter.value) + : null; + const initialInternalDate = dateFilterValue + ? getDateFromDateFilterValue(dateFilterValue) + : new Date(); const [internalDate, setInternalDate] = useState( - selectedFilter?.value ? new Date(selectedFilter.value) : new Date(), + initialInternalDate, ); const isDateTimeInput = filterDefinitionUsedInDropdown?.type === FieldMetadataType.DateTime; - const handleChange = (date: Date | null) => { - setInternalDate(date); + const handleChange = (dateFilterValue: DateFilterValue | null) => { + const newDate = dateFilterValue + ? getDateFromDateFilterValue(dateFilterValue) + : null; + + setInternalDate(newDate); if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return; selectFilter?.({ id: selectedFilter?.id ? selectedFilter.id : v4(), fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, - value: isDefined(date) ? date.toISOString() : '', + value: JSON.stringify(dateFilterValue), operand: selectedOperandInDropdown, - displayValue: isDefined(date) + displayValue: newDate ? isDateTimeInput - ? date.toLocaleString() - : date.toLocaleDateString() + ? newDate.toLocaleString() + : newDate.toLocaleDateString() : '', definition: filterDefinitionUsedInDropdown, }); @@ -59,8 +70,19 @@ export const ObjectFilterDropdownDateInput = () => { { + const dateFilterValue: DateFilterValue | null = date + ? { type: 'absolute', isoString: date.toISOString() } + : null; + handleChange(dateFilterValue); + }} + onRelativeDateChange={handleChange} + onMouseSelect={(date) => { + const dateFilterValue: DateFilterValue | null = date + ? { type: 'absolute', isoString: date.toISOString() } + : null; + handleChange(dateFilterValue); + }} isDateTimeInput={isDateTimeInput} /> ); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/DateFilterValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/DateFilterValue.ts new file mode 100644 index 000000000000..97fbdd4e04f1 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/DateFilterValue.ts @@ -0,0 +1,16 @@ +import { RelativeDateDirection } from '@/ui/input/components/internal/date/types/RelativeDateDirection'; +import { RelativeDateUnit } from '@/ui/input/components/internal/date/types/RelativeDateUnit'; + +export interface RelativeDateFilterValue { + type: 'relative'; + direction: RelativeDateDirection; + unit: RelativeDateUnit; + amount: number; +} + +export interface AbsoluteDateFilterValue { + type: 'absolute'; + isoString: string; +} + +export type DateFilterValue = RelativeDateFilterValue | AbsoluteDateFilterValue; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue.ts new file mode 100644 index 000000000000..c8dc3a7bce31 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue.ts @@ -0,0 +1,56 @@ +import { + addDays, + addMonths, + addWeeks, + addYears, + subDays, + subMonths, + subWeeks, + subYears, +} from 'date-fns'; +import { DateFilterValue } from '../types/DateFilterValue'; + +const getRelativeDate = (dateFilterValue: DateFilterValue): Date => { + if (dateFilterValue.type !== 'relative') { + throw new Error('Date filter value is not relative'); + } + + const now = new Date(); + switch (dateFilterValue.unit) { + case 'day': + return dateFilterValue.direction === 'past' + ? subDays(now, dateFilterValue.amount) + : addDays(now, dateFilterValue.amount); + case 'week': + return dateFilterValue.direction === 'past' + ? subWeeks(now, dateFilterValue.amount) + : addWeeks(now, dateFilterValue.amount); + case 'month': + return dateFilterValue.direction === 'past' + ? subMonths(now, dateFilterValue.amount) + : addMonths(now, dateFilterValue.amount); + case 'year': + return dateFilterValue.direction === 'past' + ? subYears(now, dateFilterValue.amount) + : addYears(now, dateFilterValue.amount); + default: + throw new Error( + `Unsupported relative date unit: ${dateFilterValue.unit}`, + ); + } +}; + +export const getDateFromDateFilterValue = ( + dateFilterValue: DateFilterValue, +) => { + switch (dateFilterValue.type) { + case 'absolute': + return new Date(dateFilterValue.isoString); + case 'relative': + return getRelativeDate(dateFilterValue); + default: + throw new Error( + `Unsupported date filter type: ${(dateFilterValue as any).type}`, + ); + } +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 3168859cee38..c9e82a1f100a 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -25,6 +25,8 @@ import { convertLessThanRatingToArrayOfRatingValues, convertRatingToRatingValue, } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; +import { DateFilterValue } from '@/object-record/object-filter-dropdown/types/DateFilterValue'; +import { getDateFromDateFilterValue } from '@/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue'; import { endOfDay, startOfDay } from 'date-fns'; import { format } from 'date-fns-tz'; import { Filter } from '../../object-filter-dropdown/types/Filter'; @@ -343,24 +345,40 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } break; case 'DATE': - case 'DATE_TIME': + case 'DATE_TIME': { + let dateFilterValue: DateFilterValue = { + type: 'absolute', + isoString: '2023-07-14T09:32:47.123', + }; + try { + dateFilterValue = JSON.parse(rawUIFilter.value); + } catch (e) { + console.log('???', e); + } + + const date = getDateFromDateFilterValue(dateFilterValue); + const DATE_FORMAT_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + const dateISOString = format(date, DATE_FORMAT_WITHOUT_TZ); + switch (rawUIFilter.operand) { - case ViewFilterOperand.GreaterThan: + case ViewFilterOperand.GreaterThan: { objectRecordFilters.push({ [correspondingField.name]: { - gte: rawUIFilter.value, + gte: dateISOString, } as DateFilter, }); break; - case ViewFilterOperand.LessThan: + } + case ViewFilterOperand.LessThan: { objectRecordFilters.push({ [correspondingField.name]: { - lte: rawUIFilter.value, + lte: dateISOString, } as DateFilter, }); break; + } case ViewFilterOperand.IsEmpty: - case ViewFilterOperand.IsNotEmpty: + case ViewFilterOperand.IsNotEmpty: { applyEmptyFilters( rawUIFilter.operand, correspondingField, @@ -368,16 +386,14 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.definition.type, ); break; + } case ViewFilterOperand.IsRelative: { - const date = new Date(rawUIFilter.value); - const DATE_FORMAT_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS"; - const now = new Date(); const start = format( - startOfDay(Math.min(+date, +now)), + startOfDay(Math.min(+date, +new Date())), DATE_FORMAT_WITHOUT_TZ, ); const end = format( - endOfDay(Math.max(+date, +now)), + endOfDay(Math.max(+date, +new Date())), DATE_FORMAT_WITHOUT_TZ, ); @@ -391,8 +407,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( break; } case ViewFilterOperand.Is: { - const DATE_FORMAT_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS"; - const date = new Date(rawUIFilter.value); const start = format(startOfDay(date), DATE_FORMAT_WITHOUT_TZ); const end = format(endOfDay(date), DATE_FORMAT_WITHOUT_TZ); @@ -408,14 +422,22 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( case ViewFilterOperand.IsInPast: objectRecordFilters.push({ [correspondingField.name]: { - lt: new Date().toISOString(), + lte: new Date().toISOString(), } as DateFilter, }); break; case ViewFilterOperand.IsInFuture: objectRecordFilters.push({ [correspondingField.name]: { - gt: new Date().toISOString(), + gte: new Date().toISOString(), + } as DateFilter, + }); + break; + case ViewFilterOperand.IsToday: + objectRecordFilters.push({ + [correspondingField.name]: { + gte: startOfDay(new Date()).toISOString(), + lte: endOfDay(new Date()).toISOString(), } as DateFilter, }); break; @@ -425,6 +447,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( ); } break; + } case 'RATING': switch (rawUIFilter.operand) { case ViewFilterOperand.Is: diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx similarity index 96% rename from packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx rename to packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx index 94f98b1b0377..dcfa913363b6 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx @@ -42,7 +42,7 @@ const years = Array.from( (_, i) => new Date().getFullYear() + 5 - i, ).map((year) => ({ label: year.toString(), value: year })); -type DatePickerHeaderProps = { +type AbsoluteDatePickerHeaderProps = { date: Date; onChange?: (date: Date | null) => void; onChangeMonth: (month: number) => void; @@ -55,7 +55,7 @@ type DatePickerHeaderProps = { timeZone: string; }; -export const DatePickerHeader = ({ +export const AbsoluteDatePickerHeader = ({ date, onChange, onChangeMonth, @@ -66,7 +66,7 @@ export const DatePickerHeader = ({ nextMonthButtonDisabled, isDateTimeInput, timeZone, -}: DatePickerHeaderProps) => { +}: AbsoluteDatePickerHeaderProps) => { const endOfDayDateTimeInLocalTimezone = DateTime.now().set({ day: date.getDate(), month: date.getMonth() + 1, diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index 9aa1ecca7a55..fb62d8908a84 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -10,7 +10,8 @@ import { MenuItemLeftContent } from '@/ui/navigation/menu-item/internals/compone import { StyledHoverableMenuItemBase } from '@/ui/navigation/menu-item/internals/components/StyledMenuItemBase'; import { isDefined } from '~/utils/isDefined'; -import { DatePickerHeader } from '@/ui/input/components/internal/date/components/DatePickerHeader'; +import { RelativeDateFilterValue } from '@/object-record/object-filter-dropdown/types/DateFilterValue'; +import { AbsoluteDatePickerHeader } from '@/ui/input/components/internal/date/components/AbsoluteDatePickerHeader'; import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativePickerHeader'; import { RelativeDateDirection } from '@/ui/input/components/internal/date/types/RelativeDateDirection'; import { RelativeDateUnit } from '@/ui/input/components/internal/date/types/RelativeDateUnit'; @@ -274,6 +275,9 @@ type InternalDatePickerProps = { date: Date | null; onMouseSelect?: (date: Date | null) => void; onChange?: (date: Date | null) => void; + onRelativeDateChange?: ( + dateFilterValue: RelativeDateFilterValue | null, + ) => void; clearable?: boolean; isDateTimeInput?: boolean; onEnter?: (date: Date | null) => void; @@ -293,6 +297,7 @@ export const InternalDatePicker = ({ keyboardEventsDisabled, onClear, isRelativeToNow, + onRelativeDateChange, }: InternalDatePickerProps) => { const internalDate = date ?? new Date(); @@ -467,12 +472,12 @@ export const InternalDatePicker = ({ isRelativeToNow ? ( {}} + onChange={onRelativeDateChange} /> ) : ( - void; + direction: RelativeDateDirection; + amount: number; + unit: RelativeDateUnit; + onChange?: (value: RelativeDateFilterValue) => void; }; export const RelativeDatePickerHeader = ({ direction, - value, + amount, unit, onChange, }: RelativeDatePickerHeaderProps) => { @@ -38,25 +35,26 @@ export const RelativeDatePickerHeader = ({ dropdownId="direction-select" value={direction} onChange={(newDirection) => - onChange({ + onChange?.({ + type: 'relative', direction: newDirection, - value: value, + amount: amount, unit: unit, }) } options={RELATIVE_DATE_DIRECTIONS} /> { const newNumericValue = newValue.replace(/[^0-9]/g, ''); - const value = newNumericValue - ? parseInt(newNumericValue, 10) - : undefined; + if (!newNumericValue) return; + const amount = parseInt(newNumericValue, 10); - onChange({ + onChange?.({ + type: 'relative', direction, - value, + amount, unit, }); }} @@ -66,9 +64,10 @@ export const RelativeDatePickerHeader = ({ dropdownId="unit-select" value={unit} onChange={(newUnit) => - onChange({ + onChange?.({ + type: 'relative', direction: direction, - value: value, + amount: amount, unit: newUnit, }) } From 74cd021d9cdfd0d546b57520ce975b91e368beb3 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 20 Sep 2024 01:18:32 +0200 Subject: [PATCH 05/33] Resolve variable filter values, pick relative dates --- .../ObjectFilterDropdownDateInput.tsx | 84 ++++++---- .../ObjectFilterDropdownOperandSelect.tsx | 1 + .../types/DateFilterValue.ts | 16 -- .../object-filter-dropdown/types/Filter.ts | 3 +- .../utils/getDateFromDateFilterValue.ts | 56 ------- .../utils/getRelativeDateDisplayValue.ts | 20 +++ ...turnObjectDropdownFilterIntoQueryFilter.ts | 57 ++++--- .../date/components/InternalDatePicker.tsx | 26 ++- .../date/components/RelativePickerHeader.tsx | 33 ++-- .../constants/RelativeDateDirectionOptions.ts | 10 +- .../date/constants/RelativeDateUnits.ts | 18 ++- .../date/types/RelativeDateDirection.ts | 5 - .../internal/date/types/RelativeDateUnit.ts | 6 - .../src/modules/views/types/ViewFilter.ts | 2 + .../views/types/ViewFilterValueType.ts | 4 + .../views/utils/mapViewFiltersToFilters.ts | 1 + .../computeVariableDateViewFilterValue.ts | 10 ++ .../resolveDateViewFilterValue.ts | 148 ++++++++++++++++++ .../resolveVariableViewFilterValue.ts | 54 +++++++ .../constants/standard-field-ids.ts | 1 + .../view-filter.workspace-entity.ts | 43 ++++- 21 files changed, 421 insertions(+), 177 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/DateFilterValue.ts delete mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue.ts create mode 100644 packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts delete mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateDirection.ts delete mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateUnit.ts create mode 100644 packages/twenty-front/src/modules/views/types/ViewFilterValueType.ts create mode 100644 packages/twenty-front/src/modules/views/utils/view-filter-value/computeVariableDateViewFilterValue.ts create mode 100644 packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts create mode 100644 packages/twenty-front/src/modules/views/utils/view-filter-value/resolveVariableViewFilterValue.ts diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index f1a43d40bfb8..a5e136d2969c 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -2,12 +2,18 @@ import { useRecoilValue } from 'recoil'; import { v4 } from 'uuid'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; -import { DateFilterValue } from '@/object-record/object-filter-dropdown/types/DateFilterValue'; +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 { ViewFilterValueType } from '@/views/types/ViewFilterValueType'; +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/resolveVariableViewFilterValue'; import { useState } from 'react'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { getDateFromDateFilterValue } from '../utils/getDateFromDateFilterValue'; export const ObjectFilterDropdownDateInput = () => { const { @@ -26,24 +32,21 @@ export const ObjectFilterDropdownDateInput = () => { ); const selectedFilter = useRecoilValue(selectedFilterState); - const dateFilterValue = selectedFilter?.value - ? JSON.parse(selectedFilter.value) - : null; - const initialInternalDate = dateFilterValue - ? getDateFromDateFilterValue(dateFilterValue) - : new Date(); + const initialFilterValue = resolveFilterValue(selectedFilter); const [internalDate, setInternalDate] = useState( - initialInternalDate, + initialFilterValue instanceof Date ? initialFilterValue : new Date(), ); + const [relativeDate, setRelativeDate] = useState<{ + direction: VariableDateViewFilterValueDirection; + amount: number; + unit: VariableDateViewFilterValueUnit; + } | null>(null); + const isDateTimeInput = filterDefinitionUsedInDropdown?.type === FieldMetadataType.DateTime; - const handleChange = (dateFilterValue: DateFilterValue | null) => { - const newDate = dateFilterValue - ? getDateFromDateFilterValue(dateFilterValue) - : null; - + const handleAbsoluteDateChange = (newDate: Date | null) => { setInternalDate(newDate); if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return; @@ -51,7 +54,8 @@ export const ObjectFilterDropdownDateInput = () => { selectFilter?.({ id: selectedFilter?.id ? selectedFilter.id : v4(), fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, - value: JSON.stringify(dateFilterValue), + value: newDate?.toISOString() ?? '', + valueType: ViewFilterValueType.STATIC, operand: selectedOperandInDropdown, displayValue: newDate ? isDateTimeInput @@ -63,6 +67,39 @@ export const ObjectFilterDropdownDateInput = () => { setIsObjectFilterDropdownUnfolded(false); }; + + const handleRelativeDateChange = ( + relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount: number; + unit: VariableDateViewFilterValueUnit; + } | null, + ) => { + setRelativeDate(relativeDate); + + 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, + valueType: ViewFilterValueType.VARIABLE, + operand: selectedOperandInDropdown, + displayValue: getRelativeDateDisplayValue(relativeDate), + definition: filterDefinitionUsedInDropdown, + }); + + setIsObjectFilterDropdownUnfolded(false); + }; + const isRelativeOperand = selectedOperandInDropdown === ViewFilterOperand.IsRelative; @@ -70,19 +107,10 @@ export const ObjectFilterDropdownDateInput = () => { { - const dateFilterValue: DateFilterValue | null = date - ? { type: 'absolute', isoString: date.toISOString() } - : null; - handleChange(dateFilterValue); - }} - onRelativeDateChange={handleChange} - onMouseSelect={(date) => { - const dateFilterValue: DateFilterValue | null = date - ? { type: 'absolute', isoString: date.toISOString() } - : null; - handleChange(dateFilterValue); - }} + relativeDate={relativeDate} + onChange={handleAbsoluteDateChange} + onRelativeDateChange={handleRelativeDateChange} + onMouseSelect={handleAbsoluteDateChange} isDateTimeInput={isDateTimeInput} /> ); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx index 5f500b916461..e1d812522a2e 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx @@ -66,6 +66,7 @@ export const ObjectFilterDropdownOperandSelect = () => { displayValue: selectedFilter.displayValue, operand: newOperand, value: selectedFilter.value, + valueType: selectedFilter.valueType, definition: filterDefinitionUsedInDropdown, }); } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/DateFilterValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/DateFilterValue.ts deleted file mode 100644 index 97fbdd4e04f1..000000000000 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/DateFilterValue.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { RelativeDateDirection } from '@/ui/input/components/internal/date/types/RelativeDateDirection'; -import { RelativeDateUnit } from '@/ui/input/components/internal/date/types/RelativeDateUnit'; - -export interface RelativeDateFilterValue { - type: 'relative'; - direction: RelativeDateDirection; - unit: RelativeDateUnit; - amount: number; -} - -export interface AbsoluteDateFilterValue { - type: 'absolute'; - isoString: string; -} - -export type DateFilterValue = RelativeDateFilterValue | AbsoluteDateFilterValue; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/Filter.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/Filter.ts index 52ed99ac5531..09f4c9f4a9fd 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/Filter.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/Filter.ts @@ -1,5 +1,5 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; - +import { ViewFilterValueType } from '@/views/types/ViewFilterValueType'; import { FilterDefinition } from './FilterDefinition'; export type Filter = { @@ -7,6 +7,7 @@ export type Filter = { variant?: 'default' | 'danger'; fieldMetadataId: string; value: string; + valueType?: ViewFilterValueType; displayValue: string; displayAvatarUrl?: string; operand: ViewFilterOperand; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue.ts deleted file mode 100644 index c8dc3a7bce31..000000000000 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - addDays, - addMonths, - addWeeks, - addYears, - subDays, - subMonths, - subWeeks, - subYears, -} from 'date-fns'; -import { DateFilterValue } from '../types/DateFilterValue'; - -const getRelativeDate = (dateFilterValue: DateFilterValue): Date => { - if (dateFilterValue.type !== 'relative') { - throw new Error('Date filter value is not relative'); - } - - const now = new Date(); - switch (dateFilterValue.unit) { - case 'day': - return dateFilterValue.direction === 'past' - ? subDays(now, dateFilterValue.amount) - : addDays(now, dateFilterValue.amount); - case 'week': - return dateFilterValue.direction === 'past' - ? subWeeks(now, dateFilterValue.amount) - : addWeeks(now, dateFilterValue.amount); - case 'month': - return dateFilterValue.direction === 'past' - ? subMonths(now, dateFilterValue.amount) - : addMonths(now, dateFilterValue.amount); - case 'year': - return dateFilterValue.direction === 'past' - ? subYears(now, dateFilterValue.amount) - : addYears(now, dateFilterValue.amount); - default: - throw new Error( - `Unsupported relative date unit: ${dateFilterValue.unit}`, - ); - } -}; - -export const getDateFromDateFilterValue = ( - dateFilterValue: DateFilterValue, -) => { - switch (dateFilterValue.type) { - case 'absolute': - return new Date(dateFilterValue.isoString); - case 'relative': - return getRelativeDate(dateFilterValue); - default: - throw new Error( - `Unsupported date filter type: ${(dateFilterValue as any).type}`, - ); - } -}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts new file mode 100644 index 000000000000..c39d39f662a6 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts @@ -0,0 +1,20 @@ +import { + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; +import { capitalize } from '~/utils/string/capitalize'; + +export const getRelativeDateDisplayValue = ( + relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount: number; + unit: VariableDateViewFilterValueUnit; + } | null, +) => { + if (!relativeDate) return ''; + + const isPlural = relativeDate.amount > 1; + return capitalize( + `${relativeDate.direction} ${relativeDate.amount} ${relativeDate.unit}${isPlural ? 's' : ''}`.toLowerCase(), + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index c9e82a1f100a..008a6d318737 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -16,7 +16,7 @@ import { import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { Field } from '~/generated/graphql'; +import { Field, FieldMetadataType } from '~/generated/graphql'; import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields'; import { isDefined } from '~/utils/isDefined'; @@ -25,8 +25,7 @@ import { convertLessThanRatingToArrayOfRatingValues, convertRatingToRatingValue, } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; -import { DateFilterValue } from '@/object-record/object-filter-dropdown/types/DateFilterValue'; -import { getDateFromDateFilterValue } from '@/object-record/object-filter-dropdown/utils/getDateFromDateFilterValue'; +import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveVariableViewFilterValue'; import { endOfDay, startOfDay } from 'date-fns'; import { format } from 'date-fns-tz'; import { Filter } from '../../object-filter-dropdown/types/Filter'; @@ -346,19 +345,16 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( break; case 'DATE': case 'DATE_TIME': { - let dateFilterValue: DateFilterValue = { - type: 'absolute', - isoString: '2023-07-14T09:32:47.123', - }; - try { - dateFilterValue = JSON.parse(rawUIFilter.value); - } catch (e) { - console.log('???', e); - } + // new Date() causes a re-render loop? + + const resolvedFilterValue = + resolveFilterValue(rawUIFilter); - const date = getDateFromDateFilterValue(dateFilterValue); const DATE_FORMAT_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS"; - const dateISOString = format(date, DATE_FORMAT_WITHOUT_TZ); + const dateISOString = + resolvedFilterValue instanceof Date + ? format(resolvedFilterValue, DATE_FORMAT_WITHOUT_TZ) + : '2023-01-01T00:00:00.000'; switch (rawUIFilter.operand) { case ViewFilterOperand.GreaterThan: { @@ -388,25 +384,38 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( break; } case ViewFilterOperand.IsRelative: { - const start = format( - startOfDay(Math.min(+date, +new Date())), - DATE_FORMAT_WITHOUT_TZ, - ); - const end = format( - endOfDay(Math.max(+date, +new Date())), - DATE_FORMAT_WITHOUT_TZ, - ); + /* const dateRange = z + .object({ start: z.date(), end: z.date() }) + .safeParse(resolvedFilterValue).data; + + const defaultDateRange = resolveDateViewFilterValue({ + value: 'PAST_1_DAY', + valueType: ViewFilterValueType.VARIABLE, + }) as { start: Date; end: Date }; + + const { start, end } = dateRange ?? defaultDateRange; // Does not work objectRecordFilters.push({ [correspondingField.name]: { - gte: start, - lte: end, + gte: start.toISOString(), + lte: end.toISOString(), + } as DateFilter, + }); + break; */ + + objectRecordFilters.push({ + [correspondingField.name]: { + lte: dateISOString, } as DateFilter, }); break; } case ViewFilterOperand.Is: { + const isValid = resolvedFilterValue instanceof Date; + const defaultDate = new Date(); + const date = isValid ? resolvedFilterValue : defaultDate; + const start = format(startOfDay(date), DATE_FORMAT_WITHOUT_TZ); const end = format(endOfDay(date), DATE_FORMAT_WITHOUT_TZ); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index fb62d8908a84..635a11a7c7af 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -10,12 +10,13 @@ import { MenuItemLeftContent } from '@/ui/navigation/menu-item/internals/compone import { StyledHoverableMenuItemBase } from '@/ui/navigation/menu-item/internals/components/StyledMenuItemBase'; import { isDefined } from '~/utils/isDefined'; -import { RelativeDateFilterValue } from '@/object-record/object-filter-dropdown/types/DateFilterValue'; import { AbsoluteDatePickerHeader } from '@/ui/input/components/internal/date/components/AbsoluteDatePickerHeader'; import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativePickerHeader'; -import { RelativeDateDirection } from '@/ui/input/components/internal/date/types/RelativeDateDirection'; -import { RelativeDateUnit } from '@/ui/input/components/internal/date/types/RelativeDateUnit'; import { UserContext } from '@/users/contexts/UserContext'; +import { + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; import { useContext } from 'react'; import 'react-datepicker/dist/react-datepicker.css'; @@ -273,10 +274,19 @@ const StyledButton = styled(MenuItemLeftContent)` type InternalDatePickerProps = { isRelativeToNow?: boolean; date: Date | null; + relativeDate?: { + direction: VariableDateViewFilterValueDirection; + amount: number; + unit: VariableDateViewFilterValueUnit; + } | null; onMouseSelect?: (date: Date | null) => void; onChange?: (date: Date | null) => void; onRelativeDateChange?: ( - dateFilterValue: RelativeDateFilterValue | null, + relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount: number; + unit: VariableDateViewFilterValueUnit; + } | null, ) => void; clearable?: boolean; isDateTimeInput?: boolean; @@ -298,6 +308,7 @@ export const InternalDatePicker = ({ onClear, isRelativeToNow, onRelativeDateChange, + relativeDate, }: InternalDatePickerProps) => { const internalDate = date ?? new Date(); @@ -445,6 +456,7 @@ export const InternalDatePicker = ({ const endOfDayInLocalTimezone = endOfDayDateTimeInLocalTimezone.toJSDate(); const dateToUse = isDateTimeInput ? endOfDayInLocalTimezone : dateWithoutTime; + return ( isRelativeToNow ? ( ) : ( diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx index 665ffdb7ddd6..52f56d23f22c 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx @@ -1,11 +1,12 @@ import { Select } from '@/ui/input/components/Select'; import { TextInput } from '@/ui/input/components/TextInput'; -import { RelativeDateUnit } from '../types/RelativeDateUnit'; -import { RelativeDateFilterValue } from '@/object-record/object-filter-dropdown/types/DateFilterValue'; import { RELATIVE_DATE_DIRECTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateDirectionOptions'; import { RELATIVE_DATE_UNITS } from '@/ui/input/components/internal/date/constants/RelativeDateUnits'; -import { RelativeDateDirection } from '@/ui/input/components/internal/date/types/RelativeDateDirection'; +import { + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; import styled from '@emotion/styled'; const StyledContainer = styled.div` @@ -17,10 +18,14 @@ const StyledContainer = styled.div` `; type RelativeDatePickerHeaderProps = { - direction: RelativeDateDirection; + direction: VariableDateViewFilterValueDirection; amount: number; - unit: RelativeDateUnit; - onChange?: (value: RelativeDateFilterValue) => void; + unit: VariableDateViewFilterValueUnit; + onChange?: (value: { + direction: VariableDateViewFilterValueDirection; + amount: number; + unit: VariableDateViewFilterValueUnit; + }) => void; }; export const RelativeDatePickerHeader = ({ @@ -36,7 +41,6 @@ export const RelativeDatePickerHeader = ({ value={direction} onChange={(newDirection) => onChange?.({ - type: 'relative', direction: newDirection, amount: amount, unit: unit, @@ -49,12 +53,11 @@ export const RelativeDatePickerHeader = ({ onChange={(newValue) => { const newNumericValue = newValue.replace(/[^0-9]/g, ''); if (!newNumericValue) return; - const amount = parseInt(newNumericValue, 10); + const newAmount = parseInt(newNumericValue, 10); onChange?.({ - type: 'relative', direction, - amount, + amount: newAmount, unit, }); }} @@ -65,13 +68,15 @@ export const RelativeDatePickerHeader = ({ value={unit} onChange={(newUnit) => onChange?.({ - type: 'relative', - direction: direction, - amount: amount, + direction, + amount, unit: newUnit, }) } - options={RELATIVE_DATE_UNITS} + options={RELATIVE_DATE_UNITS.map((unit) => ({ + ...unit, + label: `${unit.label}${amount > 1 ? 's' : ''}`, + }))} /> ); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts index 93eb2a413289..0c24896b4b27 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts @@ -1,10 +1,10 @@ -import { RelativeDateDirection } from '../types/RelativeDateDirection'; +import { VariableDateViewFilterValueDirection } from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; export const RELATIVE_DATE_DIRECTIONS: { - value: RelativeDateDirection; + value: VariableDateViewFilterValueDirection; label: string; }[] = [ - { value: RelativeDateDirection.Past, label: 'Past' }, - { value: RelativeDateDirection.Next, label: 'Next' }, - { value: RelativeDateDirection.This, label: 'This' }, + { value: 'PAST', label: 'Past' }, + { value: 'THIS', label: 'This' }, + { value: 'NEXT', label: 'Next' }, ]; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnits.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnits.ts index 54b4ab1615d5..131d8bddcb3f 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnits.ts +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnits.ts @@ -1,9 +1,11 @@ -import { RelativeDateUnit } from '../types/RelativeDateUnit'; +import { VariableDateViewFilterValueUnit } from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; -export const RELATIVE_DATE_UNITS: { value: RelativeDateUnit; label: string }[] = - [ - { value: RelativeDateUnit.Day, label: 'Day' }, - { value: RelativeDateUnit.Week, label: 'Week' }, - { value: RelativeDateUnit.Month, label: 'Month' }, - { value: RelativeDateUnit.Year, label: 'Year' }, - ]; +export const RELATIVE_DATE_UNITS: { + value: VariableDateViewFilterValueUnit; + label: string; +}[] = [ + { value: 'DAY', label: 'Day' }, + { value: 'WEEK', label: 'Week' }, + { value: 'MONTH', label: 'Month' }, + { value: 'YEAR', label: 'Year' }, +]; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateDirection.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateDirection.ts deleted file mode 100644 index 068bb21c04ac..000000000000 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateDirection.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum RelativeDateDirection { - Past = 'past', - Next = 'next', - This = 'this', -} diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateUnit.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateUnit.ts deleted file mode 100644 index 43d8e0976328..000000000000 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/types/RelativeDateUnit.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum RelativeDateUnit { - Day = 'day', - Week = 'week', - Month = 'month', - Year = 'year', -} diff --git a/packages/twenty-front/src/modules/views/types/ViewFilter.ts b/packages/twenty-front/src/modules/views/types/ViewFilter.ts index 608e13918550..a0622a81a33a 100644 --- a/packages/twenty-front/src/modules/views/types/ViewFilter.ts +++ b/packages/twenty-front/src/modules/views/types/ViewFilter.ts @@ -1,3 +1,4 @@ +import { ViewFilterValueType } from '@/views/types/ViewFilterValueType'; import { ViewFilterOperand } from './ViewFilterOperand'; export type ViewFilter = { @@ -11,4 +12,5 @@ export type ViewFilter = { createdAt?: string; updatedAt?: string; viewId?: string; + valueType?: ViewFilterValueType; }; diff --git a/packages/twenty-front/src/modules/views/types/ViewFilterValueType.ts b/packages/twenty-front/src/modules/views/types/ViewFilterValueType.ts new file mode 100644 index 000000000000..6f71d8d9c7e0 --- /dev/null +++ b/packages/twenty-front/src/modules/views/types/ViewFilterValueType.ts @@ -0,0 +1,4 @@ +export enum ViewFilterValueType { + STATIC = 'STATIC', + VARIABLE = 'VARIABLE', +} diff --git a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts index 104ba6afdaae..356c6be66a84 100644 --- a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts +++ b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts @@ -21,6 +21,7 @@ export const mapViewFiltersToFilters = ( id: viewFilter.id, fieldMetadataId: viewFilter.fieldMetadataId, value: viewFilter.value, + valueType: viewFilter.valueType, displayValue: viewFilter.displayValue, operand: viewFilter.operand, definition: availableFilterDefinition, diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/computeVariableDateViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/computeVariableDateViewFilterValue.ts new file mode 100644 index 000000000000..ab3577278c01 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/computeVariableDateViewFilterValue.ts @@ -0,0 +1,10 @@ +import { + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; + +export const computeVariableDateViewFilterValue = ( + direction: VariableDateViewFilterValueDirection, + amount: number, + unit: VariableDateViewFilterValueUnit, +) => `${direction}_${amount}_${unit}`; diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts new file mode 100644 index 000000000000..8958712a6b33 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts @@ -0,0 +1,148 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; +import { + addDays, + addMonths, + addWeeks, + addYears, + endOfDay, + endOfMonth, + endOfWeek, + endOfYear, + startOfDay, + startOfMonth, + startOfWeek, + startOfYear, + subDays, + subMonths, + subWeeks, + subYears, +} from 'date-fns'; + +import { z } from 'zod'; + +const variableDateViewFilterValueDirectionSchema = z.enum([ + 'NEXT', + 'THIS', + 'PAST', +]); + +export type VariableDateViewFilterValueDirection = z.infer< + typeof variableDateViewFilterValueDirectionSchema +>; + +const variableDateViewFilterValueUnitSchema = z.enum([ + 'DAY', + 'WEEK', + 'MONTH', + 'YEAR', +]); + +export type VariableDateViewFilterValueUnit = z.infer< + typeof variableDateViewFilterValueUnitSchema +>; + +const variableDateViewFilterValueSchema = z.string().transform((value) => { + const [direction, amount, unit] = value.split('_'); + return z + .tuple([ + variableDateViewFilterValueDirectionSchema, + z.number().int().positive(), + variableDateViewFilterValueUnitSchema, + ]) + .parse([direction, parseInt(amount), unit]); +}); + +const addUnit = ( + date: Date, + amount: number, + unit: VariableDateViewFilterValueUnit, +) => { + switch (unit) { + case 'DAY': + return addDays(date, amount); + case 'WEEK': + return addWeeks(date, amount); + case 'MONTH': + return addMonths(date, amount); + case 'YEAR': + return addYears(date, amount); + } +}; + +const subUnit = ( + date: Date, + amount: number, + unit: VariableDateViewFilterValueUnit, +) => { + switch (unit) { + case 'DAY': + return subDays(date, amount); + case 'WEEK': + return subWeeks(date, amount); + case 'MONTH': + return subMonths(date, amount); + case 'YEAR': + return subYears(date, amount); + } +}; + +const startOfUnit = (date: Date, unit: VariableDateViewFilterValueUnit) => { + switch (unit) { + case 'DAY': + return startOfDay(date); + case 'WEEK': + return startOfWeek(date); + case 'MONTH': + return startOfMonth(date); + case 'YEAR': + return startOfYear(date); + } +}; + +const endOfUnit = (date: Date, unit: VariableDateViewFilterValueUnit) => { + switch (unit) { + case 'DAY': + return endOfDay(date); + case 'WEEK': + return endOfWeek(date); + case 'MONTH': + return endOfMonth(date); + case 'YEAR': + return endOfYear(date); + } +}; + +const resolveVariableDateViewFilterValue = (value: string) => { + const [direction, amount, unit] = + variableDateViewFilterValueSchema.parse(value); + const now = new Date(); + + switch (direction) { + case 'NEXT': + return { + start: now, + end: addUnit(now, amount, unit), + }; + case 'PAST': + return { + start: subUnit(now, amount, unit), + end: now, + }; + case 'THIS': + return { + start: startOfUnit(now, unit), + end: endOfUnit(now, unit), + }; + } +}; + +export const resolveDateViewFilterValue = ( + viewFilter: Pick, +) => { + if (!viewFilter.value) return null; + + if (viewFilter.valueType === 'VARIABLE') { + return resolveVariableDateViewFilterValue(viewFilter.value); + } + return new Date(viewFilter.value); +}; diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveVariableViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveVariableViewFilterValue.ts new file mode 100644 index 000000000000..6e3699677de4 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveVariableViewFilterValue.ts @@ -0,0 +1,54 @@ +import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { ViewFilter } from '@/views/types/ViewFilter'; +import { Field, FieldMetadataType } from '~/generated/graphql'; +import { resolveDateViewFilterValue } from './resolveDateViewFilterValue'; + +export const resolveVariableViewFilterValue = ( + viewFilter: Pick, + fields: Field[], +) => { + const field = fields.find((field) => field.id === viewFilter.fieldMetadataId); + + if (!field) throw new Error('Field not found'); + + switch (field.type) { + case FieldMetadataType.Date: + case FieldMetadataType.DateTime: + return resolveDateViewFilterValue(viewFilter); + /* case FieldMetadataType.Number: + return +viewFilter.value; + */ + default: + return viewFilter.value; + } +}; + +type ResolvedFilterValue = T extends + | FieldMetadataType.Date + | FieldMetadataType.DateTime + ? ReturnType + : T extends FieldMetadataType.Number + ? number + : string; + +export const resolveFilterValue = ( + filter?: + | (Pick & { + definition: { + type: Filter['definition']['type']; + }; + }) + | null, +): ResolvedFilterValue | null => { + if (!filter || !filter.value) return null; + + switch (filter.definition.type) { + case FieldMetadataType.Date: + case FieldMetadataType.DateTime: + return resolveDateViewFilterValue(filter) as ResolvedFilterValue; + case FieldMetadataType.Number: + return Number(filter.value) as ResolvedFilterValue; + default: + return filter.value as ResolvedFilterValue; + } +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index 8f0d135fdbb3..1c4522b72ac5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -360,6 +360,7 @@ export const VIEW_FILTER_STANDARD_FIELD_IDS = { fieldMetadataId: '20202020-c9aa-4c94-8d0e-9592f5008fb0', operand: '20202020-bd23-48c4-9fab-29d1ffb80310', value: '20202020-1e55-4a1e-a1d2-fefb86a5fce5', + valueType: '20202020-518f-4840-a5b9-c5d74689839e', displayValue: '20202020-1270-4ebf-9018-c0ec10d5038e', view: '20202020-4f5b-487e-829c-3d881c163611', }; diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts index 149171e6eb7f..351317f2e930 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts @@ -1,18 +1,23 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { VIEW_FILTER_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; +import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; +import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { VIEW_FILTER_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; + +export enum ViewFilterValueType { + STATIC = 'STATIC', + VARIABLE = 'VARIABLE', +} @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.viewFilter, @@ -50,6 +55,30 @@ export class ViewFilterWorkspaceEntity extends BaseWorkspaceEntity { }) value: string; + @WorkspaceField({ + standardId: VIEW_FILTER_STANDARD_FIELD_IDS.valueType, + type: FieldMetadataType.SELECT, + label: 'Value type', + description: 'View filter value type', + defaultValue: ViewFilterValueType.STATIC, + options: [ + { + value: ViewFilterValueType.STATIC, + label: 'Static', + position: 0, + color: 'green', + }, + { + value: ViewFilterValueType.VARIABLE, + label: 'Variable', + position: 1, + color: 'red', + }, + ], + }) + @WorkspaceIsNullable() + valueType?: string | null; + @WorkspaceField({ standardId: VIEW_FILTER_STANDARD_FIELD_IDS.displayValue, type: FieldMetadataType.TEXT, From d2bc804ab30c072b9f631ceee78739e5fbf29a73 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 20 Sep 2024 02:01:32 +0200 Subject: [PATCH 06/33] Temporarily disable all new date operands, fix view filter workspace entity definition --- .../utils/getOperandsForFilterType.ts | 10 +++++----- .../standard-objects/view-filter.workspace-entity.ts | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index 26e867181487..c450058a8e35 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -34,11 +34,11 @@ export const getOperandsForFilterType = ( case 'DATE_TIME': case 'DATE': return [ - ViewFilterOperand.Is, - ViewFilterOperand.IsRelative, - ViewFilterOperand.IsInPast, - ViewFilterOperand.IsInFuture, - ViewFilterOperand.IsToday, + // ViewFilterOperand.Is, + // ViewFilterOperand.IsRelative, + // ViewFilterOperand.IsInPast, + // ViewFilterOperand.IsInFuture, + // ViewFilterOperand.IsToday, ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan, ...emptyOperands, diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts index 351317f2e930..6cd8e00b092e 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts @@ -60,7 +60,6 @@ export class ViewFilterWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.SELECT, label: 'Value type', description: 'View filter value type', - defaultValue: ViewFilterValueType.STATIC, options: [ { value: ViewFilterValueType.STATIC, From 146df0583f57fac6fd0fe73b52f63efb93d00b46 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 20 Sep 2024 02:37:51 +0200 Subject: [PATCH 07/33] Past and future date filters work --- .../ObjectFilterDropdownOperandSelect.tsx | 6 +++-- .../utils/getOperandLabel.ts | 4 +++ .../utils/getOperandsForFilterType.ts | 4 +-- ...turnObjectDropdownFilterIntoQueryFilter.ts | 27 ++++++++++--------- .../EditableFilterDropdownButton.tsx | 8 +++++- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx index e1d812522a2e..1586c6e170a9 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx @@ -36,15 +36,17 @@ export const ObjectFilterDropdownOperandSelect = () => { ); const handleOperandChange = (newOperand: ViewFilterOperand) => { - const isEmptyOperand = [ + const isValuelessOperand = [ ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, ].includes(newOperand); setSelectedOperandInDropdown(newOperand); setIsObjectFilterDropdownOperandSelectUnfolded(false); - if (isEmptyOperand) { + if (isValuelessOperand) { selectFilter?.({ id: v4(), fieldMetadataId: filterDefinitionUsedInDropdown?.fieldMetadataId ?? '', diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts index 9843ec48fb31..16f7af8a92b5 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts @@ -55,6 +55,10 @@ export const getOperandLabelShort = ( return '\u00A0> '; case ViewFilterOperand.LessThan: return '\u00A0< '; + case ViewFilterOperand.IsInPast: + return ': Past'; + case ViewFilterOperand.IsInFuture: + return ': Future'; default: return ': '; } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index c450058a8e35..4cdf0dc75447 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -36,8 +36,8 @@ export const getOperandsForFilterType = ( return [ // ViewFilterOperand.Is, // ViewFilterOperand.IsRelative, - // ViewFilterOperand.IsInPast, - // ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, // ViewFilterOperand.IsToday, ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan, diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 008a6d318737..456e5b422607 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -26,7 +26,7 @@ import { convertRatingToRatingValue, } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveVariableViewFilterValue'; -import { endOfDay, startOfDay } from 'date-fns'; +import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns'; import { format } from 'date-fns-tz'; import { Filter } from '../../object-filter-dropdown/types/Filter'; @@ -292,16 +292,18 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( (field) => field.id === rawUIFilter.fieldMetadataId, ); - const isEmptyOperand = [ + const isValuelessOperand = [ ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, ].includes(rawUIFilter.operand); if (!correspondingField) { continue; } - if (!isEmptyOperand) { + if (!isValuelessOperand) { if (!isDefined(rawUIFilter.value) || rawUIFilter.value === '') { continue; } @@ -345,8 +347,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( break; case 'DATE': case 'DATE_TIME': { - // new Date() causes a re-render loop? - const resolvedFilterValue = resolveFilterValue(rawUIFilter); @@ -356,6 +356,8 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( ? format(resolvedFilterValue, DATE_FORMAT_WITHOUT_TZ) : '2023-01-01T00:00:00.000'; + const now = roundToNearestMinutes(new Date()); + switch (rawUIFilter.operand) { case ViewFilterOperand.GreaterThan: { objectRecordFilters.push({ @@ -431,25 +433,26 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( case ViewFilterOperand.IsInPast: objectRecordFilters.push({ [correspondingField.name]: { - lte: new Date().toISOString(), + lte: now.toISOString(), } as DateFilter, }); break; case ViewFilterOperand.IsInFuture: objectRecordFilters.push({ [correspondingField.name]: { - gte: new Date().toISOString(), + gte: now.toISOString(), } as DateFilter, }); break; - case ViewFilterOperand.IsToday: + case ViewFilterOperand.IsToday: { objectRecordFilters.push({ [correspondingField.name]: { - gte: startOfDay(new Date()).toISOString(), - lte: endOfDay(new Date()).toISOString(), + gte: startOfDay(now).toISOString(), + lte: endOfDay(now).toISOString(), } as DateFilter, }); break; + } default: throw new Error( `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, // @@ -531,7 +534,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } break; case 'RELATION': { - if (!isEmptyOperand) { + if (!isValuelessOperand) { try { JSON.parse(rawUIFilter.value); } catch (e) { @@ -828,7 +831,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } break; case 'SELECT': { - if (isEmptyOperand) { + if (isValuelessOperand) { applyEmptyFilters( rawUIFilter.operand, correspondingField, diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index 23613d5c35aa..0625c562069c 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -10,6 +10,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { EditableFilterChip } from '@/views/components/EditableFilterChip'; import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { isDefined } from '~/utils/isDefined'; type EditableFilterDropdownButtonProps = { @@ -70,7 +71,12 @@ export const EditableFilterDropdownButton = ({ const { id: fieldId, value, operand } = viewFilter; if ( !value && - ![FilterOperand.IsEmpty, FilterOperand.IsNotEmpty].includes(operand) + ![ + FilterOperand.IsEmpty, + FilterOperand.IsNotEmpty, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ].includes(operand) ) { removeCombinedViewFilter(fieldId); } From f5a1b14bda7407cd5f75d33b3ffcaca7f11b543e Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 20 Sep 2024 03:14:37 +0200 Subject: [PATCH 08/33] Add isToday filter operand to date: and query does not work, otherwise ok. --- .../ObjectFilterDropdownOperandSelect.tsx | 1 + .../utils/getOperandLabel.ts | 2 ++ .../utils/getOperandsForFilterType.ts | 2 +- .../turnObjectDropdownFilterIntoQueryFilter.ts | 18 ++++++++++++++---- .../EditableFilterDropdownButton.tsx | 1 + 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx index 1586c6e170a9..ae625da40ab8 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx @@ -41,6 +41,7 @@ export const ObjectFilterDropdownOperandSelect = () => { ViewFilterOperand.IsNotEmpty, ViewFilterOperand.IsInPast, ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, ].includes(newOperand); setSelectedOperandInDropdown(newOperand); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts index 16f7af8a92b5..2218c5b6b221 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts @@ -59,6 +59,8 @@ export const getOperandLabelShort = ( return ': Past'; case ViewFilterOperand.IsInFuture: return ': Future'; + case ViewFilterOperand.IsToday: + return ': Today'; default: return ': '; } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index 4cdf0dc75447..7b7e3f6927f1 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -38,7 +38,7 @@ export const getOperandsForFilterType = ( // ViewFilterOperand.IsRelative, ViewFilterOperand.IsInPast, ViewFilterOperand.IsInFuture, - // ViewFilterOperand.IsToday, + ViewFilterOperand.IsToday, ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan, ...emptyOperands, diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 456e5b422607..a6b0dbfecb45 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -297,6 +297,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( ViewFilterOperand.IsNotEmpty, ViewFilterOperand.IsInPast, ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, ].includes(rawUIFilter.operand); if (!correspondingField) { @@ -445,11 +446,20 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( }); break; case ViewFilterOperand.IsToday: { + // And query is not working, in this case gte is ignored objectRecordFilters.push({ - [correspondingField.name]: { - gte: startOfDay(now).toISOString(), - lte: endOfDay(now).toISOString(), - } as DateFilter, + and: [ + { + [correspondingField.name]: { + lte: endOfDay(now).toISOString(), + } as DateFilter, + }, + { + [correspondingField.name]: { + gte: startOfDay(now).toISOString(), + } as DateFilter, + }, + ], }); break; } diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index 0625c562069c..898106c7e398 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -76,6 +76,7 @@ export const EditableFilterDropdownButton = ({ FilterOperand.IsNotEmpty, ViewFilterOperand.IsInPast, ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, ].includes(operand) ) { removeCombinedViewFilter(fieldId); From 7462f63164b46980972a1522f2490afa40f2547e Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 20 Sep 2024 03:16:07 +0200 Subject: [PATCH 09/33] Fix checkForDeletedAtFilter this binding --- .../graphql-query-parsers/graphql-query.parser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts index fc1be7e98b64..7c4f8ee75106 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts @@ -50,9 +50,9 @@ export class GraphqlQueryParser { }; } - private checkForDeletedAtFilter( + private checkForDeletedAtFilter = ( filter: FindOptionsWhere | FindOptionsWhere[], - ): boolean { + ): boolean => { if (Array.isArray(filter)) { return filter.some(this.checkForDeletedAtFilter); } @@ -72,7 +72,7 @@ export class GraphqlQueryParser { } return false; - } + }; parseOrder( orderBy: RecordOrderBy, From 09bf125b2497a0612ffce7fdc220823f8887c376 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Sat, 21 Sep 2024 04:15:46 +0200 Subject: [PATCH 10/33] Simplify filter value resolution --- .../ObjectFilterDropdownDateInput.tsx | 6 ++- ...turnObjectDropdownFilterIntoQueryFilter.ts | 7 ++- .../view-filter-value/resolveFilterValue.ts | 24 +++++++++ .../resolveNumberViewFilterValue.ts | 7 +++ .../resolveVariableViewFilterValue.ts | 54 ------------------- 5 files changed, 38 insertions(+), 60 deletions(-) create mode 100644 packages/twenty-front/src/modules/views/utils/view-filter-value/resolveFilterValue.ts create mode 100644 packages/twenty-front/src/modules/views/utils/view-filter-value/resolveNumberViewFilterValue.ts delete mode 100644 packages/twenty-front/src/modules/views/utils/view-filter-value/resolveVariableViewFilterValue.ts diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index a5e136d2969c..79c8bda9c1f9 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -11,7 +11,7 @@ import { VariableDateViewFilterValueDirection, VariableDateViewFilterValueUnit, } from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; -import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveVariableViewFilterValue'; +import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue'; import { useState } from 'react'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -32,7 +32,9 @@ export const ObjectFilterDropdownDateInput = () => { ); const selectedFilter = useRecoilValue(selectedFilterState); - const initialFilterValue = resolveFilterValue(selectedFilter); + const initialFilterValue = selectedFilter + ? resolveFilterValue(selectedFilter) + : null; const [internalDate, setInternalDate] = useState( initialFilterValue instanceof Date ? initialFilterValue : new Date(), ); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index a6b0dbfecb45..3957aadfc383 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -16,7 +16,7 @@ import { import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { Field, FieldMetadataType } from '~/generated/graphql'; +import { Field } from '~/generated/graphql'; import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields'; import { isDefined } from '~/utils/isDefined'; @@ -25,7 +25,7 @@ import { convertLessThanRatingToArrayOfRatingValues, convertRatingToRatingValue, } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; -import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveVariableViewFilterValue'; +import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue'; import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns'; import { format } from 'date-fns-tz'; import { Filter } from '../../object-filter-dropdown/types/Filter'; @@ -348,8 +348,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( break; case 'DATE': case 'DATE_TIME': { - const resolvedFilterValue = - resolveFilterValue(rawUIFilter); + const resolvedFilterValue = resolveFilterValue(rawUIFilter); const DATE_FORMAT_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS"; const dateISOString = diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveFilterValue.ts new file mode 100644 index 000000000000..04e32d418c6b --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveFilterValue.ts @@ -0,0 +1,24 @@ +import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType'; +import { resolveNumberViewFilterValue } from '@/views/utils/view-filter-value/resolveNumberViewFilterValue'; +import { resolveDateViewFilterValue } from './resolveDateViewFilterValue'; + +type ResolvedFilterValue = T extends 'DATE' | 'DATE_TIME' + ? ReturnType + : T extends 'NUMBER' + ? ReturnType + : string; + +export const resolveFilterValue = ( + filter: Pick & { definition: { type: T } }, +) => { + switch (filter.definition.type) { + case 'DATE': + case 'DATE_TIME': + return resolveDateViewFilterValue(filter) as ResolvedFilterValue; + case 'NUMBER': + return resolveNumberViewFilterValue(filter) as ResolvedFilterValue; + default: + return filter.value as ResolvedFilterValue; + } +}; diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveNumberViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveNumberViewFilterValue.ts new file mode 100644 index 000000000000..4e26ca096332 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveNumberViewFilterValue.ts @@ -0,0 +1,7 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; + +export const resolveNumberViewFilterValue = ( + viewFilter: Pick, +) => { + return viewFilter.value === '' ? null : +viewFilter.value; +}; diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveVariableViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveVariableViewFilterValue.ts deleted file mode 100644 index 6e3699677de4..000000000000 --- a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveVariableViewFilterValue.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; -import { ViewFilter } from '@/views/types/ViewFilter'; -import { Field, FieldMetadataType } from '~/generated/graphql'; -import { resolveDateViewFilterValue } from './resolveDateViewFilterValue'; - -export const resolveVariableViewFilterValue = ( - viewFilter: Pick, - fields: Field[], -) => { - const field = fields.find((field) => field.id === viewFilter.fieldMetadataId); - - if (!field) throw new Error('Field not found'); - - switch (field.type) { - case FieldMetadataType.Date: - case FieldMetadataType.DateTime: - return resolveDateViewFilterValue(viewFilter); - /* case FieldMetadataType.Number: - return +viewFilter.value; - */ - default: - return viewFilter.value; - } -}; - -type ResolvedFilterValue = T extends - | FieldMetadataType.Date - | FieldMetadataType.DateTime - ? ReturnType - : T extends FieldMetadataType.Number - ? number - : string; - -export const resolveFilterValue = ( - filter?: - | (Pick & { - definition: { - type: Filter['definition']['type']; - }; - }) - | null, -): ResolvedFilterValue | null => { - if (!filter || !filter.value) return null; - - switch (filter.definition.type) { - case FieldMetadataType.Date: - case FieldMetadataType.DateTime: - return resolveDateViewFilterValue(filter) as ResolvedFilterValue; - case FieldMetadataType.Number: - return Number(filter.value) as ResolvedFilterValue; - default: - return filter.value as ResolvedFilterValue; - } -}; From 7855ba699649ec60b896ced8895bec01948d082a Mon Sep 17 00:00:00 2001 From: ad-elias Date: Sat, 21 Sep 2024 14:45:00 +0200 Subject: [PATCH 11/33] Add 'is' filter operand to date. And query does not work. --- .../utils/getOperandsForFilterType.ts | 2 +- ...turnObjectDropdownFilterIntoQueryFilter.ts | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index 7b7e3f6927f1..01e9997c4643 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -34,7 +34,7 @@ export const getOperandsForFilterType = ( case 'DATE_TIME': case 'DATE': return [ - // ViewFilterOperand.Is, + ViewFilterOperand.Is, // ViewFilterOperand.IsRelative, ViewFilterOperand.IsInPast, ViewFilterOperand.IsInFuture, diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 3957aadfc383..9dc89c75c855 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -415,18 +415,22 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } case ViewFilterOperand.Is: { const isValid = resolvedFilterValue instanceof Date; - const defaultDate = new Date(); - const date = isValid ? resolvedFilterValue : defaultDate; + const date = isValid ? resolvedFilterValue : now; - const start = format(startOfDay(date), DATE_FORMAT_WITHOUT_TZ); - const end = format(endOfDay(date), DATE_FORMAT_WITHOUT_TZ); - - // Does not work + // And query is not working, lte is ignored objectRecordFilters.push({ - [correspondingField.name]: { - gte: start, - lte: end, - } as DateFilter, + and: [ + { + [correspondingField.name]: { + lte: endOfDay(date).toISOString(), + } as DateFilter, + }, + { + [correspondingField.name]: { + gte: startOfDay(date).toISOString(), + } as DateFilter, + }, + ], }); break; } @@ -445,7 +449,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( }); break; case ViewFilterOperand.IsToday: { - // And query is not working, in this case gte is ignored + // And query is not working, lte is ignored objectRecordFilters.push({ and: [ { From 031ea2720b9166186f593fbf09ae26cf973b919e Mon Sep 17 00:00:00 2001 From: ad-elias Date: Sat, 21 Sep 2024 14:55:20 +0200 Subject: [PATCH 12/33] Rename 'greater than' and 'less than' date filters --- .../utils/getOperandLabel.ts | 8 ++++++++ .../utils/getOperandsForFilterType.ts | 4 ++-- ...turnObjectDropdownFilterIntoQueryFilter.ts | 20 +++++++------------ .../modules/views/types/ViewFilterOperand.ts | 2 ++ 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts index 2218c5b6b221..b68049b51750 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts @@ -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: @@ -55,6 +59,10 @@ export const getOperandLabelShort = ( return '\u00A0> '; case ViewFilterOperand.LessThan: return '\u00A0< '; + case ViewFilterOperand.IsBefore: + return '\u00A0< '; + case ViewFilterOperand.IsAfter: + return '\u00A0> '; case ViewFilterOperand.IsInPast: return ': Past'; case ViewFilterOperand.IsInFuture: diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index 01e9997c4643..12b544603e12 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -39,8 +39,8 @@ export const getOperandsForFilterType = ( ViewFilterOperand.IsInPast, ViewFilterOperand.IsInFuture, ViewFilterOperand.IsToday, - ViewFilterOperand.GreaterThan, - ViewFilterOperand.LessThan, + ViewFilterOperand.IsBefore, + ViewFilterOperand.IsAfter, ...emptyOperands, ]; case 'RATING': diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 9dc89c75c855..6d3336d77281 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -27,7 +27,6 @@ import { } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue'; import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns'; -import { format } from 'date-fns-tz'; import { Filter } from '../../object-filter-dropdown/types/Filter'; export type ObjectDropdownFilter = Omit & { @@ -349,28 +348,23 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( case 'DATE': case 'DATE_TIME': { const resolvedFilterValue = resolveFilterValue(rawUIFilter); - - const DATE_FORMAT_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm:ss.SSS"; - const dateISOString = - resolvedFilterValue instanceof Date - ? format(resolvedFilterValue, DATE_FORMAT_WITHOUT_TZ) - : '2023-01-01T00:00:00.000'; - const now = roundToNearestMinutes(new Date()); + const date = + resolvedFilterValue instanceof Date ? resolvedFilterValue : now; switch (rawUIFilter.operand) { - case ViewFilterOperand.GreaterThan: { + case ViewFilterOperand.IsAfter: { objectRecordFilters.push({ [correspondingField.name]: { - gte: dateISOString, + gt: date.toISOString(), } as DateFilter, }); break; } - case ViewFilterOperand.LessThan: { + case ViewFilterOperand.IsBefore: { objectRecordFilters.push({ [correspondingField.name]: { - lte: dateISOString, + lt: date.toISOString(), } as DateFilter, }); break; @@ -408,7 +402,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( objectRecordFilters.push({ [correspondingField.name]: { - lte: dateISOString, + lte: date.toISOString(), } as DateFilter, }); break; diff --git a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts index f9a41a545337..0d6446de9ea4 100644 --- a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts +++ b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts @@ -4,6 +4,8 @@ export enum ViewFilterOperand { IsNot = 'isNot', LessThan = 'lessThan', GreaterThan = 'greaterThan', + IsBefore = 'isBefore', + IsAfter = 'isAfter', Contains = 'contains', DoesNotContain = 'doesNotContain', IsEmpty = 'isEmpty', From 5c5b5f0ec7cac3de42c158aab8afdc38eec99d8e Mon Sep 17 00:00:00 2001 From: ad-elias Date: Sun, 22 Sep 2024 18:32:44 +0200 Subject: [PATCH 13/33] Enable relative date filtering. And query does not work, date range highlighting is not yet implemented. --- .../ObjectFilterDropdownDateInput.tsx | 9 +++- .../utils/getOperandsForFilterType.ts | 2 +- ...turnObjectDropdownFilterIntoQueryFilter.ts | 30 ++++++----- .../date/components/RelativePickerHeader.tsx | 52 +++++++++++++------ .../resolveDateViewFilterValue.ts | 34 +++++++----- 5 files changed, 84 insertions(+), 43 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index 79c8bda9c1f9..fdf1bb1a24bd 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -2,6 +2,7 @@ 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'; @@ -31,7 +32,11 @@ 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; @@ -43,7 +48,7 @@ export const ObjectFilterDropdownDateInput = () => { direction: VariableDateViewFilterValueDirection; amount: number; unit: VariableDateViewFilterValueUnit; - } | null>(null); + } | null>(initialFilterValue instanceof Date ? null : initialFilterValue); const isDateTimeInput = filterDefinitionUsedInDropdown?.type === FieldMetadataType.DateTime; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index 12b544603e12..ba1116aca320 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -35,7 +35,7 @@ export const getOperandsForFilterType = ( case 'DATE': return [ ViewFilterOperand.Is, - // ViewFilterOperand.IsRelative, + ViewFilterOperand.IsRelative, ViewFilterOperand.IsInPast, ViewFilterOperand.IsInFuture, ViewFilterOperand.IsToday, diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 6d3336d77281..36318f4f7424 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -25,8 +25,11 @@ import { convertLessThanRatingToArrayOfRatingValues, convertRatingToRatingValue, } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; +import { ViewFilterValueType } from '@/views/types/ViewFilterValueType'; +import { resolveDateViewFilterValue } from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue'; import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns'; +import { z } from 'zod'; import { Filter } from '../../object-filter-dropdown/types/Filter'; export type ObjectDropdownFilter = Omit & { @@ -380,7 +383,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( break; } case ViewFilterOperand.IsRelative: { - /* const dateRange = z + const dateRange = z .object({ start: z.date(), end: z.date() }) .safeParse(resolvedFilterValue).data; @@ -391,19 +394,20 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( const { start, end } = dateRange ?? defaultDateRange; - // Does not work - objectRecordFilters.push({ - [correspondingField.name]: { - gte: start.toISOString(), - lte: end.toISOString(), - } as DateFilter, - }); - break; */ - + // And query is not working, lte is ignored objectRecordFilters.push({ - [correspondingField.name]: { - lte: date.toISOString(), - } as DateFilter, + and: [ + { + [correspondingField.name]: { + gte: start.toISOString(), + } as DateFilter, + }, + { + [correspondingField.name]: { + lte: end.toISOString(), + } as DateFilter, + }, + ], }); break; } diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx index 52f56d23f22c..2495d3048b48 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx @@ -4,10 +4,12 @@ import { TextInput } from '@/ui/input/components/TextInput'; import { RELATIVE_DATE_DIRECTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateDirectionOptions'; import { RELATIVE_DATE_UNITS } from '@/ui/input/components/internal/date/constants/RelativeDateUnits'; import { + variableDateViewFilterValueAmountSchema, VariableDateViewFilterValueDirection, VariableDateViewFilterValueUnit, } from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; import styled from '@emotion/styled'; +import { useEffect, useState } from 'react'; const StyledContainer = styled.div` display: flex; @@ -34,6 +36,23 @@ export const RelativeDatePickerHeader = ({ unit, onChange, }: RelativeDatePickerHeaderProps) => { + const [amountString, setAmountString] = useState( + amount ? amount.toString() : '', + ); + + useEffect(() => { + setAmountString(amount ? amount.toString() : ''); + }, [amount]); + + const textInputValue = direction === 'THIS' ? '' : amountString; + const textInputPlaceholder = direction === 'THIS' ? '-' : 'Number'; + + const isUnitPlural = amount > 1 && direction !== 'THIS'; + const unitSelectOptions = RELATIVE_DATE_UNITS.map((unit) => ({ + ...unit, + label: `${unit.label}${isUnitPlural ? 's' : ''}`, + })); + return ( ({ - ...unit, - label: `${unit.label}${amount > 1 ? 's' : ''}`, - }))} + options={unitSelectOptions} /> ); diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts index 8958712a6b33..5b68d36c30dc 100644 --- a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts @@ -8,6 +8,7 @@ import { endOfMonth, endOfWeek, endOfYear, + roundToNearestMinutes, startOfDay, startOfMonth, startOfWeek, @@ -20,7 +21,7 @@ import { import { z } from 'zod'; -const variableDateViewFilterValueDirectionSchema = z.enum([ +export const variableDateViewFilterValueDirectionSchema = z.enum([ 'NEXT', 'THIS', 'PAST', @@ -30,7 +31,12 @@ export type VariableDateViewFilterValueDirection = z.infer< typeof variableDateViewFilterValueDirectionSchema >; -const variableDateViewFilterValueUnitSchema = z.enum([ +export const variableDateViewFilterValueAmountSchema = z + .number() + .int() + .positive(); + +export const variableDateViewFilterValueUnitSchema = z.enum([ 'DAY', 'WEEK', 'MONTH', @@ -42,14 +48,15 @@ export type VariableDateViewFilterValueUnit = z.infer< >; const variableDateViewFilterValueSchema = z.string().transform((value) => { - const [direction, amount, unit] = value.split('_'); + const [direction, amountStr, unit] = value.split('_'); + const amount = parseInt(amountStr); return z - .tuple([ - variableDateViewFilterValueDirectionSchema, - z.number().int().positive(), - variableDateViewFilterValueUnitSchema, - ]) - .parse([direction, parseInt(amount), unit]); + .object({ + direction: variableDateViewFilterValueDirectionSchema, + amount: variableDateViewFilterValueAmountSchema, + unit: variableDateViewFilterValueUnitSchema, + }) + .parse({ direction, amount, unit }); }); const addUnit = ( @@ -113,25 +120,28 @@ const endOfUnit = (date: Date, unit: VariableDateViewFilterValueUnit) => { }; const resolveVariableDateViewFilterValue = (value: string) => { - const [direction, amount, unit] = - variableDateViewFilterValueSchema.parse(value); - const now = new Date(); + const relativeDate = variableDateViewFilterValueSchema.parse(value); + const { direction, amount, unit } = relativeDate; + const now = roundToNearestMinutes(new Date()); switch (direction) { case 'NEXT': return { start: now, end: addUnit(now, amount, unit), + ...relativeDate, }; case 'PAST': return { start: subUnit(now, amount, unit), end: now, + ...relativeDate, }; case 'THIS': return { start: startOfUnit(now, unit), end: endOfUnit(now, unit), + ...relativeDate, }; } }; From 7e1eafe953ee6c57a24b78eabea67499b51b2626 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Mon, 23 Sep 2024 09:19:02 +0300 Subject: [PATCH 14/33] Highlight selected relative dates --- .../ObjectFilterDropdownDateInput.tsx | 4 +-- .../date/components/InternalDatePicker.tsx | 36 +++++++++++-------- .../date/utils/getHighlightedDates.ts | 18 ++++++++++ .../resolveDateViewFilterValue.ts | 14 ++++++-- 4 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index fdf1bb1a24bd..9b2157aee556 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -112,9 +112,9 @@ export const ObjectFilterDropdownDateInput = () => { return ( void; onChange?: (date: Date | null) => void; onRelativeDateChange?: ( @@ -306,9 +304,9 @@ export const InternalDatePicker = ({ isDateTimeInput, keyboardEventsDisabled, onClear, - isRelativeToNow, + isRelative, onRelativeDateChange, - relativeDate, + relativeDateFilterValue, }: InternalDatePickerProps) => { const internalDate = date ?? new Date(); @@ -457,18 +455,25 @@ export const InternalDatePicker = ({ const dateToUse = isDateTimeInput ? endOfDayInLocalTimezone : dateWithoutTime; + const relativeDate = resolveVariableDateViewFilterValue( + relativeDateFilterValue, + ); + + const highlightedDates = relativeDate + ? getHighlightedDates(relativeDate.start, relativeDate.end) + : undefined; + + const selectedDates = isRelative ? highlightedDates : [dateToUse]; + return ( - +
- isRelativeToNow ? ( + isRelative ? (
{clearable && ( diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts new file mode 100644 index 000000000000..3614eb2dc112 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts @@ -0,0 +1,18 @@ +import { addDays, addMonths, startOfDay, subMonths } from 'date-fns'; + +export const getHighlightedDates = (start: Date, end: Date): Date[] => { + const highlightedDates: Date[] = []; + const currentDate = startOfDay(new Date()); + const twoMonthsAgo = subMonths(currentDate, 2); + const twoMonthsFromNow = addMonths(currentDate, 2); + + let dateToHighlight = start < twoMonthsAgo ? twoMonthsAgo : start; + const lastDate = end > twoMonthsFromNow ? twoMonthsFromNow : end; + + while (dateToHighlight <= lastDate) { + highlightedDates.push(dateToHighlight); + dateToHighlight = addDays(dateToHighlight, 1); + } + + return highlightedDates; +}; diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts index 5b68d36c30dc..c8ddc45bb095 100644 --- a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts @@ -119,8 +119,11 @@ const endOfUnit = (date: Date, unit: VariableDateViewFilterValueUnit) => { } }; -const resolveVariableDateViewFilterValue = (value: string) => { - const relativeDate = variableDateViewFilterValueSchema.parse(value); +const resolveVariableDateViewFilterValueFromRelativeDate = (relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount: number; + unit: VariableDateViewFilterValueUnit; +}) => { const { direction, amount, unit } = relativeDate; const now = roundToNearestMinutes(new Date()); @@ -146,6 +149,13 @@ const resolveVariableDateViewFilterValue = (value: string) => { } }; +export const resolveVariableDateViewFilterValue = (value?: string | null) => { + if (!value) return null; + + const relativeDate = variableDateViewFilterValueSchema.parse(value); + return resolveVariableDateViewFilterValueFromRelativeDate(relativeDate); +}; + export const resolveDateViewFilterValue = ( viewFilter: Pick, ) => { From 10f4f872cc97c6aafc99f01d7d7670e91def2686 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Mon, 23 Sep 2024 10:07:19 +0300 Subject: [PATCH 15/33] Remove unused code --- .../components/ObjectFilterDropdownDateInput.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index 9b2157aee556..221848344ac0 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -44,12 +44,6 @@ export const ObjectFilterDropdownDateInput = () => { initialFilterValue instanceof Date ? initialFilterValue : new Date(), ); - const [relativeDate, setRelativeDate] = useState<{ - direction: VariableDateViewFilterValueDirection; - amount: number; - unit: VariableDateViewFilterValueUnit; - } | null>(initialFilterValue instanceof Date ? null : initialFilterValue); - const isDateTimeInput = filterDefinitionUsedInDropdown?.type === FieldMetadataType.DateTime; @@ -82,8 +76,6 @@ export const ObjectFilterDropdownDateInput = () => { unit: VariableDateViewFilterValueUnit; } | null, ) => { - setRelativeDate(relativeDate); - if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return; const value = relativeDate From 3b9c891644e5565bb989f74c0accb3be61716c53 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Mon, 23 Sep 2024 15:27:22 +0300 Subject: [PATCH 16/33] Fix broken date selection --- .../components/internal/date/components/InternalDatePicker.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index d340045d9316..d2bacd3763bc 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -470,6 +470,7 @@ export const InternalDatePicker = ({
{clearable && ( From 3fc73c52fa8dbf949e85d80180251bec3bdecf62 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Mon, 23 Sep 2024 20:31:17 +0300 Subject: [PATCH 17/33] Refactor --- .../ObjectFilterDropdownDateInput.tsx | 12 +++++++++- .../date/components/InternalDatePicker.tsx | 22 ++++++++++--------- .../date/utils/getHighlightedDates.ts | 8 ++++++- .../resolveDateViewFilterValue.ts | 2 +- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index 221848344ac0..a0be6487e5d5 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -102,11 +102,21 @@ export const ObjectFilterDropdownDateInput = () => { const isRelativeOperand = selectedOperandInDropdown === ViewFilterOperand.IsRelative; + const resolvedValue = selectedFilter + ? resolveFilterValue(selectedFilter) + : null; + + const relativeDate = + resolvedValue && !(resolvedValue instanceof Date) + ? resolvedValue + : undefined; + return ( void; onChange?: (date: Date | null) => void; onRelativeDateChange?: ( @@ -305,8 +312,9 @@ export const InternalDatePicker = ({ keyboardEventsDisabled, onClear, isRelative, + relativeDate, onRelativeDateChange, - relativeDateFilterValue, + highlightedDateRange, }: InternalDatePickerProps) => { const internalDate = date ?? new Date(); @@ -455,13 +463,7 @@ export const InternalDatePicker = ({ const dateToUse = isDateTimeInput ? endOfDayInLocalTimezone : dateWithoutTime; - const relativeDate = resolveVariableDateViewFilterValue( - relativeDateFilterValue, - ); - - const highlightedDates = relativeDate - ? getHighlightedDates(relativeDate.start, relativeDate.end) - : undefined; + const highlightedDates = getHighlightedDates(highlightedDateRange); const selectedDates = isRelative ? highlightedDates : [dateToUse]; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts index 3614eb2dc112..89e193965a6c 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts @@ -1,6 +1,12 @@ import { addDays, addMonths, startOfDay, subMonths } from 'date-fns'; -export const getHighlightedDates = (start: Date, end: Date): Date[] => { +export const getHighlightedDates = (highlightedDateRange?: { + start: Date; + end: Date; +}): Date[] => { + if (!highlightedDateRange) return []; + const { start, end } = highlightedDateRange; + const highlightedDates: Date[] = []; const currentDate = startOfDay(new Date()); const twoMonthsAgo = subMonths(currentDate, 2); diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts index c8ddc45bb095..6fbd304d13c5 100644 --- a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts @@ -149,7 +149,7 @@ const resolveVariableDateViewFilterValueFromRelativeDate = (relativeDate: { } }; -export const resolveVariableDateViewFilterValue = (value?: string | null) => { +const resolveVariableDateViewFilterValue = (value?: string | null) => { if (!value) return null; const relativeDate = variableDateViewFilterValueSchema.parse(value); From b8dc5e66eb69c7af16364551e1c6cf6126a9a3ee Mon Sep 17 00:00:00 2001 From: ad-elias Date: Tue, 24 Sep 2024 09:14:24 +0300 Subject: [PATCH 18/33] Remove comments --- .../utils/turnObjectDropdownFilterIntoQueryFilter.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 36318f4f7424..71b400c77cd7 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -394,7 +394,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( const { start, end } = dateRange ?? defaultDateRange; - // And query is not working, lte is ignored objectRecordFilters.push({ and: [ { @@ -415,7 +414,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( const isValid = resolvedFilterValue instanceof Date; const date = isValid ? resolvedFilterValue : now; - // And query is not working, lte is ignored objectRecordFilters.push({ and: [ { @@ -447,7 +445,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( }); break; case ViewFilterOperand.IsToday: { - // And query is not working, lte is ignored objectRecordFilters.push({ and: [ { From 20d652f2e7d642454af0d9e3c5e611892d0eac61 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Tue, 24 Sep 2024 16:26:10 +0300 Subject: [PATCH 19/33] Improve naming --- .../date/components/RelativePickerHeader.tsx | 9 ++++----- .../date/constants/RelativeDateDirectionOptions.ts | 10 ---------- .../constants/RelativeDateDirectionSelectOptions.ts | 13 +++++++++++++ ...ateUnits.ts => RelativeDateUnitSelectOptions.ts} | 6 ++++-- .../internal/date/utils/getHighlightedDates.ts | 8 ++++---- 5 files changed, 25 insertions(+), 21 deletions(-) delete mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionOptions.ts create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionSelectOptions.ts rename packages/twenty-front/src/modules/ui/input/components/internal/date/constants/{RelativeDateUnits.ts => RelativeDateUnitSelectOptions.ts} (75%) diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx index 2495d3048b48..8c6aaf325640 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativePickerHeader.tsx @@ -1,8 +1,7 @@ +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 { Select } from '@/ui/input/components/Select'; import { TextInput } from '@/ui/input/components/TextInput'; - -import { RELATIVE_DATE_DIRECTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateDirectionOptions'; -import { RELATIVE_DATE_UNITS } from '@/ui/input/components/internal/date/constants/RelativeDateUnits'; import { variableDateViewFilterValueAmountSchema, VariableDateViewFilterValueDirection, @@ -48,7 +47,7 @@ export const RelativeDatePickerHeader = ({ const textInputPlaceholder = direction === 'THIS' ? '-' : 'Number'; const isUnitPlural = amount > 1 && direction !== 'THIS'; - const unitSelectOptions = RELATIVE_DATE_UNITS.map((unit) => ({ + const unitSelectOptions = RELATIVE_DATE_UNITS_SELECT_OPTIONS.map((unit) => ({ ...unit, label: `${unit.label}${isUnitPlural ? 's' : ''}`, })); @@ -65,7 +64,7 @@ export const RelativeDatePickerHeader = ({ unit: unit, }) } - options={RELATIVE_DATE_DIRECTIONS} + options={RELATIVE_DATE_DIRECTION_SELECT_OPTIONS} /> twoMonthsFromNow ? twoMonthsFromNow : end; + let dateToHighlight = start < minDate ? minDate : start; + const lastDate = end > maxDate ? maxDate : end; while (dateToHighlight <= lastDate) { highlightedDates.push(dateToHighlight); From c2dcef5d53eb9e978bf1597a33a97672eb4a81d1 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Tue, 24 Sep 2024 17:37:04 +0300 Subject: [PATCH 20/33] Default to ViewFilterValueType.STATIC --- .../view/standard-objects/view-filter.workspace-entity.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts index 6cd8e00b092e..8a142a770415 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts @@ -60,6 +60,7 @@ export class ViewFilterWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.SELECT, label: 'Value type', description: 'View filter value type', + defaultValue: `'${ViewFilterValueType.STATIC}'`, options: [ { value: ViewFilterValueType.STATIC, @@ -75,8 +76,7 @@ export class ViewFilterWorkspaceEntity extends BaseWorkspaceEntity { }, ], }) - @WorkspaceIsNullable() - valueType?: string | null; + valueType: string | null; @WorkspaceField({ standardId: VIEW_FILTER_STANDARD_FIELD_IDS.displayValue, From 26d03e73c82ae78f548c2add8cc34a31a303e17d Mon Sep 17 00:00:00 2001 From: ad-elias Date: Tue, 24 Sep 2024 21:57:48 +0300 Subject: [PATCH 21/33] Improve readability --- package.json | 1 + .../ObjectFilterDropdownDateInput.tsx | 3 ++- .../utils/getRelativeDateDisplayValue.ts | 12 +++++++----- .../components/AbsoluteDatePickerHeader.tsx | 18 ++---------------- .../date/utils/getMonthSelectOptions.ts | 16 ++++++++++++++++ yarn.lock | 8 ++++++++ 6 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getMonthSelectOptions.ts diff --git a/package.json b/package.json index 640ba3bb5c96..1e57fde1ae42 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index a0be6487e5d5..9f4da78e88f3 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -14,6 +14,7 @@ import { } 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'; export const ObjectFilterDropdownDateInput = () => { @@ -58,7 +59,7 @@ export const ObjectFilterDropdownDateInput = () => { value: newDate?.toISOString() ?? '', valueType: ViewFilterValueType.STATIC, operand: selectedOperandInDropdown, - displayValue: newDate + displayValue: isDefined(newDate) ? isDateTimeInput ? newDate.toLocaleString() : newDate.toLocaleDateString() diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts index c39d39f662a6..2546015a038a 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts @@ -2,8 +2,8 @@ import { VariableDateViewFilterValueDirection, VariableDateViewFilterValueUnit, } from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; +import { plural } from 'pluralize'; import { capitalize } from '~/utils/string/capitalize'; - export const getRelativeDateDisplayValue = ( relativeDate: { direction: VariableDateViewFilterValueDirection; @@ -12,9 +12,11 @@ export const getRelativeDateDisplayValue = ( } | null, ) => { if (!relativeDate) return ''; + const { direction, amount, unit } = relativeDate; + + const directionStr = capitalize(direction.toLowerCase()); + const amountStr = direction === 'THIS' ? '' : amount; + const unitStr = amount > 1 ? plural(unit.toLowerCase()) : unit.toLowerCase(); - const isPlural = relativeDate.amount > 1; - return capitalize( - `${relativeDate.direction} ${relativeDate.amount} ${relativeDate.unit}${isPlural ? 's' : ''}`.toLowerCase(), - ); + return `${directionStr} ${amountStr} ${unitStr}`; }; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx index dcfa913363b6..1efc985d34f6 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx @@ -6,6 +6,7 @@ import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { Select } from '@/ui/input/components/Select'; import { DateTimeInput } from '@/ui/input/components/internal/date/components/DateTimeInput'; +import { getMonthSelectOptions } from '@/ui/input/components/internal/date/utils/getMonthSelectOptions'; import { MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID, MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID, @@ -22,21 +23,6 @@ const StyledCustomDatePickerHeader = styled.div` gap: ${({ theme }) => theme.spacing(1)}; `; -const months = [ - { label: 'January', value: 0 }, - { label: 'February', value: 1 }, - { label: 'March', value: 2 }, - { label: 'April', value: 3 }, - { label: 'May', value: 4 }, - { label: 'June', value: 5 }, - { label: 'July', value: 6 }, - { label: 'August', value: 7 }, - { label: 'September', value: 8 }, - { label: 'October', value: 9 }, - { label: 'November', value: 10 }, - { label: 'December', value: 11 }, -]; - const years = Array.from( { length: 200 }, (_, i) => new Date().getFullYear() + 5 - i, @@ -90,7 +76,7 @@ export const AbsoluteDatePickerHeader = ({ @@ -88,6 +89,7 @@ export const RelativeDatePickerHeader = ({ disabled={direction === 'THIS'} />