Skip to content

Commit

Permalink
Merge pull request #3 from napisani/add-export
Browse files Browse the repository at this point in the history
add export / copy feature to table
  • Loading branch information
napisani authored Sep 29, 2024
2 parents 2730afe + 17b3689 commit ac09c1a
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 7 deletions.
140 changes: 140 additions & 0 deletions web/src/ExportTableRows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { useCallback, useState } from 'react'
import { DataHeaders } from './query-results'

const exportToCSV = (
rows: Record<string, any>[],
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, any>[]): 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<string, any>[]
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 (
<div>
<label
style={{ display: 'inline-block', margin: '8px' }}
htmlFor="exportType"
>
Export type:
</label>
<select
style={{ display: 'inline-block', margin: '8px' }}
disabled={selectedRows.length === 0}
value={exportType}
onChange={(e) => {
setExportType(e.target.value)
}}
id="exportType"
name="exportType"
>
<option value="csv">CSV</option>
<option value="json">JSON</option>
<option value="tab">Tab</option>
</select>

<label
style={{ display: 'inline-block', margin: '8px' }}
htmlFor="includeHeader"
>
Include header:
</label>
<input
disabled={selectedRows.length === 0}
id="includeHeader"
type="checkbox"
checked={includeHeader}
onChange={(e) => setIncludeHeader(e.target.checked)}
/>
<button
style={{ display: 'inline-block', margin: '8px' }}
disabled={selectedRows.length === 0}
onClick={handleExport}
>
Export
</button>
<button
style={{ display: 'inline-block', margin: '8px' }}
disabled={selectedRows.length === 0}
onClick={copyToClipboard}
>
Copy
</button>
</div>
)
}
46 changes: 39 additions & 7 deletions web/src/Table.tsx
Original file line number Diff line number Diff line change
@@ -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<Record<string, any>[]>([])

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]))
Expand All @@ -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<string, any> }) => node.data
)
setSelectedRows(selectedData)
console.log('Selected row:', selectedData[selectedData.length - 1])
}, [])

const gridRef = React.useRef<any>()

return (
<div
style={{
Expand All @@ -45,16 +73,20 @@ export function Table({ content, headers, settings }: TableProps) {
>
<div
className={settings.tableTheme}
style={{ height: `${settings.gridCellHeightPx}px` }} // the grid will fill the size of the parent container
style={{ height: `${settings.gridCellHeightPx}px` }}
>
<AgGridReact
ref={gridRef}
rowData={data}
columnDefs={columns}
pagination={true}
paginationPageSize={paginationPageSize}
paginationPageSizeSelector={paginationPageSizeSelector}
rowSelection="multiple"
onSelectionChanged={onSelectionChanged}
/>
</div>
<ExportTableRows headers={headers} selectedRows={selectedRows} />
</div>
)
}

0 comments on commit ac09c1a

Please sign in to comment.