Table.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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 Table: React.FC<TableProps> = ({
  41. columns: columnsData,
  42. data,
  43. onRowClick,
  44. isLoading,
  45. disableGlobalFilter = false,
  46. disableHover,
  47. enablePagination,
  48. }) => {
  49. const {
  50. getTableProps,
  51. getTableBodyProps,
  52. page,
  53. setGlobalFilter,
  54. prepareRow,
  55. headerGroups,
  56. visibleColumns,
  57. // Pagination options
  58. canPreviousPage,
  59. canNextPage,
  60. pageOptions,
  61. pageCount,
  62. gotoPage,
  63. nextPage,
  64. previousPage,
  65. setPageSize,
  66. state: { pageIndex, pageSize },
  67. } = useTable(
  68. {
  69. columns: columnsData,
  70. data,
  71. },
  72. useGlobalFilter,
  73. usePagination
  74. );
  75. useEffect(() => {
  76. if (!enablePagination) {
  77. setPageSize(data.length);
  78. }
  79. }, [data, enablePagination]);
  80. const renderRows = () => {
  81. if (isLoading) {
  82. return (
  83. <StyledTr disableHover={true} selected={false}>
  84. <StyledTd colSpan={visibleColumns.length}>
  85. <Loading />
  86. </StyledTd>
  87. </StyledTr>
  88. );
  89. }
  90. if (!page.length) {
  91. return (
  92. <StyledTr disableHover={true} selected={false}>
  93. <StyledTd colSpan={visibleColumns.length}>No data available</StyledTd>
  94. </StyledTr>
  95. );
  96. }
  97. return (
  98. <>
  99. {page.map((row) => {
  100. prepareRow(row);
  101. return (
  102. <StyledTr
  103. disableHover={disableHover}
  104. {...row.getRowProps()}
  105. enablePointer={!!onRowClick}
  106. onClick={() => onRowClick && onRowClick(row)}
  107. selected={false}
  108. >
  109. {/* TODO: This is actually broken, not sure why but we need the width to be properly setted, this is a temporary solution */}
  110. {row.cells.map((cell) => {
  111. console.log(cell.getCellProps());
  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. <Flex>
  132. {!disableGlobalFilter && (
  133. <GlobalFilter setGlobalFilter={setGlobalFilter} />
  134. )}
  135. {enablePagination && (
  136. <FlexEnd style={{ marginBottom: "15px" }}>
  137. <PageCountWrapper>
  138. Page size:
  139. <Selector
  140. activeValue={String(pageSize)}
  141. options={[
  142. {
  143. label: "10",
  144. value: "10",
  145. },
  146. {
  147. label: "20",
  148. value: "20",
  149. },
  150. {
  151. label: "50",
  152. value: "50",
  153. },
  154. {
  155. label: "100",
  156. value: "100",
  157. },
  158. ]}
  159. setActiveValue={(val) => setPageSize(Number(val))}
  160. width="70px"
  161. ></Selector>
  162. </PageCountWrapper>
  163. <PaginationActionsWrapper>
  164. <PaginationAction
  165. disabled={!canPreviousPage}
  166. onClick={previousPage}
  167. >
  168. {"<"}
  169. </PaginationAction>
  170. <PageCounter>
  171. {pageIndex + 1} of {pageCount}
  172. </PageCounter>
  173. <PaginationAction disabled={!canNextPage} onClick={nextPage}>
  174. {">"}
  175. </PaginationAction>
  176. </PaginationActionsWrapper>
  177. </FlexEnd>
  178. )}
  179. </Flex>
  180. <StyledTable {...getTableProps()}>
  181. <StyledTHead>
  182. {headerGroups.map((headerGroup) => (
  183. <StyledTr
  184. {...headerGroup.getHeaderGroupProps()}
  185. disableHover={true}
  186. >
  187. {headerGroup.headers.map((column) => (
  188. <StyledTh {...column.getHeaderProps()}>
  189. {column.render("Header")}
  190. </StyledTh>
  191. ))}
  192. </StyledTr>
  193. ))}
  194. </StyledTHead>
  195. <tbody {...getTableBodyProps()}>{renderRows()}</tbody>
  196. </StyledTable>
  197. {enablePagination && (
  198. <FlexEnd style={{ marginTop: "15px" }}>
  199. <PageCountWrapper>
  200. Page size:
  201. <Selector
  202. activeValue={String(pageSize)}
  203. options={[
  204. {
  205. label: "10",
  206. value: "10",
  207. },
  208. {
  209. label: "20",
  210. value: "20",
  211. },
  212. {
  213. label: "50",
  214. value: "50",
  215. },
  216. {
  217. label: "100",
  218. value: "100",
  219. },
  220. ]}
  221. setActiveValue={(val) => setPageSize(Number(val))}
  222. width="70px"
  223. ></Selector>
  224. </PageCountWrapper>
  225. <PaginationActionsWrapper>
  226. <PaginationAction
  227. disabled={!canPreviousPage}
  228. onClick={previousPage}
  229. >
  230. {"<"}
  231. </PaginationAction>
  232. <PageCounter>
  233. {pageIndex + 1} of {pageCount}
  234. </PageCounter>
  235. <PaginationAction disabled={!canNextPage} onClick={nextPage}>
  236. {">"}
  237. </PaginationAction>
  238. </PaginationActionsWrapper>
  239. </FlexEnd>
  240. )}
  241. </TableWrapper>
  242. );
  243. };
  244. export default Table;
  245. const TableWrapper = styled.div`
  246. padding-bottom: 20px;
  247. `;
  248. const FlexEnd = styled.div`
  249. display: flex;
  250. justify-content: flex-end;
  251. align-items: center;
  252. width: 100%;
  253. `;
  254. const Flex = styled.div`
  255. display: flex;
  256. `;
  257. const PaginationActionsWrapper = styled.div``;
  258. const PageCountWrapper = styled.div`
  259. display: flex;
  260. align-items: center;
  261. justify-content: space-between;
  262. min-width: 160px;
  263. margin-right: 10px;
  264. `;
  265. const PaginationAction = styled.button`
  266. border: none;
  267. background: unset;
  268. color: white;
  269. padding: 10px;
  270. cursor: pointer;
  271. border-radius: 5px;
  272. :hover {
  273. background: #ffffff40;
  274. }
  275. :disabled {
  276. color: #ffffff88;
  277. cursor: unset;
  278. :hover {
  279. background: unset;
  280. }
  281. }
  282. `;
  283. const PageCounter = styled.span`
  284. margin: 0 5px;
  285. `;
  286. type StyledTrProps = {
  287. enablePointer?: boolean;
  288. disableHover?: boolean;
  289. selected?: boolean;
  290. };
  291. export const StyledTr = styled.tr`
  292. line-height: 2.2em;
  293. background: ${(props: StyledTrProps) => (props.selected ? "#ffffff11" : "")};
  294. :hover {
  295. background: ${(props: StyledTrProps) =>
  296. props.disableHover ? "" : "#ffffff22"};
  297. }
  298. cursor: ${(props: StyledTrProps) =>
  299. props.enablePointer ? "pointer" : "unset"};
  300. `;
  301. export const StyledTd = styled.td`
  302. font-size: 13px;
  303. color: #ffffff;
  304. :first-child {
  305. padding-left: 10px;
  306. }
  307. :last-child {
  308. padding-right: 10px;
  309. }
  310. user-select: text;
  311. `;
  312. export const StyledTHead = styled.thead`
  313. width: 100%;
  314. border-top: 1px solid #aaaabb22;
  315. border-bottom: 1px solid #aaaabb22;
  316. position: sticky;
  317. `;
  318. export const StyledTh = styled.th`
  319. text-align: left;
  320. font-size: 13px;
  321. font-weight: 500;
  322. color: #aaaabb;
  323. :first-child {
  324. padding-left: 10px;
  325. }
  326. :last-child {
  327. padding-right: 10px;
  328. }
  329. `;
  330. export const StyledTable = styled.table`
  331. width: 100%;
  332. min-width: 500px;
  333. border-collapse: collapse;
  334. `;
  335. const SearchInput = styled.input`
  336. outline: none;
  337. border: none;
  338. font-size: 13px;
  339. background: none;
  340. width: 100%;
  341. color: white;
  342. padding: 0;
  343. height: 20px;
  344. `;
  345. const SearchRow = styled.div`
  346. display: flex;
  347. width: 100%;
  348. font-size: 13px;
  349. color: #ffffff55;
  350. border-radius: 4px;
  351. user-select: none;
  352. align-items: center;
  353. padding: 10px 0px;
  354. min-width: 300px;
  355. max-width: min-content;
  356. background: #ffffff11;
  357. margin-bottom: 15px;
  358. margin-top: 0px;
  359. i {
  360. width: 18px;
  361. height: 18px;
  362. margin-left: 12px;
  363. margin-right: 12px;
  364. font-size: 20px;
  365. }
  366. `;