diff --git a/.changeset/angry-cows-leave.md b/.changeset/angry-cows-leave.md new file mode 100644 index 00000000..59932dd3 --- /dev/null +++ b/.changeset/angry-cows-leave.md @@ -0,0 +1,5 @@ +--- +"gboost-ui": patch +--- + +Auto select comparator if only one in QueryTable Filter diff --git a/.changeset/light-dryers-eat.md b/.changeset/light-dryers-eat.md new file mode 100644 index 00000000..a20c364d --- /dev/null +++ b/.changeset/light-dryers-eat.md @@ -0,0 +1,5 @@ +--- +"gboost-ui": patch +--- + +Fix margin between bottom of table and pagination diff --git a/.changeset/long-wombats-greet.md b/.changeset/long-wombats-greet.md new file mode 100644 index 00000000..7f66b912 --- /dev/null +++ b/.changeset/long-wombats-greet.md @@ -0,0 +1,5 @@ +--- +"gboost-ui": minor +--- + +Add refreshRef to QueryTable to allow manual refreshing diff --git a/.changeset/selfish-geese-add.md b/.changeset/selfish-geese-add.md new file mode 100644 index 00000000..f4a2e2da --- /dev/null +++ b/.changeset/selfish-geese-add.md @@ -0,0 +1,5 @@ +--- +"gboost-ui": minor +--- + +Add refresh button on QueryTable action bar diff --git a/packages/gboost-ui/src/QueryTable/ActionBar/ActionBar.tsx b/packages/gboost-ui/src/QueryTable/ActionBar/ActionBar.tsx index 3d0a1520..addecfb9 100644 --- a/packages/gboost-ui/src/QueryTable/ActionBar/ActionBar.tsx +++ b/packages/gboost-ui/src/QueryTable/ActionBar/ActionBar.tsx @@ -1,17 +1,19 @@ -import { ReactElement, RefObject, useMemo } from "react"; -import { Heading } from "@aws-amplify/ui-react"; +import { MutableRefObject, ReactElement, RefObject, useMemo } from "react"; +import { Button, Heading, Icon } from "@aws-amplify/ui-react"; import { Box } from "../../Box.js"; import { Column } from "../QueryTable.js"; import { DownloadAction } from "./DownloadAction.js"; import { FilterAction, InternalFilter } from "./FilterAction/FilterAction.js"; import { ColumnVisibilityAction } from "./ColumnVisibilityAction.js"; import { Density, DensityAction } from "./DensityAction.js"; +import { MdRefresh } from "react-icons/md"; interface ActionBarProps { columns: Column[]; columnVisibility: Record; density: Density; disableMultiFilter: boolean; + disableRefresh: boolean; download: boolean; downloadFileName: string; filters: InternalFilter[]; @@ -20,6 +22,8 @@ interface ActionBarProps { onChangeColumnVisibility: (columnVisibility: Record) => void; onChangeDensity: (density: Density) => void; onFilter: (filters: InternalFilter[]) => void; + onRefresh: () => void; + refreshRef?: MutableRefObject; rows: Record[]; ActionMenu?: ReactElement; } @@ -33,6 +37,7 @@ export function ActionBar(props: ActionBarProps): ReactElement { columnVisibility, density, disableMultiFilter, + disableRefresh, download, downloadFileName, filters, @@ -41,6 +46,8 @@ export function ActionBar(props: ActionBarProps): ReactElement { onChangeColumnVisibility: handleChangeColumnVisibility, onChangeDensity: handleChangeDensity, onFilter, + onRefresh, + refreshRef, rows, ActionMenu, } = props; @@ -59,6 +66,11 @@ export function ActionBar(props: ActionBarProps): ReactElement { > {heading ? {heading} : } + {!disableRefresh && ( + + )} {filterColumns.length && ( = useCallback( (e) => { setDirty(true); - handleUpdateFilter(id, { + const newFilter: InternalFilter = { ...filter, column: e.target.value, comparator: "", value: "", - }); + }; + const newComparators = + filterColumnsObj[e.target.value]?.filterOptions?.comparators || []; + // if there is only 1 comparator for the column, pre-select it for user + if (!filter.comparator && newComparators.length === 1) { + newFilter.comparator = newComparators[0].value; + } + handleUpdateFilter(id, newFilter); }, - [filter, handleUpdateFilter, id] + [filter, filterColumnsObj, handleUpdateFilter, id] ); const handleChangeComparator: ChangeEventHandler = useCallback( @@ -67,6 +80,16 @@ export function FilterRow({ handleUpdateFilter(id, { ...filter, value }); setDirty(false); }, [filter, handleUpdateFilter, id, value]); + useEffect(() => { + // if only 1 comparator option, pre-select it + if (!filter.comparator && filterOptions?.comparators.length === 1) { + handleUpdateFilter(id, { + ...filter, + comparator: filterOptions?.comparators[0].value, + value: "", + }); + } + }, [filter, filterOptions?.comparators, handleUpdateFilter, id]); return ( <> = useCallback( + (e) => { + setFilter((f) => { + const newFilter: InternalFilter = { + ...filter, + column: e.target.value, + }; + const newComparators = + filterColumnsObj[e.target.value]?.filterOptions?.comparators || []; + // if there is only 1 comparator for the column, pre-select it for user + if (!filter.comparator && newComparators.length === 1) { + newFilter.comparator = newComparators[0].value; + } + return newFilter; + }); + }, + [filter, filterColumnsObj] + ); return ( <> setFilter((f) => ({ ...f, column: e.target.value }))} + onChange={handleChangeColumn} placeholder="Column" value={filter.column} > diff --git a/packages/gboost-ui/src/QueryTable/QueryTable.tsx b/packages/gboost-ui/src/QueryTable/QueryTable.tsx index efca3be4..ecc5d173 100644 --- a/packages/gboost-ui/src/QueryTable/QueryTable.tsx +++ b/packages/gboost-ui/src/QueryTable/QueryTable.tsx @@ -1,4 +1,5 @@ import { + MutableRefObject, ReactElement, Reducer, useCallback, @@ -76,6 +77,10 @@ export interface OnQueryParams { nextToken: string; pageSize: number; sorts: Sort[]; + /** + * True if query was invoked by refresh + */ + refresh: boolean; } type OnQuerySuccessReturnValue = { rows: Row[]; @@ -90,11 +95,6 @@ export type OnQueryReturnValue = type SelectAction = "select" | "unselect"; interface QueryTableProps { columns: Column[]; - /** - * Number of placeholder rows that show when loading - * @default 10 - */ - countLoadingRows?: number; /** * @default false */ @@ -103,6 +103,11 @@ interface QueryTableProps { * @default false */ disableMultiFilter?: boolean; + /** + * Removes refresh button in QueryTable's Action Bar + * @default false + */ + disableRefresh?: boolean; /** * Enable CSV file download * @default false @@ -173,6 +178,10 @@ interface QueryTableProps { * Function called upon update to selected rows */ onSelect?: (action: SelectAction, rows: T[], selected: T[]) => void; + /** + * Ref to enable manually refreshing with refreshRef.click() + */ + refreshRef?: MutableRefObject; /** * Action Button Component placed on top right of table, often used for creating a row or * displaying an actions menu button for user to perform actions on selected @@ -203,6 +212,7 @@ interface TableState { pageSize: number; pageSizeOptions: number[]; prevTokens: string[]; + refresh: boolean; // if true, indicates query is from refresh rows: T[]; selected: T[]; sorts: Sort[]; @@ -235,7 +245,12 @@ function tableReducer( case "changeDensity": return { ...state, density: action.density }; case "changeError": - return { ...state, loading: false, errorMessage: action.message }; + return { + ...state, + loading: false, + errorMessage: action.message, + refresh: false, + }; case "changeLoading": return { ...state, loading: action.loading, errorMessage: "" }; case "changePage": @@ -274,6 +289,7 @@ function tableReducer( rows: action.rows, nextNextToken: action.nextNextToken, loading: false, + refresh: false, }; case "changeSelected": return { @@ -283,8 +299,7 @@ function tableReducer( case "filter": return { ...state, filters: action.filters }; case "refresh": - // trigger useEffect to run again - return { ...state, filters: [...state.filters] }; + return { ...state, refresh: true }; case "sort": return { ...state, sorts: action.sorts }; default: @@ -306,9 +321,9 @@ export function QueryTable>( ): ReactElement { const { columns = [], - countLoadingRows = 10, disableMultiSort = false, disableMultiFilter = false, + disableRefresh = false, download = false, downloadFileName = "data.csv", enableSelect = false, @@ -325,6 +340,7 @@ export function QueryTable>( onQuery, // eslint-disable-next-line @typescript-eslint/no-empty-function onSelect = (s) => {}, + refreshRef, ActionButton, tableProps, } = props; @@ -342,6 +358,7 @@ export function QueryTable>( pageSize, pageSizeOptions, prevTokens, + refresh, rows, selected, sorts, @@ -362,6 +379,7 @@ export function QueryTable>( pageSize: initPageSize, pageSizeOptions: [10, 20, 50], prevTokens: [], + refresh: false, rows: [], selected: initSelected, sorts: initSorts, @@ -379,6 +397,7 @@ export function QueryTable>( nextToken, pageSize, sorts, + refresh, }); if ("rows" in res) { dispatch({ @@ -398,7 +417,7 @@ export function QueryTable>( } } fetchData(); - }, [filters, nextToken, onQuery, pageSize, sorts]); + }, [filters, nextToken, onQuery, pageSize, refresh, sorts]); const handlePageChange = useCallback(async (newPage: number) => { dispatch({ type: "changePage", page: newPage }); }, []); @@ -412,7 +431,7 @@ export function QueryTable>( - {[...Array(countLoadingRows)].map((e, i) => ( + {[...Array(pageSize)].map((e, i) => ( ))} @@ -499,6 +518,7 @@ export function QueryTable>( columnVisibility={columnVisibility} density={density} disableMultiFilter={disableMultiFilter} + disableRefresh={disableRefresh} download={download} downloadFileName={downloadFileName} filters={filters} @@ -511,11 +531,19 @@ export function QueryTable>( dispatch({ type: "changeDensity", density }) } onFilter={handleFilter} + onRefresh={() => dispatch({ type: "refresh" })} + refreshRef={refreshRef} rows={rows} ActionMenu={ActionButton} /> )} - + {enableSelect && ( @@ -572,7 +600,6 @@ export function QueryTable>( {spanTableEl} {!hidePagination && (