import { Box, Button, chakra, Checkbox, Flex, HStack, Stack, Text, } from '@chakra-ui/react' import { AlignLeftTextIcon, CalendarIcon, CodeIcon } from 'assets/icons' import { ResultHeaderCell, ResultsTablePreferences } from 'models' import React, { useEffect, useRef, useState } from 'react' import { LoadingRows } from './LoadingRows' import { useReactTable, getCoreRowModel, ColumnOrderState, ColumnDef, } from '@tanstack/react-table' import { BlockIcon } from 'components/editor/BlocksSideBar/BlockIcon' import { ColumnSettingsButton } from './ColumnsSettingsButton' import { useTypebot } from 'contexts/TypebotContext' import { useDebounce } from 'use-debounce' import { ResultsActionButtons } from './ResultsActionButtons' import { Row } from './Row' import { HeaderRow } from './HeaderRow' import { CellValueType, TableData } from 'services/typebots/results' type ResultsTableProps = { resultHeader: ResultHeaderCell[] data: TableData[] hasMore?: boolean preferences?: ResultsTablePreferences onScrollToBottom: () => void onLogOpenIndex: (index: number) => () => void onResultExpandIndex: (index: number) => () => void } export const ResultsTable = ({ resultHeader, data, hasMore, preferences, onScrollToBottom, onLogOpenIndex, onResultExpandIndex, }: ResultsTableProps) => { const { updateTypebot } = useTypebot() const [rowSelection, setRowSelection] = useState>({}) const [columnsVisibility, setColumnsVisibility] = useState< Record >(preferences?.columnsVisibility || {}) const [columnsWidth, setColumnsWidth] = useState>( preferences?.columnsWidth || {} ) const [debouncedColumnsWidth] = useDebounce(columnsWidth, 500) const [columnsOrder, setColumnsOrder] = useState([ 'select', ...(preferences?.columnsOrder ? resultHeader .map((h) => h.id) .sort( (a, b) => preferences?.columnsOrder.indexOf(a) - preferences?.columnsOrder.indexOf(b) ) : resultHeader.map((h) => h.id)), 'logs', ]) useEffect(() => { updateTypebot({ resultsTablePreferences: { columnsVisibility, columnsOrder, columnsWidth: debouncedColumnsWidth, }, }) }, [columnsOrder, columnsVisibility, debouncedColumnsWidth, updateTypebot]) const bottomElement = useRef(null) const tableWrapper = useRef(null) const columns = React.useMemo[]>( () => [ { id: 'select', enableResizing: false, maxSize: 40, header: ({ table }) => ( ), cell: ({ row }) => (
), }, ...resultHeader.map>((header) => ({ id: header.id, accessorKey: header.label, size: header.isLong ? 400 : 200, header: () => ( {header.label} ), cell: (info) => { const value = info.getValue() as CellValueType | undefined if (!value) return return value.element || value.plainText || '' }, })), { id: 'logs', enableResizing: false, maxSize: 110, header: () => ( Logs ), cell: ({ row }) => ( ), }, ], // eslint-disable-next-line react-hooks/exhaustive-deps [resultHeader] ) const instance = useReactTable({ data, columns, state: { rowSelection, columnVisibility: columnsVisibility, columnOrder: columnsOrder, columnSizing: columnsWidth, }, getRowId: (row) => row.id.plainText, columnResizeMode: 'onChange', onRowSelectionChange: setRowSelection, onColumnVisibilityChange: setColumnsVisibility, onColumnSizingChange: setColumnsWidth, onColumnOrderChange: setColumnsOrder, getCoreRowModel: getCoreRowModel(), }) 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) return () => { observer.disconnect() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [bottomElement.current]) const handleObserver = (entities: IntersectionObserverEntry[]) => { const target = entities[0] if (target.isIntersecting) onScrollToBottom() } return ( setRowSelection({})} mr="2" /> {instance.getHeaderGroups().map((headerGroup) => ( ))} {instance.getRowModel().rows.map((row, rowIndex) => ( ))} {hasMore === true && ( )} ) } const IndeterminateCheckbox = React.forwardRef( // eslint-disable-next-line @typescript-eslint/no-explicit-any ({ indeterminate, checked, ...rest }: any, ref) => { const defaultRef = React.useRef() // eslint-disable-next-line @typescript-eslint/no-explicit-any const resolvedRef: any = ref || defaultRef return ( ) } ) export const HeaderIcon = ({ header }: { header: ResultHeaderCell }) => header.blockType ? ( ) : header.variableId ? ( ) : ( )