diff --git a/packages/features/data-table/components/filters/DateRangeFilter.tsx b/packages/features/data-table/components/filters/DateRangeFilter.tsx index 230fc55f13..1e000c1d09 100644 --- a/packages/features/data-table/components/filters/DateRangeFilter.tsx +++ b/packages/features/data-table/components/filters/DateRangeFilter.tsx @@ -25,6 +25,7 @@ import { PRESET_OPTIONS, getDefaultStartDate, getDefaultEndDate, + getDateRangeFromPreset, type PresetOption, } from "../../lib/dateRange"; import type { FilterableColumn, DateRangeFilterOptions } from "../../lib/types"; @@ -36,44 +37,6 @@ type DateRangeFilterProps = { showClearButton?: boolean; }; -const getDateRangeFromPreset = (val: string | null) => { - let startDate; - let endDate; - const preset = PRESET_OPTIONS.find((o) => o.value === val); - if (!preset) { - return { startDate: getDefaultStartDate(), endDate: getDefaultEndDate(), preset: CUSTOM_PRESET }; - } - - switch (val) { - case "tdy": // Today - startDate = dayjs().startOf("day"); - endDate = dayjs().endOf("day"); - break; - case "w": // Last 7 days - startDate = dayjs().subtract(1, "week").startOf("day"); - endDate = dayjs().endOf("day"); - break; - case "t": // Last 30 days - startDate = dayjs().subtract(30, "day").startOf("day"); - endDate = dayjs().endOf("day"); - break; - case "m": // Month to Date - startDate = dayjs().startOf("month"); - endDate = dayjs().endOf("day"); - break; - case "y": // Year to Date - startDate = dayjs().startOf("year"); - endDate = dayjs().endOf("day"); - break; - default: - startDate = getDefaultStartDate(); - endDate = getDefaultEndDate(); - break; - } - - return { startDate, endDate, preset }; -}; - export const DateRangeFilter = ({ column, options, showClearButton = false }: DateRangeFilterProps) => { const filterValue = useFilterValue(column.id, ZDateRangeFilterValue); const { updateFilter, removeFilter } = useDataTable(); @@ -137,11 +100,11 @@ export const DateRangeFilter = ({ column, options, showClearButton = false }: Da endDate, }); } else { - const r = getDateRangeFromPreset(val); + const { preset, startDate, endDate } = getDateRangeFromPreset(val); updateValues({ - preset: r.preset, - startDate: r.startDate, - endDate: r.endDate, + preset, + startDate, + endDate, }); } }; diff --git a/packages/features/data-table/hooks/useSegments.ts b/packages/features/data-table/hooks/useSegments.ts index 736f679347..ec9e6b7716 100644 --- a/packages/features/data-table/hooks/useSegments.ts +++ b/packages/features/data-table/hooks/useSegments.ts @@ -4,7 +4,9 @@ import { useCallback, useMemo, useEffect } from "react"; import { trpc } from "@calcom/trpc/react"; +import { recalculateDateRange } from "../lib/dateRange"; import { ZSegmentStorage, type UseSegments } from "../lib/types"; +import { isDateRangeFilterValue } from "../lib/utils"; export const useSegments: UseSegments = ({ tableIdentifier, @@ -25,9 +27,27 @@ export const useSegments: UseSegments = ({ setPageIndex, setSearchTerm, }) => { - const { data: segments, isFetching: isFetchingSegments } = trpc.viewer.filterSegments.list.useQuery({ + const { data: rawSegments, isFetching: isFetchingSegments } = trpc.viewer.filterSegments.list.useQuery({ tableIdentifier, }); + + // Recalculate date ranges based on the current timestamp + const segments = useMemo(() => { + if (!rawSegments) return []; + return rawSegments.map((segment) => ({ + ...segment, + activeFilters: segment.activeFilters.map((filter) => { + if (isDateRangeFilterValue(filter.v)) { + return { + ...filter, + v: recalculateDateRange(filter.v), + }; + } + return filter; + }), + })); + }, [rawSegments]); + const selectedSegment = useMemo( () => segments?.find((segment) => segment.id === segmentId), [segments, segmentId] diff --git a/packages/features/data-table/lib/dateRange.test.ts b/packages/features/data-table/lib/dateRange.test.ts new file mode 100644 index 0000000000..32bfc1b7d4 --- /dev/null +++ b/packages/features/data-table/lib/dateRange.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it, vi } from "vitest"; + +import dayjs from "@calcom/dayjs"; + +import { + getDateRangeFromPreset, + recalculateDateRange, + PRESET_OPTIONS, + CUSTOM_PRESET, + DEFAULT_PRESET, + CUSTOM_PRESET_VALUE, +} from "./dateRange"; +import { ColumnFilterType, type DateRangeFilterValue } from "./types"; + +// Mock dayjs to have consistent timestamps in tests +vi.mock("@calcom/dayjs", () => { + const mockDayjs = vi.fn(() => ({ + startOf: vi.fn().mockReturnThis(), + endOf: vi.fn().mockReturnThis(), + subtract: vi.fn().mockReturnThis(), + toISOString: vi.fn().mockReturnValue("2024-03-20T00:00:00.000Z"), + })); + return { + default: mockDayjs, + }; +}); + +describe("getDateRangeFromPreset", () => { + it("should return default dates for null value", () => { + const result = getDateRangeFromPreset(null); + expect(result.preset).toEqual(CUSTOM_PRESET); + expect(dayjs).toHaveBeenCalled(); + }); + + it("should return today's date range for 'tdy' preset", () => { + const result = getDateRangeFromPreset("tdy"); + expect(result.preset.value).toBe("tdy"); + expect(dayjs).toHaveBeenCalled(); + }); + + it("should return last 7 days range for 'w' preset", () => { + const result = getDateRangeFromPreset("w"); + expect(result.preset.value).toBe("w"); + expect(dayjs).toHaveBeenCalled(); + }); + + it("should return last 30 days range for 't' preset", () => { + const result = getDateRangeFromPreset("t"); + expect(result.preset.value).toBe("t"); + expect(dayjs).toHaveBeenCalled(); + }); + + it("should return month to date range for 'm' preset", () => { + const result = getDateRangeFromPreset("m"); + expect(result.preset.value).toBe("m"); + expect(dayjs).toHaveBeenCalled(); + }); + + it("should return year to date range for 'y' preset", () => { + const result = getDateRangeFromPreset("y"); + expect(result.preset.value).toBe("y"); + expect(dayjs).toHaveBeenCalled(); + }); +}); + +describe("recalculateDateRange", () => { + it("should return custom range as is", () => { + const customFilter: DateRangeFilterValue = { + type: ColumnFilterType.DATE_RANGE, + data: { + preset: CUSTOM_PRESET_VALUE, + startDate: "2024-03-01T00:00:00.000Z", + endDate: "2024-03-15T00:00:00.000Z", + }, + }; + + const result = recalculateDateRange(customFilter); + expect(result).toEqual(customFilter); + }); + + it("should recalculate dates for non-custom presets", () => { + const filter: DateRangeFilterValue = { + type: ColumnFilterType.DATE_RANGE, + data: { + preset: "w", + startDate: "2024-03-01T00:00:00.000Z", + endDate: "2024-03-15T00:00:00.000Z", + }, + }; + + const result = recalculateDateRange(filter); + expect(result.type).toBe(ColumnFilterType.DATE_RANGE); + expect(result.data.preset).toBe("w"); + expect(result.data.startDate).toBe("2024-03-20T00:00:00.000Z"); + expect(result.data.endDate).toBe("2024-03-20T00:00:00.000Z"); + }); +}); + +describe("PRESET_OPTIONS", () => { + it("should contain all expected preset options", () => { + const expectedValues = ["tdy", "w", "t", "m", "y", "c"]; + expect(PRESET_OPTIONS.map((o) => o.value)).toEqual(expectedValues); + }); + + it("should have correct default preset", () => { + expect(DEFAULT_PRESET.value).toBe("w"); + expect(DEFAULT_PRESET.i18nOptions).toEqual({ count: 7 }); + }); + + it("should have correct custom preset", () => { + expect(CUSTOM_PRESET.value).toBe(CUSTOM_PRESET_VALUE); + expect(CUSTOM_PRESET.labelKey).toBe("custom_range"); + }); +}); diff --git a/packages/features/data-table/lib/dateRange.ts b/packages/features/data-table/lib/dateRange.ts index 30d20cf3f1..541c3f9c52 100644 --- a/packages/features/data-table/lib/dateRange.ts +++ b/packages/features/data-table/lib/dateRange.ts @@ -1,5 +1,7 @@ import dayjs from "@calcom/dayjs"; +import { ColumnFilterType, type DateRangeFilterValue } from "./types"; + export type PresetOptionValue = "c" | "w" | "m" | "y" | "t" | "tdy"; export type PresetOption = { @@ -29,3 +31,58 @@ export const PRESET_OPTIONS: PresetOption[] = [ export const getDefaultStartDate = () => dayjs().subtract(1, "week").startOf("day"); export const getDefaultEndDate = () => dayjs().endOf("day"); + +export const getDateRangeFromPreset = (val: string | null) => { + let startDate; + let endDate; + const preset = PRESET_OPTIONS.find((o) => o.value === val); + if (!preset) { + return { startDate: getDefaultStartDate(), endDate: getDefaultEndDate(), preset: CUSTOM_PRESET }; + } + + switch (val) { + case "tdy": // Today + startDate = dayjs().startOf("day"); + endDate = dayjs().endOf("day"); + break; + case "w": // Last 7 days + startDate = dayjs().subtract(1, "week").startOf("day"); + endDate = dayjs().endOf("day"); + break; + case "t": // Last 30 days + startDate = dayjs().subtract(30, "day").startOf("day"); + endDate = dayjs().endOf("day"); + break; + case "m": // Month to Date + startDate = dayjs().startOf("month"); + endDate = dayjs().endOf("day"); + break; + case "y": // Year to Date + startDate = dayjs().startOf("year"); + endDate = dayjs().endOf("day"); + break; + default: + throw new Error(`Invalid preset value: ${val}`); + } + + return { startDate, endDate, preset }; +}; + +export const recalculateDateRange = (filterValue: DateRangeFilterValue): DateRangeFilterValue => { + // If it's a custom range, return as is + if (filterValue.data.preset === CUSTOM_PRESET_VALUE) { + return filterValue; + } + + // Recalculate dates based on the current timestamp + const { startDate, endDate } = getDateRangeFromPreset(filterValue.data.preset); + + return { + type: ColumnFilterType.DATE_RANGE, + data: { + ...filterValue.data, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + }, + }; +};