fix: recalculate date range from filter segments (#20633)

* fix: recalculate date range from filter segments

* clean up

* add unit tets for date-range utils

---------

Co-authored-by: Sean Brydon <sean@Seans-Mac-mini.local>
This commit is contained in:
Eunjae LeeandSean Brydon authored and GitHub committed 2025-04-15 08:54:44 +00:00
1 parent 6615b885d1
commit 0386723ae8
4 files changed
+197 -43

No files matched your search

@@ -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,
});
}
};
@@ -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]
@@ -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");
});
});
@@ -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(),
},
};
};