Excel component (#973)

* [components,excel][m]: initial version of the <Excel /> component

* [components,excel][m]: add support for multiple sheets

* [components,map,excel][l]: fix leaflet map markers on prod and implement excel component

* Bump version

* [components,excel][xs]: change data for one of the stories
This commit is contained in:
João Demenech
2023-07-13 13:02:37 -03:00
committed by GitHub
parent 58b7b4e753
commit f3c2a2ffa7
10 changed files with 443 additions and 24 deletions

View File

@@ -1,10 +0,0 @@
<!--
This is necessary for Leaflet maps to work.
-->
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>

View File

@@ -17,7 +17,7 @@
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"build-tailwind": "NODE_ENV=production npx tailwindcss --postcss -c tailwind.config.js -i src/index.css -o ./dist/styles.css --minify",
"build-tailwind": "NODE_ENV=production npx tailwindcss --postcss -c tailwind.config.js -i src/index.css -o ./dist/style.css --minify",
"prepare": "npm run build",
"fix-leaflet": "node ./scripts/fix-leaflet.cjs"
},
@@ -30,12 +30,14 @@
"@heroicons/react": "^2.0.17",
"@planet/maps": "^8.1.0",
"@tanstack/react-table": "^8.8.5",
"ag-grid-react": "^30.0.4",
"chroma-js": "^2.4.2",
"flexsearch": "0.7.21",
"leaflet": "^1.9.4",
"next-mdx-remote": "^4.4.1",
"ol": "^7.4.0",
"papaparse": "^5.4.1",
"postcss-url": "^10.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.9",
@@ -44,7 +46,8 @@
"react-vega": "^7.6.0",
"vega": "5.25.0",
"vega-lite": "5.1.0",
"vitest": "^0.31.4"
"vitest": "^0.31.4",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.0.7",
@@ -93,7 +96,7 @@
"require": "./dist/components.umd.js"
},
"./styles.css": {
"import": "./dist/styles.css"
"import": "./dist/style.css"
}
},
"publishConfig": {

View File

@@ -1,8 +1,9 @@
console.log('PostCSS')
console.log('PostCSS');
export default {
plugins: {
'postcss-import': {},
'postcss-url': { url: 'inline' },
tailwindcss: {},
autoprefixer: {},
},

View File

@@ -0,0 +1,99 @@
import { useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner';
import { read, utils } from 'xlsx';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
export type ExcelProps = {
url: string;
};
export function Excel({ url }: ExcelProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeSheetName, setActiveSheetName] = useState<string>();
const [workbook, setWorkbook] = useState<any>();
const [rows, setRows] = useState<any>();
const [cols, setCols] = useState<any>();
const loadSpreadsheet = (wb: any, name: string) => {
setActiveSheetName(name);
const ws = wb.Sheets[name];
const range = utils.decode_range(ws['!ref'] || 'A1');
const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
field: utils.encode_col(i),
}));
const rowsAr = utils.sheet_to_json(ws, { header: 1 });
const rows = rowsAr.map((row) => {
const obj = {};
columns.forEach((col, i) => {
obj[col.field] = row[i];
});
return obj;
});
setRows(rows);
setCols(columns);
};
useEffect(() => {
setIsLoading(true);
fetch(url)
.then((res) => res.arrayBuffer())
.then((f) => {
const wb = read(f);
setWorkbook(wb);
loadSpreadsheet(wb, wb.SheetNames[0]);
setIsLoading(false);
});
}, [url]);
return isLoading ? (
<div className="w-full flex items-center justify-center w-[600px] h-[300px]">
<LoadingSpinner />
</div>
) : (
<>
{cols && rows && (
<div>
<div
className="ag-theme-alpine"
style={{ height: 400, width: '100%' }}
>
<AgGridReact
rowData={rows}
columnDefs={cols}
defaultColDef={{
resizable: true,
minWidth: 200,
flex: 1,
sortable: true,
filter: true,
}}
></AgGridReact>
</div>
<div className="border-t">
{workbook.SheetNames.map((name: string, idx: number) => {
return (
<>
<button
key={idx}
className={`text-sm px-3 pb-2 pt-4 border-b border-l border-r ${
name == activeSheetName ? 'font-semibold' : ''
}`}
onClick={() => loadSpreadsheet(workbook, name)}
>
{name}
</button>
</>
);
})}
</div>
</div>
)}
</>
);
}

View File

@@ -6,9 +6,10 @@ import {
MapContainer,
TileLayer,
GeoJSON as GeoJSONLayer,
LayersControl
LayersControl,
} from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet';
export type MapProps = {
@@ -131,6 +132,27 @@ export function Map({
<LayersControl.Overlay key={layer.name} checked name={layer.name}>
<GeoJSONLayer
data={data}
// @ts-ignore
pointToLayer={(feature, latlng) => {
// This resolver an issue in which the bundled map was
// not finding the images
const leafletBase =
'https://unpkg.com/leaflet@1.9.4/dist/images/';
const icon = new L.Icon({
iconUrl: leafletBase + 'marker-icon.png',
iconRetinaUrl: leafletBase + 'marker-icon-2x.png',
shadowUrl: leafletBase + 'marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41],
});
const iconMarker = L.marker(latlng, { icon });
return iconMarker;
}}
style={(geoJsonFeature: any) => {
// Set the fill color of each feature when appliable
if (

View File

@@ -1,7 +1,9 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "leaflet";
@import "include";
@import "leaflet";
@import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/styles/ag-theme-alpine.css';
@import "tailwindcss/utilities";

View File

@@ -6,3 +6,4 @@ export * from "./components/VegaLite";
export * from "./components/FlatUiTable";
export * from './components/OpenLayers/OpenLayers';
export * from "./components/Map";
export * from "./components/Excel";

View File

@@ -0,0 +1,34 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Excel, ExcelProps } from '../src/components/Excel';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = {
title: 'Components/Excel',
component: Excel,
tags: ['autodocs'],
argTypes: {
url: {
description:
'Url of the file to be displayed e.g.: "https://url.to/data.csv"',
},
},
};
export default meta;
type Story = StoryObj<ExcelProps>;
export const SingleSheet: Story = {
name: 'Excel file with just one sheet',
args: {
url: 'https://sheetjs.com/pres.xlsx',
},
};
export const MultipleSheet: Story = {
name: 'Excel file with multiple sheets',
args: {
url: 'https://storage.portaljs.org/IC-Gantt-Chart-Project-Template-8857.xlsx',
},
};