| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- import React, { useEffect } from "react";
- import styled from "styled-components";
- import {
- Column,
- Row,
- useGlobalFilter,
- usePagination,
- useTable,
- } from "react-table";
- import Loading from "components/Loading";
- import Selector from "./Selector";
- import loading from "assets/loading.gif";
- const GlobalFilter: React.FunctionComponent<any> = ({
- setGlobalFilter,
- onRefresh,
- isRefreshing,
- }) => {
- const [value, setValue] = React.useState("");
- const onChange = (value: string) => {
- setValue(value);
- setGlobalFilter(value || undefined);
- };
- return (
- <SearchRowWrapper>
- <SearchRow>
- <i className="material-icons">search</i>
- <SearchInput
- value={value}
- onChange={(e: any) => {
- onChange(e.target.value);
- }}
- placeholder="Search"
- />
- </SearchRow>
- {typeof onRefresh === "function" && (
- <RefreshButton onClick={onRefresh} disabled={isRefreshing}>
- {isRefreshing ? (
- <>
- <img src={loading} alt="loading icon" />
- </>
- ) : (
- <i className="material-icons">refresh</i>
- )}
- </RefreshButton>
- )}
- </SearchRowWrapper>
- );
- };
- export type TableProps = {
- columns: Column<any>[];
- data: any[];
- onRowClick?: (row: Row) => void;
- isLoading: boolean;
- disableGlobalFilter?: boolean;
- disableHover?: boolean;
- enablePagination?: boolean;
- hasError?: boolean;
- errorMessage?: string;
- onRefresh?: () => void;
- isRefreshing?: boolean;
- };
- const MIN_PAGE_SIZE = 1;
- const Table: React.FC<TableProps> = ({
- columns: columnsData,
- data,
- onRowClick,
- isLoading,
- disableGlobalFilter = false,
- disableHover,
- enablePagination,
- hasError,
- errorMessage = "An unexpected error occurred, please try again.",
- onRefresh,
- isRefreshing = false,
- }) => {
- const {
- getTableProps,
- getTableBodyProps,
- page,
- setGlobalFilter,
- prepareRow,
- headerGroups,
- visibleColumns,
- // Pagination options
- canPreviousPage,
- canNextPage,
- pageOptions,
- pageCount,
- gotoPage,
- nextPage,
- previousPage,
- setPageSize,
- state: { pageIndex, pageSize },
- } = useTable(
- {
- columns: columnsData,
- data,
- },
- useGlobalFilter,
- usePagination
- );
- useEffect(() => {
- if (!enablePagination) {
- setPageSize(data.length || MIN_PAGE_SIZE);
- }
- }, [data, enablePagination]);
- const renderRows = () => {
- if (hasError) {
- return (
- <StyledTr disableHover={true} selected={false}>
- <StyledTd colSpan={visibleColumns.length} align="center">
- {errorMessage}
- </StyledTd>
- </StyledTr>
- );
- }
- if (isLoading) {
- return (
- <StyledTr disableHover={true} selected={false}>
- <StyledTd colSpan={visibleColumns.length} height="150px">
- <Loading />
- </StyledTd>
- </StyledTr>
- );
- }
- if (!page.length) {
- return (
- <StyledTr disableHover={true} selected={false}>
- <StyledTd colSpan={visibleColumns.length} align="center">
- No data available
- </StyledTd>
- </StyledTr>
- );
- }
- return (
- <>
- {page.map((row) => {
- prepareRow(row);
- return (
- <StyledTr
- disableHover={disableHover}
- {...row.getRowProps()}
- enablePointer={!!onRowClick}
- onClick={() => onRowClick && onRowClick(row)}
- selected={false}
- >
- {/* TODO: This is actually broken, not sure why but we need the width to be properly setted, this is a temporary solution */}
- {row.cells.map((cell) => {
- return (
- <StyledTd
- {...cell.getCellProps()}
- style={{
- width: cell.column.totalWidth,
- }}
- >
- {cell.render("Cell")}
- </StyledTd>
- );
- })}
- </StyledTr>
- );
- })}
- </>
- );
- };
- return (
- <TableWrapper>
- {!disableGlobalFilter && (
- <GlobalFilter
- setGlobalFilter={setGlobalFilter}
- onRefresh={onRefresh}
- isRefreshing={isRefreshing}
- />
- )}
- <StyledTable {...getTableProps()}>
- <StyledTHead>
- {headerGroups.map((headerGroup) => (
- <StyledTr
- {...headerGroup.getHeaderGroupProps()}
- disableHover={true}
- >
- {headerGroup.headers.map((column) => (
- <StyledTh {...column.getHeaderProps()}>
- {column.render("Header")}
- </StyledTh>
- ))}
- </StyledTr>
- ))}
- </StyledTHead>
- <tbody {...getTableBodyProps()}>{renderRows()}</tbody>
- </StyledTable>
- {enablePagination && (
- <FlexEnd style={{ marginTop: "15px" }}>
- <PageCountWrapper>
- Page size:
- <Selector
- activeValue={String(pageSize)}
- options={[
- {
- label: "10",
- value: "10",
- },
- {
- label: "20",
- value: "20",
- },
- {
- label: "50",
- value: "50",
- },
- {
- label: "100",
- value: "100",
- },
- ]}
- setActiveValue={(val) => setPageSize(Number(val))}
- width="70px"
- ></Selector>
- </PageCountWrapper>
- <PaginationActionsWrapper>
- <PaginationAction
- disabled={!canPreviousPage}
- onClick={previousPage}
- >
- {"<"}
- </PaginationAction>
- <PageCounter>
- {pageIndex + 1} of {pageCount}
- </PageCounter>
- <PaginationAction disabled={!canNextPage} onClick={nextPage}>
- {">"}
- </PaginationAction>
- </PaginationActionsWrapper>
- </FlexEnd>
- )}
- </TableWrapper>
- );
- };
- export default Table;
- const TableWrapper = styled.div`
- padding-bottom: 20px;
- `;
- const FlexEnd = styled.div`
- display: flex;
- justify-content: flex-end;
- align-items: center;
- width: 100%;
- `;
- const PaginationActionsWrapper = styled.div``;
- const PageCountWrapper = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
- min-width: 160px;
- margin-right: 10px;
- `;
- const PaginationAction = styled.button`
- border: none;
- background: unset;
- color: white;
- padding: 10px;
- cursor: pointer;
- border-radius: 5px;
- :hover {
- background: #ffffff40;
- }
- :disabled {
- color: #ffffff88;
- cursor: unset;
- :hover {
- background: unset;
- }
- }
- `;
- const PageCounter = styled.span`
- margin: 0 5px;
- `;
- type StyledTrProps = {
- enablePointer?: boolean;
- disableHover?: boolean;
- selected?: boolean;
- };
- export const StyledTr = styled.tr`
- line-height: 2.2em;
- background: ${(props: StyledTrProps) => (props.selected ? "#ffffff11" : "")};
- :hover {
- background: ${(props: StyledTrProps) =>
- props.disableHover ? "" : "#ffffff22"};
- }
- cursor: ${(props: StyledTrProps) =>
- props.enablePointer ? "pointer" : "unset"};
- `;
- export const StyledTd = styled.td`
- font-size: 13px;
- color: #ffffff;
- :first-child {
- padding-left: 10px;
- }
- :last-child {
- padding-right: 10px;
- }
- user-select: text;
- ${(props: { align?: "center" | "left" }) => {
- if (props.align) {
- return `text-align:${props.align};`;
- }
- }}
- `;
- export const StyledTHead = styled.thead`
- width: 100%;
- border-top: 1px solid #aaaabb22;
- border-bottom: 1px solid #aaaabb22;
- position: sticky;
- `;
- export const StyledTh = styled.th`
- text-align: left;
- font-size: 13px;
- font-weight: 500;
- color: #aaaabb;
- :first-child {
- padding-left: 10px;
- }
- :last-child {
- padding-right: 10px;
- }
- `;
- export const StyledTable = styled.table`
- width: 100%;
- min-width: 500px;
- border-collapse: collapse;
- `;
- const SearchInput = styled.input`
- outline: none;
- border: none;
- font-size: 13px;
- background: none;
- width: 100%;
- color: white;
- padding: 0;
- height: 20px;
- `;
- const SearchRow = styled.div`
- display: flex;
- width: 100%;
- font-size: 13px;
- color: #ffffff55;
- border-radius: 4px;
- user-select: none;
- align-items: center;
- padding: 10px 0px;
- min-width: 300px;
- max-width: min-content;
- background: #ffffff11;
- i {
- width: 18px;
- height: 18px;
- margin-left: 12px;
- margin-right: 12px;
- font-size: 20px;
- }
- `;
- const SearchRowWrapper = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- margin-top: 0px;
- `;
- const RefreshButton = styled.button`
- justify-self: flex-end;
- border: 1px solid #ffffff00;
- border-radius: 50%;
- background: inherit;
- color: #ffffff;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 35px;
- height: 35px;
- > i {
- font-size: 20px;
- }
- > img {
- width: 20px;
- height: 20px;
- }
- :hover {
- color: #ffffff88;
- border-color: #ffffff88;
- }
- `;
|