* 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>
176 lines
5.0 KiB
TypeScript
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));
|
|
}
|