-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The 'Subordinate IDs' page should show the data related to a given user that has a subordinate ID and its range. The current solution provides functionality for some buttons ('Refresh') and pagination. The main page is being rendered using a reusable component `MainPage` that allows listing elements with some basic configuration (showing links or checkboxes, and selection functionality). Signed-off-by: Carla Martinez <carlmart@redhat.com>
- Loading branch information
Showing
7 changed files
with
901 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
import React from "react"; | ||
// PatternFly | ||
import { Td, Th, Tr } from "@patternfly/react-table"; | ||
// Tables | ||
import TableLayout from "../layouts/TableLayout"; | ||
// Layouts | ||
import SkeletonOnTableLayout from "../layouts/Skeleton/SkeletonOnTableLayout"; | ||
// Data types | ||
import { SubId } from "src/utils/datatypes/globalDataTypes"; | ||
// React router DOM | ||
import { Link } from "react-router-dom"; | ||
import EmptyBodyTable from "./EmptyBodyTable"; | ||
|
||
/** | ||
* This component renders a table with the specified columns and rows. | ||
* It also allows the user to select rows and perform operations on them. | ||
* | ||
*/ | ||
|
||
type DataType = SubId; // TODO: add more data types separated by an 'or' operator ('|') | ||
|
||
interface SelectedElementsData { | ||
isElementSelectable: (element: DataType) => boolean; | ||
selectedElements: DataType[]; | ||
selectableElementsTable: DataType[]; | ||
setElementsSelected: (rule: DataType, isSelecting?: boolean) => void; | ||
clearSelectedElements: () => void; | ||
} | ||
|
||
interface ButtonsData { | ||
updateIsDeleteButtonDisabled: (value: boolean) => void; | ||
isDeletion: boolean; | ||
updateIsDeletion: (value: boolean) => void; | ||
updateIsEnableButtonDisabled?: (value: boolean) => void; | ||
updateIsDisableButtonDisabled?: (value: boolean) => void; | ||
isDisableEnableOp?: boolean; | ||
updateIsDisableEnableOp?: (value: boolean) => void; | ||
} | ||
|
||
interface PaginationData { | ||
selectedPerPage: number; | ||
updateSelectedPerPage: (selected: number) => void; | ||
} | ||
|
||
export interface PropsToTable { | ||
tableTitle: string; | ||
shownElementsList: DataType[]; | ||
pk: string; // E.g. Primary key for users --> "uid" | ||
keyNames: string[]; // E.g. for user.uid, user.description --> ["uid", "description"] | ||
columnNames: string[]; // E.g. ["User ID", "Description"] | ||
hasCheckboxes: boolean; | ||
pathname: string; // E.g. "active-users" (without the leading '/') | ||
showTableRows: boolean; | ||
showLink: boolean; | ||
elementsData?: SelectedElementsData; | ||
buttonsData?: ButtonsData; | ||
paginationData?: PaginationData; | ||
} | ||
|
||
const MainTable = (props: PropsToTable) => { | ||
// Retrieve elements data from props | ||
const shownElementsList = [...props.shownElementsList]; | ||
const columnNames = [...props.columnNames]; | ||
|
||
// When user status is updated, unselect selected rows | ||
React.useEffect(() => { | ||
if (props.buttonsData && props.buttonsData.isDisableEnableOp) { | ||
props.elementsData?.clearSelectedElements(); | ||
} | ||
}, [props.buttonsData?.isDisableEnableOp]); | ||
|
||
const isElementSelected = (element: DataType) => { | ||
if ( | ||
props.elementsData?.selectedElements.find( | ||
(selectedElement) => selectedElement[props.pk] === element[props.pk] | ||
) | ||
) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
}; | ||
|
||
// To allow shift+click to select/deselect multiple rows | ||
const [recentSelectedRowIndex, setRecentSelectedRowIndex] = React.useState< | ||
number | null | ||
>(null); | ||
const [shifting, setShifting] = React.useState(false); | ||
|
||
// On selecting one single row | ||
const onSelectElement = ( | ||
element: DataType, | ||
rowIndex: number, | ||
isSelecting: boolean | ||
) => { | ||
// If the element is shift + selecting the checkboxes, then all intermediate checkboxes should be selected | ||
if (shifting && recentSelectedRowIndex !== null) { | ||
const numberSelected = rowIndex - recentSelectedRowIndex; | ||
const intermediateIndexes = Array.from( | ||
new Array(Math.abs(numberSelected) + 1), | ||
(_, i) => i + Math.min(recentSelectedRowIndex, rowIndex) | ||
); | ||
intermediateIndexes.forEach((index) => | ||
props.elementsData?.setElementsSelected( | ||
shownElementsList[index], | ||
isSelecting | ||
) | ||
); | ||
} else { | ||
props.elementsData?.setElementsSelected(element, isSelecting); | ||
} | ||
setRecentSelectedRowIndex(rowIndex); | ||
|
||
// Resetting 'isDisableEnableOp' | ||
props.buttonsData?.updateIsDeleteButtonDisabled(false); | ||
|
||
// Update elementSelected array | ||
if (isSelecting) { | ||
// Increment the elements selected per page (++) | ||
props.paginationData?.updateSelectedPerPage( | ||
props.paginationData?.selectedPerPage + 1 | ||
); | ||
} else { | ||
// Decrement the elements selected per page (--) | ||
props.paginationData?.updateSelectedPerPage( | ||
props.paginationData?.selectedPerPage - 1 | ||
); | ||
} | ||
}; | ||
|
||
// Reset 'selectedElements array if a delete operation has been done | ||
React.useEffect(() => { | ||
if (props.buttonsData?.isDeletion) { | ||
props.elementsData?.clearSelectedElements(); | ||
props.buttonsData.updateIsDeletion(false); | ||
} | ||
}, [props.buttonsData?.isDeletion]); | ||
|
||
// Enable 'Delete' button (if any element selected) | ||
React.useEffect(() => { | ||
if (props.elementsData && props.elementsData.selectedElements.length > 0) { | ||
props.buttonsData?.updateIsDeleteButtonDisabled(false); | ||
} | ||
|
||
if ( | ||
props.elementsData && | ||
props.elementsData.selectedElements.length === 0 | ||
) { | ||
props.buttonsData?.updateIsDeleteButtonDisabled(true); | ||
} | ||
}, [props.elementsData?.selectedElements]); | ||
|
||
// Keyboard event | ||
React.useEffect(() => { | ||
const onKeyDown = (e: KeyboardEvent) => { | ||
if (e.key === "Shift") { | ||
setShifting(true); | ||
} | ||
}; | ||
const onKeyUp = (e: KeyboardEvent) => { | ||
if (e.key === "Shift") { | ||
setShifting(false); | ||
} | ||
}; | ||
|
||
document.addEventListener("keydown", onKeyDown); | ||
document.addEventListener("keyup", onKeyUp); | ||
|
||
return () => { | ||
document.removeEventListener("keydown", onKeyDown); | ||
document.removeEventListener("keyup", onKeyUp); | ||
}; | ||
}, []); | ||
|
||
// Defining table header and body from here to avoid passing specific names to the Table Layout | ||
const header = ( | ||
<Tr key="header" id="table-header"> | ||
{props.hasCheckboxes && <Th modifier="wrap"></Th>} | ||
{props.columnNames.map((columnName, idx) => ( | ||
<Th modifier="wrap" key={idx}> | ||
{columnName} | ||
</Th> | ||
))} | ||
</Tr> | ||
); | ||
|
||
const body = shownElementsList.map((element, rowIndex) => { | ||
if (element !== undefined) { | ||
return ( | ||
<Tr key={"row-" + rowIndex} id={"row-" + rowIndex}> | ||
{/* Checkboxes (if specified) */} | ||
{props.hasCheckboxes && ( | ||
<Td | ||
key={rowIndex} | ||
id={rowIndex.toString()} | ||
dataLabel="checkbox" | ||
select={{ | ||
rowIndex, | ||
onSelect: (_event, isSelecting) => | ||
onSelectElement(element, rowIndex, isSelecting), | ||
isSelected: isElementSelected(element), | ||
isDisabled: !props.elementsData?.isElementSelectable(element), | ||
}} | ||
/> | ||
)} | ||
{/* Table rows */} | ||
{props.keyNames.map((keyName, idx) => ( | ||
<Td dataLabel={columnNames[keyName]} key={idx} id={idx.toString()}> | ||
{idx === 0 && !!props.showLink ? ( | ||
<Link | ||
to={"/" + props.pathname + "/" + element[keyName]} | ||
state={element} | ||
> | ||
{element[keyName]} | ||
</Link> | ||
) : ( | ||
<>{element[keyName]}</> | ||
)} | ||
</Td> | ||
))} | ||
</Tr> | ||
); | ||
} else { | ||
return <EmptyBodyTable key={"empty-row-" + rowIndex} />; | ||
} | ||
}); | ||
|
||
const skeleton = ( | ||
<SkeletonOnTableLayout | ||
rows={4} | ||
colSpan={9} | ||
screenreaderText={"Loading table rows"} | ||
/> | ||
); | ||
|
||
return ( | ||
<TableLayout | ||
ariaLabel={props.tableTitle} | ||
variant={"compact"} | ||
hasBorders={true} | ||
classes={"pf-v5-u-mt-md"} | ||
tableId={props.pathname + "-table"} | ||
isStickyHeader={true} | ||
tableHeader={header} | ||
tableBody={!props.showTableRows ? skeleton : body} | ||
/> | ||
); | ||
}; | ||
|
||
export default MainTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.