diff --git a/app/components/p8p_data_grid_hooks.js b/app/components/p8p_data_grid_hooks.js index bab4104..c7d5ad7 100644 --- a/app/components/p8p_data_grid_hooks.js +++ b/app/components/p8p_data_grid_hooks.js @@ -7,9 +7,10 @@ //Подключение библиотек //--------------------- -import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React +import { useReducer, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером import { object2Base64XML } from "../core/utils"; //Вспомогательные функции +import { DG_AT, INITIAL_STATE, dataGridReducer } from "./p8p_data_grid_reducer"; //Редьюсер состояния //--------- //Константы @@ -38,38 +39,39 @@ const useP8PDataGrid = ({ initFilters = [], initOrders = [], storedArgs = {}, + resetPageNumberOnStoredArgsChange = true, executeStoredArgs = {}, + resetPageNumberOnExecuteStoredArgsChange = true, allowDataLoad = () => true }) => { - //Собственное состояние - таблица данных - const [dataGrid, setDataGrid] = useState({ - columnsDef: [], - groups: [], - rows: [], - filters: Array.isArray(initFilters) ? [...initFilters] : [], - orders: Array.isArray(initOrders) ? [...initOrders] : [], - pageNumber: 1, - pagesAlign: null, - pagesPosition: null, - pagesCount: 0, - fixedColumns: 0, - fixedHeader: false, - morePages: true - }); + //Подключим редьюсер состояния + const [state, dispatch] = useReducer(dataGridReducer, INITIAL_STATE({ initFilters, initOrders })); - //Собственное состояние - признак загрузки данных - const [isDataLoaded, setIsDataLoaded] = useState(false); + //Установка даных таблицы + const setDataGrid = (dataGridData, pageSize, isError) => dispatch({ type: DG_AT.SET_DATA_GRID, payload: { dataGridData, pageSize, isError } }); - //Собственное состояние - флаг загрузки - const [isLoading, setLoading] = useState(false); + //Установка фильтра таблицы + const setDataGridFilter = filters => dispatch({ type: DG_AT.SET_DATA_GRID_FILTER, payload: filters }); - //Собственное состояние - необходимость обновления данных - const [reload, setReload] = useState(true); + //Установка сортировок таблицы + const setDataGridOrder = orders => dispatch({ type: DG_AT.SET_DATA_GRID_ORDER, payload: orders }); - //Собственное состояние - дополнительные параметры процедуры + //Установка страницы таблицы + const setDataGridPageNumber = pageNumber => dispatch({ type: DG_AT.SET_DATA_GRID_PAGE_NUMBER, payload: pageNumber }); + + //Установка флага загруженности данных + const setIsDataLoaded = isDataLoaded => dispatch({ type: DG_AT.SET_IS_DATA_LOADED, payload: isDataLoaded }); + + //Установка флага активности процесса загрузки данных + const setIsLoading = isLoading => dispatch({ type: DG_AT.SET_IS_LOADING, payload: isLoading }); + + //Установка флага необходимости обновления данных + const setReload = (reload, resetPageNumber = false) => dispatch({ type: DG_AT.SET_RELOAD, payload: { reload, resetPageNumber } }); + + //Ссылка на актуальные параметры хранимой процедуры const refStoredArgs = useRef(storedArgs); - //Собственное состояние - дополнительные параметры вызова процедуры + //Ссылка на актуальные параметры исполнения хранимой процедуры const refExecuteStoredArgs = useRef(executeStoredArgs); //Признак допустимости обновления данных @@ -83,52 +85,40 @@ const useP8PDataGrid = ({ //Загрузка данных таблицы с сервера const loadData = useCallback(async () => { try { - setLoading(true); + //Начинаем загрузку + setIsLoading(true); const data = await executeStored({ stored, args: { - CFILTERS: { VALUE: object2Base64XML(dataGrid.filters, { arrayNodeName: filtersNodeName }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - CORDERS: { VALUE: object2Base64XML(dataGrid.orders, { arrayNodeName: ordersNodeName }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - NPAGE_NUMBER: dataGrid.pageNumber, + CFILTERS: { + VALUE: object2Base64XML(state.dataGrid.filters, { arrayNodeName: filtersNodeName }), + SDATA_TYPE: SERV_DATA_TYPE_CLOB + }, + CORDERS: { VALUE: object2Base64XML(state.dataGrid.orders, { arrayNodeName: ordersNodeName }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + NPAGE_NUMBER: state.dataGrid.pageNumber, NPAGE_SIZE: pageSize, - NINCLUDE_DEF: reloadDef ? 1 : dataGrid.dataLoaded ? 0 : 1, + NINCLUDE_DEF: reloadDef ? 1 : state.isDataLoaded ? 0 : 1, ...refStoredArgs.current }, respArg, ...refExecuteStoredArgs.current }); - setDataGrid(pv => ({ - ...pv, - ...data[contentNodeName], - columnsDef: data[contentNodeName].columnsDef ? [...data[contentNodeName].columnsDef] : pv.columnsDef || [], - rows: - data[contentNodeName].pagesCount > 0 || pv.pageNumber == 1 - ? [...(data[contentNodeName].rows || [])] - : [...(pv.rows || []), ...(data[contentNodeName].rows || [])], - groups: data[contentNodeName].groups - ? data[contentNodeName].pagesCount > 0 || pv.pageNumber == 1 - ? [...(data[contentNodeName].groups || [])] - : [...(pv.groups || []), ...data[contentNodeName].groups.filter(g => !pv.groups.find(pg => pg.name == g.name))] - : [...(pv.groups || [])], - morePages: data[contentNodeName].morePages && (data[contentNodeName].rows || []).length >= pageSize - })); - //Устанавливаем признак загрузки данных с учетом возможных ошибок - setIsDataLoaded(!isRespErr(data)); + //Устанавливаем полученные данные и признак загрузки данных с учетом возможных ошибок + setDataGrid(data[contentNodeName], pageSize, isRespErr(data)); } catch (e) { //Если произошла ошибка - данные не загружены setIsDataLoaded(false); } finally { //Сбрасываем признаки загрузки и перезагрузки данных - setLoading(false); - setReload(false); + setIsLoading(false); } }, [ SERV_DATA_TYPE_CLOB, contentNodeName, - dataGrid.dataLoaded, - dataGrid.filters, - dataGrid.orders, - dataGrid.pageNumber, + state.isDataLoaded, + state.dataGrid.filters, + state.dataGrid.orders, + state.dataGrid.pageNumber, executeStored, filtersNodeName, isRespErr, @@ -140,48 +130,30 @@ const useP8PDataGrid = ({ ]); //При изменении состояния фильтра - const handleFilterChanged = useCallback(({ filters }) => { - setDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1 })); - setReload(true); - }, []); + const handleFilterChanged = useCallback(({ filters }) => setDataGridFilter(filters), []); //При изменении состояния сортировки - const handleOrderChanged = useCallback(({ orders }) => { - setDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1 })); - setReload(true); - }, []); + const handleOrderChanged = useCallback(({ orders }) => setDataGridOrder(orders), []); //При изменении количества отображаемых страниц - const handlePagesCountChanged = useCallback(() => { - setDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 })); - setReload(true); - }, []); + const handlePagesCountChanged = useCallback(() => setDataGridPageNumber(state.dataGrid.pageNumber + 1), [state.dataGrid.pageNumber]); //При изменении страницы отображения - const handlePageChange = useCallback(({ page }) => { - setDataGrid(pv => ({ ...pv, pageNumber: page })); - setReload(true); - }, []); + const handlePageChange = useCallback(({ page }) => setDataGridPageNumber(page), []); //При необходимости обновления таблицы const doReload = useCallback( - ({ returnOnFirstPage = false }) => { - //Если это не страничный вывод или установлен признак возврата на первую страницу - if (dataGrid.pagesCount <= 0 || returnOnFirstPage) { - setDataGrid(pv => ({ ...pv, pageNumber: 1 })); - } - setReload(true); - }, - [dataGrid.pagesCount] + ({ returnOnFirstPage = false }) => setReload(true, state.dataGrid.pagesCount <= 0 || returnOnFirstPage), + [state.dataGrid.pagesCount] ); //Проверка изменений параметров const isArgsChanged = useCallback( (currentArgs, args) => { //Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера) - return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args); + return !state.isLoading && JSON.stringify(currentArgs) != JSON.stringify(args); }, - [isLoading] + [state.isLoading] ); //При изменение дополнительных параметров процедуры @@ -191,9 +163,9 @@ const useP8PDataGrid = ({ //Устанавливаем новые дополнительные параметры refStoredArgs.current = storedArgs; //При изменении дополнительных параметров необходимо перезагрузить данные - setReload(true); + setReload(true, resetPageNumberOnStoredArgsChange); } - }, [storedArgs, isArgsChanged]); + }, [storedArgs, resetPageNumberOnStoredArgsChange, isArgsChanged]); //При изменение дополнительных параметров вызова процедуры useEffect(() => { @@ -202,19 +174,28 @@ const useP8PDataGrid = ({ //Устанавливаем новые дополнительные параметры refExecuteStoredArgs.current = executeStoredArgs; //При изменении дополнительных параметров необходимо перезагрузить данные - setReload(true); + setReload(true, resetPageNumberOnExecuteStoredArgsChange); } - }, [executeStoredArgs, isArgsChanged]); + }, [executeStoredArgs, resetPageNumberOnExecuteStoredArgsChange, isArgsChanged]); //При необходимости обновить данные таблицы useEffect(() => { - if (isAllowDataLoad && reload) { + if (isAllowDataLoad && state.reload) { loadData(); } - }, [isAllowDataLoad, reload, loadData]); + }, [isAllowDataLoad, state.reload, loadData]); //Возвращаем данные таблицы - return { dataGrid, isDataLoaded, isLoading, handleFilterChanged, handleOrderChanged, handlePagesCountChanged, handlePageChange, doReload }; + return { + dataGrid: state.dataGrid, + isDataLoaded: state.isDataLoaded, + isLoading: state.isLoading, + handleFilterChanged, + handleOrderChanged, + handlePagesCountChanged, + handlePageChange, + doReload + }; }; //---------------- diff --git a/app/components/p8p_data_grid_reducer.js b/app/components/p8p_data_grid_reducer.js new file mode 100644 index 0000000..3ac9182 --- /dev/null +++ b/app/components/p8p_data_grid_reducer.js @@ -0,0 +1,112 @@ +/* + Парус 8 - Панели мониторинга + Таблица данных - редьюсер состояния +*/ + +//--------- +//Константы +//--------- + +//Типы действий +const DG_AT = { + SET_DATA_GRID: "SET_DATA_GRID", //Установка даных таблицы + SET_DATA_GRID_FILTER: "SET_DATA_GRID_FILTER", //Установка фильтра таблицы + SET_DATA_GRID_ORDER: "SET_DATA_GRID_ORDER", //Установка сортировок таблицы + SET_DATA_GRID_PAGE_NUMBER: "SET_DATA_GRID_PAGE_NUMBER", //Установка страницы таблицы + SET_IS_DATA_LOADED: "SET_IS_DATA_LOADED", //Установка флага загруженности данных + SET_IS_LOADING: "SET_IS_LOADING", //Установка флага активности процесса загрузки данных + SET_RELOAD: "SET_RELOAD" //Установка флага необходимости обновления данных +}; + +//Состояние приложения по умолчанию +const INITIAL_STATE = ({ initFilters, initOrders }) => ({ + dataGrid: { + columnsDef: [], + groups: [], + rows: [], + filters: Array.isArray(initFilters) ? [...initFilters] : [], + orders: Array.isArray(initOrders) ? [...initOrders] : [], + pageNumber: 1, + pagesAlign: null, + pagesPosition: null, + pagesCount: 0, + fixedColumns: 0, + fixedHeader: false, + morePages: true + }, + isDataLoaded: false, + isLoading: false, + reload: true +}); + +//----------- +//Тело модуля +//----------- + +//Обработчики действий +const handlers = { + //Установка даных таблицы + [DG_AT.SET_DATA_GRID]: (state, { payload }) => { + const { dataGridData, pageSize, isError } = payload; + return { + ...state, + dataGrid: { + ...state.dataGrid, + ...dataGridData, + columnsDef: dataGridData.columnsDef ? [...dataGridData.columnsDef] : state.dataGrid.columnsDef || [], + rows: + dataGridData.pagesCount > 0 || state.dataGrid.pageNumber == 1 + ? [...(dataGridData.rows || [])] + : [...(state.dataGrid.rows || []), ...(dataGridData.rows || [])], + groups: dataGridData.groups + ? dataGridData.pagesCount > 0 || state.dataGrid.pageNumber == 1 + ? [...(dataGridData.groups || [])] + : [...(state.dataGrid.groups || []), ...dataGridData.groups.filter(g => !state.dataGrid.groups.find(pg => pg.name == g.name))] + : [...(state.dataGrid.groups || [])], + morePages: dataGridData.morePages && (dataGridData.rows || []).length >= pageSize + }, + isDataLoaded: isError === true ? false : true + }; + }, + //Установка фильтра таблицы + [DG_AT.SET_DATA_GRID_FILTER]: (state, { payload }) => ({ + ...state, + dataGrid: { ...state.dataGrid, filters: [...payload], pageNumber: 1 }, + reload: true + }), + //Установка сортировок таблицы + [DG_AT.SET_DATA_GRID_ORDER]: (state, { payload }) => ({ + ...state, + dataGrid: { ...state.dataGrid, orders: [...payload], pageNumber: 1 }, + reload: true + }), + //Установка страницы таблицы + [DG_AT.SET_DATA_GRID_PAGE_NUMBER]: (state, { payload }) => ({ ...state, dataGrid: { ...state.dataGrid, pageNumber: payload }, reload: true }), + //Установка флага загруженности данных + [DG_AT.SET_IS_DATA_LOADED]: (state, { payload }) => ({ ...state, isDataLoaded: payload }), + //Установка флага активности процесса загрузки данных + [DG_AT.SET_IS_LOADING]: (state, { payload }) => ({ ...state, isLoading: payload, reload: payload === false ? false : state.reload }), + //Установка флага необходимости обновления данных + [DG_AT.SET_RELOAD]: (state, { payload }) => ({ + ...state, + reload: payload.reload, + ...(payload.resetPageNumber ? { dataGrid: { ...state.dataGrid, pageNumber: 1 } } : {}) + }), + //Обработчик по умолчанию + DEFAULT: state => state +}; + +//---------------- +//Интерфейс модуля +//---------------- + +//Константы +export { DG_AT, INITIAL_STATE }; + +//Редьюсер состояния +export const dataGridReducer = (state, action) => { + //Подберём обработчик + const handle = handlers[action.type] || handlers.DEFAULT; + //Исполним его + return handle(state, action); +};