Skip to content

Commit

Permalink
Added pagination and filter properties for the BucketViewer component
Browse files Browse the repository at this point in the history
  • Loading branch information
Gutts-n committed Jan 23, 2024
1 parent 96904ae commit 27c99ad
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/red-socks-grow.md
@@ -0,0 +1,5 @@
---
'@portaljs/components': patch
---

Added pagination and filter properties for the BucketViewer component
168 changes: 156 additions & 12 deletions packages/components/src/components/BucketViewer.tsx
@@ -1,46 +1,131 @@
import { useEffect, useState } from 'react';
import { CSSProperties, ReactNode, useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner';

export interface BucketViewerFilterSearchedDataEvent {
startDate?: Date;
endDate?: Date;
}

export interface BucketViewerProps {
onLoadTotalNumberOfItems?: (total: number) => void;
domain: string;
suffix?: string;
className?: string;
downloadComponent?: ReactNode;
paginationConfig?: BucketViewerPaginationConfig;
filterState?: BucketViewerFilterSearchedDataEvent;
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
}

export interface BucketViewerPaginationConfig {
containerClassName?: string;
containerStyles?: CSSProperties;
itemsPerPage: number;
}

export interface BucketViewerData {
fileName: string;
downloadFileUri: string;
dateProps?: {
date: Date;
dateFormatter: (date: Date) => string;
dateFormatter?: (date: Date) => string;
};
}

export function BucketViewer({
domain,
downloadComponent,
suffix,
dataMapperFn,
className,
filterState,
paginationConfig,
onLoadTotalNumberOfItems
}: BucketViewerProps) {

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[]>([]);
suffix = suffix ?? '/';
const [paginatedData, setPaginatedData] = useState<BucketViewerData[]>([]);
const [filteredData, setFilteredData] = useState<BucketViewerData[]>([]);

useEffect(() => {
setIsLoading(true);
fetch(`${domain}${suffix}`)
.then((res) => dataMapperFn(res))
.then((data) => setBucketFiles(data))
.then((data) => {
setBucketFiles(data);
setFilteredData(data);
})
.finally(() => setIsLoading(false));
}, [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 ? (
<div className="w-full flex items-center justify-center h-[300px]">
<LoadingSpinner />
</div>
) : bucketFiles ? (
<>
{...bucketFiles?.map((data, i) => (
{...(paginationConfig && bucketFiles
? paginatedData
: filteredData
)?.map((data, i) => (
<ul
onClick={() => {
const anchorId = `download_anchor_${i}`;
Expand All @@ -49,7 +134,7 @@ export function BucketViewer({
document.createElement('a');
a.id = anchorId;
if (a.download) a.click();
else {
else {
setIsLoading(true);
fetch(data.downloadFileUri)
.then((res) => res.blob())
Expand All @@ -63,19 +148,78 @@ export function BucketViewer({
}
}}
key={i}
onMouseEnter={() => setShowDownloadComponentOnLine(i)}
onMouseLeave={() => setShowDownloadComponentOnLine(undefined)}
className={`${
className ??
'mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer'
}`}
>
<li>{data.fileName}</li>
{data.dateProps ? (
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
) : (
<></>
)}
{
downloadComponent && showDownloadComponentOnLine === i
? downloadComponent
: <></>
}
<div>
<li>{data.fileName}</li>
{data.dateProps && data.dateProps.dateFormatter ? (
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
) : (
<></>
)}
</div>
</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;
}
57 changes: 57 additions & 0 deletions packages/components/stories/BucketViewer.stories.ts
@@ -1,6 +1,7 @@
import { type Meta, type StoryObj } from '@storybook/react';

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
const meta: Meta = {
Expand All @@ -16,6 +17,16 @@ const meta: Meta = {
description:
'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`,
},
},
};

Expand Down Expand Up @@ -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()
}
})
)
}
},
};

0 comments on commit 27c99ad

Please sign in to comment.