mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Refactor table editor filter state to store as URL params
This commit is contained in:
1 parent
d960037708
commit
de6231198d
13 files changed
+257
-315
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react'
|
||||
import { isEqual } from 'lodash'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useMonaco } from '@monaco-editor/react'
|
||||
import { DndProvider } from 'react-dnd'
|
||||
@@ -19,10 +20,12 @@ import { cleanupProps, initTable, saveStorageDebounced } from './SupabaseGrid.ut
|
||||
import { useUrlState } from 'hooks'
|
||||
|
||||
/**
|
||||
* Supabase Grid.
|
||||
*
|
||||
* React component to render database table.
|
||||
* Supabase Grid: React component to render database table.
|
||||
*/
|
||||
|
||||
// [JOSHEN TODO] Updating of rows when filters updating feels very choppy
|
||||
// Rows do not get updated immediately for some reason despite a short debounce
|
||||
|
||||
export const SupabaseGrid = forwardRef<SupabaseGridRef, SupabaseGridProps>((props, ref) => {
|
||||
const monaco = useMonaco()
|
||||
const _props = cleanupProps(props)
|
||||
@@ -60,8 +63,9 @@ const SupabaseGridLayout = forwardRef<SupabaseGridRef, SupabaseGridProps>((props
|
||||
const { editable, storageRef, gridProps, headerActions } = props
|
||||
const dispatch = useDispatch()
|
||||
const state = useTrackedState()
|
||||
|
||||
const gridRef = useRef<DataGridHandle>(null)
|
||||
const [mounted, setMount] = useState(false)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
const [{ sort: sorts, filter: filters }, setParams] = useUrlState({
|
||||
arrayKeys: ['sort', 'filter'],
|
||||
@@ -83,19 +87,23 @@ const SupabaseGridLayout = forwardRef<SupabaseGridRef, SupabaseGridProps>((props
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted) setMount(true)
|
||||
if (!mounted) setMounted(true)
|
||||
}, [])
|
||||
|
||||
// [Joshen] This where we refetch the data is say a sort or filter was updated
|
||||
useEffect(() => {
|
||||
if (state.refreshPageFlag == REFRESH_PAGE_IMMEDIATELY) {
|
||||
console.log('Refresh page')
|
||||
fetchPage(state, dispatch)
|
||||
} else if (state.refreshPageFlag != 0) {
|
||||
refreshPageDebounced(state, dispatch)
|
||||
fetchPage(state, dispatch, sorts as string[], filters as string[])
|
||||
} else if (state.refreshPageFlag !== 0) {
|
||||
refreshPageDebounced(state, dispatch, sorts as string[], filters as string[])
|
||||
}
|
||||
}, [state.refreshPageFlag])
|
||||
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
dispatch({ type: 'UPDATE_FILTERS_SORTS', payload: {} })
|
||||
}
|
||||
}, [JSON.stringify(filters)])
|
||||
|
||||
useEffect(() => {
|
||||
if (state.isInitialComplete && storageRef && state.table) {
|
||||
saveStorageDebounced(state, storageRef)
|
||||
@@ -104,14 +112,14 @@ const SupabaseGridLayout = forwardRef<SupabaseGridRef, SupabaseGridProps>((props
|
||||
state.table,
|
||||
state.isInitialComplete,
|
||||
state.gridColumns,
|
||||
state.sorts,
|
||||
state.filters,
|
||||
state.sorts, // [JOSHEN TODO] To update accordingly
|
||||
// state.filters, // [JOSHEN TODO] To update accordingly
|
||||
storageRef,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (state.totalRows === TOTAL_ROWS_RESET) {
|
||||
fetchCount(state, dispatch)
|
||||
fetchCount(state, dispatch, filters as string[])
|
||||
}
|
||||
}, [state.totalRows])
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ import { uuidv4 } from 'lib/helpers'
|
||||
import { STORAGE_KEY_PREFIX } from './constants'
|
||||
import { IMetaService } from './services/meta'
|
||||
import { InitialStateType } from './store/reducers'
|
||||
import { Dictionary, SupabaseGridProps, SupaColumn, SupaTable } from './types'
|
||||
import { Dictionary, Sort, SupabaseGridProps, SupaColumn, SupaTable } from './types'
|
||||
import { getGridColumns } from './utils/gridColumns'
|
||||
import { FilterOperatorOptions } from './components/header/filter'
|
||||
import { Filter } from 'components/grid/types'
|
||||
|
||||
export function defaultErrorHandler(error: any) {
|
||||
console.log('Supabase grid error: ', error)
|
||||
@@ -31,6 +32,41 @@ export function cleanupProps(props: SupabaseGridProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// [JOSHEN TODO] Write tests for this
|
||||
export function formatSortURLParams(sort?: string[]) {
|
||||
return (
|
||||
Array.isArray(sort)
|
||||
? sort
|
||||
.map((s) => {
|
||||
const [column, order] = s.split(':')
|
||||
// Reject any possible malformed sort param
|
||||
if (!column || !order) return undefined
|
||||
else return { column, ascending: order === 'asc' }
|
||||
})
|
||||
.filter((s) => s !== undefined)
|
||||
: []
|
||||
) as Sort[]
|
||||
}
|
||||
|
||||
// [JOSHEN TODO] Write tests for this
|
||||
export function formatFilterURLParams(filter?: string[]): Filter[] {
|
||||
return (
|
||||
Array.isArray(filter)
|
||||
? filter
|
||||
.map((f) => {
|
||||
const [column, operatorAbbrev, value] = f.split(':')
|
||||
const operator = FilterOperatorOptions.find(
|
||||
(option) => option.abbrev === operatorAbbrev
|
||||
)
|
||||
// Reject any possible malformed filter param
|
||||
if (!column || !operatorAbbrev || !operator) return undefined
|
||||
else return { id: uuidv4(), column, operator: operator.value, value: value || '' }
|
||||
})
|
||||
.filter((f) => f !== undefined)
|
||||
: []
|
||||
) as Filter[]
|
||||
}
|
||||
|
||||
export function initTable(
|
||||
props: SupabaseGridProps,
|
||||
state: InitialStateType,
|
||||
@@ -45,43 +81,17 @@ export function initTable(
|
||||
onAddColumn: props.editable ? props.onAddColumn : undefined,
|
||||
})
|
||||
|
||||
// [JOSHEN WE LEFT OFF HERE TO FIGURE OUT HOW TO REPLACE EXISTING FILTER AND SORT]
|
||||
const sorts = Array.isArray(sort)
|
||||
? sort
|
||||
.map((s) => {
|
||||
const [column, order] = s.split(':')
|
||||
// Reject any possible malformed sort param
|
||||
if (!column || !order) return undefined
|
||||
else return { column, ascending: order === 'asc' }
|
||||
})
|
||||
.filter((s) => s !== undefined)
|
||||
: []
|
||||
const savedState = props.storageRef
|
||||
? onLoadStorage(props.storageRef, table.name, table.schema)
|
||||
: undefined
|
||||
|
||||
const filters = Array.isArray(filter)
|
||||
? filter
|
||||
.map((f) => {
|
||||
const [column, operatorAbbrev, value] = f.split(':')
|
||||
const operator = FilterOperatorOptions.find(
|
||||
(option) => option.abbrev === operatorAbbrev
|
||||
)
|
||||
// Reject any possible malformed filter param
|
||||
if (!column || !operatorAbbrev || !operator || !value) return undefined
|
||||
else return { id: uuidv4(), column, operator: operator.value, value }
|
||||
})
|
||||
.filter((f) => f !== undefined)
|
||||
: []
|
||||
|
||||
let savedState
|
||||
if (props.storageRef) {
|
||||
savedState = onLoadStorage(props.storageRef, table.name, table.schema)
|
||||
}
|
||||
|
||||
console.log('initTable', savedState, {
|
||||
sort,
|
||||
sorts,
|
||||
filter,
|
||||
filters,
|
||||
})
|
||||
// [JOSHEN TODO] Temp force update savedState to use URL param
|
||||
// Load sort and filters via URL param only if given, otherwise get
|
||||
// from saved state in local storage
|
||||
const sorts = formatSortURLParams(sort)
|
||||
const filters = formatFilterURLParams(filter)
|
||||
if (sorts.length > 0) savedState.sorts = sorts
|
||||
if (filters.length > 0) savedState.filters = filters
|
||||
|
||||
dispatch({
|
||||
type: 'INIT_TABLE',
|
||||
@@ -227,13 +237,14 @@ export function onLoadStorage(storageRef: string, tableName: string, schema?: st
|
||||
|
||||
export const saveStorageDebounced = AwesomeDebouncePromise(saveStorage, 500)
|
||||
|
||||
// [JOSHEN TODO] Need to fix this
|
||||
function saveStorage(state: InitialStateType, storageRef: string) {
|
||||
if (!state.table) return
|
||||
|
||||
const config = {
|
||||
gridColumns: state.gridColumns,
|
||||
sorts: state.sorts,
|
||||
filters: state.filters,
|
||||
// filters: state.filters,
|
||||
}
|
||||
const storageKey = getStorageKey(STORAGE_KEY_PREFIX, storageRef)
|
||||
const savedStr = localStorage.getItem(storageKey)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import AwesomeDebouncePromise from 'awesome-debounce-promise';
|
||||
import { Filter } from '../../../types';
|
||||
|
||||
const updateFilterValue = (
|
||||
payload: {
|
||||
filterIdx: number;
|
||||
value: Filter;
|
||||
},
|
||||
dispatch: (value: unknown) => void
|
||||
) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_FILTER',
|
||||
payload: payload,
|
||||
});
|
||||
};
|
||||
export const updateFilterValueDebounced = AwesomeDebouncePromise(
|
||||
updateFilterValue,
|
||||
550
|
||||
);
|
||||
@@ -1,14 +1,16 @@
|
||||
import { FC } from 'react'
|
||||
import { Button, IconPlus, IconFilter, Popover } from '@supabase/ui'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { useDispatch, useTrackedState } from '../../../store'
|
||||
|
||||
import { useUrlState } from 'hooks'
|
||||
import FilterRow from './FilterRow'
|
||||
import { useTrackedState } from 'components/grid/store'
|
||||
import { formatFilterURLParams } from 'components/grid/SupabaseGrid.utils'
|
||||
|
||||
const FilterPopover: FC = () => {
|
||||
const state = useTrackedState()
|
||||
const [{ filter: filters }]: any = useUrlState({ arrayKeys: ['filter'] })
|
||||
const btnText =
|
||||
state.filters.length > 0
|
||||
? `Filtered by ${state.filters.length} rule${state.filters.length > 1 ? 's' : ''}`
|
||||
filters.length > 0
|
||||
? `Filtered by ${filters.length} rule${filters.length > 1 ? 's' : ''}`
|
||||
: 'Filter'
|
||||
|
||||
return (
|
||||
@@ -31,27 +33,32 @@ export default FilterPopover
|
||||
|
||||
const Filter: FC = () => {
|
||||
const state = useTrackedState()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [{ filter: filters }, setParams] = useUrlState({ arrayKeys: ['filter'] })
|
||||
const formattedFilters = formatFilterURLParams(filters as string[])
|
||||
|
||||
function onAddFilter() {
|
||||
dispatch({
|
||||
type: 'ADD_FILTER',
|
||||
payload: {
|
||||
id: uuidv4(),
|
||||
column: state.table?.columns[0].name,
|
||||
operator: '=',
|
||||
value: '',
|
||||
},
|
||||
setParams((prevParams) => {
|
||||
const existingFilters = (prevParams?.filter ?? []) as string[]
|
||||
const column = state.table?.columns[0].name
|
||||
return {
|
||||
...prevParams,
|
||||
filter: existingFilters.concat([`${column}:eq:`]),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2 py-2">
|
||||
<div className="space-y-2">
|
||||
{state.filters.map((filter, index) => (
|
||||
<FilterRow key={`filter-${filter.id}`} filterIdx={index} now={Date.now()} />
|
||||
{formattedFilters.map((filter, index) => (
|
||||
<FilterRow
|
||||
key={`filter-${(filters as string[])[index]}-${[index]}`}
|
||||
filter={filter}
|
||||
filterIdx={index}
|
||||
/>
|
||||
))}
|
||||
{state.filters.length == 0 && (
|
||||
{formattedFilters.length == 0 && (
|
||||
<div className="space-y-1 px-3">
|
||||
<h5 className="text-scale-1100 text-sm">No filters applied to this view</h5>
|
||||
<p className="text-scale-900 text-xs">Add a column below to filter the view</p>
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { FC, memo, useState, useEffect, ChangeEvent } from 'react'
|
||||
import { debounce } from 'lodash'
|
||||
import { FC, memo, useState, useEffect, ChangeEvent, useCallback } from 'react'
|
||||
import { Button, Input, IconChevronDown, IconX } from '@supabase/ui'
|
||||
import { DropdownControl } from '../../common'
|
||||
import { useDispatch, useTrackedState } from '../../../store'
|
||||
import { FilterOperatorOptions } from './Filter.constants'
|
||||
import { updateFilterValueDebounced } from './Filter.utils'
|
||||
|
||||
/**
|
||||
* use `now` to trigger re-render as filterIdx won't change value
|
||||
* if not filterText state will not updated on delete filter
|
||||
*/
|
||||
import { useUrlState } from 'hooks'
|
||||
import { Filter } from 'components/grid/types'
|
||||
import { DropdownControl } from 'components/grid/components/common'
|
||||
import { useTrackedState } from 'components/grid/store'
|
||||
import { FilterOperatorOptions } from './Filter.constants'
|
||||
|
||||
type FilterRowProps = {
|
||||
filter: Filter
|
||||
filterIdx: number
|
||||
now: number
|
||||
}
|
||||
|
||||
const FilterRow: FC<FilterRowProps> = ({ filterIdx }) => {
|
||||
// [JOSHEN TODO] Area of improvement: Input field loses focus after the debounce (because of useUrlState?)
|
||||
// [JOSHEN TODO] Area of improvement: Pretty sure we can refactor for DRY for updates on each column, operator and value
|
||||
const FilterRow: FC<FilterRowProps> = ({ filter, filterIdx }) => {
|
||||
const state = useTrackedState()
|
||||
const dispatch = useDispatch()
|
||||
const filter = state.filters[filterIdx]
|
||||
const [_, setParams] = useUrlState()
|
||||
|
||||
const column = state.table?.columns.find((x) => x.name === filter.column)
|
||||
const columnOptions =
|
||||
state.table?.columns?.map((x) => {
|
||||
@@ -26,43 +27,87 @@ const FilterRow: FC<FilterRowProps> = ({ filterIdx }) => {
|
||||
const [filterValue, setFilterValue] = useState(filter.value)
|
||||
|
||||
useEffect(() => {
|
||||
const filter = state.filters[filterIdx]
|
||||
setFilterValue(filter.value)
|
||||
}, [filterIdx])
|
||||
|
||||
function onRemoveFilter() {
|
||||
setParams((prevParams) => {
|
||||
const existingFilters = (prevParams?.filter ?? []) as string[]
|
||||
const updatedFilters = existingFilters.filter((filter: string, idx: number) => {
|
||||
if (idx !== filterIdx) return filter
|
||||
})
|
||||
return {
|
||||
...prevParams,
|
||||
filter: updatedFilters,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onColumnChange(column: string | number) {
|
||||
dispatch({
|
||||
type: 'UPDATE_FILTER',
|
||||
payload: { filterIdx, value: { ...filter, column } },
|
||||
setParams((prevParams) => {
|
||||
const existingFilters = (prevParams?.filter ?? []) as string[]
|
||||
const updatedFilters = existingFilters.map((filter: string, idx: number) => {
|
||||
if (idx === filterIdx) {
|
||||
const [_, operator, value] = filter.split(':')
|
||||
return `${column}:${operator}:${value}`
|
||||
} else {
|
||||
return filter
|
||||
}
|
||||
})
|
||||
return {
|
||||
...prevParams,
|
||||
filter: updatedFilters,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onOperatorChange(operator: string | number) {
|
||||
dispatch({
|
||||
type: 'UPDATE_FILTER',
|
||||
payload: { filterIdx, value: { ...filter, operator } },
|
||||
setParams((prevParams) => {
|
||||
const existingFilters = (prevParams?.filter ?? []) as string[]
|
||||
const updatedFilters = existingFilters.map((filter: string, idx: number) => {
|
||||
if (idx === filterIdx) {
|
||||
const [column, _, value] = filter.split(':')
|
||||
const selectedOperator = FilterOperatorOptions.find((option) => option.value === operator)
|
||||
return `${column}:${selectedOperator?.abbrev}:${value}`
|
||||
} else {
|
||||
return filter
|
||||
}
|
||||
})
|
||||
return {
|
||||
...prevParams,
|
||||
filter: updatedFilters,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onFilterChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
function onValueChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
const value = event.target.value
|
||||
setFilterValue(value)
|
||||
updateFilterValueDebounced(
|
||||
{
|
||||
filterIdx,
|
||||
value: { ...filter, value: value },
|
||||
},
|
||||
dispatch
|
||||
)
|
||||
}
|
||||
|
||||
function onRemoveFilter() {
|
||||
dispatch({
|
||||
type: 'REMOVE_FILTER',
|
||||
payload: { index: filterIdx },
|
||||
debounceHandler({
|
||||
filterIdx,
|
||||
value: { ...filter, value: value },
|
||||
})
|
||||
}
|
||||
|
||||
const updateFilterValue = (payload: { filterIdx: number; value: Filter }) => {
|
||||
setParams((prevParams) => {
|
||||
const existingFilters = (prevParams?.filter ?? []) as string[]
|
||||
const updatedFilters = existingFilters.map((filter: string, idx: number) => {
|
||||
if (idx === filterIdx) {
|
||||
const [column, operator] = filter.split(':')
|
||||
return `${column}:${operator}:${payload.value.value}`
|
||||
} else {
|
||||
return filter
|
||||
}
|
||||
})
|
||||
return {
|
||||
...prevParams,
|
||||
filter: updatedFilters,
|
||||
}
|
||||
})
|
||||
}
|
||||
const debounceHandler = useCallback(debounce(updateFilterValue, 600), [])
|
||||
|
||||
return (
|
||||
<div className="sb-grid-filter-row px-3">
|
||||
<DropdownControl align="start" options={columnOptions} onSelect={onColumnChange}>
|
||||
@@ -97,7 +142,7 @@ const FilterRow: FC<FilterRowProps> = ({ filterIdx }) => {
|
||||
className="w-full"
|
||||
placeholder="Enter a value"
|
||||
value={filterValue}
|
||||
onChange={onFilterChange}
|
||||
onChange={onValueChange}
|
||||
/>
|
||||
<Button
|
||||
icon={<IconX strokeWidth={1.5} size={14} />}
|
||||
|
||||
@@ -1,37 +1,34 @@
|
||||
import { useReducer } from 'react';
|
||||
import { createContainer } from 'react-tracked';
|
||||
import { useReducer } from 'react'
|
||||
import { createContainer } from 'react-tracked'
|
||||
import {
|
||||
BaseReducer,
|
||||
CallbackReducer,
|
||||
ColumnReducer,
|
||||
FilterReducer,
|
||||
RowReducer,
|
||||
SortReducer,
|
||||
initialState,
|
||||
InitialStateType,
|
||||
} from './reducers';
|
||||
} from './reducers'
|
||||
|
||||
const combineReducers = (...reducers: Function[]) => (
|
||||
state: any = initialState,
|
||||
action: any
|
||||
): any => {
|
||||
for (let i = 0; i < reducers.length; i++) state = reducers[i](state, action);
|
||||
return state;
|
||||
};
|
||||
const combineReducers =
|
||||
(...reducers: Function[]) =>
|
||||
(state: any = initialState, action: any): any => {
|
||||
for (let i = 0; i < reducers.length; i++) state = reducers[i](state, action)
|
||||
return state
|
||||
}
|
||||
|
||||
const storeReducers = combineReducers(
|
||||
BaseReducer,
|
||||
CallbackReducer,
|
||||
ColumnReducer,
|
||||
FilterReducer,
|
||||
RowReducer,
|
||||
SortReducer
|
||||
);
|
||||
)
|
||||
|
||||
const useValue = () => useReducer(storeReducers, initialState);
|
||||
const useValue = () => useReducer(storeReducers, initialState)
|
||||
|
||||
export const {
|
||||
Provider: StoreProvider,
|
||||
useTrackedState,
|
||||
useUpdate: useDispatch,
|
||||
} = createContainer<InitialStateType, React.Dispatch<any>, unknown>(useValue);
|
||||
} = createContainer<InitialStateType, React.Dispatch<any>, unknown>(useValue)
|
||||
@@ -1,16 +1,16 @@
|
||||
import { CalculatedColumn } from '@supabase/react-data-grid';
|
||||
import { GridProps, SavedState, SupaTable } from '../../types';
|
||||
import { REFRESH_PAGE_IMMEDIATELY } from '../../constants';
|
||||
import { IRowService, SqlRowService } from '../../services/row';
|
||||
import { IMetaService, SqlMetaService } from '../../services/meta';
|
||||
import { CalculatedColumn } from '@supabase/react-data-grid'
|
||||
import { GridProps, SavedState, SupaTable } from '../../types'
|
||||
import { REFRESH_PAGE_IMMEDIATELY, TOTAL_ROWS_RESET } from '../../constants'
|
||||
import { IRowService, SqlRowService } from '../../services/row'
|
||||
import { IMetaService, SqlMetaService } from '../../services/meta'
|
||||
|
||||
export interface BaseInitialState {
|
||||
table: SupaTable | null;
|
||||
metaService: IMetaService | null;
|
||||
rowService: IRowService | null;
|
||||
refreshPageFlag: number;
|
||||
isInitialComplete: boolean;
|
||||
editable: boolean;
|
||||
table: SupaTable | null
|
||||
metaService: IMetaService | null
|
||||
rowService: IRowService | null
|
||||
refreshPageFlag: number
|
||||
isInitialComplete: boolean
|
||||
editable: boolean
|
||||
}
|
||||
|
||||
export const baseInitialState: BaseInitialState = {
|
||||
@@ -20,29 +20,33 @@ export const baseInitialState: BaseInitialState = {
|
||||
refreshPageFlag: 0,
|
||||
isInitialComplete: false,
|
||||
editable: false,
|
||||
};
|
||||
}
|
||||
|
||||
export type INIT_ACTIONTYPE =
|
||||
| {
|
||||
type: 'INIT_CLIENT';
|
||||
type: 'INIT_CLIENT'
|
||||
payload: {
|
||||
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>;
|
||||
};
|
||||
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'INIT_TABLE';
|
||||
type: 'INIT_TABLE'
|
||||
payload: {
|
||||
table: SupaTable;
|
||||
gridColumns: CalculatedColumn<any, any>[];
|
||||
gridProps?: GridProps;
|
||||
savedState?: SavedState;
|
||||
editable?: boolean;
|
||||
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>;
|
||||
onError: (error: any) => void;
|
||||
};
|
||||
};
|
||||
table: SupaTable
|
||||
gridColumns: CalculatedColumn<any, any>[]
|
||||
gridProps?: GridProps
|
||||
savedState?: SavedState
|
||||
editable?: boolean
|
||||
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>
|
||||
onError: (error: any) => void
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'UPDATE_FILTERS_SORTS'
|
||||
payload: {}
|
||||
}
|
||||
|
||||
type BASE_ACTIONTYPE = INIT_ACTIONTYPE;
|
||||
type BASE_ACTIONTYPE = INIT_ACTIONTYPE
|
||||
|
||||
const BaseReducer = (state: BaseInitialState, action: BASE_ACTIONTYPE) => {
|
||||
switch (action.type) {
|
||||
@@ -50,7 +54,7 @@ const BaseReducer = (state: BaseInitialState, action: BASE_ACTIONTYPE) => {
|
||||
return {
|
||||
...state,
|
||||
metaService: new SqlMetaService(action.payload.onSqlQuery),
|
||||
};
|
||||
}
|
||||
}
|
||||
case 'INIT_TABLE': {
|
||||
return {
|
||||
@@ -64,11 +68,18 @@ const BaseReducer = (state: BaseInitialState, action: BASE_ACTIONTYPE) => {
|
||||
refreshPageFlag: REFRESH_PAGE_IMMEDIATELY,
|
||||
isInitialComplete: true,
|
||||
editable: action.payload.editable || false,
|
||||
};
|
||||
}
|
||||
}
|
||||
case 'UPDATE_FILTERS_SORTS': {
|
||||
const newState: any = { ...state }
|
||||
newState.page = 1
|
||||
newState.refreshPageFlag = Date.now()
|
||||
newState.totalRows = TOTAL_ROWS_RESET
|
||||
return newState
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
return state
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default BaseReducer;
|
||||
export default BaseReducer
|
||||
@@ -1,110 +0,0 @@
|
||||
import update from 'immutability-helper'
|
||||
import { TOTAL_ROWS_RESET } from '../../constants'
|
||||
import { Filter } from '../../types'
|
||||
import { getInitialFilters } from '../../utils'
|
||||
import { INIT_ACTIONTYPE } from './base'
|
||||
|
||||
export interface FilterInitialState {
|
||||
filters: Filter[]
|
||||
}
|
||||
|
||||
export const filterInitialState: FilterInitialState = { filters: [] }
|
||||
|
||||
type FILTER_ACTIONTYPE =
|
||||
| INIT_ACTIONTYPE
|
||||
| {
|
||||
type: 'SET_FILTERS'
|
||||
payload: Filter[]
|
||||
}
|
||||
| {
|
||||
type: 'ADD_FILTER'
|
||||
payload: Filter
|
||||
}
|
||||
| {
|
||||
type: 'REMOVE_FILTER'
|
||||
payload: { index: number }
|
||||
}
|
||||
| {
|
||||
type: 'UPDATE_FILTER'
|
||||
payload: {
|
||||
filterIdx: number
|
||||
value: Filter
|
||||
}
|
||||
}
|
||||
|
||||
const FilterReducer = (state: FilterInitialState, action: FILTER_ACTIONTYPE) => {
|
||||
switch (action.type) {
|
||||
case 'INIT_TABLE': {
|
||||
console.log('Filter Reducer INIT_TABLE', {
|
||||
savedState: action.payload.savedState,
|
||||
filters: getInitialFilters(action.payload.table, action.payload.savedState),
|
||||
})
|
||||
return {
|
||||
...state,
|
||||
filters: getInitialFilters(action.payload.table, action.payload.savedState),
|
||||
}
|
||||
}
|
||||
case 'SET_FILTERS':
|
||||
return {
|
||||
...state,
|
||||
filters: action.payload,
|
||||
}
|
||||
case 'ADD_FILTER': {
|
||||
const isValid = isValidFilter(action.payload)
|
||||
const newState: any = {
|
||||
...state,
|
||||
filters: update(state.filters, { $push: [action.payload] }),
|
||||
}
|
||||
if (isValid) {
|
||||
newState.page = 1
|
||||
newState.refreshPageFlag = Date.now()
|
||||
newState.totalRows = TOTAL_ROWS_RESET
|
||||
}
|
||||
return newState
|
||||
}
|
||||
case 'REMOVE_FILTER': {
|
||||
const removeIdx = action.payload.index
|
||||
const removeFilter = state.filters[removeIdx]
|
||||
|
||||
const isValid = isValidFilter(removeFilter)
|
||||
const newState: any = {
|
||||
...state,
|
||||
filters: update(state.filters, {
|
||||
$splice: [[removeIdx, 1]],
|
||||
}),
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
newState.page = 1
|
||||
newState.refreshPageFlag = Date.now()
|
||||
newState.totalRows = TOTAL_ROWS_RESET
|
||||
}
|
||||
return newState
|
||||
}
|
||||
case 'UPDATE_FILTER': {
|
||||
const updatedFilter = state.filters[action.payload.filterIdx]
|
||||
const previousIsValid = isValidFilter(updatedFilter)
|
||||
const afterIsValid = isValidFilter(action.payload.value)
|
||||
const newState: any = {
|
||||
...state,
|
||||
filters: update(state.filters, {
|
||||
[action.payload.filterIdx]: { $set: action.payload.value },
|
||||
}),
|
||||
}
|
||||
if (previousIsValid || afterIsValid) {
|
||||
newState.page = 1
|
||||
newState.refreshPageFlag = Date.now()
|
||||
newState.totalRows = TOTAL_ROWS_RESET
|
||||
}
|
||||
return newState
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default FilterReducer
|
||||
|
||||
function isValidFilter(filter: Filter) {
|
||||
return filter && filter.column && filter.column != '' && filter.value && filter.value != ''
|
||||
}
|
||||
@@ -1,24 +1,13 @@
|
||||
import BaseReducer, { BaseInitialState, baseInitialState } from './base';
|
||||
import CallbackReducer, {
|
||||
CallbackInitialState,
|
||||
callbackInitialState,
|
||||
} from './callback';
|
||||
import ColumnReducer, {
|
||||
ColumnInitialState,
|
||||
columnInitialState,
|
||||
} from './column';
|
||||
import RowReducer, { RowInitialState, rowInitialState } from './row';
|
||||
import SortReducer, { SortInitialState, sortInitialState } from './sort';
|
||||
import FilterReducer, {
|
||||
FilterInitialState,
|
||||
filterInitialState,
|
||||
} from './filter';
|
||||
import BaseReducer, { BaseInitialState, baseInitialState } from './base'
|
||||
import CallbackReducer, { CallbackInitialState, callbackInitialState } from './callback'
|
||||
import ColumnReducer, { ColumnInitialState, columnInitialState } from './column'
|
||||
import RowReducer, { RowInitialState, rowInitialState } from './row'
|
||||
import SortReducer, { SortInitialState, sortInitialState } from './sort'
|
||||
|
||||
export interface InitialStateType
|
||||
extends BaseInitialState,
|
||||
CallbackInitialState,
|
||||
ColumnInitialState,
|
||||
FilterInitialState,
|
||||
RowInitialState,
|
||||
SortInitialState {}
|
||||
|
||||
@@ -26,16 +15,8 @@ export const initialState = {
|
||||
...baseInitialState,
|
||||
...callbackInitialState,
|
||||
...columnInitialState,
|
||||
...filterInitialState,
|
||||
...rowInitialState,
|
||||
...sortInitialState,
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
BaseReducer,
|
||||
CallbackReducer,
|
||||
ColumnReducer,
|
||||
FilterReducer,
|
||||
RowReducer,
|
||||
SortReducer,
|
||||
};
|
||||
export { BaseReducer, CallbackReducer, ColumnReducer, RowReducer, SortReducer }
|
||||
@@ -33,8 +33,8 @@ export interface Filter {
|
||||
|
||||
export interface SavedState {
|
||||
filters: Filter[]
|
||||
gridColumns: CalculatedColumn<any, any>[]
|
||||
sorts: Sort[]
|
||||
gridColumns: CalculatedColumn<any, any>[]
|
||||
}
|
||||
|
||||
export interface DragItem {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { Filter, SavedState, SupaTable } from '../types'
|
||||
|
||||
// [JOSHEN TODO] Deprecated but double check
|
||||
export function getInitialFilters(table: SupaTable, savedState?: SavedState): Filter[] {
|
||||
if (savedState?.filters) {
|
||||
// verify column still exists
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import AwesomeDebouncePromise from 'awesome-debounce-promise'
|
||||
import { InitialStateType } from '../store/reducers'
|
||||
import { formatSortURLParams, formatFilterURLParams } from '../SupabaseGrid.utils'
|
||||
|
||||
export async function fetchCount(state: InitialStateType, dispatch: (value: unknown) => void) {
|
||||
export async function fetchCount(
|
||||
state: InitialStateType,
|
||||
dispatch: (value: unknown) => void,
|
||||
filter?: string[] // Come directly from URL param
|
||||
) {
|
||||
if (!state.rowService) return
|
||||
const { data, error } = await state.rowService.count(state.filters)
|
||||
const filters = formatFilterURLParams(filter)
|
||||
const { data, error } = await state.rowService.count(filters)
|
||||
if (error) {
|
||||
// TODO: handle fetch rows count error
|
||||
} else {
|
||||
@@ -14,18 +20,22 @@ export async function fetchCount(state: InitialStateType, dispatch: (value: unkn
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPage(state: InitialStateType, dispatch: (value: unknown) => void) {
|
||||
export async function fetchPage(
|
||||
state: InitialStateType,
|
||||
dispatch: (value: unknown) => void,
|
||||
sort?: string[], // Come directly from URL param
|
||||
filter?: string[] // Come directly from URL param
|
||||
) {
|
||||
if (!state.rowService) return
|
||||
dispatch({ type: 'SET_IS_LOADING', payload: { isLoading: true } })
|
||||
console.log('Fetch page', {
|
||||
filters: state.filters,
|
||||
sorts: state.sorts,
|
||||
})
|
||||
|
||||
const sorts = formatSortURLParams(sort)
|
||||
const filters = formatFilterURLParams(filter)
|
||||
const { data, error } = await state.rowService.fetchPage(
|
||||
state.page,
|
||||
state.rowsPerPage,
|
||||
state.filters,
|
||||
state.sorts
|
||||
filters,
|
||||
sorts
|
||||
)
|
||||
if (error) {
|
||||
// TODO: handle fetch rows data error
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
type UrlStateParams = {
|
||||
export type UrlStateParams = {
|
||||
[k: string]: string | string[] | undefined
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user