Compare commits

...

26 Commits

Author SHA1 Message Date
Leonardo Yuri Farias
f7f03fddca Merge pull request #1085 from datopian/changeset-release/main
Version Packages
2024-01-31 13:20:53 -03:00
github-actions[bot]
0891dfde2d Version Packages 2024-01-31 09:31:04 +00:00
Anuar Ustayev (aka Anu)
c904e3731b Merge pull request #1083 from datopian/feature/table-with-integration-with-datastore-api
Created integration with datastore api for table component
2024-01-31 15:28:17 +06:00
Gutts-n
86a2945ee6 Created integration with datastore api for table component 2024-01-29 14:07:42 -03:00
Leonardo Yuri Farias
09daa98b28 Merge pull request #1082 from datopian/changeset-release/main
Version Packages
2024-01-25 16:49:22 -03:00
github-actions[bot]
b511c9f71b Version Packages 2024-01-25 19:48:44 +00:00
Leonardo Yuri Farias
464cda6db8 Merge pull request #1081 from datopian/fix/changed-the-download-behavior
Fixed error to remove anchor from document
2024-01-25 16:45:58 -03:00
Gutts-n
2bbf313489 Fixed error to remove anchor from document 2024-01-25 16:45:39 -03:00
Gutts-n
c26b76368d Fixed error to remove anchor from document 2024-01-25 16:43:47 -03:00
Leonardo Yuri Farias
af11f0cfd5 Merge pull request #1080 from datopian/changeset-release/main
Version Packages
2024-01-25 16:20:34 -03:00
github-actions[bot]
9ae2b31113 Version Packages 2024-01-25 19:19:58 +00:00
Leonardo Yuri Farias
2bffd130c8 Merge pull request #1079 from datopian/fix/changed-the-download-behavior
Changed behavior of the download data bucket viewer component
2024-01-25 16:17:14 -03:00
Gutts-n
058d23678a Added changeset to the PR 2024-01-25 16:16:49 -03:00
Gutts-n
540a08934c Changed behavior of the download data bucket viewer component 2024-01-25 16:10:22 -03:00
Leonardo Yuri Farias
7d010cfee4 Merge pull request #1078 from datopian/changeset-release/main
Version Packages
2024-01-24 17:23:14 -03:00
github-actions[bot]
dd79da1c6b Version Packages 2024-01-24 20:22:57 +00:00
Leonardo Yuri Farias
a58e2b81f7 Merge pull request #1077 from datopian/feature/download-loading-message
Created property to present a component while loading the download of the file and fixed download bug on pagination
2024-01-24 17:20:09 -03:00
Gutts-n
6d7acd27ed Created property to present a component while is loading the download of the file and fixed download bug on pagination 2024-01-24 17:15:14 -03:00
Leonardo Yuri Farias
7c30842c7d Merge pull request #1076 from datopian/changeset-release/main
Version Packages
2024-01-24 11:08:44 -03:00
github-actions[bot]
35ca1d6dfd Version Packages 2024-01-24 14:08:13 +00:00
Leonardo Yuri Farias
a7e90b64af Merge pull request #1075 from datopian/fix/download-button-presented-on-start-of-bucket-viewer
Fixed problem presenting the download component in the first load of …
2024-01-24 11:05:18 -03:00
Gutts-n
26dcffc279 Fixed problem presenting the download component in the first load of the bucket viewer 2024-01-24 11:03:08 -03:00
Leonardo Yuri Farias
d18e3dd486 Merge pull request #1074 from datopian/changeset-release/main
Version Packages
2024-01-23 16:54:19 -03:00
github-actions[bot]
8d7059acb4 Version Packages 2024-01-23 19:53:24 +00:00
Leonardo Yuri Farias
09d5324d4e Merge pull request #1073 from datopian/feature/search-and-pagination-for-bucket-viewer
Fixed bug on filter by startDate
2024-01-23 16:50:37 -03:00
Gutts-n
cf24042a91 Fixed bug on filter by startDate 2024-01-23 16:49:15 -03:00
6 changed files with 409 additions and 195 deletions

View File

@@ -1,5 +1,41 @@
# @portaljs/components # @portaljs/components
## 0.5.10
### Patch Changes
- [#1083](https://github.com/datopian/portaljs/pull/1083) [`86a2945e`](https://github.com/datopian/portaljs/commit/86a2945ee68dfcea0299984ca9cc9070d68fe1c2) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Created integration with datastore api for table component
## 0.5.9
### Patch Changes
- [#1081](https://github.com/datopian/portaljs/pull/1081) [`2bbf3134`](https://github.com/datopian/portaljs/commit/2bbf3134896df3ecc66560bdf95bece143614c7b) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Fixed error to remove anchor from document
## 0.5.8
### Patch Changes
- [#1079](https://github.com/datopian/portaljs/pull/1079) [`058d2367`](https://github.com/datopian/portaljs/commit/058d23678a024890f8a6d909ded9fc8fc11cf145) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Changed the download behaviour of the bucket viewer component and removed loading component while downloading
## 0.5.7
### Patch Changes
- [#1077](https://github.com/datopian/portaljs/pull/1077) [`6d7acd27`](https://github.com/datopian/portaljs/commit/6d7acd27ed9299cbcc14eab906f2f0eb414656b8) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Created property to present a component while is loading the download of the file and fixed download bug on pagination
## 0.5.6
### Patch Changes
- [#1075](https://github.com/datopian/portaljs/pull/1075) [`26dcffc2`](https://github.com/datopian/portaljs/commit/26dcffc279057f80a579134e862085ba042c06c3) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Fixed problem presenting the download component in the first load of the bucket viewer
## 0.5.5
### Patch Changes
- [#1073](https://github.com/datopian/portaljs/pull/1073) [`cf24042a`](https://github.com/datopian/portaljs/commit/cf24042a910567e98eeb75ade42ce0149bdb62d1) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Fixed filter by startDate error
## 0.5.4 ## 0.5.4
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@portaljs/components", "name": "@portaljs/components",
"version": "0.5.4", "version": "0.5.10",
"type": "module", "type": "module",
"description": "https://portaljs.org", "description": "https://portaljs.org",
"keywords": [ "keywords": [

View File

@@ -1,17 +1,19 @@
import { CSSProperties, ReactNode, useEffect, useState } from 'react'; import { CSSProperties, ReactNode, useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
export interface BucketViewerFilterSearchedDataEvent { export interface BucketViewerFilterSearchedDataEvent {
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
} }
export interface BucketViewerProps { export interface BucketViewerProps {
onLoadTotalNumberOfItems?: (total: number) => void; onLoadTotalNumberOfItems?: (total: number) => void;
domain: string; domain: string;
downloadConfig?: {
hoverOfTheFileComponent?: ReactNode;
};
suffix?: string; suffix?: string;
className?: string; className?: string;
downloadComponent?: ReactNode;
paginationConfig?: BucketViewerPaginationConfig; paginationConfig?: BucketViewerPaginationConfig;
filterState?: BucketViewerFilterSearchedDataEvent; filterState?: BucketViewerFilterSearchedDataEvent;
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>; dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
@@ -34,20 +36,20 @@ export interface BucketViewerData {
export function BucketViewer({ export function BucketViewer({
domain, domain,
downloadComponent,
suffix, suffix,
dataMapperFn, dataMapperFn,
className, className,
filterState, filterState,
paginationConfig, paginationConfig,
onLoadTotalNumberOfItems downloadConfig,
onLoadTotalNumberOfItems,
}: BucketViewerProps) { }: BucketViewerProps) {
suffix = suffix ?? '/'; suffix = suffix ?? '/';
downloadComponent = downloadComponent ?? <></>;
const { hoverOfTheFileComponent } = downloadConfig ?? {};
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [showDownloadComponentOnLine, setShowDownloadComponentOnLine] = useState(0); const [showDownloadComponentOnLine, setShowDownloadComponentOnLine] =
useState(-1);
const [currentPage, setCurrentPage] = useState<number>(0); const [currentPage, setCurrentPage] = useState<number>(0);
const [lastPage, setLastPage] = useState<number>(0); const [lastPage, setLastPage] = useState<number>(0);
const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]); const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]);
@@ -65,56 +67,59 @@ export function BucketViewer({
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, [domain, suffix]); }, [domain, suffix]);
useEffect( useEffect(() => {
() => { if (paginationConfig) {
if(paginationConfig) { const startIndex = paginationConfig
const startIndex = paginationConfig ? currentPage * paginationConfig.itemsPerPage
? currentPage * paginationConfig.itemsPerPage : 0;
: 0;
const endIndex = paginationConfig const endIndex = paginationConfig
? startIndex + paginationConfig.itemsPerPage ? startIndex + paginationConfig.itemsPerPage
: 0; : 0;
setLastPage(Math.ceil(filteredData.length / paginationConfig.itemsPerPage) - 1); setLastPage(
setPaginatedData(filteredData.slice(startIndex, endIndex)); Math.ceil(filteredData.length / paginationConfig.itemsPerPage) - 1
} );
}, setPaginatedData(filteredData.slice(startIndex, endIndex));
[currentPage, filteredData] }
); }, [currentPage, filteredData]);
useEffect(
() => {
if(onLoadTotalNumberOfItems) onLoadTotalNumberOfItems(filteredData.length);
},
[filteredData]
)
useEffect(() => { useEffect(() => {
if(!filterState) return; if (onLoadTotalNumberOfItems) onLoadTotalNumberOfItems(filteredData.length);
}, [filteredData]);
if(filterState.startDate && filterState.startDate) { useEffect(() => {
setFilteredData(bucketFiles.filter(({ dateProps }) => if (!filterState) return;
dateProps
? if (filterState.startDate && filterState.endDate) {
dateProps.date.getTime() >= filterState.startDate.getTime() setFilteredData(
&& dateProps.date.getTime() <= filterState.endDate.getTime() bucketFiles.filter(({ dateProps }) =>
dateProps
? dateProps.date.getTime() >= filterState.startDate.getTime() &&
dateProps.date.getTime() <= filterState.endDate.getTime()
: true : true
)); )
} else if(filterState.startDate) { );
setFilteredData(bucketFiles.filter(({ dateProps }) => } else if (filterState.startDate) {
dateProps ? dateProps.date.getTime() >= filterState.startDate.getTime() : true setFilteredData(
)); bucketFiles.filter(({ dateProps }) =>
} else if(filterState.endDate) { dateProps
setFilteredData(bucketFiles.filter(({ dateProps }) => ? dateProps.date.getTime() >= filterState.startDate.getTime()
dateProps ? dateProps.date.getTime() <= filterState.endDate.getTime() : true : true
)); )
} else { );
setFilteredData(bucketFiles); } else if (filterState.endDate) {
} setFilteredData(
}, bucketFiles.filter(({ dateProps }) =>
[filterState] dateProps
) ? dateProps.date.getTime() <= filterState.endDate.getTime()
: true
)
);
} else {
setFilteredData(bucketFiles);
}
}, [filterState]);
return isLoading ? ( return isLoading ? (
<div className="w-full flex items-center justify-center h-[300px]"> <div className="w-full flex items-center justify-center h-[300px]">
@@ -122,67 +127,61 @@ export function BucketViewer({
</div> </div>
) : bucketFiles ? ( ) : bucketFiles ? (
<> <>
{...(paginationConfig && bucketFiles {...(paginationConfig && bucketFiles ? paginatedData : filteredData)?.map(
? paginatedData (data, i) => (
: filteredData <ul
)?.map((data, i) => ( onClick={() => {
<ul const a: HTMLAnchorElement = document.createElement('a');
onClick={() => { a.href = data.downloadFileUri;
const anchorId = `download_anchor_${i}`; a.target = `_blank`;
const a: HTMLAnchorElement = a.download = data.fileName;
(document.getElementById(anchorId) as HTMLAnchorElement | null) ?? document.body.appendChild(a);
document.createElement('a'); a.click();
a.id = anchorId; document.body.removeChild(a);
if (a.download) a.click(); }}
else { key={i}
setIsLoading(true); onMouseEnter={() => setShowDownloadComponentOnLine(i)}
fetch(data.downloadFileUri) onMouseLeave={() => setShowDownloadComponentOnLine(undefined)}
.then((res) => res.blob()) className={`${
.then((res) => { className ??
a.href = URL.createObjectURL(res); 'mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer'
a.download = res.name ?? data.fileName; }`}
document.body.appendChild(a); >
a.click(); {hoverOfTheFileComponent && showDownloadComponentOnLine === i ? (
}) hoverOfTheFileComponent
.finally(() => setIsLoading(false));
}
}}
key={i}
onMouseEnter={() => setShowDownloadComponentOnLine(i)}
onMouseLeave={() => setShowDownloadComponentOnLine(undefined)}
className={`${
className ??
'mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer'
}`}
>
{
downloadComponent && showDownloadComponentOnLine === i
? downloadComponent
: <></>
}
<div>
<li>{data.fileName}</li>
{data.dateProps && data.dateProps.dateFormatter ? (
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
) : ( ) : (
<></> <></>
)} )}
</div> <div className="flex justify-between w-full items-center">
</ul> <div>
))} <li>{data.fileName}</li>
{data.dateProps && data.dateProps.dateFormatter ? (
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
) : (
<></>
)}
</div>
</div>
</ul>
)
)}
{paginationConfig ? ( {paginationConfig ? (
<ul className={ <ul
paginationConfig.containerClassName className={
? paginationConfig.containerClassName paginationConfig.containerClassName
: "flex justify-end gap-x-[0.5rem] w-full" ? paginationConfig.containerClassName
} : 'flex justify-end gap-x-[0.5rem] w-full'
style={paginationConfig.containerStyles ?? {}} }
> style={paginationConfig.containerStyles ?? {}}
>
<li> <li>
<button <button
className="hover:cursor-pointer hover:disabled:cursor-not-allowed" className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
disabled={currentPage === 0} disabled={currentPage === 0}
onClick={() => setCurrentPage(0)}>First</button> onClick={() => setCurrentPage(0)}
>
First
</button>
</li> </li>
<li> <li>
<button <button
@@ -193,9 +192,7 @@ export function BucketViewer({
Previous Previous
</button> </button>
</li> </li>
<label> <label>{currentPage + 1}</label>
{currentPage + 1}
</label>
<li> <li>
<button <button

View File

@@ -6,6 +6,8 @@ import {
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
PaginationState,
Table as ReactTable,
useReactTable, useReactTable,
} from '@tanstack/react-table'; } from '@tanstack/react-table';
@@ -25,12 +27,19 @@ import DebouncedInput from './DebouncedInput';
import loadData from '../lib/loadData'; import loadData from '../lib/loadData';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
export type TableData = { cols: {key: string, name: string}[]; data: any[]; total: number };
export type TableProps = { export type TableProps = {
data?: Array<{ [key: string]: number | string }>; data?: Array<{ [key: string]: number | string }>;
cols?: Array<{ [key: string]: string }>; cols?: Array<{ [key: string]: string }>;
csv?: string; csv?: string;
url?: string; url?: string;
fullWidth?: boolean; fullWidth?: boolean;
datastoreConfig?: {
dataStoreURI: string;
rowsPerPage?: number;
dataMapperFn: (data) => Promise<TableData> | TableData;
};
}; };
export const Table = ({ export const Table = ({
@@ -39,8 +48,28 @@ export const Table = ({
csv = '', csv = '',
url = '', url = '',
fullWidth = false, fullWidth = false,
datastoreConfig,
}: TableProps) => { }: TableProps) => {
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [pageMap, setPageMap] = useState(new Map<number, boolean>());
const {
dataMapperFn,
dataStoreURI,
rowsPerPage = 10,
} = datastoreConfig ?? {};
const [globalFilter, setGlobalFilter] = useState('');
const [isLoadingPage, setIsLoadingPage] = useState<boolean>(false);
const [totalOfRows, setTotalOfRows] = useState<number>(0);
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: rowsPerPage,
});
const [lastIndex, setLastIndex] = useState(pageSize);
const [startIndex, setStartIndex] = useState(0);
const [hasSorted, setHasSorted] = useState(false);
if (csv) { if (csv) {
const out = parseCsv(csv); const out = parseCsv(csv);
@@ -62,21 +91,56 @@ export const Table = ({
); );
}, [data, cols]); }, [data, cols]);
const [globalFilter, setGlobalFilter] = useState(''); let table: ReactTable<unknown>;
const table = useReactTable({ if (datastoreConfig) {
data, useEffect(() => {
columns: tableCols, setIsLoading(true);
getCoreRowModel: getCoreRowModel(), fetch(`${dataStoreURI}&limit=${rowsPerPage}&offset=0`)
state: { .then((res) => res.json())
globalFilter, .then(async (res) => {
}, const { data, cols, total } = await dataMapperFn(res);
globalFilterFn: globalFilterFn, setData(data);
onGlobalFilterChange: setGlobalFilter, setCols(cols);
getFilteredRowModel: getFilteredRowModel(), setTotalOfRows(Math.ceil(total / rowsPerPage));
getPaginationRowModel: getPaginationRowModel(), pageMap.set(0, true);
getSortedRowModel: getSortedRowModel(), })
}); .finally(() => setIsLoading(false));
}, [dataStoreURI]);
table = useReactTable({
data,
pageCount: totalOfRows,
columns: tableCols,
getCoreRowModel: getCoreRowModel(),
state: {
pagination: { pageIndex, pageSize },
},
getFilteredRowModel: getFilteredRowModel(),
manualPagination: true,
onPaginationChange: setPagination,
getSortedRowModel: getSortedRowModel(),
});
useEffect(() => {
if (!hasSorted) return;
queryDataByText(globalFilter);
}, [table.getState().sorting]);
} else {
table = useReactTable({
data,
columns: tableCols,
getCoreRowModel: getCoreRowModel(),
state: {
globalFilter,
},
globalFilterFn: globalFilterFn,
onGlobalFilterChange: setGlobalFilter,
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
}
useEffect(() => { useEffect(() => {
if (url) { if (url) {
@@ -91,6 +155,70 @@ export const Table = ({
} }
}, [url]); }, [url]);
const queryDataByText = (filter) => {
setIsLoadingPage(true);
const sortedParam = getSortParam();
fetch(
`${dataStoreURI}&limit=${rowsPerPage}&offset=0&q=${filter}${sortedParam}`
)
.then((res) => res.json())
.then(async (res) => {
const { data, total = 0 } = await dataMapperFn(res);
setTotalOfRows(Math.ceil(total / rowsPerPage));
setData(data);
const newMap = new Map();
newMap.set(0, true);
setPageMap(newMap);
table.setPageIndex(0);
setStartIndex(0);
setLastIndex(pageSize);
})
.finally(() => setIsLoadingPage(false));
};
const getSortParam = () => {
const sort = table.getState().sorting;
return sort.length == 0
? ``
: '&sort=' +
sort
.map(
(x, i) =>
`${x.id}${
i === sort.length - 1 ? (x.desc ? ` desc` : ` asc`) : `,`
}`
)
.reduce((x1, x2) => x1 + x2);
};
const queryPaginatedData = (newPageIndex) => {
let newStartIndex = newPageIndex * pageSize;
setStartIndex(newStartIndex);
setLastIndex(newStartIndex + pageSize);
if (!pageMap.get(newPageIndex)) pageMap.set(newPageIndex, true);
else return;
const sortedParam = getSortParam();
setIsLoadingPage(true);
fetch(
`${dataStoreURI}&limit=${rowsPerPage}&offset=${
newStartIndex + pageSize
}&q=${globalFilter}${sortedParam}`
)
.then((res) => res.json())
.then(async (res) => {
const { data: responseData } = await dataMapperFn(res);
responseData.forEach((e) => {
data[newStartIndex] = e;
newStartIndex++;
});
setData([...data]);
})
.finally(() => setIsLoadingPage(false));
};
return isLoading ? ( return isLoading ? (
<div className="w-full h-full min-h-[500px] flex items-center justify-center"> <div className="w-full h-full min-h-[500px] flex items-center justify-center">
<LoadingSpinner /> <LoadingSpinner />
@@ -99,7 +227,10 @@ export const Table = ({
<div className={`${fullWidth ? 'w-[90vw] ml-[calc(50%-45vw)]' : 'w-full'}`}> <div className={`${fullWidth ? 'w-[90vw] ml-[calc(50%-45vw)]' : 'w-full'}`}>
<DebouncedInput <DebouncedInput
value={globalFilter ?? ''} value={globalFilter ?? ''}
onChange={(value: any) => setGlobalFilter(String(value))} onChange={(value: any) => {
if (datastoreConfig) queryDataByText(String(value));
setGlobalFilter(String(value));
}}
className="p-2 text-sm shadow border border-block" className="p-2 text-sm shadow border border-block"
placeholder="Search all columns..." placeholder="Search all columns..."
/> />
@@ -114,7 +245,10 @@ export const Table = ({
className: h.column.getCanSort() className: h.column.getCanSort()
? 'cursor-pointer select-none' ? 'cursor-pointer select-none'
: '', : '',
onClick: h.column.getToggleSortingHandler(), onClick: (v) => {
setHasSorted(true);
h.column.getToggleSortingHandler()(v);
},
}} }}
> >
{flexRender(h.column.columnDef.header, h.getContext())} {flexRender(h.column.columnDef.header, h.getContext())}
@@ -135,15 +269,28 @@ export const Table = ({
))} ))}
</thead> </thead>
<tbody> <tbody>
{table.getRowModel().rows.map((r) => ( {datastoreConfig && isLoadingPage ? (
<tr key={r.id} className="border-b border-b-slate-200"> <tr>
{r.getVisibleCells().map((c) => ( <td colSpan={cols.length} rowSpan={cols.length}>
<td key={c.id} className="py-2"> <div className="w-full h-full flex items-center justify-center pt-6">
{flexRender(c.column.columnDef.cell, c.getContext())} <LoadingSpinner />
</td> </div>
))} </td>
</tr> </tr>
))} ) : (
(datastoreConfig
? table.getRowModel().rows.slice(startIndex, lastIndex)
: table.getRowModel().rows
).map((r) => (
<tr key={r.id} className="border-b border-b-slate-200">
{r.getVisibleCells().map((c) => (
<td key={c.id} className="py-2">
{flexRender(c.column.columnDef.cell, c.getContext())}
</td>
))}
</tr>
))
)}
</tbody> </tbody>
</table> </table>
<div className="flex gap-2 items-center justify-center mt-10"> <div className="flex gap-2 items-center justify-center mt-10">
@@ -151,7 +298,10 @@ export const Table = ({
className={`w-6 h-6 ${ className={`w-6 h-6 ${
!table.getCanPreviousPage() ? 'opacity-25' : 'opacity-100' !table.getCanPreviousPage() ? 'opacity-25' : 'opacity-100'
}`} }`}
onClick={() => table.setPageIndex(0)} onClick={() => {
if (datastoreConfig) queryPaginatedData(0);
table.setPageIndex(0);
}}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
> >
<ChevronDoubleLeftIcon /> <ChevronDoubleLeftIcon />
@@ -160,7 +310,12 @@ export const Table = ({
className={`w-6 h-6 ${ className={`w-6 h-6 ${
!table.getCanPreviousPage() ? 'opacity-25' : 'opacity-100' !table.getCanPreviousPage() ? 'opacity-25' : 'opacity-100'
}`} }`}
onClick={() => table.previousPage()} onClick={() => {
if (datastoreConfig) {
queryPaginatedData(table.getState().pagination.pageIndex - 1);
}
table.previousPage();
}}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
> >
<ChevronLeftIcon /> <ChevronLeftIcon />
@@ -176,7 +331,11 @@ export const Table = ({
className={`w-6 h-6 ${ className={`w-6 h-6 ${
!table.getCanNextPage() ? 'opacity-25' : 'opacity-100' !table.getCanNextPage() ? 'opacity-25' : 'opacity-100'
}`} }`}
onClick={() => table.nextPage()} onClick={() => {
if (datastoreConfig)
queryPaginatedData(table.getState().pagination.pageIndex + 1);
table.nextPage();
}}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
> >
<ChevronRightIcon /> <ChevronRightIcon />
@@ -185,7 +344,11 @@ export const Table = ({
className={`w-6 h-6 ${ className={`w-6 h-6 ${
!table.getCanNextPage() ? 'opacity-25' : 'opacity-100' !table.getCanNextPage() ? 'opacity-25' : 'opacity-100'
}`} }`}
onClick={() => table.setPageIndex(table.getPageCount() - 1)} onClick={() => {
const pageIndexToNavigate = table.getPageCount() - 1;
if (datastoreConfig) queryPaginatedData(pageIndexToNavigate);
table.setPageIndex(pageIndexToNavigate);
}}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
> >
<ChevronDoubleRightIcon /> <ChevronDoubleRightIcon />

View File

@@ -1,6 +1,9 @@
import { type Meta, type StoryObj } from '@storybook/react'; import { type Meta, type StoryObj } from '@storybook/react';
import { BucketViewer, BucketViewerProps } from '../src/components/BucketViewer'; import {
BucketViewer,
BucketViewerProps,
} from '../src/components/BucketViewer';
import LoadingSpinner from '../src/components/LoadingSpinner'; import LoadingSpinner from '../src/components/LoadingSpinner';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
@@ -10,19 +13,16 @@ const meta: Meta = {
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
domain: { domain: {
description: description: 'Bucket domain URI',
'Bucket domain URI',
}, },
suffix: { suffix: {
description: description: 'Suffix of bucket domain',
'Suffix of bucket domain',
}, },
downloadComponent: { downloadConfig: {
description: description: `Bucket file download configuration`,
'Component to be displayed on hover of each bucket data',
}, },
filterState: { filterState: {
description: `State with values used to filter the bucket files` description: `State with values used to filter the bucket files`,
}, },
paginationConfig: { paginationConfig: {
description: `Configuration to show and stylise the pagination on the component`, description: `Configuration to show and stylise the pagination on the component`,
@@ -42,17 +42,15 @@ export const Normal: Story = {
suffix: '/', suffix: '/',
dataMapperFn: async (rawData: Response) => { dataMapperFn: async (rawData: Response) => {
const result = await rawData.json(); const result = await rawData.json();
return result.objects.map( return result.objects.map((e) => ({
e => ({ downloadFileUri: e.downloadLink,
downloadFileUri: e.downloadLink, fileName: e.key.replace(/^(\w+\/)/g, ''),
fileName: e.key.replace(/^(\w+\/)/g, '') , dateProps: {
dateProps: { date: new Date(e.uploaded),
date: new Date(e.uploaded), dateFormatter: (date) => date.toLocaleDateString(),
dateFormatter: (date) => date.toLocaleDateString() },
} }));
}) },
)
}
}, },
}; };
@@ -62,21 +60,19 @@ export const WithPagination: Story = {
domain: 'https://ssen-smart-meter.datopian.workers.dev', domain: 'https://ssen-smart-meter.datopian.workers.dev',
suffix: '/', suffix: '/',
paginationConfig: { paginationConfig: {
itemsPerPage: 3 itemsPerPage: 3,
}, },
dataMapperFn: async (rawData: Response) => { dataMapperFn: async (rawData: Response) => {
const result = await rawData.json(); const result = await rawData.json();
return result.objects.map( return result.objects.map((e) => ({
e => ({ downloadFileUri: e.downloadLink,
downloadFileUri: e.downloadLink, fileName: e.key.replace(/^(\w+\/)/g, ''),
fileName: e.key.replace(/^(\w+\/)/g, '') , dateProps: {
dateProps: { date: new Date(e.uploaded),
date: new Date(e.uploaded), dateFormatter: (date) => date.toLocaleDateString(),
dateFormatter: (date) => date.toLocaleDateString() },
} }));
}) },
)
}
}, },
}; };
@@ -85,19 +81,17 @@ export const WithComponentOnHoverOfEachBucketFile: Story = {
args: { args: {
domain: 'https://ssen-smart-meter.datopian.workers.dev', domain: 'https://ssen-smart-meter.datopian.workers.dev',
suffix: '/', suffix: '/',
downloadComponent: LoadingSpinner(), downloadConfig: { hoverOfTheFileComponent: `HOVER COMPONENT` },
dataMapperFn: async (rawData: Response) => { dataMapperFn: async (rawData: Response) => {
const result = await rawData.json(); const result = await rawData.json();
return result.objects.map( return result.objects.map((e) => ({
e => ({ downloadFileUri: e.downloadLink,
downloadFileUri: e.downloadLink, fileName: e.key.replace(/^(\w+\/)/g, ''),
fileName: e.key.replace(/^(\w+\/)/g, '') , dateProps: {
dateProps: { date: new Date(e.uploaded),
date: new Date(e.uploaded), dateFormatter: (date) => date.toLocaleDateString(),
dateFormatter: (date) => date.toLocaleDateString() },
} }));
}) },
)
}
}, },
}; };

View File

@@ -9,17 +9,22 @@ const meta: Meta = {
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
data: { data: {
description: "Data to be displayed in the table, must also set \"cols\" to work." description:
'Data to be displayed in the table, must also set "cols" to work.',
}, },
cols: { cols: {
description: "Columns to be displayed in the table, must also set \"data\" to work." description:
'Columns to be displayed in the table, must also set "data" to work.',
}, },
csv: { csv: {
description: "CSV data as string.", description: 'CSV data as string.',
}, },
url: { url: {
description: "Fetch the data from a CSV file remotely." description: 'Fetch the data from a CSV file remotely.',
} },
datastoreConfig: {
description: `Configuration to use CKAN's datastore API extension integrated with the component`,
},
}, },
}; };
@@ -29,7 +34,7 @@ type Story = StoryObj<TableProps>;
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const FromColumnsAndData: Story = { export const FromColumnsAndData: Story = {
name: "Table from columns and data", name: 'Table from columns and data',
args: { args: {
data: [ data: [
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 }, { id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
@@ -49,21 +54,40 @@ export const FromColumnsAndData: Story = {
}, },
}; };
export const WithDataStoreIntegration: Story = {
name: 'Table with datastore integration',
args: {
datastoreConfig: {
dataStoreURI: `https://www.civicdata.com/api/action/datastore_search?resource_id=46ec0807-31ff-497f-bfa0-f31c796cdee8`,
dataMapperFn: ({
result,
}: {
result: { fields: { id }[]; records: []; total: number };
}) => {
return {
data: result.records,
cols: result.fields.map((x) => ({ key: x.id, name: x.id })),
total: result.total,
};
},
},
},
};
export const FromRawCSV: Story = { export const FromRawCSV: Story = {
name: "Table from raw CSV", name: 'Table from raw CSV',
args: { args: {
csv: ` csv: `
Year,Temp Anomaly Year,Temp Anomaly
1850,-0.418 1850,-0.418
2020,0.923 2020,0.923
` `,
} },
}; };
export const FromURL: Story = { export const FromURL: Story = {
name: "Table from URL", name: 'Table from URL',
args: { args: {
url: "https://raw.githubusercontent.com/datasets/finance-vix/main/data/vix-daily.csv" url: 'https://raw.githubusercontent.com/datasets/finance-vix/main/data/vix-daily.csv',
} },
}; };