mirror of
				https://gitlab.nic.cz/turris/reforis/foris-js.git
				synced 2025-10-30 22:20:31 +01:00 
			
		
		
		
	Add RichTable component with header, body, and pagination
This commit is contained in:
		
							
								
								
									
										66
									
								
								src/common/RichTable/RichTable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/common/RichTable/RichTable.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| /* | ||||
|  * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/) | ||||
|  * | ||||
|  * This is free software, licensed under the GNU General Public License v3. | ||||
|  * See /LICENSE for more information. | ||||
|  */ | ||||
|  | ||||
| import React, { useMemo, useState } from "react"; | ||||
|  | ||||
| import { | ||||
|     flexRender, | ||||
|     getCoreRowModel, | ||||
|     getSortedRowModel, | ||||
|     getPaginationRowModel, | ||||
|     useReactTable, | ||||
| } from "@tanstack/react-table"; | ||||
|  | ||||
| import RichTableBody from "./RichTableBody"; | ||||
| import RichTableHeader from "./RichTableHeader"; | ||||
| import RichTablePagination from "./RichTablePagination"; | ||||
|  | ||||
| const fallbackData = []; | ||||
|  | ||||
| const RichTable = ({ | ||||
|     columns, | ||||
|     data, | ||||
|     withPagination, | ||||
|     pageSize = 10, | ||||
|     pageIndex = 0, | ||||
| }) => { | ||||
|     const tableColumns = useMemo(() => columns, []); | ||||
|     const [tableData, _] = useState(data ?? fallbackData); | ||||
|     const [sorting, setSorting] = useState([]); | ||||
|     const [pagination, setPagination] = useState({ | ||||
|         pageIndex, | ||||
|         pageSize, | ||||
|     }); | ||||
|  | ||||
|     const table = useReactTable({ | ||||
|         columns: tableColumns, | ||||
|         data: tableData, | ||||
|         getCoreRowModel: getCoreRowModel(), | ||||
|         getSortedRowModel: getSortedRowModel(), | ||||
|         getPaginationRowModel: getPaginationRowModel(), | ||||
|         onPaginationChange: setPagination, | ||||
|         onSortingChange: setSorting, | ||||
|         state: { | ||||
|             sorting, | ||||
|             pagination, | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     return ( | ||||
|         <div className="table-responsive"> | ||||
|             <table className="table table-hover text-nowrap"> | ||||
|                 <RichTableHeader table={table} flexRender={flexRender} /> | ||||
|                 <RichTableBody table={table} flexRender={flexRender} /> | ||||
|             </table> | ||||
|             {withPagination && ( | ||||
|                 <RichTablePagination table={table} tablePageSize={pageSize} /> | ||||
|             )} | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| export default RichTable; | ||||
							
								
								
									
										33
									
								
								src/common/RichTable/RichTableBody.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/common/RichTable/RichTableBody.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| /* | ||||
|  * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/) | ||||
|  * | ||||
|  * This is free software, licensed under the GNU General Public License v3. | ||||
|  * See /LICENSE for more information. | ||||
|  */ | ||||
|  | ||||
| import React from "react"; | ||||
|  | ||||
| const RichTableBody = ({ table, flexRender }) => { | ||||
|     return ( | ||||
|         <tbody> | ||||
|             {table.getRowModel().rows.map((row) => { | ||||
|                 return ( | ||||
|                     <tr key={row.id}> | ||||
|                         {row.getVisibleCells().map((cell) => { | ||||
|                             return ( | ||||
|                                 <td key={cell.id}> | ||||
|                                     {flexRender( | ||||
|                                         cell.column.columnDef.cell, | ||||
|                                         cell.getContext() | ||||
|                                     )} | ||||
|                                 </td> | ||||
|                             ); | ||||
|                         })} | ||||
|                     </tr> | ||||
|                 ); | ||||
|             })} | ||||
|         </tbody> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| export default RichTableBody; | ||||
							
								
								
									
										73
									
								
								src/common/RichTable/RichTableHeader.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/common/RichTable/RichTableHeader.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| /* | ||||
|  * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/) | ||||
|  * | ||||
|  * This is free software, licensed under the GNU General Public License v3. | ||||
|  * See /LICENSE for more information. | ||||
|  */ | ||||
|  | ||||
| import React from "react"; | ||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||
| import { | ||||
|     faSquareCaretUp, | ||||
|     faSquareCaretDown, | ||||
| } from "@fortawesome/free-solid-svg-icons"; | ||||
|  | ||||
| const RichTableHeader = ({ table, flexRender }) => { | ||||
|     const getThTitle = (header) => | ||||
|         header.column.getCanSort() | ||||
|             ? header.column.getNextSortingOrder() === "asc" | ||||
|                 ? _("Sort ascending") | ||||
|                 : header.column.getNextSortingOrder() === "desc" | ||||
|                   ? _("Sort descending") | ||||
|                   : _("Clear sort") | ||||
|             : undefined; | ||||
|  | ||||
|     return ( | ||||
|         <thead className="thead-light"> | ||||
|             {table.getHeaderGroups().map((headerGroup) => ( | ||||
|                 <tr key={headerGroup.id}> | ||||
|                     {headerGroup.headers.map((header) => { | ||||
|                         return ( | ||||
|                             <th key={header.id} colSpan={header.colSpan}> | ||||
|                                 {header.isPlaceholder ? null : ( | ||||
|                                     <button | ||||
|                                         className={`btn btn-link text-decoration-none text-reset fw-bold p-0 d-flex align-items-center | ||||
|                                                     ${ | ||||
|                                                         header.column.getCanSort() | ||||
|                                                             ? "d-flex align-items-center" | ||||
|                                                             : "" | ||||
|                                                     } | ||||
|                                                 `} | ||||
|                                         onClick={header.column.getToggleSortingHandler()} | ||||
|                                         title={getThTitle(header)} | ||||
|                                     > | ||||
|                                         {flexRender( | ||||
|                                             header.column.columnDef.header, | ||||
|                                             header.getContext() | ||||
|                                         )} | ||||
|                                         {{ | ||||
|                                             asc: ( | ||||
|                                                 <FontAwesomeIcon | ||||
|                                                     icon={faSquareCaretUp} | ||||
|                                                     className="ms-1 text-primary" | ||||
|                                                 /> | ||||
|                                             ), | ||||
|                                             desc: ( | ||||
|                                                 <FontAwesomeIcon | ||||
|                                                     icon={faSquareCaretDown} | ||||
|                                                     className="ms-1 text-primary" | ||||
|                                                 /> | ||||
|                                             ), | ||||
|                                         }[header.column.getIsSorted()] ?? null} | ||||
|                                     </button> | ||||
|                                 )} | ||||
|                             </th> | ||||
|                         ); | ||||
|                     })} | ||||
|                 </tr> | ||||
|             ))} | ||||
|         </thead> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| export default RichTableHeader; | ||||
							
								
								
									
										153
									
								
								src/common/RichTable/RichTablePagination.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/common/RichTable/RichTablePagination.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| /* | ||||
|  * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/) | ||||
|  * | ||||
|  * This is free software, licensed under the GNU General Public License v3. | ||||
|  * See /LICENSE for more information. | ||||
|  */ | ||||
|  | ||||
| import React from "react"; | ||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||
| import { | ||||
|     faAngleLeft, | ||||
|     faAnglesLeft, | ||||
|     faAngleRight, | ||||
|     faAnglesRight, | ||||
| } from "@fortawesome/free-solid-svg-icons"; | ||||
|  | ||||
| const RichTablePagination = ({ table, tablePageSize }) => { | ||||
|     const prevPagBtnDisabled = !table.getCanPreviousPage(); | ||||
|     const nextPagBtnDisabled = !table.getCanNextPage(); | ||||
|  | ||||
|     return ( | ||||
|         <nav | ||||
|             aria-label={_("Pagination navigation bar")} | ||||
|             className="d-flex gap-2 justify-content-start align-items-center mx-2 mb-1 text-nowrap" | ||||
|         > | ||||
|             <ul className="pagination pagination-sm mb-0"> | ||||
|                 <li | ||||
|                     className={`page-item ${prevPagBtnDisabled ? "disabled" : ""}`.trim()} | ||||
|                     style={ | ||||
|                         prevPagBtnDisabled | ||||
|                             ? { cursor: "not-allowed" } | ||||
|                             : { cursor: "pointer" } | ||||
|                     } | ||||
|                 > | ||||
|                     <button | ||||
|                         className="page-link" | ||||
|                         aria-label={_("First page")} | ||||
|                         onClick={() => table.firstPage()} | ||||
|                         disabled={prevPagBtnDisabled} | ||||
|                     > | ||||
|                         <FontAwesomeIcon icon={faAnglesLeft} /> | ||||
|                     </button> | ||||
|                 </li> | ||||
|                 <li | ||||
|                     className={`page-item ${prevPagBtnDisabled ? "disabled" : ""}`.trim()} | ||||
|                     style={ | ||||
|                         prevPagBtnDisabled | ||||
|                             ? { cursor: "not-allowed" } | ||||
|                             : { cursor: "pointer" } | ||||
|                     } | ||||
|                 > | ||||
|                     <button | ||||
|                         className="page-link" | ||||
|                         aria-label={_("Previous page")} | ||||
|                         onClick={() => table.previousPage()} | ||||
|                         disabled={prevPagBtnDisabled} | ||||
|                     > | ||||
|                         <FontAwesomeIcon icon={faAngleLeft} /> | ||||
|                     </button> | ||||
|                 </li> | ||||
|                 <li | ||||
|                     className={`page-item ${nextPagBtnDisabled ? "disabled" : ""}`.trim()} | ||||
|                     style={ | ||||
|                         nextPagBtnDisabled | ||||
|                             ? { cursor: "not-allowed" } | ||||
|                             : { cursor: "pointer" } | ||||
|                     } | ||||
|                 > | ||||
|                     <button | ||||
|                         className="page-link" | ||||
|                         aria-label={_("Next page")} | ||||
|                         onClick={() => table.nextPage()} | ||||
|                         disabled={nextPagBtnDisabled} | ||||
|                     > | ||||
|                         <FontAwesomeIcon icon={faAngleRight} /> | ||||
|                     </button> | ||||
|                 </li> | ||||
|                 <li | ||||
|                     className={`page-item ${nextPagBtnDisabled ? "disabled" : ""}`.trim()} | ||||
|                     style={ | ||||
|                         nextPagBtnDisabled | ||||
|                             ? { cursor: "not-allowed" } | ||||
|                             : { cursor: "pointer" } | ||||
|                     } | ||||
|                 > | ||||
|                     <button | ||||
|                         className="page-link" | ||||
|                         aria-label={_("Last page")} | ||||
|                         onClick={() => table.lastPage()} | ||||
|                         disabled={nextPagBtnDisabled} | ||||
|                     > | ||||
|                         <FontAwesomeIcon icon={faAnglesRight} /> | ||||
|                     </button> | ||||
|                 </li> | ||||
|             </ul> | ||||
|             <span> | ||||
|                 {_("Page")}  | ||||
|                 <span className="fw-bold"> | ||||
|                     {table.getState().pagination.pageIndex + 1} | ||||
|                      {_("of")}  | ||||
|                     {table.getPageCount().toLocaleString()} | ||||
|                 </span> | ||||
|             </span> | ||||
|             <div | ||||
|                 className="vr mx-1 align-self-center" | ||||
|                 style={{ height: "1.5rem" }} | ||||
|             /> | ||||
|             <span> | ||||
|                 {_("Go to page:")} | ||||
|                 <div className="d-inline-block ms-1 input-group input-group-sm w-auto"> | ||||
|                     <input | ||||
|                         type="number" | ||||
|                         min="1" | ||||
|                         max={table.getPageCount()} | ||||
|                         defaultValue={table.getState().pagination.pageIndex + 1} | ||||
|                         onChange={(e) => { | ||||
|                             const page = e.target.value | ||||
|                                 ? Number(e.target.value) - 1 | ||||
|                                 : 0; | ||||
|                             table.setPageIndex(page); | ||||
|                         }} | ||||
|                         className="form-control w-auto" | ||||
|                         aria-label={_("Page number")} | ||||
|                     /> | ||||
|                 </div> | ||||
|             </span> | ||||
|             <select | ||||
|                 className="form-select form-select-sm w-auto" | ||||
|                 aria-label={_("Select page size")} | ||||
|                 value={table.getState().pagination.pageSize} | ||||
|                 onChange={(e) => { | ||||
|                     table.setPageSize(Number(e.target.value)); | ||||
|                 }} | ||||
|             > | ||||
|                 {[ | ||||
|                     // if tablePageSize is not in the list, add it | ||||
|                     tablePageSize === 10 ? null : tablePageSize, | ||||
|                     10, | ||||
|                     20, | ||||
|                     30, | ||||
|                     40, | ||||
|                     50, | ||||
|                 ].map((pageSize) => ( | ||||
|                     <option key={pageSize} value={pageSize}> | ||||
|                         {_("Show")} {pageSize} | ||||
|                     </option> | ||||
|                 ))} | ||||
|             </select> | ||||
|         </nav> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| export default RichTablePagination; | ||||
| @@ -43,6 +43,7 @@ export { Modal, ModalBody, ModalFooter, ModalHeader } from "./bootstrap/Modal"; | ||||
| export { default as RebootButton } from "./common/RebootButton"; | ||||
| export { default as WiFiSettings } from "./common/WiFiSettings/WiFiSettings"; | ||||
| export { default as ResetWiFiSettings } from "./common/WiFiSettings/ResetWiFiSettings"; | ||||
| export { default as RichTable } from "./common/RichTable/RichTable"; | ||||
| // Form | ||||
| export { default as ForisForm } from "./form/components/ForisForm"; | ||||
| export { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user