⚗️ Add infinite scroll in results table
This commit is contained in:
@ -17,7 +17,7 @@ export const LoadingRows = ({ totalColumns }: LoadingRowsProps) => {
|
|||||||
border="1px"
|
border="1px"
|
||||||
as="td"
|
as="td"
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
flex="0"
|
width="50px"
|
||||||
>
|
>
|
||||||
<Checkbox isDisabled />
|
<Checkbox isDisabled />
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -30,7 +30,7 @@ export const LoadingRows = ({ totalColumns }: LoadingRowsProps) => {
|
|||||||
border="1px"
|
border="1px"
|
||||||
as="td"
|
as="td"
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
flex="1"
|
width="180px"
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
<Skeleton height="5px" w="full" />
|
<Skeleton height="5px" w="full" />
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { Box, Checkbox, Flex } from '@chakra-ui/react'
|
import { Box, Checkbox, Flex } from '@chakra-ui/react'
|
||||||
import { Answer, Result } from 'bot-engine'
|
import { Answer, Result } from 'bot-engine'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import { Hooks, useFlexLayout, useRowSelect, useTable } from 'react-table'
|
import { Hooks, useFlexLayout, useRowSelect, useTable } from 'react-table'
|
||||||
import { parseSubmissionsColumns } from 'services/publicTypebot'
|
import { parseSubmissionsColumns } from 'services/publicTypebot'
|
||||||
import { parseDateToReadable } from 'services/results'
|
import { parseDateToReadable } from 'services/results'
|
||||||
@ -13,12 +13,16 @@ const defaultCellWidth = 180
|
|||||||
|
|
||||||
type SubmissionsTableProps = {
|
type SubmissionsTableProps = {
|
||||||
results?: (Result & { answers: Answer[] })[]
|
results?: (Result & { answers: Answer[] })[]
|
||||||
|
hasMore?: boolean
|
||||||
onNewSelection: (selection: string[]) => void
|
onNewSelection: (selection: string[]) => void
|
||||||
|
onScrollToBottom: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubmissionsTable = ({
|
export const SubmissionsTable = ({
|
||||||
results,
|
results,
|
||||||
|
hasMore,
|
||||||
onNewSelection,
|
onNewSelection,
|
||||||
|
onScrollToBottom,
|
||||||
}: SubmissionsTableProps) => {
|
}: SubmissionsTableProps) => {
|
||||||
const { publishedTypebot } = useTypebot()
|
const { publishedTypebot } = useTypebot()
|
||||||
const columns: any = React.useMemo(
|
const columns: any = React.useMemo(
|
||||||
@ -34,8 +38,11 @@ export const SubmissionsTable = ({
|
|||||||
{}
|
{}
|
||||||
),
|
),
|
||||||
})),
|
})),
|
||||||
[results]
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[results?.length]
|
||||||
)
|
)
|
||||||
|
const bottomElement = useRef<HTMLDivElement | null>(null)
|
||||||
|
const tableWrapper = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
@ -57,10 +64,35 @@ export const SubmissionsTable = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [selectedFlatRows])
|
}, [selectedFlatRows])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!bottomElement.current) return
|
||||||
|
const options: IntersectionObserverInit = {
|
||||||
|
root: tableWrapper.current,
|
||||||
|
threshold: 0,
|
||||||
|
}
|
||||||
|
const observer = new IntersectionObserver(handleObserver, options)
|
||||||
|
if (bottomElement.current) observer.observe(bottomElement.current)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [bottomElement.current])
|
||||||
|
|
||||||
|
const handleObserver = (entities: any[]) => {
|
||||||
|
const target = entities[0]
|
||||||
|
if (target.isIntersecting) onScrollToBottom()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex overflowX="scroll" maxW="full" className="table-wrapper" rounded="md">
|
<Flex
|
||||||
<Box as="table" rounded="md" {...getTableProps()} w="full">
|
overflow="scroll"
|
||||||
<Box as="thead">
|
maxW="full"
|
||||||
|
maxH="full"
|
||||||
|
className="table-wrapper"
|
||||||
|
rounded="md"
|
||||||
|
data-testid="table-wrapper"
|
||||||
|
pb="20"
|
||||||
|
ref={tableWrapper}
|
||||||
|
>
|
||||||
|
<Box as="table" rounded="md" {...getTableProps()} w="full" h="full">
|
||||||
|
<Box as="thead" pos="sticky" top="0" zIndex={2}>
|
||||||
{headerGroups.map((headerGroup: any) => {
|
{headerGroups.map((headerGroup: any) => {
|
||||||
return (
|
return (
|
||||||
<Flex as="tr" {...headerGroup.getHeaderGroupProps()}>
|
<Flex as="tr" {...headerGroup.getHeaderGroupProps()}>
|
||||||
@ -75,6 +107,7 @@ export const SubmissionsTable = ({
|
|||||||
color="gray.500"
|
color="gray.500"
|
||||||
fontWeight="normal"
|
fontWeight="normal"
|
||||||
textAlign="left"
|
textAlign="left"
|
||||||
|
bgColor={'white'}
|
||||||
{...column.getHeaderProps()}
|
{...column.getHeaderProps()}
|
||||||
style={{
|
style={{
|
||||||
width: idx === 0 ? '50px' : `${defaultCellWidth}px`,
|
width: idx === 0 ? '50px' : `${defaultCellWidth}px`,
|
||||||
@ -90,13 +123,17 @@ export const SubmissionsTable = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box as="tbody" {...getTableBodyProps()}>
|
<Box as="tbody" {...getTableBodyProps()}>
|
||||||
{results === undefined && (
|
{rows.map((row: any, idx: number) => {
|
||||||
<LoadingRows totalColumns={columns.length} />
|
|
||||||
)}
|
|
||||||
{rows.map((row: any) => {
|
|
||||||
prepareRow(row)
|
prepareRow(row)
|
||||||
return (
|
return (
|
||||||
<Flex as="tr" {...row.getRowProps()}>
|
<Flex
|
||||||
|
as="tr"
|
||||||
|
{...row.getRowProps()}
|
||||||
|
ref={(ref) => {
|
||||||
|
if (results && idx === results.length - 10)
|
||||||
|
bottomElement.current = ref
|
||||||
|
}}
|
||||||
|
>
|
||||||
{row.cells.map((cell: any, idx: number) => {
|
{row.cells.map((cell: any, idx: number) => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -105,6 +142,7 @@ export const SubmissionsTable = ({
|
|||||||
border="1px"
|
border="1px"
|
||||||
as="td"
|
as="td"
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
|
bgColor={'white'}
|
||||||
{...cell.getCellProps()}
|
{...cell.getCellProps()}
|
||||||
style={{
|
style={{
|
||||||
width: idx === 0 ? '50px' : `${defaultCellWidth}px`,
|
width: idx === 0 ? '50px' : `${defaultCellWidth}px`,
|
||||||
@ -117,6 +155,9 @@ export const SubmissionsTable = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
{(results === undefined || hasMore === true) && (
|
||||||
|
<LoadingRows totalColumns={columns.length} />
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -85,24 +85,16 @@ const createTypebots = async () => {
|
|||||||
const createResults = () => {
|
const createResults = () => {
|
||||||
return prisma.result.createMany({
|
return prisma.result.createMany({
|
||||||
data: [
|
data: [
|
||||||
{
|
...Array.from(Array(200)).map((_, idx) => {
|
||||||
typebotId: 'typebot1',
|
const today = new Date()
|
||||||
},
|
return {
|
||||||
{
|
id: `result${idx}`,
|
||||||
typebotId: 'typebot1',
|
typebotId: 'typebot2',
|
||||||
},
|
createdAt: new Date(
|
||||||
{
|
today.setTime(today.getTime() + 1000 * 60 * 60 * 24 * idx)
|
||||||
id: 'result1',
|
),
|
||||||
typebotId: 'typebot2',
|
}
|
||||||
},
|
}),
|
||||||
{
|
|
||||||
id: 'result2',
|
|
||||||
typebotId: 'typebot2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'result3',
|
|
||||||
typebotId: 'typebot2',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -110,24 +102,12 @@ const createResults = () => {
|
|||||||
const createAnswers = () => {
|
const createAnswers = () => {
|
||||||
return prisma.answer.createMany({
|
return prisma.answer.createMany({
|
||||||
data: [
|
data: [
|
||||||
{
|
...Array.from(Array(200)).map((_, idx) => ({
|
||||||
resultId: 'result1',
|
resultId: `result${idx}`,
|
||||||
content: 'content 1',
|
content: `content${idx}`,
|
||||||
stepId: 'step1',
|
stepId: 'step1',
|
||||||
blockId: 'block1',
|
blockId: 'block1',
|
||||||
},
|
})),
|
||||||
{
|
|
||||||
resultId: 'result2',
|
|
||||||
content: 'content 2',
|
|
||||||
stepId: 'step1',
|
|
||||||
blockId: 'block1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resultId: 'result3',
|
|
||||||
content: 'content 3',
|
|
||||||
stepId: 'step1',
|
|
||||||
blockId: 'block1',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
describe('ResultsPage', () => {
|
describe('ResultsPage', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.intercept({ url: '/api/typebots/typebot2/results?', method: 'GET' }).as(
|
cy.intercept({ url: '/api/typebots/typebot2/results*', method: 'GET' }).as(
|
||||||
|
'getResults'
|
||||||
|
)
|
||||||
|
cy.intercept({ url: '/api/typebots/typebot2/results*', method: 'GET' }).as(
|
||||||
'getResults'
|
'getResults'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -13,19 +16,32 @@ describe('ResultsPage', () => {
|
|||||||
cy.signIn('test2@gmail.com')
|
cy.signIn('test2@gmail.com')
|
||||||
cy.visit('/typebots/typebot2/results')
|
cy.visit('/typebots/typebot2/results')
|
||||||
cy.wait('@getResults')
|
cy.wait('@getResults')
|
||||||
cy.findByText('content 2').should('exist')
|
cy.findByText('content198').should('exist')
|
||||||
cy.findByText('content 3').should('exist')
|
cy.findByText('content197').should('exist')
|
||||||
cy.findAllByRole('checkbox').eq(2).check({ force: true })
|
cy.findAllByRole('checkbox').eq(2).check({ force: true })
|
||||||
cy.findAllByRole('checkbox').eq(3).check({ force: true })
|
cy.findAllByRole('checkbox').eq(3).check({ force: true })
|
||||||
cy.findByRole('button', { name: 'Delete 2' }).click()
|
cy.findByRole('button', { name: 'Delete 2' }).click({ force: true })
|
||||||
cy.findByRole('button', { name: 'Delete' }).click()
|
cy.findByRole('button', { name: 'Delete' }).click()
|
||||||
cy.findByText('content 2').should('not.exist')
|
cy.findByText('content198').should('not.exist')
|
||||||
cy.findByText('content 3').should('not.exist')
|
cy.findByText('content197').should('not.exist')
|
||||||
|
cy.wait(200)
|
||||||
|
cy.findAllByRole('checkbox').first().check({ force: true })
|
||||||
|
cy.findByRole('button', { name: 'Delete 198' }).click({ force: true })
|
||||||
|
cy.findByRole('button', { name: 'Delete' }).click()
|
||||||
|
cy.findAllByRole('row').should('have.length', 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.only('submissions table should have infinite scroll', () => {
|
it('submissions table should have infinite scroll', () => {
|
||||||
cy.signIn('test2@gmail.com')
|
cy.signIn('test2@gmail.com')
|
||||||
cy.visit('/typebots/typebot2/results')
|
cy.visit('/typebots/typebot2/results')
|
||||||
cy.wait('@getResults')
|
cy.findByText('content50').should('not.exist')
|
||||||
|
cy.findByText('content199').should('exist')
|
||||||
|
cy.findByTestId('table-wrapper').scrollTo('bottom')
|
||||||
|
cy.findByText('content149').should('exist')
|
||||||
|
cy.findByTestId('table-wrapper').scrollTo('bottom')
|
||||||
|
cy.findByText('content99').should('exist')
|
||||||
|
cy.findByTestId('table-wrapper').scrollTo('bottom')
|
||||||
|
cy.findByText('content50').should('exist')
|
||||||
|
cy.findByText('content0').should('exist')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -19,10 +19,18 @@ export const ResultsContent = () => {
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { stats } = useStats({
|
const { stats, mutate } = useStats({
|
||||||
typebotId: typebot?.id,
|
typebotId: typebot?.id,
|
||||||
onError: (err) => toast({ title: err.name, description: err.message }),
|
onError: (err) => toast({ title: err.name, description: err.message }),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleDeletedResults = (total: number) => {
|
||||||
|
if (!stats) return
|
||||||
|
mutate({
|
||||||
|
stats: { ...stats, totalStarts: stats.totalStarts - total },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex h="full" w="full">
|
<Flex h="full" w="full">
|
||||||
<Flex
|
<Flex
|
||||||
@ -66,6 +74,7 @@ export const ResultsContent = () => {
|
|||||||
) : (
|
) : (
|
||||||
<SubmissionsContent
|
<SubmissionsContent
|
||||||
typebotId={typebot.id}
|
typebotId={typebot.id}
|
||||||
|
onDeleteResults={handleDeletedResults}
|
||||||
totalResults={stats?.totalStarts ?? 0}
|
totalResults={stats?.totalStarts ?? 0}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -12,12 +12,19 @@ import {
|
|||||||
import { DownloadIcon, TrashIcon } from 'assets/icons'
|
import { DownloadIcon, TrashIcon } from 'assets/icons'
|
||||||
import { ConfirmModal } from 'components/modals/ConfirmModal'
|
import { ConfirmModal } from 'components/modals/ConfirmModal'
|
||||||
import { SubmissionsTable } from 'components/results/SubmissionsTable'
|
import { SubmissionsTable } from 'components/results/SubmissionsTable'
|
||||||
import React, { useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { deleteResults, useResults } from 'services/results'
|
import { deleteAllResults, deleteResults, useResults } from 'services/results'
|
||||||
|
|
||||||
type Props = { typebotId: string; totalResults: number }
|
type Props = {
|
||||||
export const SubmissionsContent = ({ typebotId, totalResults }: Props) => {
|
typebotId: string
|
||||||
const [lastResultId, setLastResultId] = useState<string>()
|
totalResults: number
|
||||||
|
onDeleteResults: (total: number) => void
|
||||||
|
}
|
||||||
|
export const SubmissionsContent = ({
|
||||||
|
typebotId,
|
||||||
|
totalResults,
|
||||||
|
onDeleteResults,
|
||||||
|
}: Props) => {
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false)
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false)
|
||||||
|
|
||||||
@ -28,12 +35,13 @@ export const SubmissionsContent = ({ typebotId, totalResults }: Props) => {
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { results, mutate } = useResults({
|
const { data, mutate, setSize, hasMore } = useResults({
|
||||||
lastResultId,
|
|
||||||
typebotId,
|
typebotId,
|
||||||
onError: (err) => toast({ title: err.name, description: err.message }),
|
onError: (err) => toast({ title: err.name, description: err.message }),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const results = useMemo(() => data?.flatMap((d) => d.results), [data])
|
||||||
|
|
||||||
const handleNewSelection = (newSelection: string[]) => {
|
const handleNewSelection = (newSelection: string[]) => {
|
||||||
if (newSelection.length === selectedIds.length) return
|
if (newSelection.length === selectedIds.length) return
|
||||||
setSelectedIds(newSelection)
|
setSelectedIds(newSelection)
|
||||||
@ -41,14 +49,21 @@ export const SubmissionsContent = ({ typebotId, totalResults }: Props) => {
|
|||||||
|
|
||||||
const handleDeleteSelection = async () => {
|
const handleDeleteSelection = async () => {
|
||||||
setIsDeleteLoading(true)
|
setIsDeleteLoading(true)
|
||||||
const { error } = await deleteResults(typebotId, selectedIds)
|
const { error } =
|
||||||
|
totalSelected === totalResults
|
||||||
|
? await deleteAllResults(typebotId)
|
||||||
|
: await deleteResults(typebotId, selectedIds)
|
||||||
if (error) toast({ description: error.message, title: error.name })
|
if (error) toast({ description: error.message, title: error.name })
|
||||||
else
|
else {
|
||||||
mutate({
|
mutate(
|
||||||
results: (results ?? []).filter((result) =>
|
totalSelected === totalResults
|
||||||
selectedIds.includes(result.id)
|
? []
|
||||||
),
|
: data?.map((d) => ({
|
||||||
})
|
results: d.results.filter((r) => !selectedIds.includes(r.id)),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
onDeleteResults(totalSelected)
|
||||||
|
}
|
||||||
setIsDeleteLoading(false)
|
setIsDeleteLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +75,11 @@ export const SubmissionsContent = ({ typebotId, totalResults }: Props) => {
|
|||||||
[results?.length, selectedIds.length, totalResults]
|
[results?.length, selectedIds.length, totalResults]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleScrolledToBottom = useCallback(
|
||||||
|
() => setSize((state) => state + 1),
|
||||||
|
[setSize]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack maxW="1200px" w="full">
|
<Stack maxW="1200px" w="full">
|
||||||
<Flex w="full" justifyContent="flex-end">
|
<Flex w="full" justifyContent="flex-end">
|
||||||
@ -90,28 +110,33 @@ export const SubmissionsContent = ({ typebotId, totalResults }: Props) => {
|
|||||||
{totalSelected}
|
{totalSelected}
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
<ConfirmModal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onConfirm={handleDeleteSelection}
|
|
||||||
onClose={onClose}
|
|
||||||
message={
|
|
||||||
<Text>
|
|
||||||
You are about to delete{' '}
|
|
||||||
<strong>
|
|
||||||
{totalSelected} submission
|
|
||||||
{totalSelected > 0 ? 's' : ''}
|
|
||||||
</strong>
|
|
||||||
. Are you sure you wish to continue?
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
confirmButtonLabel={'Delete'}
|
|
||||||
/>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onConfirm={handleDeleteSelection}
|
||||||
|
onClose={onClose}
|
||||||
|
message={
|
||||||
|
<Text>
|
||||||
|
You are about to delete{' '}
|
||||||
|
<strong>
|
||||||
|
{totalSelected} submission
|
||||||
|
{totalSelected > 1 ? 's' : ''}
|
||||||
|
</strong>
|
||||||
|
. Are you sure you wish to continue?
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
confirmButtonLabel={'Delete'}
|
||||||
|
/>
|
||||||
</Fade>
|
</Fade>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<SubmissionsTable results={results} onNewSelection={handleNewSelection} />
|
<SubmissionsTable
|
||||||
|
results={results}
|
||||||
|
onNewSelection={handleNewSelection}
|
||||||
|
onScrollToBottom={handleScrolledToBottom}
|
||||||
|
hasMore={hasMore}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
"stripe": "^8.195.0",
|
"stripe": "^8.195.0",
|
||||||
"styled-components": "^5.3.3",
|
"styled-components": "^5.3.3",
|
||||||
"svg-round-corners": "^0.3.0",
|
"svg-round-corners": "^0.3.0",
|
||||||
"swr": "^1.1.1",
|
"swr": "^1.1.2",
|
||||||
"use-debounce": "^7.0.1",
|
"use-debounce": "^7.0.1",
|
||||||
"utils": "*"
|
"utils": "*"
|
||||||
},
|
},
|
||||||
|
@ -1,30 +1,55 @@
|
|||||||
import { Result } from 'bot-engine'
|
import { Result } from 'bot-engine'
|
||||||
import useSWR from 'swr'
|
import useSWRInfinite from 'swr/infinite'
|
||||||
import { fetcher, sendRequest } from './utils'
|
import { fetcher, sendRequest } from './utils'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { Answer } from 'db'
|
import { Answer } from 'db'
|
||||||
|
|
||||||
|
const getKey = (
|
||||||
|
typebotId: string,
|
||||||
|
pageIndex: number,
|
||||||
|
previousPageData: {
|
||||||
|
results: ResultWithAnswers[]
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
if (previousPageData && previousPageData.results.length === 0) return null
|
||||||
|
if (pageIndex === 0) return `/api/typebots/${typebotId}/results`
|
||||||
|
console.log(previousPageData.results)
|
||||||
|
return `/api/typebots/${typebotId}/results?lastResultId=${
|
||||||
|
previousPageData.results[previousPageData.results.length - 1].id
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResultWithAnswers = Result & { answers: Answer[] }
|
||||||
export const useResults = ({
|
export const useResults = ({
|
||||||
lastResultId,
|
|
||||||
typebotId,
|
typebotId,
|
||||||
onError,
|
onError,
|
||||||
}: {
|
}: {
|
||||||
lastResultId?: string
|
|
||||||
typebotId: string
|
typebotId: string
|
||||||
onError: (error: Error) => void
|
onError: (error: Error) => void
|
||||||
}) => {
|
}) => {
|
||||||
const params = stringify({
|
const { data, error, mutate, setSize, size } = useSWRInfinite<
|
||||||
lastResultId,
|
{ results: ResultWithAnswers[] },
|
||||||
})
|
|
||||||
const { data, error, mutate } = useSWR<
|
|
||||||
{ results: (Result & { answers: Answer[] })[] },
|
|
||||||
Error
|
Error
|
||||||
>(`/api/typebots/${typebotId}/results?${params}`, fetcher)
|
>(
|
||||||
|
(
|
||||||
|
pageIndex: number,
|
||||||
|
previousPageData: {
|
||||||
|
results: ResultWithAnswers[]
|
||||||
|
}
|
||||||
|
) => getKey(typebotId, pageIndex, previousPageData),
|
||||||
|
fetcher,
|
||||||
|
{ revalidateAll: true }
|
||||||
|
)
|
||||||
|
|
||||||
if (error) onError(error)
|
if (error) onError(error)
|
||||||
return {
|
return {
|
||||||
results: data?.results,
|
data,
|
||||||
isLoading: !error && !data,
|
isLoading: !error && !data,
|
||||||
mutate,
|
mutate,
|
||||||
|
setSize,
|
||||||
|
size,
|
||||||
|
hasMore:
|
||||||
|
data && data.length > 0 && data[data.length - 1].results.length > 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +66,12 @@ export const deleteResults = async (typebotId: string, ids: string[]) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const deleteAllResults = async (typebotId: string) =>
|
||||||
|
sendRequest({
|
||||||
|
url: `/api/typebots/${typebotId}/results`,
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
|
||||||
export const parseDateToReadable = (dateStr: string): string => {
|
export const parseDateToReadable = (dateStr: string): string => {
|
||||||
const date = new Date(dateStr)
|
const date = new Date(dateStr)
|
||||||
return (
|
return (
|
||||||
|
@ -7103,7 +7103,7 @@ svgo@^2.7.0:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
stable "^0.1.8"
|
stable "^0.1.8"
|
||||||
|
|
||||||
swr@^1.1.1:
|
swr@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/swr/-/swr-1.1.2.tgz#9f3de2541931fccf03c48f322f1fc935a7551612"
|
resolved "https://registry.yarnpkg.com/swr/-/swr-1.1.2.tgz#9f3de2541931fccf03c48f322f1fc935a7551612"
|
||||||
integrity sha512-UsM0eo5T+kRPyWFZtWRx2XR5qzohs/LS4lDC0GCyLpCYFmsfTk28UCVDbOE9+KtoXY4FnwHYiF+ZYEU3hnJ1lQ==
|
integrity sha512-UsM0eo5T+kRPyWFZtWRx2XR5qzohs/LS4lDC0GCyLpCYFmsfTk28UCVDbOE9+KtoXY4FnwHYiF+ZYEU3hnJ1lQ==
|
||||||
|
Reference in New Issue
Block a user