diff --git a/web/src/ExportTableRows.tsx b/web/src/ExportTableRows.tsx new file mode 100644 index 0000000..06e3fb5 --- /dev/null +++ b/web/src/ExportTableRows.tsx @@ -0,0 +1,140 @@ +import { useCallback, useState } from 'react' +import { DataHeaders } from './query-results' + +const exportToCSV = ( + rows: Record[], + headers: string[], + includeHeader: boolean = true, + separator = ',' +): string => { + const csvContent = [ + ...rows.map((row) => + headers.map((header) => JSON.stringify(row[header] ?? '')).join(separator) + ), + ] + if (includeHeader) { + csvContent.unshift(headers.join(separator)) + } + return csvContent.join('\n') +} + +const exportToJSON = (rows: Record[]): string => { + return JSON.stringify(rows, null, 2) +} + +function downloadStringAsFile( + content: string, + filename: string, + mimeType: string +): void { + const blob = new Blob([content], { type: mimeType }) + + const url = URL.createObjectURL(blob) + + const link = document.createElement('a') + link.href = url + link.download = filename + + document.body.appendChild(link) + + link.click() + + document.body.removeChild(link) + URL.revokeObjectURL(url) +} + +export function ExportTableRows({ + selectedRows, + headers, +}: Readonly<{ + selectedRows: Record[] + headers: DataHeaders +}>) { + const [exportType, setExportType] = useState('csv') + const [includeHeader, setIncludeHeader] = useState(true) + + const getRowsInFormat = useCallback(() => { + if (selectedRows.length === 0) { + return '' + } + + const headerNames = headers.map((header) => header.name) + + switch (exportType) { + case 'csv': + return exportToCSV(selectedRows, headerNames, includeHeader, ',') + case 'json': + return exportToJSON(selectedRows) + case 'tab': + return exportToCSV(selectedRows, headerNames, includeHeader, '\t') + default: + throw new Error(`Unsupported export type: ${exportType}`) + } + }, [selectedRows, headers, exportType]) + + const handleExport = useCallback(() => { + const rowsInFormat = getRowsInFormat() + const filename = `dbbg_export.${exportType}` + const mimeType = `text/${exportType}` + + downloadStringAsFile(rowsInFormat, filename, mimeType) + }, [getRowsInFormat, exportType]) + + const copyToClipboard = useCallback(() => { + const rowsInFormat = getRowsInFormat() + navigator.clipboard.writeText(rowsInFormat) + }, [getRowsInFormat]) + + return ( +
+ + + + + setIncludeHeader(e.target.checked)} + /> + + +
+ ) +} diff --git a/web/src/Table.tsx b/web/src/Table.tsx index 06f1c0c..60d1515 100644 --- a/web/src/Table.tsx +++ b/web/src/Table.tsx @@ -1,26 +1,43 @@ -import 'ag-grid-community/styles/ag-grid.css' // Mandatory CSS required by the grid +import React, { useMemo, useState, useCallback } from 'react' +import 'ag-grid-community/styles/ag-grid.css' import 'ag-grid-community/styles/ag-theme-alpine.min.css' import 'ag-grid-community/styles/ag-theme-balham.min.css' import 'ag-grid-community/styles/ag-theme-material.min.css' import 'ag-grid-community/styles/ag-theme-quartz.min.css' -import { AgGridReact } from 'ag-grid-react' // AG Grid Component +import { AgGridReact } from 'ag-grid-react' import { TableSettings } from './TableControls' import { AttributeMap, DataHeader, DataHeaders } from './query-results' import { parseByInferredType } from './utils' +import { ExportTableRows } from './ExportTableRows' -import { useMemo } from 'react' export interface TableProps { content: AttributeMap[] headers: DataHeaders settings: TableSettings } + const paginationPageSize = 10 const paginationPageSizeSelector = [10, 20, 50, 100, 200, 500, 1000] export function Table({ content, headers, settings }: TableProps) { - const columns = headers.map((header: DataHeader) => ({ - field: header.name, - })) + const [selectedRows, setSelectedRows] = useState[]>([]) + + const columns = useMemo( + () => [ + { + headerName: '', + field: 'selection', + checkboxSelection: true, + headerCheckboxSelection: true, + width: 50, + pinned: 'left' as const, + }, + ...headers.map((header: DataHeader) => ({ + field: header.name, + })), + ], + [headers] + ) const data = useMemo(() => { const headerMap = new Map(headers.map((header) => [header.name, header])) @@ -37,6 +54,17 @@ export function Table({ content, headers, settings }: TableProps) { ) }, [content, headers]) + const onSelectionChanged = useCallback(() => { + const selectedNodes = gridRef.current.api.getSelectedNodes() + const selectedData = selectedNodes.map( + (node: { data: Record }) => node.data + ) + setSelectedRows(selectedData) + console.log('Selected row:', selectedData[selectedData.length - 1]) + }, []) + + const gridRef = React.useRef() + return (
+
) }