Table.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import React, { useEffect } from "react";
  2. import styled from "styled-components";
  3. import {
  4. Column,
  5. Row,
  6. useGlobalFilter,
  7. usePagination,
  8. useTable,
  9. } from "react-table";
  10. import Loading from "components/Loading";
  11. import Selector from "./Selector";
  12. const GlobalFilter: React.FunctionComponent<any> = ({ setGlobalFilter }) => {
  13. const [value, setValue] = React.useState("");
  14. const onChange = (value: string) => {
  15. setValue(value);
  16. setGlobalFilter(value || undefined);
  17. };
  18. return (
  19. <SearchRow>
  20. <i className="material-icons">search</i>
  21. <SearchInput
  22. value={value}
  23. onChange={(e: any) => {
  24. onChange(e.target.value);
  25. }}
  26. placeholder="Search"
  27. />
  28. </SearchRow>
  29. );
  30. };
  31. export type TableProps = {
  32. columns: Column<any>[];
  33. data: any[];
  34. onRowClick?: (row: Row) => void;
  35. isLoading: boolean;
  36. disableGlobalFilter?: boolean;
  37. disableHover?: boolean;
  38. enablePagination?: boolean;
  39. };
  40. const MIN_PAGE_SIZE = 1;
  41. const Table: React.FC<TableProps> = ({
  42. columns: columnsData,
  43. data,
  44. onRowClick,
  45. isLoading,
  46. disableGlobalFilter = false,
  47. disableHover,
  48. enablePagination,
  49. }) => {
  50. const {
  51. getTableProps,
  52. getTableBodyProps,
  53. page,
  54. setGlobalFilter,
  55. prepareRow,
  56. headerGroups,
  57. visibleColumns,
  58. // Pagination options
  59. canPreviousPage,
  60. canNextPage,
  61. pageOptions,
  62. pageCount,
  63. gotoPage,
  64. nextPage,
  65. previousPage,
  66. setPageSize,
  67. state: { pageIndex, pageSize },
  68. } = useTable(
  69. {
  70. columns: columnsData,
  71. data,
  72. },
  73. useGlobalFilter,
  74. usePagination
  75. );
  76. useEffect(() => {
  77. if (!enablePagination) {
  78. setPageSize(data.length || MIN_PAGE_SIZE);
  79. }
  80. }, [data, enablePagination]);
  81. const renderRows = () => {
  82. if (isLoading) {
  83. return (
  84. <StyledTr disableHover={true} selected={false}>
  85. <StyledTd colSpan={visibleColumns.length}>
  86. <Loading />
  87. </StyledTd>
  88. </StyledTr>
  89. );
  90. }
  91. if (!page.length) {
  92. return (
  93. <StyledTr disableHover={true} selected={false}>
  94. <StyledTd colSpan={visibleColumns.length}>No data available</StyledTd>
  95. </StyledTr>
  96. );
  97. }
  98. return (
  99. <>
  100. {page.map((row) => {
  101. prepareRow(row);
  102. return (
  103. <StyledTr
  104. disableHover={disableHover}
  105. {...row.getRowProps()}
  106. enablePointer={!!onRowClick}
  107. onClick={() => onRowClick && onRowClick(row)}
  108. selected={false}
  109. >
  110. {/* TODO: This is actually broken, not sure why but we need the width to be properly setted, this is a temporary solution */}
  111. {row.cells.map((cell) => {
  112. return (
  113. <StyledTd
  114. {...cell.getCellProps()}
  115. style={{
  116. width: cell.column.totalWidth,
  117. }}
  118. >
  119. {cell.render("Cell")}
  120. </StyledTd>
  121. );
  122. })}
  123. </StyledTr>
  124. );
  125. })}
  126. </>
  127. );
  128. };
  129. return (
  130. <TableWrapper>
  131. {!disableGlobalFilter && (
  132. <GlobalFilter setGlobalFilter={setGlobalFilter} />
  133. )}
  134. <StyledTable {...getTableProps()}>
  135. <StyledTHead>
  136. {headerGroups.map((headerGroup) => (
  137. <StyledTr
  138. {...headerGroup.getHeaderGroupProps()}
  139. disableHover={true}
  140. >
  141. {headerGroup.headers.map((column) => (
  142. <StyledTh {...column.getHeaderProps()}>
  143. {column.render("Header")}
  144. </StyledTh>
  145. ))}
  146. </StyledTr>
  147. ))}
  148. </StyledTHead>
  149. <tbody {...getTableBodyProps()}>{renderRows()}</tbody>
  150. </StyledTable>
  151. {enablePagination && (
  152. <FlexEnd style={{ marginTop: "15px" }}>
  153. <PageCountWrapper>
  154. Page size:
  155. <Selector
  156. activeValue={String(pageSize)}
  157. options={[
  158. {
  159. label: "10",
  160. value: "10",
  161. },
  162. {
  163. label: "20",
  164. value: "20",
  165. },
  166. {
  167. label: "50",
  168. value: "50",
  169. },
  170. {
  171. label: "100",
  172. value: "100",
  173. },
  174. ]}
  175. setActiveValue={(val) => setPageSize(Number(val))}
  176. width="70px"
  177. ></Selector>
  178. </PageCountWrapper>
  179. <PaginationActionsWrapper>
  180. <PaginationAction
  181. disabled={!canPreviousPage}
  182. onClick={previousPage}
  183. >
  184. {"<"}
  185. </PaginationAction>
  186. <PageCounter>
  187. {pageIndex + 1} of {pageCount}
  188. </PageCounter>
  189. <PaginationAction disabled={!canNextPage} onClick={nextPage}>
  190. {">"}
  191. </PaginationAction>
  192. </PaginationActionsWrapper>
  193. </FlexEnd>
  194. )}
  195. </TableWrapper>
  196. );
  197. };
  198. export default Table;
  199. const TableWrapper = styled.div`
  200. padding-bottom: 20px;
  201. `;
  202. const FlexEnd = styled.div`
  203. display: flex;
  204. justify-content: flex-end;
  205. align-items: center;
  206. width: 100%;
  207. `;
  208. const PaginationActionsWrapper = styled.div``;
  209. const PageCountWrapper = styled.div`
  210. display: flex;
  211. align-items: center;
  212. justify-content: space-between;
  213. min-width: 160px;
  214. margin-right: 10px;
  215. `;
  216. const PaginationAction = styled.button`
  217. border: none;
  218. background: unset;
  219. color: white;
  220. padding: 10px;
  221. cursor: pointer;
  222. border-radius: 5px;
  223. :hover {
  224. background: #ffffff40;
  225. }
  226. :disabled {
  227. color: #ffffff88;
  228. cursor: unset;
  229. :hover {
  230. background: unset;
  231. }
  232. }
  233. `;
  234. const PageCounter = styled.span`
  235. margin: 0 5px;
  236. `;
  237. type StyledTrProps = {
  238. enablePointer?: boolean;
  239. disableHover?: boolean;
  240. selected?: boolean;
  241. };
  242. export const StyledTr = styled.tr`
  243. line-height: 2.2em;
  244. background: ${(props: StyledTrProps) => (props.selected ? "#ffffff11" : "")};
  245. :hover {
  246. background: ${(props: StyledTrProps) =>
  247. props.disableHover ? "" : "#ffffff22"};
  248. }
  249. cursor: ${(props: StyledTrProps) =>
  250. props.enablePointer ? "pointer" : "unset"};
  251. `;
  252. export const StyledTd = styled.td`
  253. font-size: 13px;
  254. color: #ffffff;
  255. :first-child {
  256. padding-left: 10px;
  257. }
  258. :last-child {
  259. padding-right: 10px;
  260. }
  261. user-select: text;
  262. `;
  263. export const StyledTHead = styled.thead`
  264. width: 100%;
  265. border-top: 1px solid #aaaabb22;
  266. border-bottom: 1px solid #aaaabb22;
  267. position: sticky;
  268. `;
  269. export const StyledTh = styled.th`
  270. text-align: left;
  271. font-size: 13px;
  272. font-weight: 500;
  273. color: #aaaabb;
  274. :first-child {
  275. padding-left: 10px;
  276. }
  277. :last-child {
  278. padding-right: 10px;
  279. }
  280. `;
  281. export const StyledTable = styled.table`
  282. width: 100%;
  283. min-width: 500px;
  284. border-collapse: collapse;
  285. `;
  286. const SearchInput = styled.input`
  287. outline: none;
  288. border: none;
  289. font-size: 13px;
  290. background: none;
  291. width: 100%;
  292. color: white;
  293. padding: 0;
  294. height: 20px;
  295. `;
  296. const SearchRow = styled.div`
  297. display: flex;
  298. width: 100%;
  299. font-size: 13px;
  300. color: #ffffff55;
  301. border-radius: 4px;
  302. user-select: none;
  303. align-items: center;
  304. padding: 10px 0px;
  305. min-width: 300px;
  306. max-width: min-content;
  307. background: #ffffff11;
  308. margin-bottom: 15px;
  309. margin-top: 0px;
  310. i {
  311. width: 18px;
  312. height: 18px;
  313. margin-left: 12px;
  314. margin-right: 12px;
  315. font-size: 20px;
  316. }
  317. `;