2
0

Table.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. console.log(cell.getCellProps());
  113. return (
  114. <StyledTd
  115. {...cell.getCellProps()}
  116. style={{
  117. width: cell.column.totalWidth,
  118. }}
  119. >
  120. {cell.render("Cell")}
  121. </StyledTd>
  122. );
  123. })}
  124. </StyledTr>
  125. );
  126. })}
  127. </>
  128. );
  129. };
  130. return (
  131. <TableWrapper>
  132. {!disableGlobalFilter && (
  133. <GlobalFilter setGlobalFilter={setGlobalFilter} />
  134. )}
  135. <StyledTable {...getTableProps()}>
  136. <StyledTHead>
  137. {headerGroups.map((headerGroup) => (
  138. <StyledTr
  139. {...headerGroup.getHeaderGroupProps()}
  140. disableHover={true}
  141. >
  142. {headerGroup.headers.map((column) => (
  143. <StyledTh {...column.getHeaderProps()}>
  144. {column.render("Header")}
  145. </StyledTh>
  146. ))}
  147. </StyledTr>
  148. ))}
  149. </StyledTHead>
  150. <tbody {...getTableBodyProps()}>{renderRows()}</tbody>
  151. </StyledTable>
  152. {enablePagination && (
  153. <FlexEnd style={{ marginTop: "15px" }}>
  154. <PageCountWrapper>
  155. Page size:
  156. <Selector
  157. activeValue={String(pageSize)}
  158. options={[
  159. {
  160. label: "10",
  161. value: "10",
  162. },
  163. {
  164. label: "20",
  165. value: "20",
  166. },
  167. {
  168. label: "50",
  169. value: "50",
  170. },
  171. {
  172. label: "100",
  173. value: "100",
  174. },
  175. ]}
  176. setActiveValue={(val) => setPageSize(Number(val))}
  177. width="70px"
  178. ></Selector>
  179. </PageCountWrapper>
  180. <PaginationActionsWrapper>
  181. <PaginationAction
  182. disabled={!canPreviousPage}
  183. onClick={previousPage}
  184. >
  185. {"<"}
  186. </PaginationAction>
  187. <PageCounter>
  188. {pageIndex + 1} of {pageCount}
  189. </PageCounter>
  190. <PaginationAction disabled={!canNextPage} onClick={nextPage}>
  191. {">"}
  192. </PaginationAction>
  193. </PaginationActionsWrapper>
  194. </FlexEnd>
  195. )}
  196. </TableWrapper>
  197. );
  198. };
  199. export default Table;
  200. const TableWrapper = styled.div`
  201. padding-bottom: 20px;
  202. `;
  203. const FlexEnd = styled.div`
  204. display: flex;
  205. justify-content: flex-end;
  206. align-items: center;
  207. width: 100%;
  208. `;
  209. const PaginationActionsWrapper = styled.div``;
  210. const PageCountWrapper = styled.div`
  211. display: flex;
  212. align-items: center;
  213. justify-content: space-between;
  214. min-width: 160px;
  215. margin-right: 10px;
  216. `;
  217. const PaginationAction = styled.button`
  218. border: none;
  219. background: unset;
  220. color: white;
  221. padding: 10px;
  222. cursor: pointer;
  223. border-radius: 5px;
  224. :hover {
  225. background: #ffffff40;
  226. }
  227. :disabled {
  228. color: #ffffff88;
  229. cursor: unset;
  230. :hover {
  231. background: unset;
  232. }
  233. }
  234. `;
  235. const PageCounter = styled.span`
  236. margin: 0 5px;
  237. `;
  238. type StyledTrProps = {
  239. enablePointer?: boolean;
  240. disableHover?: boolean;
  241. selected?: boolean;
  242. };
  243. export const StyledTr = styled.tr`
  244. line-height: 2.2em;
  245. background: ${(props: StyledTrProps) => (props.selected ? "#ffffff11" : "")};
  246. :hover {
  247. background: ${(props: StyledTrProps) =>
  248. props.disableHover ? "" : "#ffffff22"};
  249. }
  250. cursor: ${(props: StyledTrProps) =>
  251. props.enablePointer ? "pointer" : "unset"};
  252. `;
  253. export const StyledTd = styled.td`
  254. font-size: 13px;
  255. color: #ffffff;
  256. :first-child {
  257. padding-left: 10px;
  258. }
  259. :last-child {
  260. padding-right: 10px;
  261. }
  262. user-select: text;
  263. `;
  264. export const StyledTHead = styled.thead`
  265. width: 100%;
  266. border-top: 1px solid #aaaabb22;
  267. border-bottom: 1px solid #aaaabb22;
  268. position: sticky;
  269. `;
  270. export const StyledTh = styled.th`
  271. text-align: left;
  272. font-size: 13px;
  273. font-weight: 500;
  274. color: #aaaabb;
  275. :first-child {
  276. padding-left: 10px;
  277. }
  278. :last-child {
  279. padding-right: 10px;
  280. }
  281. `;
  282. export const StyledTable = styled.table`
  283. width: 100%;
  284. min-width: 500px;
  285. border-collapse: collapse;
  286. `;
  287. const SearchInput = styled.input`
  288. outline: none;
  289. border: none;
  290. font-size: 13px;
  291. background: none;
  292. width: 100%;
  293. color: white;
  294. padding: 0;
  295. height: 20px;
  296. `;
  297. const SearchRow = styled.div`
  298. display: flex;
  299. width: 100%;
  300. font-size: 13px;
  301. color: #ffffff55;
  302. border-radius: 4px;
  303. user-select: none;
  304. align-items: center;
  305. padding: 10px 0px;
  306. min-width: 300px;
  307. max-width: min-content;
  308. background: #ffffff11;
  309. margin-bottom: 15px;
  310. margin-top: 0px;
  311. i {
  312. width: 18px;
  313. height: 18px;
  314. margin-left: 12px;
  315. margin-right: 12px;
  316. font-size: 20px;
  317. }
  318. `;