83 lines
3.1 KiB
TypeScript
83 lines
3.1 KiB
TypeScript
|
import { useState, useCallback, useEffect, useRef, ChangeEvent } from 'react';
|
||
|
import { utils, read, writeFileXLSX, WorkBook } from 'xlsx';
|
||
|
import { DataEditor, GridCellKind, GridCell, GridColumn, Item, DataEditorRef, EditableGridCell } from '@glideapps/glide-data-grid';
|
||
|
import "@glideapps/glide-data-grid/dist/index.css";
|
||
|
|
||
|
// this will store the raw data objects
|
||
|
let data: any[] = [];
|
||
|
// this will store the header names
|
||
|
let header: string[] = [];
|
||
|
|
||
|
function App() {
|
||
|
const [cols, setCols] = useState<GridColumn[]>([]); // gdg column objects
|
||
|
const [rows, setRows] = useState<number>(0); // number of rows
|
||
|
const ref = useRef<DataEditorRef>(null); // gdg ref
|
||
|
|
||
|
// read/write between gdg and the backing data store
|
||
|
const getContent = useCallback((cell: Item): GridCell => {
|
||
|
const [col, row] = cell;
|
||
|
return {
|
||
|
kind: GridCellKind.Text,
|
||
|
allowOverlay: true,
|
||
|
readonly: false,
|
||
|
displayData: String(data[row]?.[header[col]]??""),
|
||
|
data: data[row]?.[header[col]],
|
||
|
};
|
||
|
}, []);
|
||
|
const onCellEdited = useCallback((cell: Item, newValue: EditableGridCell) => {
|
||
|
const [ col, row ] = cell;
|
||
|
data[row][header[col]] = newValue.data;
|
||
|
}, []);
|
||
|
|
||
|
// update the data store from a workbook object
|
||
|
const parse_wb = (wb: WorkBook) => {
|
||
|
const sheet = wb.Sheets[wb.SheetNames[0]];
|
||
|
data = utils.sheet_to_json<any>(sheet);
|
||
|
const range = utils.decode_range(sheet["!ref"]??"A1"); range.e.r = range.s.r;
|
||
|
header = utils.sheet_to_json<string[]>(sheet, {header: 1, range})[0];
|
||
|
setCols(header.map(h => ({title: h, id: h} as GridColumn)));
|
||
|
setRows(data.length);
|
||
|
if(data.length > 0) {
|
||
|
let cells = data.map(
|
||
|
(_,R) => Array.from({length:header.length}, (_,C) => ({cell: ([C,R] as Item)}))
|
||
|
).flat();
|
||
|
ref.current?.updateCells(cells);
|
||
|
}
|
||
|
};
|
||
|
// file input element onchange event handler
|
||
|
const onChange = useCallback(async (e: ChangeEvent<HTMLInputElement>) => {
|
||
|
if(!e.target?.files) return;
|
||
|
parse_wb(read(await e.target.files[0].arrayBuffer()));
|
||
|
}, []);
|
||
|
// when the component loads, fetch and display a sample workbook
|
||
|
useEffect(() => {
|
||
|
(async() => {
|
||
|
parse_wb(read(await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer()));
|
||
|
})();
|
||
|
}, []);
|
||
|
|
||
|
// export data
|
||
|
const exportXLSX = useCallback(() => {
|
||
|
// generate worksheet using data with the order specified in the columns array
|
||
|
const ws = utils.json_to_sheet(data, {header: cols.map(c => c.id ?? c.title)});
|
||
|
// rewrite header row with titles
|
||
|
utils.sheet_add_aoa(ws, [cols.map(c => c.title ?? c.id)], {origin: "A1"});
|
||
|
// create workbook
|
||
|
const wb = utils.book_new();
|
||
|
utils.book_append_sheet(wb, ws, "Export"); // replace with sheet name
|
||
|
// download file
|
||
|
writeFileXLSX(wb, "sheetjs-gdg.xlsx");
|
||
|
}, []);
|
||
|
|
||
|
return ( <>
|
||
|
<input type="file" onChange={onChange} />
|
||
|
<button onClick={exportXLSX}><b>Export XLSX!</b></button>
|
||
|
<div className="App">
|
||
|
<DataEditor getCellContent={getContent} columns={cols} rows={rows} onCellEdited={onCellEdited} ref={ref}/>
|
||
|
</div>
|
||
|
<div id="portal"></div>
|
||
|
</>
|
||
|
)
|
||
|
}
|
||
|
export default App;
|