Files
calendar/packages/features/data-table/hooks/useSegments.ts
T
0386723ae8 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>
2025-04-15 08:54:44 +00:00

176 lines
5.0 KiB
TypeScript

// eslint-disable-next-line no-restricted-imports
import { isEqual } from "lodash";
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,
activeFilters,
sorting,
columnVisibility,
columnSizing,
pageSize,
searchTerm,
defaultPageSize,
segmentId,
setSegmentId,
setActiveFilters,
setSorting,
setColumnVisibility,
setColumnSizing,
setPageSize,
setPageIndex,
setSearchTerm,
}) => {
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]
);
useEffect(() => {
if (segments && segmentId > 0 && !isFetchingSegments) {
const segment = segments.find((segment) => segment.id === segmentId);
if (!segment) {
// If segmentId is invalid (or not found), clear the segmentId from the query params,
// but we still keep all the other states like activeFilters, etc.
// This is useful when someone shares a URL that is inaccessible to someone else.
setSegmentId(null);
}
}
}, [segments, segmentId, setSegmentId, isFetchingSegments]);
useEffect(() => {
// this hook doesn't include segmentId in the dependency array
// because we want to only run this once, when the component mounts
if (segmentId === -1) {
const segments = getSegmentsFromLocalStorage();
if (segments[tableIdentifier]) {
setSegmentId(segments[tableIdentifier].segmentId);
}
}
}, [tableIdentifier, setSegmentId]);
useEffect(() => {
if (selectedSegment) {
// segment is selected, so we apply the filters, sorting, etc. from the segment
setActiveFilters(selectedSegment.activeFilters);
setSorting(selectedSegment.sorting);
setColumnVisibility(selectedSegment.columnVisibility);
setColumnSizing(selectedSegment.columnSizing);
setPageSize(selectedSegment.perPage);
setSearchTerm(selectedSegment.searchTerm);
setPageIndex(0);
}
}, [
selectedSegment,
setActiveFilters,
setSorting,
setColumnVisibility,
setColumnSizing,
setPageSize,
setPageIndex,
setSearchTerm,
]);
const canSaveSegment = useMemo(() => {
if (!selectedSegment) {
// if no segment is selected, we can save the segment if there are any active filters, sorting, etc.
return (
activeFilters.length > 0 ||
sorting.length > 0 ||
Object.keys(columnVisibility).length > 0 ||
Object.keys(columnSizing).length > 0 ||
pageSize !== defaultPageSize ||
searchTerm?.length > 0
);
} else {
// if a segment is selected, we can save the segment if the active filters, sorting, etc. are different from the segment
return (
!isEqual(activeFilters, selectedSegment.activeFilters) ||
!isEqual(sorting, selectedSegment.sorting) ||
!isEqual(columnVisibility, selectedSegment.columnVisibility) ||
!isEqual(columnSizing, selectedSegment.columnSizing) ||
!isEqual(pageSize, selectedSegment.perPage) ||
!isEqual(searchTerm, selectedSegment.searchTerm)
);
}
}, [
selectedSegment,
activeFilters,
sorting,
columnVisibility,
columnSizing,
pageSize,
searchTerm,
defaultPageSize,
]);
const setAndPersistSegmentId = useCallback(
(segmentId: number | null) => {
setSegmentId(segmentId);
saveSegmentToLocalStorage({ tableIdentifier, segmentId });
},
[tableIdentifier, setSegmentId]
);
return {
segments: segments ?? [],
selectedSegment,
canSaveSegment,
setAndPersistSegmentId,
isSegmentEnabled: true,
};
};
const LOCAL_STORAGE_KEY = "data-table:segments";
function getSegmentsFromLocalStorage() {
try {
return ZSegmentStorage.parse(JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? "{}"));
} catch {
return {};
}
}
function saveSegmentToLocalStorage({
tableIdentifier,
segmentId,
}: {
tableIdentifier: string;
segmentId: number | null;
}) {
const segments = getSegmentsFromLocalStorage();
if (segmentId) {
segments[tableIdentifier] = { segmentId };
} else {
delete segments[tableIdentifier];
}
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(segments));
}