Skip to content

Commit

Permalink
🪥 Consult submissions
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Dec 30, 2021
1 parent f088f69 commit 1093453
Show file tree
Hide file tree
Showing 25 changed files with 587 additions and 150 deletions.
8 changes: 8 additions & 0 deletions apps/builder/assets/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,11 @@ export const UploadIcon = (props: IconProps) => (
<polyline points="16 16 12 12 8 16"></polyline>
</Icon>
)

export const DownloadIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</Icon>
)
44 changes: 44 additions & 0 deletions apps/builder/components/results/SubmissionsTable/LoadingRows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Checkbox, Flex, Skeleton } from '@chakra-ui/react'
import React from 'react'

type LoadingRowsProps = {
totalColumns: number
}

export const LoadingRows = ({ totalColumns }: LoadingRowsProps) => {
return (
<>
{Array.from(Array(3)).map((row, idx) => (
<Flex as="tr" key={idx}>
<Flex
key={idx}
py={2}
px={4}
border="1px"
as="td"
borderColor="gray.200"
flex="0"
>
<Checkbox isDisabled />
</Flex>
{Array.from(Array(totalColumns)).map((cell, idx) => {
return (
<Flex
key={idx}
py={2}
px={4}
border="1px"
as="td"
borderColor="gray.200"
flex="1"
align="center"
>
<Skeleton height="5px" w="full" />
</Flex>
)
})}
</Flex>
))}
</>
)
}
116 changes: 92 additions & 24 deletions apps/builder/components/results/SubmissionsTable/SubmissionsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,77 +1,145 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react/jsx-key */
import { Box, Flex } from '@chakra-ui/react'
import { Box, Checkbox, Flex } from '@chakra-ui/react'
import { Answer, Result } from 'bot-engine'
import { useTypebot } from 'contexts/TypebotContext'
import React from 'react'
import { useTable } from 'react-table'
import React, { useEffect } from 'react'
import { Hooks, useRowSelect, useTable } from 'react-table'
import { parseSubmissionsColumns } from 'services/publicTypebot'
import { parseDateToReadable } from 'services/results'
import { LoadingRows } from './LoadingRows'

// eslint-disable-next-line @typescript-eslint/ban-types
type SubmissionsTableProps = {}
type SubmissionsTableProps = {
results?: (Result & { answers: Answer[] })[]
onNewSelection: (selection: string[]) => void
}

export const SubmissionsTable = ({}: SubmissionsTableProps) => {
export const SubmissionsTable = ({
results,
onNewSelection,
}: SubmissionsTableProps) => {
const { publishedTypebot } = useTypebot()
const columns: any = React.useMemo(
() => parseSubmissionsColumns(publishedTypebot),
[publishedTypebot]
)
const data = React.useMemo(() => [], [])
const { getTableProps, headerGroups, rows, prepareRow, getTableBodyProps } =
useTable({ columns, data })
const data = React.useMemo(
() =>
(results ?? []).map((result) => ({
createdAt: parseDateToReadable(result.createdAt),
...result.answers.reduce(
(o, answer) => ({ ...o, [answer.blockId]: answer.content }),
{}
),
})),
[results]
)

const {
getTableProps,
headerGroups,
rows,
prepareRow,
getTableBodyProps,
selectedFlatRows,
} = useTable({ columns, data }, useRowSelect, checkboxColumnHook) as any

useEffect(() => {
onNewSelection(selectedFlatRows.map((row: any) => row.id))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedFlatRows])

return (
<Flex overflowX="scroll" maxW="full" className="table-wrapper" rounded="md">
<Box as="table" rounded="md" {...getTableProps()}>
<Box as="table" rounded="md" {...getTableProps()} w="full">
<Box as="thead">
{headerGroups.map((headerGroup) => {
{headerGroups.map((headerGroup: any) => {
return (
<Box as="tr" {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => {
<Flex as="tr" {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column: any, idx: number) => {
return (
<Box
<Flex
py={2}
px={4}
border="1px"
borderColor="gray.200"
as="th"
minW="200px"
color="gray.500"
fontWeight="normal"
textAlign="left"
minW={idx > 0 ? '200px' : 'unset'}
flex={idx > 0 ? '1' : '0'}
{...column.getHeaderProps()}
>
{column.render('Header')}
</Box>
</Flex>
)
})}
</Box>
</Flex>
)
})}
</Box>

<Box as="tbody" {...getTableBodyProps()}>
{rows.map((row) => {
{results === undefined && (
<LoadingRows totalColumns={columns.length} />
)}
{rows.map((row: any) => {
prepareRow(row)
return (
<Box as="tr" {...row.getRowProps()}>
{row.cells.map((cell) => {
<Flex as="tr" {...row.getRowProps()}>
{row.cells.map((cell: any, idx: number) => {
return (
<Box
<Flex
py={2}
px={4}
border="1px"
as="td"
minW="200px"
borderColor="gray.200"
minW={idx > 0 ? '200px' : 'unset'}
{...cell.getCellProps()}
flex={idx > 0 ? '1' : '0'}
>
{cell.render('Cell')}
</Box>
</Flex>
)
})}
</Box>
</Flex>
)
})}
</Box>
</Box>
</Flex>
)
}

const checkboxColumnHook = (hooks: Hooks<any>) => {
hooks.visibleColumns.push((columns) => [
{
id: 'selection',
Header: ({ getToggleAllRowsSelectedProps }: any) => (
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
),
Cell: ({ row }: any) => (
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
),
},
...columns,
])
}

const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, checked, ...rest }: any, ref) => {
const defaultRef = React.useRef()
const resolvedRef: any = ref || defaultRef

return (
<Checkbox
ref={resolvedRef}
{...rest}
isIndeterminate={indeterminate}
isChecked={checked}
/>
)
}
)
37 changes: 34 additions & 3 deletions apps/builder/layouts/results/ResultsContent.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Button, Flex, HStack, Stack } from '@chakra-ui/react'
import {
Button,
Flex,
HStack,
Stack,
Tag,
useToast,
Text,
} from '@chakra-ui/react'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { useTypebot } from 'contexts/TypebotContext'
import { useRouter } from 'next/router'
import React, { useMemo } from 'react'
import { useResultsCount } from 'services/results'
import { AnalyticsContent } from './AnalyticsContent'
import { SubmissionsContent } from './SubmissionContent'

Expand All @@ -13,6 +22,15 @@ export const ResultsContent = () => {
() => router.pathname.endsWith('analytics'),
[router.pathname]
)
const toast = useToast({
position: 'top-right',
status: 'error',
})

const { totalResults } = useResultsCount({
typebotId: typebot?.id,
onError: (err) => toast({ title: err.name, description: err.message }),
})
return (
<Flex h="full" w="full" justifyContent="center" align="flex-start">
<Stack maxW="1200px" w="full" pt="4" spacing={6}>
Expand All @@ -24,7 +42,12 @@ export const ResultsContent = () => {
size="sm"
href={`/typebots/${typebot?.id}/results`}
>
Submissions
<Text>Submissions</Text>
{totalResults && (
<Tag size="sm" colorScheme="blue" ml="1">
{totalResults}
</Tag>
)}
</Button>
<Button
as={NextChakraLink}
Expand All @@ -36,7 +59,15 @@ export const ResultsContent = () => {
Analytics
</Button>
</HStack>
{isAnalytics ? <AnalyticsContent /> : <SubmissionsContent />}
{typebot &&
(isAnalytics ? (
<AnalyticsContent />
) : (
<SubmissionsContent
typebotId={typebot.id}
totalResults={totalResults ?? 0}
/>
))}
</Stack>
</Flex>
)
Expand Down
75 changes: 70 additions & 5 deletions apps/builder/layouts/results/SubmissionContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,75 @@
import {
Button,
HStack,
Stack,
Tag,
useToast,
Text,
Fade,
Flex,
} from '@chakra-ui/react'
import { DownloadIcon, TrashIcon } from 'assets/icons'
import { SubmissionsTable } from 'components/results/SubmissionsTable'
import React from 'react'
import React, { useMemo, useState } from 'react'
import { useResults } from 'services/results'

type Props = { typebotId: string; totalResults: number }
export const SubmissionsContent = ({ typebotId, totalResults }: Props) => {
const [lastResultId, setLastResultId] = useState<string>()
const [selectedIds, setSelectedIds] = useState<string[]>([])

const toast = useToast({
position: 'top-right',
status: 'error',
})

const { results } = useResults({
lastResultId,
typebotId,
onError: (err) => toast({ title: err.name, description: err.message }),
})

const handleNewSelection = (newSelection: string[]) => {
if (newSelection.length === selectedIds.length) return
setSelectedIds(newSelection)
}

const totalSelected = useMemo(
() =>
selectedIds.length === results?.length
? totalResults
: selectedIds.length,
[results?.length, selectedIds.length, totalResults]
)

export const SubmissionsContent = () => {
return (
<>
<SubmissionsTable />
</>
<Stack>
<Flex w="full" justifyContent="flex-end">
<HStack>
<HStack as={Button} colorScheme="blue">
<DownloadIcon />
<Text>Export</Text>
<Fade in={totalSelected > 0} unmountOnExit>
<Tag colorScheme="blue" variant="subtle" size="sm">
{totalSelected}
</Tag>
</Fade>
</HStack>
<Fade in={totalSelected > 0} unmountOnExit>
<HStack as={Button} colorScheme="red">
<TrashIcon />
<Text>Delete</Text>
{totalSelected > 0 && (
<Tag colorScheme="red" variant="subtle" size="sm">
{totalSelected}
</Tag>
)}
</HStack>
</Fade>
</HStack>
</Flex>

<SubmissionsTable results={results} onNewSelection={handleNewSelection} />
</Stack>
)
}
2 changes: 2 additions & 0 deletions apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"next-auth": "beta",
"nodemailer": "^6.7.2",
"nprogress": "^0.2.0",
"qs": "^6.10.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-frame-component": "^5.2.1",
Expand All @@ -59,6 +60,7 @@
"@types/micro-cors": "^0.1.2",
"@types/node": "^16.11.9",
"@types/nprogress": "^0.2.0",
"@types/qs": "^6.9.7",
"@types/react": "^17.0.37",
"@types/react-table": "^7.7.9",
"@types/testing-library__cypress": "^5.0.9",
Expand Down
Loading

0 comments on commit 1093453

Please sign in to comment.