diff --git a/strudel-taskflows/src/components/FilterContext.tsx b/strudel-taskflows/src/components/FilterContext.tsx index 2f790884..4623155d 100644 --- a/strudel-taskflows/src/components/FilterContext.tsx +++ b/strudel-taskflows/src/components/FilterContext.tsx @@ -12,7 +12,11 @@ export interface FilterState { expandedGroup: string | number | boolean; } -const FilterContextAPI = React.createContext<{state: FilterState; dispatch: React.Dispatch} | undefined>(undefined); +const FilterContextAPI = React.createContext<{ + activeFilters: FilterState['activeFilters']; + expandedGroup: FilterState['expandedGroup']; + dispatch: React.Dispatch +} | undefined>(undefined); const initialState: FilterState = { activeFilters: [], @@ -39,6 +43,7 @@ function filterReducer(state: FilterState, action: FilterAction): FilterState { } else if (filter.value) { activeFilters.push(filter); } + console.log(activeFilters); return { ...state, activeFilters @@ -77,7 +82,11 @@ export const FilterContext: React.FC = ({ children }) => { const [state, dispatch] = useReducer(filterReducer, { ...initialState, activeFilters }); - const value = { state, dispatch }; + const value = { + activeFilters: state.activeFilters, + expandedGroup: state.expandedGroup, + dispatch + }; /** * Emit a change event when state.activeFilters changes @@ -90,9 +99,9 @@ export const FilterContext: React.FC = ({ * If activeFilters is changed from outside the context (e.g. filters are reset) * then the new value should be dispatched. */ - useEffect(() => { - dispatch({ type: 'SET_ACTIVE_FILTERS', payload: activeFilters }); - }, [activeFilters]); + // useEffect(() => { + // dispatch({ type: 'SET_ACTIVE_FILTERS', payload: activeFilters }); + // }, [activeFilters]); return ( diff --git a/strudel-taskflows/src/components/FilterField.tsx b/strudel-taskflows/src/components/FilterField.tsx index af6780aa..16959547 100644 --- a/strudel-taskflows/src/components/FilterField.tsx +++ b/strudel-taskflows/src/components/FilterField.tsx @@ -56,9 +56,9 @@ export const FilterField: React.FC = ({ filterProps, ...rest }) => { - const { state, dispatch } = useFilters(); + const { activeFilters, dispatch } = useFilters(); const [value, setValue] = useState>(null); - const currentFilter = state.activeFilters.find((filter) => filter.field === field); + const currentFilter = activeFilters.find((filter) => filter.field === field); const isActive = hasValue(currentFilter?.value); /** @@ -133,7 +133,7 @@ export const FilterField: React.FC = ({ ); } case 'DateRange': { - const currentDateRange = state.activeFilters.find((filter) => filter.field === filter.field)?.value; + const currentDateRange = activeFilters.find((filter) => filter.field === filter.field)?.value; const hasValue = currentDateRange && Array.isArray(currentDateRange) && currentDateRange.length === 2; const currentMin = hasValue && Array.isArray(currentDateRange) ? currentDateRange[0] : null; const currentMax = hasValue && Array.isArray(currentDateRange) ? currentDateRange[1] : null; @@ -202,7 +202,7 @@ export const FilterField: React.FC = ({ } else if (hasValue(value)) { handleCancelFilter(); } - },[JSON.stringify(state.activeFilters)]); + },[JSON.stringify(activeFilters)]); return ( { @@ -26,7 +26,7 @@ export const Filters: React.FC = ({ children, ...rest }) => { - const [activeFilters, setActiveFilters] = useState([]); + const { activeFilters, dispatch } = useFilters(); /** * Count the number of active filters in this group by using @@ -44,74 +44,64 @@ export const Filters: React.FC = ({ } }) - const handleChange = (filters: FilterState['activeFilters']) => { - setActiveFilters(filters); - } - const handleReset = () => { - setActiveFilters([]); + dispatch({ type: 'SET_ACTIVE_FILTERS', payload: []}) } - useEffect(() => { - if (onChange) onChange(activeFilters); - }, [activeFilters]) - return ( - - - - {header && ( - + + {header && ( + + + {header} + {activeChildren > 0 && ( + + )} + + - - - )} - {grouped && ( - - {children} - - )} - {!grouped && ( - - {children} - - )} - - - + Reset + + + + )} + {grouped && ( + + {children} + + )} + {!grouped && ( + + {children} + + )} + + ) } \ No newline at end of file diff --git a/strudel-taskflows/src/pages/explore-data/[id].tsx b/strudel-taskflows/src/pages/explore-data/[id].tsx index 0bd31dcf..0fbe0c16 100644 --- a/strudel-taskflows/src/pages/explore-data/[id].tsx +++ b/strudel-taskflows/src/pages/explore-data/[id].tsx @@ -2,7 +2,7 @@ import { Box, Container, Paper, Stack, Typography } from '@mui/material'; import React from 'react'; import { useParams } from 'react-router-dom'; import { PageHeader } from '../../components/PageHeader'; -import { useExploreData } from './_context/ContextProvider'; +import { taskflow } from './_config/taskflow.config'; /** * Work in Progress: @@ -10,16 +10,16 @@ import { useExploreData } from './_context/ContextProvider'; * Detail view for a selected row from the` ` in the explore-data Task Flow. */ const DataDetailPage: React.FC = () => { - const { state } = useExploreData(); const params = useParams(); - const entity = state.data?.find((d) => { + const dataIdField = taskflow.data.items.idField; + const columns = taskflow.pages.index.tableColumns; + const data: any[] = []; + const entity = data?.find((d) => { if (params.id) { - return d[state.dataIdField].toString() === params.id.toString(); + return d[dataIdField].toString() === params.id.toString(); } }); - console.log(state); - console.log(entity); - const entityTitle = entity ? entity[state.columns[0].field] : 'Not Found'; + const entityTitle = entity ? entity[columns[0].field] : 'Not Found'; /** * Content to render on the page for this component @@ -43,10 +43,10 @@ const DataDetailPage: React.FC = () => { > - {state.columns[1].field} + {columns[1].field} - {entity && entity[state.columns[1].field]} + {entity && entity[columns[1].field]} diff --git a/strudel-taskflows/src/pages/explore-data/_components/DataView.tsx b/strudel-taskflows/src/pages/explore-data/_components/DataView.tsx index a135cf6f..0f2f7f62 100644 --- a/strudel-taskflows/src/pages/explore-data/_components/DataView.tsx +++ b/strudel-taskflows/src/pages/explore-data/_components/DataView.tsx @@ -6,18 +6,23 @@ import { SciDataGrid } from '../../../components/SciDataGrid'; import { filterData } from '../../../utils/filters.utils'; import { createFilterParams } from '../../../utils/queryParams.utils'; import { taskflow } from '../_config/taskflow.config'; -import { useExploreData } from '../_context/ContextProvider'; -import { setPreviewItem } from '../_context/actions'; +import { useFilters } from '../../../components/FilterContext'; +interface DataViewProps { + searchTerm: string; + setPreviewItem: React.Dispatch>; +} /** * Query the data rows and render as an interactive table */ -export const DataView: React.FC = () => { - const { state, dispatch } = useExploreData(); +export const DataView: React.FC = ({ searchTerm, setPreviewItem }) => { + const { activeFilters } = useFilters(); const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(25); const [offset, setOffest] = useState(page * pageSize); const dataSource = taskflow.data.items.source; + const dataIdField = taskflow.data.items.idField; + const columns = taskflow.pages.index.tableColumns; const filterConfigs = taskflow.pages.index.tableFilters; const queryMode = taskflow.data.items.queryMode; const staticParams = taskflow.data.items.staticParams; @@ -26,7 +31,7 @@ export const DataView: React.FC = () => { queryParams = { limit: pageSize.toString(), offset: offset.toString(), - ...createFilterParams(state.activeFilters, state.filters) + ...createFilterParams(activeFilters, filterConfigs) } } const queryString = new URLSearchParams(queryParams).toString() @@ -42,7 +47,7 @@ export const DataView: React.FC = () => { }); const handleRowClick = (rowData: any) => { - dispatch(setPreviewItem(rowData.row)); + setPreviewItem(rowData.row); }; const handlePaginationModelChange = (model: GridPaginationModel) => { @@ -88,13 +93,13 @@ export const DataView: React.FC = () => { )} row[state.dataIdField]} - columns={state.columns} + getRowId={(row) => row[dataIdField]} + columns={columns} disableColumnSelector autoHeight initialState={{ diff --git a/strudel-taskflows/src/pages/explore-data/_components/DataViewHeader.tsx b/strudel-taskflows/src/pages/explore-data/_components/DataViewHeader.tsx index f185291d..8f3cab6a 100644 --- a/strudel-taskflows/src/pages/explore-data/_components/DataViewHeader.tsx +++ b/strudel-taskflows/src/pages/explore-data/_components/DataViewHeader.tsx @@ -1,10 +1,10 @@ import FilterListIcon from '@mui/icons-material/FilterList'; import { Button, Stack, TextField, Typography } from '@mui/material'; import React from 'react'; -import { useExploreData } from '../_context/ContextProvider'; -import { setSearch } from '../_context/actions'; interface DataViewHeaderProps { + searchTerm: string; + setSearchTerm: React.Dispatch>; onToggleFiltersPanel: () => void; } @@ -12,12 +12,12 @@ interface DataViewHeaderProps { * Data table header section with filters button and search bar */ export const DataViewHeader: React.FC = ({ + searchTerm, + setSearchTerm, onToggleFiltersPanel, }) => { - const { dispatch } = useExploreData(); - const handleSearch: React.ChangeEventHandler = (evt) => { - dispatch(setSearch(evt.target.value)); + setSearchTerm(evt.target.value); }; return ( @@ -40,6 +40,7 @@ export const DataViewHeader: React.FC = ({ variant="outlined" label="Search" size="small" + value={searchTerm} onChange={handleSearch} /> diff --git a/strudel-taskflows/src/pages/explore-data/_components/FiltersPanel.tsx b/strudel-taskflows/src/pages/explore-data/_components/FiltersPanel.tsx index 35174448..1dde22ed 100644 --- a/strudel-taskflows/src/pages/explore-data/_components/FiltersPanel.tsx +++ b/strudel-taskflows/src/pages/explore-data/_components/FiltersPanel.tsx @@ -1,15 +1,8 @@ -import { Box, Stack } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers'; import React from 'react'; -import { CheckboxList } from '../../../components/CheckboxList'; -import { StrudelSlider } from '../../../components/StrudelSlider'; -import { DataFilter, FilterConfig, FilterOperator } from '../../../types/filters.types'; -import { useExploreData } from '../_context/ContextProvider'; -import { setActiveFilters, setFilter } from '../_context/actions'; -import { taskflow } from '../_config/taskflow.config'; -import { Filters } from '../../../components/Filters'; +import { FilterState, useFilters } from '../../../components/FilterContext'; import { FilterField } from '../../../components/FilterField'; -import { FilterState } from '../../../components/FilterContext'; +import { Filters } from '../../../components/Filters'; +import { taskflow } from '../_config/taskflow.config'; interface FiltersPanelProps { onClose: () => any @@ -21,11 +14,12 @@ interface FiltersPanelProps { * The input values will filter data in the main table. */ export const FiltersPanel: React.FC = (props) => { - const {state, dispatch} = useExploreData(); + const { dispatch: filterDispatch } = useFilters(); - const handleFiltersChange = (filters: FilterState["activeFilters"]) => { - dispatch(setActiveFilters(filters as DataFilter[])); - } + // const handleFiltersChange = (filters: FilterState["activeFilters"]) => { + // filterDispatch({ type: 'SET_ACTIVE_FILTERS', payload: filters }); + // // setActiveFilters(filters as DataFilter[]) + // } /** * Content to render on the page for this component @@ -34,7 +28,7 @@ export const FiltersPanel: React.FC = (props) => { any + previewItem: any; + onClose: () => void; } /** * Panel to show extra information about a row in a separate panel * next to the ``. */ -export const PreviewPanel: React.FC = (props) => { - const { state } = useExploreData(); +export const PreviewPanel: React.FC = ({ previewItem, onClose }) => { + const columns = taskflow.pages.index.tableColumns; + const dataIdField = taskflow.data.items.idField; /** * Content to render on the page for this component @@ -32,11 +34,11 @@ export const PreviewPanel: React.FC = (props) => { - - {state.previewItem[state.columns[0].field]} + + {previewItem[columns[0].field]} - + Row description, subtitle, or helper text. @@ -71,7 +73,7 @@ export const PreviewPanel: React.FC = (props) => { /> - + diff --git a/strudel-taskflows/src/pages/explore-data/_context/ContextProvider.tsx b/strudel-taskflows/src/pages/explore-data/_context/ContextProvider.tsx deleted file mode 100644 index d28f8b5a..00000000 --- a/strudel-taskflows/src/pages/explore-data/_context/ContextProvider.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { useContext, useEffect } from 'react'; -import { ExploreDataAction, ExploreDataActionType, setData, setFilteredData } from './actions'; -import { DataFilter, FilterConfig } from '../../../types/filters.types'; -import { filterData } from '../../../utils/filters.utils'; - -export interface ExploreDataState { - columns: any[]; - count?: number; - data?: any[]; - dataIdField: string; - filters: FilterConfig[]; - filteredData?: any[]; - activeFilters: DataFilter[]; - filterValues?: any; - previewItem?: any; - searchTerm?: string; - showFiltersPanel?: boolean; - tablePage: number, - tablePageSize: number -} - -/** - * ExploreDataProvider props are the same as the State except - * some of the required props in the State are optional props. - * These props have default values set in the initialState object. - */ -// interface ExploreDataProviderProps extends Omit { -interface ExploreDataProviderProps extends Partial { - activeFilters?: DataFilter[]; - columns?: any[]; - tablePage?: number; - tablePageSize?: number; - children: React.ReactNode; -} - -const ExploreDataContext = React.createContext<{state: ExploreDataState; dispatch: React.Dispatch} | undefined>(undefined); - -const initialState: ExploreDataState = { - data: [], - columns: [], - filters: [], - filterValues: {}, - activeFilters: [], - dataIdField: 'id', - tablePage: 0, - tablePageSize: 25 -} - -const initState = (initialState: ExploreDataState, props: ExploreDataProviderProps) => { - const {children, ...rest} = props; - return { - ...initialState, - ...rest - } -}; - -function exploreDataReducer(state: ExploreDataState, action: ExploreDataAction): ExploreDataState { - switch (action.type) { - case ExploreDataActionType.SET_DATA: { - return { - ...state, - data: action.payload - } - } - case ExploreDataActionType.SET_SEARCH: { - return { - ...state, - searchTerm: action.payload - } - } - case ExploreDataActionType.SET_FILTERED_DATA: { - return { - ...state, - filteredData: action.payload - } - } - case ExploreDataActionType.SET_FILTER: { - console.log(action); - const filter = action.payload; - const existingIndex = state.activeFilters.findIndex((f) => f.field === filter.field); - const activeFilters = [...state.activeFilters]; - if (existingIndex > -1) { - if (filter.value) { - activeFilters[existingIndex] = filter; - } else { - activeFilters.splice(existingIndex, 1); - } - } else if (filter.value) { - activeFilters.push(filter); - } - return { - ...state, - activeFilters - } - } - case ExploreDataActionType.SET_ACTIVE_FILTERS: { - return { - ...state, - activeFilters: action.payload - } - } - case ExploreDataActionType.SET_PREVIEW_ITEM: { - return { - ...state, - previewItem: action.payload - } - } - default: { - throw new Error(`Unhandled action type: ${action.type}`) - } - } -} - -export const ExploreDataProvider: React.FC = (props) => { - const [state, dispatch] = React.useReducer(exploreDataReducer, initState(initialState, props)); - const value = { state, dispatch }; - - useEffect(() => { - console.log(props.data); - dispatch(setData(props.data)); - }, [props.data]); - - useEffect(() => { - if (state.data) { - const filteredData = filterData(state.data, state.activeFilters, state.searchTerm); - dispatch(setFilteredData(filteredData)); - } - }, [state.data, state.searchTerm, JSON.stringify(state.activeFilters)]); - - return ( - - {props.children} - - ) -} - -export const useExploreData = () => { - const context = useContext(ExploreDataContext) - if (context === undefined) { - throw new Error('useExploreData must be used within an ExploreDataProvider') - } - return context -} \ No newline at end of file diff --git a/strudel-taskflows/src/pages/explore-data/_context/actions.ts b/strudel-taskflows/src/pages/explore-data/_context/actions.ts deleted file mode 100644 index d7e3f381..00000000 --- a/strudel-taskflows/src/pages/explore-data/_context/actions.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { DataFilter } from '../../../types/filters.types'; -import { ExploreDataState } from './ContextProvider'; - -export enum ExploreDataActionType { - SET_DATA = 'SET_DATA', - SET_SEARCH = 'SET_SEARCH', - SET_FILTERED_DATA = 'SET_FILTERED_DATA', - SET_FILTER = 'SET_FILTER', - SET_ACTIVE_FILTERS = 'SET_ACTIVE_FILTERS', - SET_PREVIEW_ITEM = 'SET_PREVIEW_ITEM' -} - -export interface ExploreDataAction { - type: ExploreDataActionType; - payload?: any; -} - -export const setData = (data: ExploreDataState['data']): ExploreDataAction => ({ - type: ExploreDataActionType.SET_DATA, - payload: data, -}); - -export const setSearch = (searchTerm: ExploreDataState['searchTerm']): ExploreDataAction => ({ - type: ExploreDataActionType.SET_SEARCH, - payload: searchTerm, -}); - -export const setFilteredData = (data: ExploreDataState['filteredData']): ExploreDataAction => ({ - type: ExploreDataActionType.SET_FILTERED_DATA, - payload: data, -}); - -export const setFilter = (filter: DataFilter): ExploreDataAction => ({ - type: ExploreDataActionType.SET_FILTER, - payload: filter, -}); - -export const setActiveFilters = (activeFilters: ExploreDataState['activeFilters']): ExploreDataAction => ({ - type: ExploreDataActionType.SET_ACTIVE_FILTERS, - payload: activeFilters, -}); - -export const setPreviewItem = (rowItem: ExploreDataState['previewItem']): ExploreDataAction => ({ - type: ExploreDataActionType.SET_PREVIEW_ITEM, - payload: rowItem, -}); \ No newline at end of file diff --git a/strudel-taskflows/src/pages/explore-data/_layout.tsx b/strudel-taskflows/src/pages/explore-data/_layout.tsx index bedeb52e..38123eef 100644 --- a/strudel-taskflows/src/pages/explore-data/_layout.tsx +++ b/strudel-taskflows/src/pages/explore-data/_layout.tsx @@ -1,35 +1,20 @@ -import React from 'react'; import { Box } from '@mui/material'; +import React from 'react'; import { Outlet } from 'react-router'; -import { ExploreDataProvider } from './_context/ContextProvider'; import { TopBar } from '../../components/TopBar'; -import { useDataFromSource } from '../../utils/useDataFromSource'; -import { taskflow } from './_config/taskflow.config'; /** * Top-level wrapper for the explore-data Task Flow templates. * Inner pages are rendered inside the `` component */ const ExploreDataLayout: React.FC = () => { - // const entities = useDataFromSource(taskflow.data.items.source); - - /** - * Content to render on the page for this component - */ return ( - - - + ) diff --git a/strudel-taskflows/src/pages/explore-data/index.tsx b/strudel-taskflows/src/pages/explore-data/index.tsx index 9e5cee8a..d9a2dc0f 100644 --- a/strudel-taskflows/src/pages/explore-data/index.tsx +++ b/strudel-taskflows/src/pages/explore-data/index.tsx @@ -1,13 +1,12 @@ import { Box, Grid, Paper } from '@mui/material'; import React, { useState } from 'react'; -import { FiltersPanel } from './_components/FiltersPanel'; -import { PreviewPanel } from './_components/PreviewPanel'; +import { FilterContext } from '../../components/FilterContext'; import { PageHeader } from '../../components/PageHeader'; -import { useExploreData } from './_context/ContextProvider'; -import { setPreviewItem } from './_context/actions'; -import { taskflow } from './_config/taskflow.config'; import { DataView } from './_components/DataView'; import { DataViewHeader } from './_components/DataViewHeader'; +import { FiltersPanel } from './_components/FiltersPanel'; +import { PreviewPanel } from './_components/PreviewPanel'; +import { taskflow } from './_config/taskflow.config'; /** * Main explorer page in the explore-data Task Flow. @@ -15,7 +14,8 @@ import { DataViewHeader } from './_components/DataViewHeader'; * main table, and the table row preview panel. */ const DataExplorer: React.FC = () => { - const {state, dispatch} = useExploreData(); + const [searchTerm, setSearchTerm] = useState(''); + const [previewItem, setPreviewItem] = useState(); const [showFiltersPanel, setShowFiltersPanel] = useState(true); const handleCloseFilters = () => { @@ -27,43 +27,52 @@ const DataExplorer: React.FC = () => { } const handleClosePreview = () => { - dispatch(setPreviewItem(null)); + setPreviewItem(null); } return ( - - - - {showFiltersPanel && ( - - + + + + + {showFiltersPanel && ( + + + + )} + + + + + - )} - - - - - + {previewItem && ( + + + + )} - {state.previewItem && ( - - - - )} - - + + ) } diff --git a/strudel-taskflows/src/utils/filters.utils.ts b/strudel-taskflows/src/utils/filters.utils.ts index 64c2060b..e9944394 100644 --- a/strudel-taskflows/src/utils/filters.utils.ts +++ b/strudel-taskflows/src/utils/filters.utils.ts @@ -106,6 +106,7 @@ export const filterByDataFilters = (allData: any[], filters: DataFilter[], filte }; export const filterData = (allData: any[], filters: DataFilter[], filterConfigs: FilterConfig[], searchText?: string) => { + console.log(searchText); const filteredByText = filterBySearchText(allData, searchText); const filteredByTextAndDataFilters = filterByDataFilters(filteredByText, filters, filterConfigs); return filteredByTextAndDataFilters;