import React, { useMemo } from "react"; import { IListSelect, useCachedQueryResult, useListSelect } from "./util"; import { isFunction } from "lodash-es"; import { IHasID } from "src/utils/data"; import { useFilter } from "./FilterProvider"; import { ListFilterModel } from "src/models/list-filter/filter"; import { QueryResult } from "@apollo/client"; interface IListContextOptions { selectable?: boolean; items: T[]; } export type IListContextState = IListSelect & { selectable: boolean; items: T[]; }; export const ListStateContext = React.createContext( null ); export const ListContext = ( props: IListContextOptions & { children?: | ((props: IListContextState) => React.ReactNode) | React.ReactNode; } ) => { const { selectable = false, items, children } = props; const listSelect = useListSelect(items); const state: IListContextState = { selectable, items, ...listSelect, }; return ( {isFunction(children) ? (children as (props: IListContextState) => React.ReactNode)(state) : children} ); }; export function useListContext() { const context = React.useContext(ListStateContext); if (context === null) { throw new Error("useListContext must be used within a ListStateContext"); } return context as IListContextState; } const emptyState: IListContextState = { selectable: false, selectedIds: new Set(), getSelected: () => [], onSelectChange: () => {}, onSelectAll: () => {}, onSelectNone: () => {}, items: [], hasSelection: false, selectedItems: [], }; export function useListContextOptional() { const context = React.useContext(ListStateContext); if (context === null) { return emptyState as IListContextState; } return context as IListContextState; } interface IQueryResultContextOptions< T extends QueryResult, E extends IHasID = IHasID, M = unknown > { filterHook?: (filter: ListFilterModel) => ListFilterModel; useResult: (filter: ListFilterModel) => T; useMetadataInfo?: (filter: ListFilterModel) => M; getCount: (data: T) => number; getItems: (data: T) => E[]; } export interface IQueryResultContextState< T extends QueryResult = QueryResult, E extends IHasID = IHasID, M = unknown > { effectiveFilter: ListFilterModel; result: T; cachedResult: T; metadataInfo?: M; items: E[]; totalCount: number; } export const QueryResultStateContext = React.createContext(null); export const QueryResultContext = < T extends QueryResult, E extends IHasID = IHasID, M = unknown >( props: IQueryResultContextOptions & { children?: | ((props: IQueryResultContextState) => React.ReactNode) | React.ReactNode; } ) => { const { filterHook, useResult, useMetadataInfo, getItems, getCount, children, } = props; const { filter } = useFilter(); const effectiveFilter = useMemo(() => { if (filterHook) { return filterHook(filter.clone()); } return filter; }, [filter, filterHook]); // metadata filter is the effective filter with the sort, page size and page number removed const metadataFilter = useMemo( () => effectiveFilter.metadataInfo(), [effectiveFilter] ); const result = useResult(effectiveFilter); const metadataInfo = useMetadataInfo?.(metadataFilter); // use cached query result for pagination const cachedResult = useCachedQueryResult(effectiveFilter, result); const items = useMemo(() => getItems(result), [getItems, result]); const totalCount = useMemo( () => getCount(cachedResult), [getCount, cachedResult] ); const state: IQueryResultContextState = { effectiveFilter, result, cachedResult, items, totalCount, metadataInfo, }; return ( {isFunction(children) ? (children as (props: IQueryResultContextState) => React.ReactNode)( state ) : children} ); }; export function useQueryResultContext< T extends QueryResult, E extends IHasID = IHasID, M = unknown >() { const context = React.useContext(QueryResultStateContext); if (context === null) { throw new Error( "useQueryResultContext must be used within a ListStateContext" ); } return context as IQueryResultContextState; }