1368ffe55d
* 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>
320 lines
8.6 KiB
TypeScript
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>;
|
|
}
|