From 32a876bbd4d90488af26c08b3380564aec44d01c Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Thu, 27 Nov 2025 22:33:10 +0530 Subject: [PATCH] part 4 of filter/sort drilldown onChartDatum click (#16142) --- .../buildDateFilterForDayGranularity.test.ts | 80 +++++++ ...uildDateRangeFiltersForGranularity.test.ts | 200 ++++++++++++++++++ .../calculateQuarterDateRange.test.ts | 72 +++++++ .../utils/buildDateFilterForDayGranularity.ts | 55 +++++ .../buildDateRangeFiltersForGranularity.ts | 67 ++++++ .../graph/utils/buildFilterFromChartBucket.ts | 55 ++++- .../graph/utils/buildFiltersFromDateRange.ts | 39 ++++ .../graph/utils/calculateQuarterDateRange.ts | 38 ++++ .../graph/utils/isCyclicalDateGranularity.ts | 13 ++ .../graph/utils/isTimeRangeDateGranularity.ts | 14 ++ 10 files changed, 629 insertions(+), 4 deletions(-) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/buildDateFilterForDayGranularity.test.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/buildDateRangeFiltersForGranularity.test.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/calculateQuarterDateRange.test.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildDateFilterForDayGranularity.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildDateRangeFiltersForGranularity.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildFiltersFromDateRange.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/calculateQuarterDateRange.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/isCyclicalDateGranularity.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/isTimeRangeDateGranularity.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/buildDateFilterForDayGranularity.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/buildDateFilterForDayGranularity.test.ts new file mode 100644 index 0000000000..af96a8e3dc --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/buildDateFilterForDayGranularity.test.ts @@ -0,0 +1,80 @@ +import { ViewFilterOperand } from 'twenty-shared/types'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { buildDateFilterForDayGranularity } from '../buildDateFilterForDayGranularity'; + +describe('buildDateFilterForDayGranularity', () => { + describe('DATE field type', () => { + it('should return single IS filter with plain date for DATE field', () => { + const date = new Date('2024-03-15T10:00:00Z'); + const result = buildDateFilterForDayGranularity( + date, + FieldMetadataType.DATE, + 'createdAt', + ); + + expect(result).toHaveLength(1); + expect(result[0].fieldName).toBe('createdAt'); + expect(result[0].operand).toBe(ViewFilterOperand.IS); + expect(result[0].value).toMatch(/^\d{4}-\d{2}-\d{2}$/); + }); + }); + + describe('DATE_TIME field type', () => { + it('should return IS_AFTER and IS_BEFORE filters for DATE_TIME field', () => { + const date = new Date('2024-03-15T10:00:00Z'); + const result = buildDateFilterForDayGranularity( + date, + FieldMetadataType.DATE_TIME, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + expect(result[0].value).toMatch(/^\d{4}-\d{2}-\d{2}T/); + expect(result[1].value).toMatch(/^\d{4}-\d{2}-\d{2}T/); + }); + + it('should apply timezone for DATE_TIME field when provided', () => { + const date = new Date('2024-03-15T10:00:00Z'); + const timezone = 'America/New_York'; + + const result = buildDateFilterForDayGranularity( + date, + FieldMetadataType.DATE_TIME, + 'createdAt', + timezone, + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + }); + }); + + describe('unsupported field types', () => { + it('should return empty array for non-date field types', () => { + const date = new Date('2024-03-15T10:00:00Z'); + const result = buildDateFilterForDayGranularity( + date, + FieldMetadataType.TEXT as FieldMetadataType.DATE, + 'name', + ); + + expect(result).toEqual([]); + }); + }); + + describe('field name handling', () => { + it('should use provided fieldName in filters', () => { + const date = new Date('2024-03-15'); + const result = buildDateFilterForDayGranularity( + date, + FieldMetadataType.DATE, + 'customFieldName', + ); + + expect(result[0].fieldName).toBe('customFieldName'); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/buildDateRangeFiltersForGranularity.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/buildDateRangeFiltersForGranularity.test.ts new file mode 100644 index 0000000000..e7cac93549 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/buildDateRangeFiltersForGranularity.test.ts @@ -0,0 +1,200 @@ +import { + ObjectRecordGroupByDateGranularity, + ViewFilterOperand, +} from 'twenty-shared/types'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { buildDateRangeFiltersForGranularity } from '../buildDateRangeFiltersForGranularity'; + +describe('buildDateRangeFiltersForGranularity', () => { + describe('WEEK granularity', () => { + it('should return IS_AFTER and IS_BEFORE filters for DATE field', () => { + const testDate = new Date('2024-03-15'); + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.WEEK, + FieldMetadataType.DATE, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + }); + + it('should return ISO strings for DATE_TIME field', () => { + const testDate = new Date('2024-03-15T10:00:00Z'); + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.WEEK, + FieldMetadataType.DATE_TIME, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].value).toMatch(/^\d{4}-\d{2}-\d{2}T/); + expect(result[1].value).toMatch(/^\d{4}-\d{2}-\d{2}T/); + }); + }); + + describe('MONTH granularity', () => { + it('should return IS_AFTER and IS_BEFORE filters for DATE field', () => { + const testDate = new Date('2024-03-15'); + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.MONTH, + FieldMetadataType.DATE, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + }); + + it('should return ISO strings for DATE_TIME field without timezone', () => { + const testDate = new Date('2024-03-15T10:00:00Z'); + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.MONTH, + FieldMetadataType.DATE_TIME, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + expect(result[0].value).toMatch(/^\d{4}-\d{2}-\d{2}T/); + expect(result[1].value).toMatch(/^\d{4}-\d{2}-\d{2}T/); + }); + + it('should apply timezone for DATE_TIME field when provided', () => { + const testDate = new Date('2024-03-15T10:00:00Z'); + const timezone = 'America/New_York'; + + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.MONTH, + FieldMetadataType.DATE_TIME, + 'createdAt', + timezone, + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + }); + }); + + describe('QUARTER granularity', () => { + it('should return filters for Q1 date', () => { + const testDate = new Date('2024-02-15'); + + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.QUARTER, + FieldMetadataType.DATE, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + }); + + it('should return ISO strings for DATE_TIME field', () => { + const testDate = new Date('2024-02-15'); + + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.QUARTER, + FieldMetadataType.DATE_TIME, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + expect(result[0].value).toMatch(/^\d{4}-\d{2}-\d{2}T/); + expect(result[1].value).toMatch(/^\d{4}-\d{2}-\d{2}T/); + }); + }); + + describe('YEAR granularity', () => { + it('should return start and end of year for DATE field', () => { + const testDate = new Date('2024-06-15'); + + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.YEAR, + FieldMetadataType.DATE, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + }); + + it('should return ISO strings for DATE_TIME field', () => { + const testDate = new Date('2024-06-15T10:00:00Z'); + + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.YEAR, + FieldMetadataType.DATE_TIME, + 'createdAt', + ); + + expect(result).toHaveLength(2); + expect(result[0].value).toMatch(/^2024-01-01T/); + expect(result[1].value).toMatch(/^2024-12-31T/); + }); + + it('should apply timezone for DATE_TIME field', () => { + const testDate = new Date('2024-06-15T10:00:00Z'); + const timezone = 'Asia/Tokyo'; + + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.YEAR, + FieldMetadataType.DATE_TIME, + 'createdAt', + timezone, + ); + + expect(result).toHaveLength(2); + expect(result[0].operand).toBe(ViewFilterOperand.IS_AFTER); + expect(result[1].operand).toBe(ViewFilterOperand.IS_BEFORE); + }); + }); + + describe('Field name handling', () => { + it('should use provided fieldName in filters', () => { + const testDate = new Date('2024-03-15'); + + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.MONTH, + FieldMetadataType.DATE, + 'customFieldName', + ); + + expect(result[0].fieldName).toBe('customFieldName'); + expect(result[1].fieldName).toBe('customFieldName'); + }); + + it('should handle subField-style fieldName', () => { + const testDate = new Date('2024-03-15'); + + const result = buildDateRangeFiltersForGranularity( + testDate, + ObjectRecordGroupByDateGranularity.MONTH, + FieldMetadataType.DATE, + 'parent.subField', + ); + + expect(result[0].fieldName).toBe('parent.subField'); + expect(result[1].fieldName).toBe('parent.subField'); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/calculateQuarterDateRange.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/calculateQuarterDateRange.test.ts new file mode 100644 index 0000000000..e7347e27e7 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/calculateQuarterDateRange.test.ts @@ -0,0 +1,72 @@ +import { calculateQuarterDateRange } from '../calculateQuarterDateRange'; + +describe('calculateQuarterDateRange', () => { + describe('Q1 (January - March)', () => { + it('should return Q1 range for January date', () => { + const date = new Date('2024-01-15'); + const result = calculateQuarterDateRange(date); + + expect(result.rangeStartDate.getFullYear()).toBe(2024); + expect(result.rangeStartDate.getMonth()).toBe(0); + expect(result.rangeStartDate.getDate()).toBe(1); + + expect(result.rangeEndDate.getFullYear()).toBe(2024); + expect(result.rangeEndDate.getMonth()).toBe(2); + expect(result.rangeEndDate.getDate()).toBe(31); + }); + + it('should return Q1 range for March date', () => { + const date = new Date('2024-03-20'); + const result = calculateQuarterDateRange(date); + + expect(result.rangeStartDate.getMonth()).toBe(0); + expect(result.rangeEndDate.getMonth()).toBe(2); + }); + }); + + describe('Q2 (April - June)', () => { + it('should return Q2 range for April date', () => { + const date = new Date('2024-04-15'); + const result = calculateQuarterDateRange(date); + + expect(result.rangeStartDate.getMonth()).toBe(3); + expect(result.rangeStartDate.getDate()).toBe(1); + expect(result.rangeEndDate.getMonth()).toBe(5); + expect(result.rangeEndDate.getDate()).toBe(30); + }); + }); + + describe('Q3 (July - September)', () => { + it('should return Q3 range for August date', () => { + const date = new Date('2024-08-15'); + const result = calculateQuarterDateRange(date); + + expect(result.rangeStartDate.getMonth()).toBe(6); + expect(result.rangeStartDate.getDate()).toBe(1); + expect(result.rangeEndDate.getMonth()).toBe(8); + expect(result.rangeEndDate.getDate()).toBe(30); + }); + }); + + describe('Q4 (October - December)', () => { + it('should return Q4 range for November date', () => { + const date = new Date('2024-11-15'); + const result = calculateQuarterDateRange(date); + + expect(result.rangeStartDate.getMonth()).toBe(9); + expect(result.rangeStartDate.getDate()).toBe(1); + expect(result.rangeEndDate.getMonth()).toBe(11); + expect(result.rangeEndDate.getDate()).toBe(31); + }); + }); + + describe('year boundary', () => { + it('should handle year correctly for Q1', () => { + const date = new Date('2025-02-15'); + const result = calculateQuarterDateRange(date); + + expect(result.rangeStartDate.getFullYear()).toBe(2025); + expect(result.rangeEndDate.getFullYear()).toBe(2025); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildDateFilterForDayGranularity.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildDateFilterForDayGranularity.ts new file mode 100644 index 0000000000..f2f8a65d45 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildDateFilterForDayGranularity.ts @@ -0,0 +1,55 @@ +import { TZDate } from '@date-fns/tz'; +import { ViewFilterOperand } from 'twenty-shared/types'; +import { + getEndUnitOfDateTime, + getPlainDateFromDate, + getStartUnitOfDateTime, +} from 'twenty-shared/utils'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +type ChartFilter = { + fieldName: string; + operand: ViewFilterOperand; + value: string; +}; + +export const buildDateFilterForDayGranularity = ( + parsedBucketDate: Date, + fieldType: FieldMetadataType, + fieldName: string, + timezone?: string, +): ChartFilter[] => { + if (fieldType === FieldMetadataType.DATE) { + return [ + { + fieldName, + operand: ViewFilterOperand.IS, + value: getPlainDateFromDate(parsedBucketDate), + }, + ]; + } + + if (fieldType === FieldMetadataType.DATE_TIME) { + const dateInTimezone = timezone + ? new TZDate(parsedBucketDate, timezone) + : parsedBucketDate; + + const startOfDayDate = getStartUnitOfDateTime(dateInTimezone, 'DAY'); + const endOfDayDate = getEndUnitOfDateTime(dateInTimezone, 'DAY'); + + return [ + { + fieldName, + operand: ViewFilterOperand.IS_AFTER, + value: startOfDayDate.toISOString(), + }, + { + fieldName, + operand: ViewFilterOperand.IS_BEFORE, + value: endOfDayDate.toISOString(), + }, + ]; + } + + return []; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildDateRangeFiltersForGranularity.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildDateRangeFiltersForGranularity.ts new file mode 100644 index 0000000000..87cacea19f --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildDateRangeFiltersForGranularity.ts @@ -0,0 +1,67 @@ +import { buildFiltersFromDateRange } from '@/page-layout/widgets/graph/utils/buildFiltersFromDateRange'; +import { calculateQuarterDateRange } from '@/page-layout/widgets/graph/utils/calculateQuarterDateRange'; +import { TZDate } from '@date-fns/tz'; +import { + ObjectRecordGroupByDateGranularity, + type ViewFilterOperand, +} from 'twenty-shared/types'; +import { + getEndUnitOfDateTime, + getStartUnitOfDateTime, +} from 'twenty-shared/utils'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export type RangeChartFilter = { + fieldName: string; + operand: ViewFilterOperand; + value: string; +}; + +export const buildDateRangeFiltersForGranularity = ( + parsedBucketDate: Date, + dateGranularity: ObjectRecordGroupByDateGranularity, + fieldType: FieldMetadataType, + fieldName: string, + timezone?: string, +): RangeChartFilter[] => { + const dateInTimezone = + fieldType === FieldMetadataType.DATE_TIME && timezone + ? new TZDate(parsedBucketDate, timezone) + : parsedBucketDate; + + if (dateGranularity === ObjectRecordGroupByDateGranularity.WEEK) { + return buildFiltersFromDateRange( + getStartUnitOfDateTime(dateInTimezone, 'WEEK'), + getEndUnitOfDateTime(dateInTimezone, 'WEEK'), + fieldType, + fieldName, + ); + } + + if (dateGranularity === ObjectRecordGroupByDateGranularity.MONTH) { + return buildFiltersFromDateRange( + getStartUnitOfDateTime(dateInTimezone, 'MONTH'), + getEndUnitOfDateTime(dateInTimezone, 'MONTH'), + fieldType, + fieldName, + ); + } + + if (dateGranularity === ObjectRecordGroupByDateGranularity.QUARTER) { + const quarterRange = calculateQuarterDateRange(dateInTimezone, timezone); + + return buildFiltersFromDateRange( + quarterRange.rangeStartDate, + quarterRange.rangeEndDate, + fieldType, + fieldName, + ); + } + + return buildFiltersFromDateRange( + getStartUnitOfDateTime(dateInTimezone, 'YEAR'), + getEndUnitOfDateTime(dateInTimezone, 'YEAR'), + fieldType, + fieldName, + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildFilterFromChartBucket.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildFilterFromChartBucket.ts index ab5a33529e..bb624fbfb1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildFilterFromChartBucket.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildFilterFromChartBucket.ts @@ -1,11 +1,19 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; +import { buildDateFilterForDayGranularity } from '@/page-layout/widgets/graph/utils/buildDateFilterForDayGranularity'; +import { buildDateRangeFiltersForGranularity } from '@/page-layout/widgets/graph/utils/buildDateRangeFiltersForGranularity'; +import { isCyclicalDateGranularity } from '@/page-layout/widgets/graph/utils/isCyclicalDateGranularity'; +import { isTimeRangeDateGranularity } from '@/page-layout/widgets/graph/utils/isTimeRangeDateGranularity'; import { isNonEmptyString } from '@sniptt/guards'; import { + ObjectRecordGroupByDateGranularity, ViewFilterOperand, - type ObjectRecordGroupByDateGranularity, } from 'twenty-shared/types'; -import { getFilterTypeFromFieldType, isDefined } from 'twenty-shared/utils'; +import { + getFilterTypeFromFieldType, + isDefined, + isFieldMetadataDateKind, +} from 'twenty-shared/utils'; import { FieldMetadataType } from '~/generated-metadata/graphql'; type ChartFilter = { @@ -17,9 +25,9 @@ type ChartFilter = { type BuildFilterFromChartBucketParams = { fieldMetadataItem: FieldMetadataItem; bucketRawValue: unknown; - dateGranularity?: ObjectRecordGroupByDateGranularity | null; // TODO: Will be used for date filtering + dateGranularity?: ObjectRecordGroupByDateGranularity | null; subFieldName?: string | null; - timezone?: string; // TODO: Will be used for date filtering + timezone?: string; }; const formatChartFilterValue = ( @@ -43,7 +51,9 @@ const formatChartFilterValue = ( export const buildFilterFromChartBucket = ({ fieldMetadataItem, bucketRawValue, + dateGranularity, subFieldName, + timezone, }: BuildFilterFromChartBucketParams): ChartFilter[] => { const fieldName = isNonEmptyString(subFieldName) ? `${fieldMetadataItem.name}.${subFieldName}` @@ -59,6 +69,43 @@ export const buildFilterFromChartBucket = ({ ]; } + if (isFieldMetadataDateKind(fieldMetadataItem.type)) { + const parsedBucketDate = new Date(String(bucketRawValue)); + + if (isNaN(parsedBucketDate.getTime())) { + return []; + } + + if (isCyclicalDateGranularity(dateGranularity)) { + return []; + } + + if ( + !isDefined(dateGranularity) || + dateGranularity === ObjectRecordGroupByDateGranularity.DAY || + dateGranularity === ObjectRecordGroupByDateGranularity.NONE + ) { + return buildDateFilterForDayGranularity( + parsedBucketDate, + fieldMetadataItem.type, + fieldName, + timezone, + ); + } + + if (isTimeRangeDateGranularity(dateGranularity)) { + return buildDateRangeFiltersForGranularity( + parsedBucketDate, + dateGranularity, + fieldMetadataItem.type, + fieldName, + timezone, + ); + } + + return []; + } + const availableOperands = getRecordFilterOperands({ filterType: getFilterTypeFromFieldType(fieldMetadataItem.type), subFieldName: subFieldName ?? undefined, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildFiltersFromDateRange.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildFiltersFromDateRange.ts new file mode 100644 index 0000000000..fab7f752dd --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildFiltersFromDateRange.ts @@ -0,0 +1,39 @@ +import { type RangeChartFilter } from '@/page-layout/widgets/graph/utils/buildDateRangeFiltersForGranularity'; +import { ViewFilterOperand } from 'twenty-shared/types'; +import { getPlainDateFromDate } from 'twenty-shared/utils'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export const buildFiltersFromDateRange = ( + rangeStartDate: Date, + rangeEndDate: Date, + fieldType: FieldMetadataType, + fieldName: string, +): RangeChartFilter[] => { + if (fieldType === FieldMetadataType.DATE_TIME) { + return [ + { + fieldName, + operand: ViewFilterOperand.IS_AFTER, + value: rangeStartDate.toISOString(), + }, + { + fieldName, + operand: ViewFilterOperand.IS_BEFORE, + value: rangeEndDate.toISOString(), + }, + ]; + } + + return [ + { + fieldName, + operand: ViewFilterOperand.IS_AFTER, + value: getPlainDateFromDate(rangeStartDate), + }, + { + fieldName, + operand: ViewFilterOperand.IS_BEFORE, + value: getPlainDateFromDate(rangeEndDate), + }, + ]; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/calculateQuarterDateRange.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/calculateQuarterDateRange.ts new file mode 100644 index 0000000000..2f88047e8a --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/calculateQuarterDateRange.ts @@ -0,0 +1,38 @@ +import { TZDate } from '@date-fns/tz'; +import { getEndUnitOfDateTime } from 'twenty-shared/utils'; + +type QuarterDateRange = { + rangeStartDate: Date; + rangeEndDate: Date; +}; + +export const calculateQuarterDateRange = ( + parsedBucketDate: Date, + timezone?: string, +): QuarterDateRange => { + const quarterStartMonthIndex = + Math.floor(parsedBucketDate.getMonth() / 3) * 3; + const rangeStartDate = timezone + ? new TZDate( + parsedBucketDate.getFullYear(), + quarterStartMonthIndex, + 1, + timezone, + ) + : new Date(parsedBucketDate.getFullYear(), quarterStartMonthIndex, 1); + + const quarterFinalMonthIndex = quarterStartMonthIndex + 2; + const rangeEndDate = getEndUnitOfDateTime( + timezone + ? new TZDate( + parsedBucketDate.getFullYear(), + quarterFinalMonthIndex, + 1, + timezone, + ) + : new Date(parsedBucketDate.getFullYear(), quarterFinalMonthIndex, 1), + 'MONTH', + ); + + return { rangeStartDate, rangeEndDate }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/isCyclicalDateGranularity.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/isCyclicalDateGranularity.ts new file mode 100644 index 0000000000..b99334e243 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/isCyclicalDateGranularity.ts @@ -0,0 +1,13 @@ +import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +export const isCyclicalDateGranularity = ( + granularity?: ObjectRecordGroupByDateGranularity | null, +): boolean => { + if (!isDefined(granularity)) return false; + return [ + ObjectRecordGroupByDateGranularity.DAY_OF_THE_WEEK, + ObjectRecordGroupByDateGranularity.MONTH_OF_THE_YEAR, + ObjectRecordGroupByDateGranularity.QUARTER_OF_THE_YEAR, + ].includes(granularity); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/isTimeRangeDateGranularity.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/isTimeRangeDateGranularity.ts new file mode 100644 index 0000000000..46e58f7d12 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/isTimeRangeDateGranularity.ts @@ -0,0 +1,14 @@ +import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +export const isTimeRangeDateGranularity = ( + granularity?: ObjectRecordGroupByDateGranularity | null, +): boolean => { + if (!isDefined(granularity)) return false; + return [ + ObjectRecordGroupByDateGranularity.WEEK, + ObjectRecordGroupByDateGranularity.MONTH, + ObjectRecordGroupByDateGranularity.QUARTER, + ObjectRecordGroupByDateGranularity.YEAR, + ].includes(granularity); +};