import { useMemo , useState , useEffect , useRef , useCallback } from 'react' ; import { useSelector } from 'react-redux' ; import isEqual from 'lodash/isEqual' ; import difference from 'lodash/difference' ; import { Record , SortPayload , ReduxState , Identifier } from '../../types' ; import { useGetMany } from '../../dataProvider' ; import { FieldInputProps , useForm } from 'react-final-form' ; import useGetMatching from '../../dataProvider/useGetMatching' ; import { useTranslate } from '../../i18n' ; import { getStatusForArrayInput as getDataStatus } from './referenceDataStatus' ; import { useResourceContext } from '../../core' ; import { usePaginationState , useSortState } from '..' ; import { ListControllerProps } from '../useListController' ; import { indexById , removeEmpty , useSafeSetState } from '../../util' ; import { ReferenceArrayInputContextValue } from './ReferenceArrayInputContext' ; example param props param { string } props . basePath param { Object } props . record param { string } props . reference param { string } props . resource param { string } props . source param { Props } props return { Object } export const useReferenceArrayInputController = ( props ) : ReferenceArrayInputContextValue & Omit < ListControllerProps , 'setSort' > => { const { filter : defaultFilter , filterToQuery = defaultFilterToQuery , input , page : initialPage = 1 , perPage : initialPerPage = 25 , sort : initialSort = { field : 'id' , order : 'DESC' } , options , reference , source , enableGetChoices , } = props ; const resource = useResourceContext ( props ) ; const translate = useTranslate ( ) ; const inputValue = useRef ( input . value ) ; const [ idsToFetch , setIdsToFetch ] = useState ( input . value ) ; const [ idsToGetFromStore , setIdsToGetFromStore ] = useState ( EmptyArray ) ; const referenceRecordsFromStore = useSelector ( ( state ) => idsToGetFromStore . map ( id => state . admin . resources [ reference ] . data [ id ] ) ) ; useEffect ( ( ) => { const newIdsToFetch = difference ( input . value , inputValue . current ) ; const newIdsToGetFromStore = difference ( input . value , newIdsToFetch ) ; if ( ! isEqual ( idsToFetch , newIdsToFetch ) ) { setIdsToFetch ( newIdsToFetch ) ; } if ( ! isEqual ( idsToGetFromStore , newIdsToGetFromStore ) ) { setIdsToGetFromStore ( newIdsToGetFromStore ) ; } inputValue . current = input . value ; } , [ idsToFetch , idsToGetFromStore , input . value , setIdsToFetch , setIdsToGetFromStore , ] ) ; const { page , setPage , perPage , setPerPage , pagination , setPagination , } = usePaginationState ( { page : initialPage , perPage : initialPerPage , } ) ; const form = useForm ( ) ; const onSelect = useCallback ( ( newIds ) => { if ( newIds . length === 0 ) { form . change ( input . name , EmptyArray ) ; return ; } const newValue = new Set ( input . value ) ; newIds . forEach ( newId => { newValue . add ( newId ) ; } ) ; form . change ( input . name , Array . from ( newValue ) ) ; } , [ form , input . value , input . name ] ) ; const onUnselectItems = useCallback ( ( ) => { form . change ( input . name , EmptyArray ) ; } , [ form , input . name ] ) ; const onToggleItem = useCallback ( ( id ) => { if ( input . value . some ( selectedId => selectedId === id ) ) { form . change ( input . name , input . value . filter ( selectedId => selectedId !== id ) ) ; } else { form . change ( input . name , [ ... input . value , id ] ) ; } } , [ form , input . name , input . value ] ) ; const sortRef = useRef ( initialSort ) ; const { sort , setSort } = useSortState ( initialSort ) ; const setSortForList = useCallback ( ( field , order = 'ASC' ) => { setSort ( { field , order } ) ; setPage ( 1 ) ; } , [ setPage , setSort ] ) ; useEffect ( ( ) => { if ( ! isEqual ( initialSort , sortRef . current ) ) { setSort ( initialSort ) ; } } , [ setSort , initialSort ] ) ; const paginationRef = useRef ( { initialPage , initialPerPage } ) ; useEffect ( ( ) => { if ( ! isEqual ( { initialPage , initialPerPage } , paginationRef . current ) ) { setPagination ( { page : initialPage , perPage : initialPerPage } ) ; } } , [ setPagination , initialPage , initialPerPage ] ) ; const [ queryFilter , setFilter ] = useState ( '' ) ; const filterRef = useRef ( defaultFilter ) ; const [ displayedFilters , setDisplayedFilters ] = useSafeSetState < { [ key ] : boolean ; } > ( { } ) ; const [ filterValues , setFilterValues ] = useSafeSetState < { [ key ] : any ; } > ( defaultFilter ) ; const hideFilter = useCallback ( ( filterName ) => { setDisplayedFilters ( previousState => { const { [ filterName ] : _ , ... newState } = previousState ; return newState ; } ) ; setFilterValues ( previousState => { const { [ filterName ] : _ , ... newState } = previousState ; return newState ; } ) ; } , [ setDisplayedFilters , setFilterValues ] ) ; const showFilter = useCallback ( ( filterName , defaultValue ) => { setDisplayedFilters ( previousState => ( { ... previousState , [ filterName ] : true , } ) ) ; setFilterValues ( previousState => ( { ... previousState , [ filterName ] : defaultValue , } ) ) ; } , [ setDisplayedFilters , setFilterValues ] ) ; const setFilters = useCallback ( ( filters , displayedFilters ) => { setFilterValues ( removeEmpty ( filters ) ) ; setDisplayedFilters ( displayedFilters ) ; setPage ( 1 ) ; } , [ setDisplayedFilters , setFilterValues , setPage ] ) ; useEffect ( ( ) => { if ( ! isEqual ( defaultFilter , filterRef . current ) ) { filterRef . current = defaultFilter ; setFilterValues ( defaultFilter ) ; } } ) ; const finalFilter = useMemo ( ( ) => ( { ... defaultFilter , ... filterToQuery ( queryFilter ) , } ) , [ queryFilter , defaultFilter , filterToQuery ] ) ; const { data : referenceRecordsFetched , loaded , refetch : refetchGetMany , } = useGetMany ( reference , idsToFetch || EmptyArray ) ; const referenceRecords = referenceRecordsFetched ? referenceRecordsFetched . concat ( referenceRecordsFromStore ) : referenceRecordsFromStore ; const finalReferenceRecords = referenceRecords . filter ( Boolean ) ; const isGetMatchingEnabled = enableGetChoices ? enableGetChoices ( finalFilter ) : true ; const { data : matchingReferences , ids : matchingReferencesIds , total , refetch : refetchGetMatching , } = useGetMatching ( reference , pagination , sort , finalFilter , source , resource , options ? { ... options , enabled : isGetMatchingEnabled } : { enabled : isGetMatchingEnabled } ) ; const finalMatchingReferences = matchingReferences && matchingReferences . length > 0 ? mergeReferences ( matchingReferences , finalReferenceRecords ) : finalReferenceRecords . length > 0 ? finalReferenceRecords : matchingReferences ; const dataStatus = getDataStatus ( { input , matchingReferences : finalMatchingReferences , referenceRecords : finalReferenceRecords , translate , } ) ; const refetch = useCallback ( ( ) => { refetchGetMany ( ) ; refetchGetMatching ( ) ; } , [ refetchGetMany , refetchGetMatching ] ) ; return { basePath : props . basePath || `/${ resource }` , choices : dataStatus . choices , currentSort : sort , data : matchingReferences && matchingReferences . length > 0 ? indexById ( matchingReferences ) : { } , displayedFilters , error : dataStatus . error , filterValues , hasCreate : false , hideFilter , ids : matchingReferencesIds || EmptyArray , loaded , loading : dataStatus . waiting && isGetMatchingEnabled , onSelect , onToggleItem , onUnselectItems , page , perPage , refetch , resource , selectedIds : input . value || EmptyArray , setFilter , setFilters , setPage , setPagination , setPerPage , setSort , setSortForList , showFilter , warning : dataStatus . warning , total , } ; } ; const EmptyArray = [ ] ; const mergeReferences = ( ref1 , ref2 ) : Record [ ] => { const res = [ ... ref1 ] ; const ids = ref1 . map ( ref => ref . id ) ; ref2 . forEach ( ref => { if ( ! ids . includes ( ref . id ) ) { ids . push ( ref . id ) ; res . push ( ref ) ; } } ) ; return res ; } ; export interface UseReferenceArrayInputOptions { basePath ? : string ; filter ? : any ; filterToQuery ? : ( filter : any ) => any ; input : FieldInputProps < any , HTMLElement > ; options ? : any ; page ? : number ; perPage ? : number ; record ? : Record ; reference : string ; resource ? : string ; sort ? : SortPayload ; source : string ; enableGetChoices ? : ( filters : any ) => boolean ; } const defaultFilterToQuery = searchText => ( { q : searchText } ) ;