Added pagination and filter properties for the BucketViewer component
This commit is contained in:
parent
96904aef0d
commit
27c99adde8
5
.changeset/red-socks-grow.md
Normal file
5
.changeset/red-socks-grow.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@portaljs/components': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Added pagination and filter properties for the BucketViewer component
|
||||||
@ -1,46 +1,131 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { CSSProperties, ReactNode, useEffect, useState } from 'react';
|
||||||
import LoadingSpinner from './LoadingSpinner';
|
import LoadingSpinner from './LoadingSpinner';
|
||||||
|
|
||||||
|
export interface BucketViewerFilterSearchedDataEvent {
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export interface BucketViewerProps {
|
export interface BucketViewerProps {
|
||||||
|
onLoadTotalNumberOfItems?: (total: number) => void;
|
||||||
domain: string;
|
domain: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
downloadComponent?: ReactNode;
|
||||||
|
paginationConfig?: BucketViewerPaginationConfig;
|
||||||
|
filterState?: BucketViewerFilterSearchedDataEvent;
|
||||||
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
|
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BucketViewerPaginationConfig {
|
||||||
|
containerClassName?: string;
|
||||||
|
containerStyles?: CSSProperties;
|
||||||
|
itemsPerPage: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface BucketViewerData {
|
export interface BucketViewerData {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
downloadFileUri: string;
|
downloadFileUri: string;
|
||||||
dateProps?: {
|
dateProps?: {
|
||||||
date: Date;
|
date: Date;
|
||||||
dateFormatter: (date: Date) => string;
|
dateFormatter?: (date: Date) => string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BucketViewer({
|
export function BucketViewer({
|
||||||
domain,
|
domain,
|
||||||
|
downloadComponent,
|
||||||
suffix,
|
suffix,
|
||||||
dataMapperFn,
|
dataMapperFn,
|
||||||
className,
|
className,
|
||||||
|
filterState,
|
||||||
|
paginationConfig,
|
||||||
|
onLoadTotalNumberOfItems
|
||||||
}: BucketViewerProps) {
|
}: BucketViewerProps) {
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
||||||
const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]);
|
|
||||||
suffix = suffix ?? '/';
|
suffix = suffix ?? '/';
|
||||||
|
downloadComponent = downloadComponent ?? <></>;
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [showDownloadComponentOnLine, setShowDownloadComponentOnLine] = useState(0);
|
||||||
|
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||||
|
const [lastPage, setLastPage] = useState<number>(0);
|
||||||
|
const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]);
|
||||||
|
const [paginatedData, setPaginatedData] = useState<BucketViewerData[]>([]);
|
||||||
|
const [filteredData, setFilteredData] = useState<BucketViewerData[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetch(`${domain}${suffix}`)
|
fetch(`${domain}${suffix}`)
|
||||||
.then((res) => dataMapperFn(res))
|
.then((res) => dataMapperFn(res))
|
||||||
.then((data) => setBucketFiles(data))
|
.then((data) => {
|
||||||
|
setBucketFiles(data);
|
||||||
|
setFilteredData(data);
|
||||||
|
})
|
||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
}, [domain, suffix]);
|
}, [domain, suffix]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if(paginationConfig) {
|
||||||
|
const startIndex = paginationConfig
|
||||||
|
? currentPage * paginationConfig.itemsPerPage
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const endIndex = paginationConfig
|
||||||
|
? startIndex + paginationConfig.itemsPerPage
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
setLastPage(Math.ceil(filteredData.length / paginationConfig.itemsPerPage) - 1);
|
||||||
|
setPaginatedData(filteredData.slice(startIndex, endIndex));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentPage, filteredData]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if(onLoadTotalNumberOfItems) onLoadTotalNumberOfItems(filteredData.length);
|
||||||
|
},
|
||||||
|
[filteredData]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!filterState) return;
|
||||||
|
|
||||||
|
if(filterState.startDate && filterState.startDate) {
|
||||||
|
setFilteredData(bucketFiles.filter(({ dateProps }) =>
|
||||||
|
dateProps
|
||||||
|
?
|
||||||
|
dateProps.date.getTime() >= filterState.startDate.getTime()
|
||||||
|
&& dateProps.date.getTime() <= filterState.endDate.getTime()
|
||||||
|
: true
|
||||||
|
));
|
||||||
|
} else if(filterState.startDate) {
|
||||||
|
setFilteredData(bucketFiles.filter(({ dateProps }) =>
|
||||||
|
dateProps ? dateProps.date.getTime() >= filterState.startDate.getTime() : true
|
||||||
|
));
|
||||||
|
} else if(filterState.endDate) {
|
||||||
|
setFilteredData(bucketFiles.filter(({ dateProps }) =>
|
||||||
|
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]">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
) : bucketFiles ? (
|
) : bucketFiles ? (
|
||||||
<>
|
<>
|
||||||
{...bucketFiles?.map((data, i) => (
|
{...(paginationConfig && bucketFiles
|
||||||
|
? paginatedData
|
||||||
|
: filteredData
|
||||||
|
)?.map((data, i) => (
|
||||||
<ul
|
<ul
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const anchorId = `download_anchor_${i}`;
|
const anchorId = `download_anchor_${i}`;
|
||||||
@ -49,7 +134,7 @@ export function BucketViewer({
|
|||||||
document.createElement('a');
|
document.createElement('a');
|
||||||
a.id = anchorId;
|
a.id = anchorId;
|
||||||
if (a.download) a.click();
|
if (a.download) a.click();
|
||||||
else {
|
else {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetch(data.downloadFileUri)
|
fetch(data.downloadFileUri)
|
||||||
.then((res) => res.blob())
|
.then((res) => res.blob())
|
||||||
@ -63,19 +148,78 @@ export function BucketViewer({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
key={i}
|
key={i}
|
||||||
|
onMouseEnter={() => setShowDownloadComponentOnLine(i)}
|
||||||
|
onMouseLeave={() => setShowDownloadComponentOnLine(undefined)}
|
||||||
className={`${
|
className={`${
|
||||||
className ??
|
className ??
|
||||||
'mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer'
|
'mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<li>{data.fileName}</li>
|
{
|
||||||
{data.dateProps ? (
|
downloadComponent && showDownloadComponentOnLine === i
|
||||||
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
|
? downloadComponent
|
||||||
) : (
|
: <></>
|
||||||
<></>
|
}
|
||||||
)}
|
<div>
|
||||||
|
<li>{data.fileName}</li>
|
||||||
|
{data.dateProps && data.dateProps.dateFormatter ? (
|
||||||
|
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
))}
|
))}
|
||||||
|
{paginationConfig ? (
|
||||||
|
<ul className={
|
||||||
|
paginationConfig.containerClassName
|
||||||
|
? paginationConfig.containerClassName
|
||||||
|
: "flex justify-end gap-x-[0.5rem] w-full"
|
||||||
|
}
|
||||||
|
style={paginationConfig.containerStyles ?? {}}
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
|
||||||
|
disabled={currentPage === 0}
|
||||||
|
onClick={() => setCurrentPage(0)}>First</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
|
||||||
|
onClick={() => setCurrentPage(currentPage - 1)}
|
||||||
|
disabled={currentPage === 0}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<label>
|
||||||
|
{currentPage + 1}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage(currentPage + 1)}
|
||||||
|
disabled={currentPage >= lastPage}
|
||||||
|
className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage(lastPage)}
|
||||||
|
disabled={currentPage >= lastPage}
|
||||||
|
className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Last
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
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';
|
||||||
|
|
||||||
// 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
|
||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
@ -16,6 +17,16 @@ const meta: Meta = {
|
|||||||
description:
|
description:
|
||||||
'Suffix of bucket domain',
|
'Suffix of bucket domain',
|
||||||
},
|
},
|
||||||
|
downloadComponent: {
|
||||||
|
description:
|
||||||
|
'Component to be displayed on hover of each bucket data',
|
||||||
|
},
|
||||||
|
filterState: {
|
||||||
|
description: `State with values used to filter the bucket files`
|
||||||
|
},
|
||||||
|
paginationConfig: {
|
||||||
|
description: `Configuration to show and stylise the pagination on the component`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,3 +55,49 @@ export const Normal: Story = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WithPagination: Story = {
|
||||||
|
name: 'With pagination',
|
||||||
|
args: {
|
||||||
|
domain: 'https://ssen-smart-meter.datopian.workers.dev',
|
||||||
|
suffix: '/',
|
||||||
|
paginationConfig: {
|
||||||
|
itemsPerPage: 3
|
||||||
|
},
|
||||||
|
dataMapperFn: async (rawData: Response) => {
|
||||||
|
const result = await rawData.json();
|
||||||
|
return result.objects.map(
|
||||||
|
e => ({
|
||||||
|
downloadFileUri: e.downloadLink,
|
||||||
|
fileName: e.key.replace(/^(\w+\/)/g, '') ,
|
||||||
|
dateProps: {
|
||||||
|
date: new Date(e.uploaded),
|
||||||
|
dateFormatter: (date) => date.toLocaleDateString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithComponentOnHoverOfEachBucketFile: Story = {
|
||||||
|
name: 'With component on hover of each bucket file',
|
||||||
|
args: {
|
||||||
|
domain: 'https://ssen-smart-meter.datopian.workers.dev',
|
||||||
|
suffix: '/',
|
||||||
|
downloadComponent: LoadingSpinner(),
|
||||||
|
dataMapperFn: async (rawData: Response) => {
|
||||||
|
const result = await rawData.json();
|
||||||
|
return result.objects.map(
|
||||||
|
e => ({
|
||||||
|
downloadFileUri: e.downloadLink,
|
||||||
|
fileName: e.key.replace(/^(\w+\/)/g, '') ,
|
||||||
|
dateProps: {
|
||||||
|
date: new Date(e.uploaded),
|
||||||
|
dateFormatter: (date) => date.toLocaleDateString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user