| 
									
										
										
										
											2023-02-07 09:24:49 +00:00
										 |  |  | 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() => { | 
					
						
							| 
									
										
										
										
											2024-04-26 04:16:13 +00:00
										 |  |  |       parse_wb(read(await (await fetch("https://docs.sheetjs.com/pres.numbers")).arrayBuffer())); | 
					
						
							| 
									
										
										
										
											2023-02-07 09:24:49 +00:00
										 |  |  |     })(); | 
					
						
							|  |  |  |   }, []); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // 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; |