diff --git a/src/common/RichTable/RichTable.js b/src/common/RichTable/RichTable.js
new file mode 100644
index 0000000..43cabc2
--- /dev/null
+++ b/src/common/RichTable/RichTable.js
@@ -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 (
+
+
+ {withPagination && (
+
+ )}
+
+ );
+};
+
+export default RichTable;
diff --git a/src/common/RichTable/RichTableBody.js b/src/common/RichTable/RichTableBody.js
new file mode 100644
index 0000000..b02d6bc
--- /dev/null
+++ b/src/common/RichTable/RichTableBody.js
@@ -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 (
+
+ {table.getRowModel().rows.map((row) => {
+ return (
+
+ {row.getVisibleCells().map((cell) => {
+ return (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+ |
+ );
+ })}
+
+ );
+ })}
+
+ );
+};
+
+export default RichTableBody;
diff --git a/src/common/RichTable/RichTableHeader.js b/src/common/RichTable/RichTableHeader.js
new file mode 100644
index 0000000..3abdf1c
--- /dev/null
+++ b/src/common/RichTable/RichTableHeader.js
@@ -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 (
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder ? null : (
+
+ )}
+ |
+ );
+ })}
+
+ ))}
+
+ );
+};
+
+export default RichTableHeader;
diff --git a/src/common/RichTable/RichTablePagination.js b/src/common/RichTable/RichTablePagination.js
new file mode 100644
index 0000000..bbafb96
--- /dev/null
+++ b/src/common/RichTable/RichTablePagination.js
@@ -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 (
+
+ );
+};
+
+export default RichTablePagination;
diff --git a/src/index.js b/src/index.js
index 4e7839f..8052fb1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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 {