Files
calendar/apps/web/modules/data-table/contexts/DataTableSegmentContext.tsx
T
Eunjae LeeGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1368ffe55d refactor: move data-table hooks/contexts/provider from features to web modules (#27833)
* refactor: move data-table hooks/contexts/provider from features to web modules

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: update broken useColumnResizing import path in DataTable.tsx

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: update remaining broken import paths for moved hooks

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* docs: update GUIDE.md import paths and file references for moved modules

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-13 00:00:40 +09:00

320 lines
8.6 KiB
TypeScript

"use client";
import { createContext, useContext, useState, useCallback, useEffect, useRef, useMemo } from "react";
import { useSegmentsNoop } from "../hooks/useSegmentsNoop";
import type {
FilterSegmentOutput,
SystemFilterSegment,
CombinedFilterSegment,
SegmentIdentifier,
UseSegments,
} from "@calcom/features/data-table/lib/types";
import { SYSTEM_SEGMENT_PREFIX } from "@calcom/features/data-table/lib/types";
import { useDataTableState } from "./DataTableStateContext";
export type DataTableSegmentContextType = {
segments: CombinedFilterSegment[];
selectedSegment: CombinedFilterSegment | undefined;
segmentId: SegmentIdentifier | null;
setSegmentId: (id: SegmentIdentifier | null, providedSegment?: CombinedFilterSegment) => void;
isSegmentEnabled: boolean;
canSaveSegment: boolean;
isValidatorPending: boolean;
clearSystemSegmentSelectionIfExists: () => void;
};
export const DataTableSegmentContext = createContext<DataTableSegmentContextType | null>(null);
export function useDataTableSegment() {
const context = useContext(DataTableSegmentContext);
if (!context) {
throw new Error("useDataTableSegment must be used within a DataTableSegmentProvider");
}
return context;
}
interface DataTableSegmentProviderProps {
children: React.ReactNode;
useSegments?: UseSegments;
segments?: FilterSegmentOutput[];
systemSegments?: SystemFilterSegment[];
}
export function DataTableSegmentProvider({
children,
useSegments = useSegmentsNoop,
segments: providedSegments,
systemSegments,
}: DataTableSegmentProviderProps) {
const {
tableIdentifier,
activeFilters,
setActiveFilters,
sorting,
setSorting,
columnVisibility,
setColumnVisibility,
columnSizing,
setColumnSizing,
pageSize,
setPageSize,
searchTerm,
setSearchTerm,
setPageIndex,
segmentIdRaw,
setSegmentIdRaw,
defaultPageSize,
validateActiveFilters,
} = useDataTableState();
const {
segments,
preferredSegmentId: fetchedPreferredSegmentId,
setPreference: setSegmentPreference,
isSegmentEnabled,
isSuccess: isSegmentFetchedSuccessfully,
} = useSegments({
tableIdentifier,
providedSegments,
systemSegments,
});
const findSelectedSegment = useCallback(
(segmentId: string) => {
return segments.find((segment) => {
if (
segment.type === "system" &&
segmentId &&
segmentId.startsWith(SYSTEM_SEGMENT_PREFIX) &&
segment.id === segmentId
) {
return true;
} else if (segment.type === "user") {
const segmentIdNumber = parseInt(segmentId, 10);
return segment.id === segmentIdNumber;
}
});
},
[segments]
);
const segmentIdObject = useMemo(() => {
if (segmentIdRaw && segmentIdRaw.startsWith(SYSTEM_SEGMENT_PREFIX)) {
return {
id: segmentIdRaw,
type: "system" as const,
};
} else if (segmentIdRaw) {
const segmentIdNumber = parseInt(segmentIdRaw, 10);
if (!Number.isNaN(segmentIdNumber)) {
return {
id: segmentIdNumber,
type: "user" as const,
};
}
}
return null;
}, [segmentIdRaw]);
const [selectedSegment, setSelectedSegment] = useState<CombinedFilterSegment | undefined>(
segmentIdRaw ? findSelectedSegment(segmentIdRaw) : undefined
);
const pendingSegmentRef = useRef<{
segmentId: SegmentIdentifier;
segment: CombinedFilterSegment;
} | null>(null);
const isValidatorReady = validateActiveFilters !== "loading";
const applySegmentFilters = useCallback(
(segment: CombinedFilterSegment) => {
const filtersToApply =
typeof validateActiveFilters === "function"
? validateActiveFilters(segment.activeFilters)
: segment.activeFilters;
setActiveFilters(filtersToApply);
if (segment.sorting) {
setSorting(segment.sorting);
}
if (segment.columnVisibility) {
setColumnVisibility(segment.columnVisibility);
}
if (segment.columnSizing) {
setColumnSizing(segment.columnSizing);
}
if (segment.perPage !== undefined) {
setPageSize(segment.perPage);
}
if (segment.searchTerm !== undefined) {
setSearchTerm(segment.searchTerm);
}
setPageIndex(0);
},
[
validateActiveFilters,
setActiveFilters,
setSorting,
setColumnVisibility,
setColumnSizing,
setPageSize,
setSearchTerm,
setPageIndex,
]
);
const setSegmentId = useCallback(
(segmentId: SegmentIdentifier | null, providedSegment?: CombinedFilterSegment) => {
if (!segmentId) {
pendingSegmentRef.current = null;
setSegmentIdRaw(null);
setSelectedSegment(undefined);
setSegmentPreference({
tableIdentifier,
segmentId: null,
});
return;
}
const segment = providedSegment || findSelectedSegment(String(segmentId.id));
if (!segment) {
pendingSegmentRef.current = null;
setSegmentIdRaw(null);
setSelectedSegment(undefined);
setSegmentPreference({
tableIdentifier,
segmentId: null,
});
return;
}
setSegmentIdRaw(String(segmentId.id));
setSelectedSegment(segment);
setSegmentPreference({
tableIdentifier,
segmentId,
});
if (validateActiveFilters === "loading") {
pendingSegmentRef.current = { segmentId, segment };
return;
}
pendingSegmentRef.current = null;
applySegmentFilters(segment);
},
[
setSegmentIdRaw,
setSegmentPreference,
tableIdentifier,
findSelectedSegment,
validateActiveFilters,
applySegmentFilters,
]
);
useEffect(() => {
if (!isValidatorReady || !pendingSegmentRef.current) {
return;
}
const { segment } = pendingSegmentRef.current;
pendingSegmentRef.current = null;
applySegmentFilters(segment);
}, [isValidatorReady, applySegmentFilters]);
useEffect(() => {
if (!isSegmentFetchedSuccessfully) {
return;
}
if (fetchedPreferredSegmentId && !segmentIdRaw) {
setSegmentId(fetchedPreferredSegmentId);
} else if (segmentIdRaw) {
const segment = findSelectedSegment(segmentIdRaw);
setSelectedSegment(segment);
if (segment) {
if (validateActiveFilters === "loading") {
pendingSegmentRef.current = {
segmentId: segmentIdObject!,
segment,
};
} else {
applySegmentFilters(segment);
}
}
}
}, [isSegmentFetchedSuccessfully]);
const clearSystemSegmentSelectionIfExists = useCallback(() => {
if (selectedSegment?.type === "system") {
setSegmentId(null);
}
}, [selectedSegment, setSegmentId]);
const hasStateChanged = useMemo(() => {
if (!selectedSegment) return false;
const isEqual = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b);
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]);
const canSaveSegment = useMemo(() => {
if (selectedSegment) {
return hasStateChanged;
}
return (
activeFilters.length > 0 ||
sorting.length > 0 ||
Object.keys(columnVisibility).length > 0 ||
Object.keys(columnSizing).length > 0 ||
pageSize !== defaultPageSize ||
(searchTerm?.length ?? 0) > 0
);
}, [
selectedSegment,
activeFilters,
sorting,
columnVisibility,
columnSizing,
pageSize,
searchTerm,
defaultPageSize,
hasStateChanged,
]);
const isValidatorPending = validateActiveFilters === "loading" && pendingSegmentRef.current !== null;
const value = useMemo(
() => ({
segments,
selectedSegment,
segmentId: segmentIdObject,
setSegmentId,
isSegmentEnabled,
canSaveSegment,
isValidatorPending,
clearSystemSegmentSelectionIfExists,
}),
[
segments,
selectedSegment,
segmentIdObject,
setSegmentId,
isSegmentEnabled,
canSaveSegment,
isValidatorPending,
clearSystemSegmentSelectionIfExists,
]
);
return <DataTableSegmentContext.Provider value={value}>{children}</DataTableSegmentContext.Provider>;
}