import { useState , useEffect , useCallback } from 'react' ; import { debounceTime , tap } from 'rxjs/operators' ; import { nanoid } from 'nanoid' ; import client from './client' ; import { ColumnOrder } from './hooks/use-sticky-state-order' ; export interface Cursor { results : any [ ] ; nextPage : ( ) => Promise < Cursor > ; previousPage : ( ) => Promise < Cursor > ; totalPages : number ; page : number ; } const removeDraftPrefix = ( s ) => s . startsWith ( 'drafts.' ) ? s . substring ( 'drafts.' . length ) : s ; interface Params { typeName : string ; pageSize : number ; selectedColumns : Set < string > ; searchField : string | null ; orderColumn : ColumnOrder ; } function usePaginatedClient ( { typeName , pageSize , selectedColumns , searchField , orderColumn , } ) { const [ loadingStatuses , setLoadingStatuses ] = useState ( new Set < string > ( ) ) ; const loading = loadingStatuses . size > 0 ; const [ total , setTotal ] = useState ( 0 ) ; const totalPages = Math . ceil ( total / pageSize ) ; const [ pageIds , setPageIds ] = useState < string [ ] > ( [ ] ) ; const [ page , setPage ] = useState ( 0 ) ; const [ results , setResults ] = useState < any [ ] > ( [ ] ) ; const [ refreshId , setRefreshId ] = useState ( nanoid ( ) ) ; const refresh = useCallback ( ( ) => setRefreshId ( nanoid ( ) ) , [ ] ) ; const [ userQuery , setUserQuery ] = useState ( '' ) ; const searchQuery = userQuery . length && searchField ? ` && ${ searchField } match "${ userQuery }*"` : '' ; const orderQuery = orderColumn ? `| order(${ orderColumn . key } ${ orderColumn . direction })` : `` ; useEffect ( ( ) => { let canceled = false ; async function getTotalCount ( ) { setLoadingStatuses ( ( prev ) => { const next = new Set ( prev ) ; next . add ( 'total_count' ) ; return next ; } ) ; const draftIds = await client . fetch < string [ ] > ( `*[_type == $typeName && _id in path("drafts.**") ${ searchQuery }]._id` , { typeName } ) ; const { draftsWithPublishedVersion , notDraftCount } = await client . fetch < { draftsWithPublishedVersion : string [ ] ; notDraftCount : number ; } > ( `{
          "draftsWithPublishedVersion": *[_type == $typeName && _id in $ids ${ searchQuery }]._id,
          "notDraftCount": count(*[_type == $typeName && !(_id in path("drafts.**")) ${ searchQuery }]),
        }` , { ids : draftIds . map ( removeDraftPrefix ) , typeName } ) ; const total = draftIds . length - draftsWithPublishedVersion . length + notDraftCount ; if ( canceled ) return ; setLoadingStatuses ( ( prev ) => { const next = new Set ( prev ) ; next . delete ( 'total_count' ) ; return next ; } ) ; setTotal ( total ) ; } getTotalCount ( ) . catch ( ( e ) => { console . warn ( e ) ; } ) ; return ( ) => { canceled = true ; } ; } , [ typeName , refreshId , searchQuery ] ) ; useEffect ( ( ) => { const getPageIds = async ( targetPage ) => { setLoadingStatuses ( ( prev ) => { const next = new Set ( prev ) ; next . add ( 'page_ids' ) ; return next ; } ) ; const draftIds = await client . fetch < string [ ] > ( `*[_type == $typeName && _id in path("drafts.**") ${ searchQuery }]._id` , { typeName } ) ; const drafts = draftIds . reduce ( ( set , next ) => { set . add ( removeDraftPrefix ( next ) ) ; return set ; } , new Set < string > ( ) ) ; const getPage = async ( start = 0 , page = 0 ) : Promise < string [ ] > => { const end = start + pageSize * 2 ; const pageIds = await client . fetch < string [ ] > ( `*[_type == $typeName ${ searchQuery }]${ orderQuery }[$start...$end]._id` , { typeName , start , end } ) ; const filteredIds = pageIds . map ( ( id , index ) => ( { id , index : start + index } ) ) . filter ( ( { id } ) => { if ( id . startsWith ( 'drafts.' ) ) return true ; if ( drafts . has ( id ) ) return false ; return true ; } ) . slice ( 0 , pageSize ) ; const ids = filteredIds . map ( ( i ) => i . id ) . map ( removeDraftPrefix ) ; if ( page >= targetPage ) return ids ; const last = filteredIds [ filteredIds . length - 1 ] ; if ( ! last ) return [ ] ; return await getPage ( last . index + 1 , page + 1 ) ; } ; const ids = await getPage ( ) ; setLoadingStatuses ( ( prev ) => { const next = new Set ( prev ) ; next . delete ( 'page_ids' ) ; return next ; } ) ; return ids ; } ; getPageIds ( page ) . then ( setPageIds ) . catch ( ( e ) => { console . warn ( e ) ; } ) ; } , [ page , pageSize , typeName , refreshId , searchQuery , orderQuery ] ) ; useEffect ( ( ) => { const ids = pageIds . map ( ( id ) => [ id , `drafts.${ id }` ] ) . flat ( ) ; const columnKeys = Array . from ( selectedColumns ) . map ( ( key ) => ( key . includes ( '.' ) ? `"${ key }": ${ key }` : key ) ) . join ( ', ' ) ; const query = `*[_id in $ids ${ searchQuery }]${ orderQuery }{ _id, _type, ${ columnKeys } }` ; async function getResults ( ) { setLoadingStatuses ( ( prev ) => { const next = new Set ( prev ) ; next . add ( 'results' ) ; return next ; } ) ; const indexes = pageIds . reduce < { [ id ] : number ; } > ( ( acc , id , index ) => { acc [ id ] = index ; return acc ; } , { } ) ; const results = await client . fetch < any [ ] > ( query , { ids } ) ; const reducedResults = Object . values ( results . reduce < { [ id ] : any ; } > ( ( acc , next ) => { const id = removeDraftPrefix ( next . _id ) ; const current = acc [ id ] ; const currentIsDraft = current ?. _id . startsWith ( 'drafts.' ) ; const nextIsDraft = next ?. _id . startsWith ( 'drafts.' ) ; const status = current && next ? 'draft' : currentIsDraft || nextIsDraft ? 'unpublished' : 'published' ; acc [ id ] = currentIsDraft ? current : next ; acc [ id ] . _status = status ; acc [ id ] . _normalizedId = id ; return acc ; } , { } ) ) ; setLoadingStatuses ( ( prev ) => { const next = new Set ( prev ) ; next . delete ( 'results' ) ; return next ; } ) ; setResults ( reducedResults . slice ( ) . sort ( ( a , b ) => indexes [ removeDraftPrefix ( a . _id ) ] - indexes [ removeDraftPrefix ( b . _id ) ] ) ) ; } getResults ( ) . catch ( ( e ) => { console . warn ( e ) ; } ) ; const typeQuery = `*[_type == $typeName]` ; const subscription = client . listen ( typeQuery , { typeName } ) . pipe ( tap ( ( result ) => { const docId = result . documentId . replace ( 'drafts.' , '' ) ; if ( ! pageIds . includes ( docId ) ) { setPageIds ( [ ... pageIds , docId ] ) ; } setLoadingStatuses ( ( prev ) => { const next = new Set ( prev ) ; next . add ( 'results' ) ; return next ; } ) ; } ) , debounceTime ( 1000 ) ) . subscribe ( getResults ) ; return ( ) => subscription . unsubscribe ( ) ; } , [ pageIds , selectedColumns , refreshId , searchQuery , orderQuery ] ) ; useEffect ( ( ) => { if ( page >= totalPages ) { setPage ( 0 ) ; } } , [ page , totalPages ] ) ; return { results , page , totalPages , setPage , loading , pageIds , total , refresh , setUserQuery , } ; } export default usePaginatedClient ;