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 { createTable, useTableInstance, getCoreRowModel, ColumnOrderState, } 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' type RowType = { id: string [key: string]: | { plainText: string element?: JSX.Element | undefined } | string } const table = createTable().setRowType() type ResultsTableProps = { resultHeader: ResultHeaderCell[] data: RowType[] 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( () => [ table.createDisplayColumn({ id: 'select', enableResizing: false, maxSize: 40, header: ({ instance }) => ( ), cell: ({ row }) => (
), }), ...resultHeader.map((header) => table.createDataColumn(header.label, { id: header.id, size: header.isLong ? 400 : 200, cell: (info) => { const value = info.getValue() if (!value) return if (typeof value === 'string') return '' return value.element || value.plainText || '' }, header: () => ( {header.label} ), }) ), table.createDisplayColumn({ id: 'logs', enableResizing: false, maxSize: 110, header: () => ( Logs ), cell: ({ row }) => ( ), }), ], // eslint-disable-next-line react-hooks/exhaustive-deps [resultHeader] ) const instance = useTableInstance(table, { data, columns, state: { rowSelection, columnVisibility: columnsVisibility, columnOrder: columnsOrder, columnSizing: columnsWidth, }, getRowId: (row) => row.id, 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 ? ( ) : ( )