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:
parent
b65e034b04
commit
fe183e0b70
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 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 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user