1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2024-11-14 17:35:35 +01:00

Add RichTable component with header, body, and pagination

This commit is contained in:
Aleksandr Gumroian 2024-11-04 22:27:21 +01:00
parent b65e034b04
commit 42294316d9
No known key found for this signature in database
GPG Key ID: 9E77849C64F0A733
5 changed files with 343 additions and 0 deletions

View File

@ -0,0 +1,70 @@
/*
* 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 = 5,
pageIndex = 0,
}) {
const tableColumns = useMemo(() => columns, [columns]);
const [tableData] = useState(data ?? fallbackData);
const [sorting, setSorting] = useState([]);
const [pagination, setPagination] = useState({
pageIndex,
pageSize,
});
const table = useReactTable({
data: tableData,
columns: tableColumns,
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}
allRows={tableData.length}
/>
)}
</div>
);
};
export default RichTable;

View File

@ -0,0 +1,48 @@
/*
* 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 propTypes from "prop-types";
RichTableBody.propTypes = {
table: propTypes.shape({
getRowModel: propTypes.func.isRequired,
}).isRequired,
flexRender: propTypes.func.isRequired,
};
function RichTableBody({ table, flexRender }) {
return (
<tbody>
{table.getRowModel().rows.map((row) => {
return (
<tr key={row.id} className="align-middle">
{row.getVisibleCells().map((cell) => {
return (
<td
key={cell.id}
{...(cell.column.columnDef.className && {
className:
cell.column.columnDef.className,
})}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
);
})}
</tr>
);
})}
</tbody>
);
}
export default RichTableBody;

View File

@ -0,0 +1,96 @@
/*
* 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 {
faSquareCaretUp,
faSquareCaretDown,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import propTypes from "prop-types";
RichTableHeader.propTypes = {
table: propTypes.shape({
getHeaderGroups: propTypes.func.isRequired,
}).isRequired,
flexRender: propTypes.func.isRequired,
};
function RichTableHeader({ table, flexRender }) {
const getThTitle = (header) => {
if (!header.column.getCanSort()) return undefined;
const nextSortingOrder = header.column.getNextSortingOrder();
if (nextSortingOrder === "asc") return _("Sort ascending");
if (nextSortingOrder === "desc") return _("Sort descending");
return _("Clear sort");
};
return (
<thead className="thead-light">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id} role="row">
{headerGroup.headers.map((header) => (
<th
key={header.id}
colSpan={header.colSpan}
{...(header.column.columnDef.headerClassName && {
className:
header.column.columnDef.headerClassName,
})}
>
{header.isPlaceholder ||
header.column.columnDef.headerIsHidden ? (
<div className="d-none" aria-hidden="true">
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</div>
) : (
<button
type="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;

View File

@ -0,0 +1,128 @@
/*
* 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 } from "react";
import {
faAngleLeft,
faAnglesLeft,
faAngleRight,
faAnglesRight,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import propTypes from "prop-types";
RichTablePagination.propTypes = {
table: propTypes.shape({
getState: propTypes.func.isRequired,
getCanPreviousPage: propTypes.func.isRequired,
getCanNextPage: propTypes.func.isRequired,
firstPage: propTypes.func.isRequired,
previousPage: propTypes.func.isRequired,
nextPage: propTypes.func.isRequired,
lastPage: propTypes.func.isRequired,
setPageSize: propTypes.func.isRequired,
getPageCount: propTypes.func.isRequired,
}).isRequired,
tablePageSize: propTypes.number,
allRows: propTypes.number,
};
function RichTablePagination({ table, tablePageSize, allRows }) {
const { pagination } = table.getState();
const prevPagBtnDisabled = !table.getCanPreviousPage();
const nextPagBtnDisabled = !table.getCanNextPage();
const pageSizes = useMemo(() => {
return [tablePageSize ?? 5, 10, 25].filter(
(value, index, self) => self.indexOf(value) === index
);
}, [tablePageSize]);
const renderPaginationButton = (icon, ariaLabel, onClick, disabled) => (
<li
className={`page-item ${disabled ? "disabled" : ""}`}
style={{ cursor: disabled ? "not-allowed" : "pointer" }}
>
<button
type="button"
className="page-link"
aria-label={ariaLabel}
onClick={onClick}
disabled={disabled}
>
<FontAwesomeIcon icon={icon} />
</button>
</li>
);
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">
{renderPaginationButton(
faAnglesLeft,
_("First page"),
() => table.firstPage(),
prevPagBtnDisabled
)}
{renderPaginationButton(
faAngleLeft,
_("Previous page"),
() => table.previousPage(),
prevPagBtnDisabled
)}
{renderPaginationButton(
faAngleRight,
_("Next page"),
() => table.nextPage(),
nextPagBtnDisabled
)}
{renderPaginationButton(
faAnglesRight,
_("Last page"),
() => table.lastPage(),
nextPagBtnDisabled
)}
</ul>
<span>
{_("Page")}&nbsp;
<span className="fw-bold">
{pagination.pageIndex + 1}
&nbsp;{_("of")}&nbsp;
{table.getPageCount().toLocaleString()}
</span>
</span>
<div
className="vr mx-1 align-self-center"
style={{ height: "1.5rem" }}
/>
<span>{_("Rows per page:")}</span>
<select
className="form-select form-select-sm w-auto"
aria-label={_("Select rows per page")}
value={pagination.pageSize}
onChange={(e) => {
table.setPageSize(Number(e.target.value));
}}
>
{pageSizes.map((pageSize) => (
<option key={pageSize} value={pageSize}>
{pageSize}
</option>
))}
<option key={allRows} value={allRows}>
{_("All")}
</option>
</select>
</nav>
);
}
export default RichTablePagination;

View File

@ -43,6 +43,7 @@ export { Modal, ModalBody, ModalFooter, ModalHeader } from "./bootstrap/Modal";
export { default as RebootButton } from "./common/RebootButton"; export { default as RebootButton } from "./common/RebootButton";
export { default as WiFiSettings } from "./common/WiFiSettings/WiFiSettings"; export { default as WiFiSettings } from "./common/WiFiSettings/WiFiSettings";
export { default as ResetWiFiSettings } from "./common/WiFiSettings/ResetWiFiSettings"; export { default as ResetWiFiSettings } from "./common/WiFiSettings/ResetWiFiSettings";
export { default as RichTable } from "./common/RichTable/RichTable";
// Form // Form
export { default as ForisForm } from "./form/components/ForisForm"; export { default as ForisForm } from "./form/components/ForisForm";
export { export {