Skip to content

Show total loaded and total filtered items in ItemTable #152

Open
campb303 opened this issue Dec 4, 2020 · 9 comments
Open

Show total loaded and total filtered items in ItemTable #152

campb303 opened this issue Dec 4, 2020 · 9 comments
Labels
feature-request Request for functionality that has not already been implemented handoff Issues that will not be fixed by the first webqueue2 team (after 7/16/21) quickfix Immediately actionable and should be fast

Comments

@campb303
Copy link
Collaborator

campb303 commented Dec 4, 2020

Add a UI element to indicate how many items are currently loaded and how many items are filtered if items are being filtered.

@campb303 campb303 added feature-request Request for functionality that has not already been implemented frontend labels Dec 4, 2020
@campb303 campb303 added this to the v1 milestone Dec 4, 2020
@campb303 campb303 modified the milestones: v1-proof-of-concept, v2-production-ready-read-only Feb 5, 2021
@campb303 campb303 removed the frontend label Mar 17, 2021
@campb303 campb303 added quickfix Immediately actionable and should be fast high-priority Needs immediate extra focus labels Mar 29, 2021
@campb303 campb303 removed the high-priority Needs immediate extra focus label Apr 27, 2021
@wrigh393
Copy link
Collaborator

Working on this.

@wrigh393
Copy link
Collaborator

Using the reduce() method should be a good way to add the total number of items that are loaded in the table. Here is a quick example of the syntax for the reduce() method:

const newValue = arr.reduce(function(previousValue, currentValue, index, array) {
  // Do stuff with previousValue and currentValue (index, array, and initialValue are optional)
}, initialValue);
  • newValue- the new number, array, string, or object that is returned
  • arr - the array being operated on
  • previousValue - the returned value of the previous iteration
  • currentValue - the current item in the array
  • index - the index of the current item
  • array - the original array on which reduce() was called
  • initialValue - a number, array, string, or object that serves as an initial value for the eventual output

In our use case, the only props that we need are previousValue, currentValue, and initialValue. previousValue and currentValue are both required but initialValue is necessary because when the array of items is first created it is an empty array, so it has no initial value to use for the previousValue prop. Adding the number of total items using this method should be simple because the data is already in an array and react-table also returns the filtered rows as an array. The next steps are to figure out the proper way to implement the UI. Here is how it could look like as a part of the QueueSelector. The only problem with displaying it here is that the data passed to the QueueSelector doesn't have access to the number of filtered rows. The likely solution to this is to create a separate component.

image

@wrigh393
Copy link
Collaborator

We are able to access the filtered values from react-table through the tableInstance.filteredRows prop. This returns an array of all the items AFTER they have been filtered.

The problem with using this as the only means of calculating the number of items being filtered is that when there is no value in any of the filters the filteredRows prop shows that ALL items are filtered. One of the ways we should be able to work around this is by using the filter() method, which allows us to filter values out of an array.

When a column is not filtered in react-table the filterValue of that column is set to undefined so, in theory, we could filter out all the filter values that are undefined and only display the total number of filtered items when the array isn't empty.

My next steps are to make sure this theory works and find a good way to expose this in the UI.

@campb303
Copy link
Collaborator Author

campb303 commented Jul 1, 2021

Desired text out: "Showing X of Y items" where X is the number of visible (filtered) items and Y is the total number of items.

Give a proof of concept that this logic works by addinga typography above the table for now. We'll plan for the UI later.

@wrigh393
Copy link
Collaborator

wrigh393 commented Jul 2, 2021

Desired text out: "Showing X of Y items" where X is the number of visible (filtered) items and Y is the total number of items.

Give a proof of concept that this logic works by adding a Typography above the table for now. We'll plan for the UI later.

This theory worked as expected. Here is a screenshot showing the results before and after filtering.

Before Fitlering
image

After Filtering
image

Next Steps

The next step for this issue are finding the best way to expose this in the UI.

@campb303 campb303 removed this from the production-ready-read-only milestone Jul 6, 2021
@wrigh393
Copy link
Collaborator

wrigh393 commented Jul 9, 2021

In order to get the number of filtered items, which is accessed from the ItemTable component we need to create a state variable that we can use to pass the length of the array that contains the filtered rows from the ItemTable.

This state variable should be defined in the AppView component so that it’s value can be passed to another component that we will create to display the total number of item and the number of items that are visible.

In order to se the value of this state variable a new prop needs to be created for the ItemTable component and we will pass the setter for the state variable to that prop. We can then use the new prop to set the state variables value and then can pass that variable to the new component.

@wrigh393
Copy link
Collaborator

To implement this feature I followed the plan that I laid out in the previous comment. This will be a summary of how this implementation works.

Create a state variable for filtered items

In order to access the number of filtered items from the ItemTable component, I created a state variable, filteredItems, in the AppView component. The setter for that state was then passed to ItemTable with a new prop. This allowed us to use the setter in ItemTable and pass it the length of the array that holds the filtered items. To do this I implemented a useEffect that runs when the length of the array holding the filtered items changes.

AppView

+ const [filteredItems, setFilteredItems] = useState(0)

//some code

- <ItemTable data={items} rowCanBeSelected={sidebarOpen} loading={isLoading} />
+ <ItemTable data={items} rowCanBeSelected={sidebarOpen} setFilteredItems={setFilteredItems} loading={isLoading} />

ItemTable

- export default function ItemTable({ data, rowCanBeSelected, loading })
+ export default function ItemTable({ data, rowCanBeSelected, loading, setFilteredItems }) {

//some code

+ useEffect(() => {
+        setFilteredItems(tableInstance.filteredRows.length)
+    }, [tableInstance.filteredRows.length])

Create a component to show information

Next, I created a component, ItemCounter to display the number of items filtered out of the total items. This component is passed two props, filteredItems and totalItems. We pass the filteredItems state variable to the filteredItems prop and the length of the items array to the totalItems prop. The actual component is just a Typography component that uses the props to show the related values. Below is the code for the component.

import { makeStyles, Typography, useTheme } from '@material-ui/core'
import React from 'react'

export default function ItemCounter({ filteredItems, totalItems }) {
    const theme = useTheme();
    const useStyles = makeStyles({
        margin: {
            margin: '0 1rem'
        }
    });
    const classes = useStyles();
    return (
        <Typography classes={{ root: classes.margin }}>{`Showing ${filteredItems} out of ${totalItems}`}</Typography>
    )
}
ItemCounter.propTypes = {
	/** The number of filtered items in the table. */
	"filteredItems": PropTypes.number.isRequired,
	/** The total number of items in the table */
	"totalitems": PropTypes.number.isRequired
};

ItemCounter.defaultProps = {
    "filteredItems": 0,
    "totalItems": 0,
};

Implement UI

To implement this component into the UI we decided that it be best next to the QueueSelector component. In order to get the desired styling a container using flexbox was created. This container also has the flex-wrap prop set to wrap which has the flex items wrap once the container is too small to fit both in a single row. The QueueSelector and ItemCounter components were wrapped with this container. In order to have the QueueSelector take up as much of the available space as possible it was wrapped in a Box that had the flex-grow set to 2 which makes the time take up double the space of the other item(s). Here is a look at the code and screenshots of the UI on desktop and mobile.

Code

//some code

+ "container": {
+    display: "flex",
+    flexWrap: "wrap",
+    alignItems: "center"
+ },
+ queueSelectorContainer: {
+    flexGrow: 2
+ },

//some code
<Box className={classes.leftCol}>
    <ItemTableAppBar title="webqueue2" setDarkMode={setDarkMode} />
+    <Box className={classes.container}>
+       <Box className={classes.queueSelectorContainer}>
            <QueueSelector
                open={queueSelectorOpen}
                setOpen={setQueueSelectorOpen}
                value={selectedQueues}
                setValue={setSelectedQueues}
             />
+        </Box>
+    <ItemCounter filteredItems={filteredItems} totalItems={items.length} />
+    </Box>
    <ItemTable data={items} rowCanBeSelected={sidebarOpen} setFilteredItems={setFilteredItems} loading={isLoading} />
</Box>

Desktop UI
image

Mobile UI
image

@wrigh393 wrigh393 linked a pull request Jul 15, 2021 that will close this issue
@campb303
Copy link
Collaborator Author

The current implementation of this works well but could benefit from some architectural changes:

Instead of prop drilling from AppView to ItemTable, it may be better to consolidate this component to the ItemTable itself. This could be acheived by adding a row to the end of the table that spans all columns and displays the same message of "Showing {number of filtered items} of {number of all items}". Furthermore, this display can be made toggleable via a prop called showItemCount with options of True or False where True is the default.

This also removes the need to modify the containing element of QueueSelector and works well on mobile.

@campb303
Copy link
Collaborator Author

After some experimentation, the previously mentioned method will not work easily because of react-window's absolute positioning for virtualizing the table. We've hardcoded a vh based height of the table body that works well when no other elements following the table body but placing something below this gets tricky.

This will not be fixed by the current team. This needs to be addressed after a better styling solution for a virtualized table is found.

@campb303 campb303 added the handoff Issues that will not be fixed by the first webqueue2 team (after 7/16/21) label Jul 30, 2021
Sign in to join this conversation on GitHub.
Labels
feature-request Request for functionality that has not already been implemented handoff Issues that will not be fixed by the first webqueue2 team (after 7/16/21) quickfix Immediately actionable and should be fast
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants