Skip to content

Commit

Permalink
Feature/swodlr UI 71 - add pagination to my data page (#98)
Browse files Browse the repository at this point in the history
* /version v1.0.0-rc.1

* /version v1.0.0-rc.2

* Pull in recent changes to 1.0.0

* /version v1.0.0-rc.3

* Fix umm-t record

* /version v1.0.0-rc.4

* Fix umm-t record

* /version v1.0.0-rc.5

* Fix umm-t record

* /version v1.0.0-rc.6

* /version v1.0.0-rc.7

* issue/manual-granule-input-hotfix: fixed how ranged of scenes are processed (#86)

Co-authored-by: jbyrne <[email protected]>

* Issues/swodlr UI 72 essential UI bug fixes (#87)

* issues/swodlr-ui-72: fix cps url params bug

* issues/swodlr-ui-72

* issues/swodlr-ui-75: fixed a couple bugs

* issues/swodlr-ui-72: fixed map movement, adjust options, data page limit, etc

* issues/swodlr-ui-72: changed spatial search beginning date in range

* issues/swodlr-ui-72-essential-fixes: cleaned up comments

---------

Co-authored-by: jbyrne <[email protected]>

* /version v1.0.0-rc.8

* feature/swodlr-ui-71: added pagination

* feature/swodlr-ui-71: change pagination button color and fix index

* feature/swodlr-ui-71: make end pagination faster

* feature/swodlr-ui-71: rebase with develop

* feature/swodlr-ui-71: change pagination button color and fix index

* feature/swodlr-ui-71: make end pagination faster

* feature/swodlr-ui-71: add page numbers to pagination

* feature/swodlr-ui-71: made products per page in history 20

* feature/swodlr-ui-71: remove first and last pagination buttons

---------

Co-authored-by: frankinspace <[email protected]>
Co-authored-by: Frank Greguska <[email protected]>
Co-authored-by: Jonathan M Smolenski <[email protected]>
Co-authored-by: jonathansmolenski <[email protected]>
Co-authored-by: jbyrne <[email protected]>
  • Loading branch information
6 people authored Apr 23, 2024
1 parent 0ff6447 commit d0a2cef
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 71 deletions.
20 changes: 18 additions & 2 deletions src/components/app/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,26 @@ tfoot {
}

.table-responsive-generatedProducts {
max-height: 75vh;
max-height: 65vh;
overflow-y: auto;
}

.center {
width:100%;
display: flex;
justify-content: center;
}

.pagination-link-item {
background-color: #1A2535;
border-color: #1A2535;
}

.pagination-link-item:active {
background-color: #1A2535;
border-color: #1A2535;
}

.dark-mode-table {
color: #FAF9F6;
border: #FAF9F6 solid 1px;
Expand Down Expand Up @@ -398,7 +414,7 @@ ABOUT PAGE

.about-page {
max-height: 82vh;
overflow-y: auto;
/* overflow-y: auto; */
}

.about-card {
Expand Down
2 changes: 1 addition & 1 deletion src/components/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const App = () => {
navigate(`/customizeProduct/configureOptions${search}`)
}
else if (stepTarget === '#added-scenes' && action === 'update') {
navigate(`/customizeProduct/selectScenes?cyclePassScene=9_515_130&showUTMAdvancedOptions=true`)
navigate(`/customizeProduct/selectScenes?cyclePassScene=12_468_45&showUTMAdvancedOptions=true`)
} else if (stepTarget === '#customization-tab' && action === 'start') {
navigate('/customizeProduct/selectScenes')
} else if (action === 'next' && stepTarget === '#my-data-page') {
Expand Down
158 changes: 158 additions & 0 deletions src/components/history/DataPagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Col, Pagination, Row, Spinner } from "react-bootstrap";
import { Product } from "../../types/graphqlTypes";
import { getUserProducts } from "../../user/userData";
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
import { addPageToHistoryPageState, setUserProducts } from "../sidebar/actions/productSlice";
import { productsPerPage } from "../../constants/rasterParameterConstants";
import { useEffect, useState } from "react";


const DataPagination = () => {
const dispatch = useAppDispatch()
const historyPageState = useAppSelector((state) => state.product.historyPageState)
const firstHistoryPageData = useAppSelector((state) => state.product.firstHistoryPageData)
const userProducts = useAppSelector((state) => state.product.userProducts)
const [noNextPage, setNoNextPage] = useState<boolean>(false)
const [noPreviousPage, setNoPreviousPage] = useState<boolean>(true)
const [waitingForPagination, setWaitingForPagination] = useState<boolean>(true)
const [currentPageNumber, setCurrentPageNumber] = useState<number>(1)
const [totalNumberOfProducts, setTotalNumberOfProducts] = useState<number>(0)

useEffect(() => {
// get the data for the first page
// go through all the user product data to get the id of each one so that
const fetchData = async () => {
await getUserProducts({limit: '1000000'}).then(response => {
const currentPageProducts = response.products as Product[]
const totalNumberOfProducts = currentPageProducts.length
setTotalNumberOfProducts(totalNumberOfProducts)
if(response.status === 'success' && currentPageProducts.length !== 0) {
const productsPerPageToInt = parseInt(productsPerPage)
const numberOfPages = Math.ceil(currentPageProducts.length/productsPerPageToInt)
for (let pageIndex = 0; pageIndex < numberOfPages; pageIndex++) {
// get index of the last product of each [productsPerPage]
const indexToUse = totalNumberOfProducts - (pageIndex * productsPerPageToInt) < productsPerPageToInt ? totalNumberOfProducts - (pageIndex * productsPerPageToInt) : productsPerPageToInt * (pageIndex + 1)-1
const idToUse = currentPageProducts[indexToUse].id
dispatch(addPageToHistoryPageState(idToUse))
}
}
}).then(() => setWaitingForPagination(false))
}
fetchData()
.catch(console.error);
}, []);

const handlePrevious = async () => {
if(noNextPage) setNoNextPage(false)
if (currentPageNumber <= 2) {
dispatch(setUserProducts(firstHistoryPageData))
setCurrentPageNumber(currentPageNumber - 1)
setNoPreviousPage(true)
} else {
await getUserProducts({limit: productsPerPage, after: historyPageState[currentPageNumber-3]}).then(response => {
const currentPageProducts = response.products as Product[]
dispatch(setUserProducts(currentPageProducts))
setCurrentPageNumber(currentPageNumber - 1)
})
}
}

const handleNext = async () => {
await getUserProducts({limit: productsPerPage, after: historyPageState[currentPageNumber-1]}).then(response => {
if(response.status === 'success') {
if(noPreviousPage) setNoPreviousPage(false)
const currentPageProducts = response.products as Product[]
dispatch(setUserProducts(currentPageProducts))
setCurrentPageNumber(currentPageNumber + 1)
} else if (response.status === 'error') {
setNoNextPage(true)
}
})
}

const handleSelectPage = async (pageNumber: number) => {
if (pageNumber === 1) {
if(noPreviousPage) setNoPreviousPage(false)
dispatch(setUserProducts(firstHistoryPageData))
setCurrentPageNumber(pageNumber)
setNoPreviousPage(true)
} else {
await getUserProducts({limit: productsPerPage, after: historyPageState[pageNumber-2]}).then(response => {
if(response.status === 'success') {
if(noPreviousPage) setNoPreviousPage(false)
const currentPageProducts = response.products as Product[]
dispatch(setUserProducts(currentPageProducts))
setCurrentPageNumber(pageNumber)
} else if (response.status === 'error') {
setNoNextPage(true)
}
})
}
}

const waitingForPaginationSpinner = () => {
return (
<div>
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>
)
}

const getPaginationItemsWithEllipsis = () => {
let numberOfSlotsFreeLeft = 0
if(currentPageNumber >= historyPageState.length-4) {
numberOfSlotsFreeLeft = 4 - (historyPageState.length - currentPageNumber)
}

let numberOfSlotsFreeRight = 0
if(currentPageNumber <= 4) {
numberOfSlotsFreeRight = 5 - currentPageNumber
}
const pagesAllowed = [currentPageNumber-2, currentPageNumber-1, currentPageNumber, currentPageNumber+1, currentPageNumber+2]
const pagesAllowedToLeftOfCurrent: number[] = []
historyPageState.forEach((pageId, index) => {
if(numberOfSlotsFreeLeft !== 0 && index+1 <= historyPageState.length - 3) {
// let another number go on right
pagesAllowedToLeftOfCurrent.push(currentPageNumber - index - 3)
numberOfSlotsFreeLeft -= 1
}
if(numberOfSlotsFreeRight !== 0 && index+1 > currentPageNumber + 3) {
// let another number go on left
pagesAllowed.push(index)
numberOfSlotsFreeRight -= 1
}
})
pagesAllowed.unshift(...pagesAllowedToLeftOfCurrent.reverse())
const pagesToShow = historyPageState.map((pageId, index) => {
const pageNumberOfIndex = index + 1
if (pageNumberOfIndex === 1 || pageNumberOfIndex === historyPageState.length) return null
if(pagesAllowed.includes(pageNumberOfIndex)) {
return <Pagination.Item key={`${pageNumberOfIndex}-pagination-item`} active={currentPageNumber === pageNumberOfIndex} onClick={() => handleSelectPage(pageNumberOfIndex)}>{pageNumberOfIndex}</Pagination.Item>
}
return null
})
if(pagesAllowed[0] > 2) pagesToShow.unshift(<Pagination.Ellipsis />)
if(pagesAllowed[pagesAllowed.length-1] < historyPageState.length-1) pagesToShow.push(<Pagination.Ellipsis />)
return pagesToShow
}

return waitingForPagination ? waitingForPaginationSpinner() : (
<Row>
<Col xs={2} style={{padding: '15px'}}><h5><b>{totalNumberOfProducts}</b> Total Products</h5></Col>
<Col xs={8}>
<Pagination data-bs-theme='dark' style={{padding: '15px'}} className="center">
<Pagination.Prev onClick={() => handlePrevious()} disabled={noPreviousPage} />
<Pagination.Item active={currentPageNumber === 1} onClick={() => handleSelectPage(1)}>1</Pagination.Item>
{getPaginationItemsWithEllipsis()}
<Pagination.Item active={currentPageNumber === historyPageState.length} onClick={() => handleSelectPage(historyPageState.length)}>{historyPageState.length}</Pagination.Item>
<Pagination.Next onClick={() => handleNext()} disabled={userProducts.length < parseInt(productsPerPage) || noNextPage} />
</Pagination>
</Col>
<Col xs={2}></Col>
</Row>
)
}

export default DataPagination;
115 changes: 62 additions & 53 deletions src/components/history/GeneratedProductHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
import { Alert, Col, OverlayTrigger, Row, Table, Tooltip, Button, Spinner } from "react-bootstrap";
import { useAppSelector } from "../../redux/hooks";
import { getUserProductsResponse, Product } from "../../types/graphqlTypes";
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
import { Product } from "../../types/graphqlTypes";
import { useEffect, useState } from "react";
import { InfoCircle, Clipboard, Download } from "react-bootstrap-icons";
import { generatedProductsLabels, infoIconsToRender, parameterHelp } from "../../constants/rasterParameterConstants";
import { generatedProductsLabels, infoIconsToRender, parameterHelp, productsPerPage } from "../../constants/rasterParameterConstants";
import { getUserProducts } from "../../user/userData";
import { useLocation, useNavigate } from "react-router-dom";
import DataPagination from "./DataPagination";
import { addPageToHistoryPageState, setFirstHistoryPageData, setUserProducts } from "../sidebar/actions/productSlice";

const GeneratedProductHistory = () => {
const dispatch = useAppDispatch()
const colorModeClass = useAppSelector((state) => state.navbar.colorModeClass)
const userProducts = useAppSelector((state) => state.product.userProducts)
const { search } = useLocation();
const navigate = useNavigate()
const [userProducts, setUserProducts] = useState<Product[]>([])
const [waitingForProductsToLoad, setWaitingForProductsToLoad] = useState(true)

useEffect(() => {
// get the data for the first page
// go through all the user product data to get the id of each one so that
const fetchData = async () => {
const userProductsResponse: getUserProductsResponse = await getUserProducts().then((response) => {
setWaitingForProductsToLoad(false)
return response
await getUserProducts({limit: productsPerPage}).then(response => {
const currentPageProducts = response.products as Product[]
if(response.status === 'success' && currentPageProducts.length !== 0) {
const idToUse = currentPageProducts[currentPageProducts.length-1].id
dispatch(setUserProducts(currentPageProducts))
dispatch(setFirstHistoryPageData(currentPageProducts))
dispatch(addPageToHistoryPageState(idToUse))
}
})
if (userProductsResponse.status === 'success') setUserProducts(userProductsResponse.products as Product[])
}
fetchData()
.catch(console.error);

if(userProducts.length === 0) fetchData().catch(console.error)
}, []);

// TODO: implement download link copy button
Expand Down Expand Up @@ -73,57 +81,58 @@ const GeneratedProductHistory = () => {
</OverlayTrigger>
)

const renderColTitle = (labelEntry: string[], index: number) => {
const renderColTitle = (labelEntry: string[], index: number) => {
let infoIcon = infoIconsToRender.includes(labelEntry[0]) ? renderInfoIcon(labelEntry[0]) : null
let labelId = (labelEntry[0] === 'downloadUrl') ? 'download-url' : ''
return (
<th key={`${labelEntry[0]}-${index}`} id={labelId}>{labelEntry[1]} {infoIcon}</th>
<th key={`${labelEntry[0]}-${index}`} id={labelId}>{labelEntry[1]} {infoIcon}</th>
)
}
}

const renderHistoryTable = () => {
return (
<div style={{padding: '0px 20px 20px 20px'}} id='history-table'>
<div className={`table-responsive-generatedProducts table-responsive`}>
<Table bordered hover className={`${colorModeClass}-table`} style={{marginBottom: '0px'}}>
<thead>
<tr>
{Object.entries(generatedProductsLabels).map((labelEntry, index) => renderColTitle(labelEntry, index))}
</tr>
</thead>
<tbody>
{userProducts.map((generatedProductObject, index) => {
const {status, utmZoneAdjust, mgrsBandAdjust, outputGranuleExtentFlag, outputSamplingGridType, rasterResolution, timestamp: dateGenerated, cycle, pass, scene, granules} = generatedProductObject
const statusToUse = status[0].state
const downloadUrl = granules && granules.length !== 0 ? granules[0].uri.split('/').pop() : 'N/A'
const utmZoneAdjustToUse = outputSamplingGridType === 'GEO' ? 'N/A' : utmZoneAdjust
const mgrsBandAdjustToUse = outputSamplingGridType === 'GEO' ? 'N/A' : mgrsBandAdjust
const outputSamplingGridTypeToUse = outputSamplingGridType === 'GEO' ? 'LAT/LON' : outputSamplingGridType
const outputGranuleExtentFlagToUse = outputGranuleExtentFlag ? '256 x 128 km' : '128 x 128 km'
const productRowValues = {cycle, pass, scene, status: statusToUse, outputGranuleExtentFlag: outputGranuleExtentFlagToUse, outputSamplingGridType: outputSamplingGridTypeToUse, rasterResolution, utmZoneAdjust: utmZoneAdjustToUse, mgrsBandAdjust: mgrsBandAdjustToUse, downloadUrl, dateGenerated}
return (
<tr className={`${colorModeClass}-table hoverable-row`} key={`generated-products-data-row-${index}`}>
{Object.entries(productRowValues).map((entry, index2) => {
let cellContents = null
if (entry[0] === 'downloadUrl' && entry[1] !== 'N/A') {
const downloadUrlString = granules[0].uri
cellContents =
<Row className='normal-row'>
<Col>{entry[1]}</Col>
<Col>{(renderCopyDownloadButton(downloadUrlString))}</Col>
<Col>{renderDownloadButton(downloadUrlString)}</Col>
</Row>
} else {
cellContents = entry[1]
}
return <td style={{}} key={`${index}-${index2}`}>{cellContents}</td>
} )}
</tr>
)})}
</tbody>
</Table>
<Table bordered hover className={`${colorModeClass}-table`} style={{marginBottom: '0'}}>
<thead>
<tr>
{Object.entries(generatedProductsLabels).map((labelEntry, index) => renderColTitle(labelEntry, index))}
</tr>
</thead>
<tbody>
{userProducts.map((generatedProductObject, index) => {
const {status, utmZoneAdjust, mgrsBandAdjust, outputGranuleExtentFlag, outputSamplingGridType, rasterResolution, timestamp: dateGenerated, cycle, pass, scene, granules} = generatedProductObject
const statusToUse = status[0].state
const downloadUrl = granules && granules.length !== 0 ? granules[0].uri.split('/').pop() : 'N/A'
const utmZoneAdjustToUse = outputSamplingGridType === 'GEO' ? 'N/A' : utmZoneAdjust
const mgrsBandAdjustToUse = outputSamplingGridType === 'GEO' ? 'N/A' : mgrsBandAdjust
const outputSamplingGridTypeToUse = outputSamplingGridType === 'GEO' ? 'LAT/LON' : outputSamplingGridType
const outputGranuleExtentFlagToUse = outputGranuleExtentFlag ? '256 x 128 km' : '128 x 128 km'
const productRowValues = {cycle, pass, scene, status: statusToUse, outputGranuleExtentFlag: outputGranuleExtentFlagToUse, outputSamplingGridType: outputSamplingGridTypeToUse, rasterResolution, utmZoneAdjust: utmZoneAdjustToUse, mgrsBandAdjust: mgrsBandAdjustToUse, downloadUrl, dateGenerated}
return (
<tr className={`${colorModeClass}-table hoverable-row`} key={`generated-products-data-row-${index}`}>
{Object.entries(productRowValues).map((entry, index2) => {
let cellContents = null
if (entry[0] === 'downloadUrl' && entry[1] !== 'N/A') {
const downloadUrlString = granules[0].uri
cellContents =
<Row className='normal-row'>
<Col>{entry[1]}</Col>
<Col>{(renderCopyDownloadButton(downloadUrlString))}</Col>
<Col>{renderDownloadButton(downloadUrlString)}</Col>
</Row>
} else {
cellContents = entry[1]
}
return <td style={{}} key={`${index}-${index2}`}>{cellContents}</td>
} )}
</tr>
)})}
</tbody>
</Table>
</div>
<DataPagination />
</div>
</div>
)
}

Expand Down Expand Up @@ -157,7 +166,7 @@ const GeneratedProductHistory = () => {
<>
<h4 className='normal-row' style={{marginTop: '70px'}}>Generated Products Data</h4>
<Col className='about-page' style={{marginRight: '50px', marginLeft: '50px'}}>
<Row className='normal-row'>{waitingForProductsToLoad ? waitingForProductsToLoadSpinner() : renderProductHistoryViews()}</Row>
<Row className='normal-row'>{userProducts.length === 0 ? waitingForProductsToLoadSpinner() : renderProductHistoryViews()}</Row>
</Col>
</>
);
Expand Down
Loading

0 comments on commit d0a2cef

Please sign in to comment.