diff --git a/README.md b/README.md index 347ca20..06edffb 100644 --- a/README.md +++ b/README.md @@ -1459,6 +1459,55 @@ const MyPanel = () => { `PKG_P8PANELS_VISUAL.TDG_FILTERS_SET_QUERY` - процедура, вспомогательная утилита, производит вызов указанной серверной процедуры отбора с учётом переданных переменных окружения и значений в `RFILTERS`\ `PKG_P8PANELS_VISUAL.UTL_ROWS_LIMITS_CALC` - процедура, вспомогательная утилита, служит для конвертации номера страницы данных и размера страницы данных в границы диапазона строк выборки (как правило, клиентскому приложению удобнее прислать на сервер текущий номер страницы и её размер, в то время к в запросах, для выборки, удобнее применять границы диапазонов строк) +**Универсальный хук** + +Для компонента существует универсальный хук `useP8PDataGrid`, который помогает автоматизировать и скрыть логику получения/обновления данных для таблицы. Использование универсального хука представлено в примере использования `P8PDataGrid` ниже. + +Для использования универсального хука на панели его необходимо импортировать: + +``` +import {useP8PDataGrid} from "../../components/p8p_data_grid"; + +const MyPanel = () => { + const { dataGrid, isDataLoaded } = useP8PDataGrid({ stored: "ХРАНИМЫЙ_ОБЪЕКТ" }); + ... +} +``` + +**Входные параметры:**\ +`stored` - обязательный, строка, имя исполняемого хранимого объекта (для пакетных - "ПАКЕТ.ОБЪЕКТ")\ +`respArg` - необязательный, строка, имя выходного параметра исполняемого объекта, значение которого необходимо вернуть как данные ответа, по умолчанию - `COUT`\ +`contentNodeName` - необязательный, строка, имя узла, содержащего информацию о таблице, по умолчанию `XDATA_GRID`\ +`filtersNodeName` - необязательный, строка, имя узла, содержащего информацию о фильтрах, отправляемых на сервер, по умолчанию `filters`. При использовании в хранимом объекте стандартной функции `PKG_P8PANELS_VISUAL.TDG_FILTERS_FROM_XML` для десериализации данных важно, чтобы данный параметр имел значение `filters`\ +`ordersNodeName` - необязательный, строка, имя узла, содержащего информацию о сортировках, отправляемых на сервер, по умолчанию `orders`. При использовании в хранимом объекте стандартной функции `PKG_P8PANELS_VISUAL.TDG_ORDERS_FROM_XML` для десериализации данных важно, чтобы данный параметр имел значение `orders`\ +`pageSize` - необязательный, число, объем выборки данных на страницу или на догрузку, по умолчанию `10`. Передается в параметр `NPAGE_SIZE` хранимого объекта из переданного свойства `stored`\ +`reloadDef` - необязательный, булево, определяет необходимость включения описания колонок таблицы в ответ, по умолчанию `false`. Передается в параметр `NINCLUDE_DEF` хранимого объекта из переданного свойства `stored`. При значении `true` соответствующий параметр `NINCLUDE_DEF` будет всегда равен `1`, в ином случае при первом обращении к хранимому объекту передается `1`, а в последующих `0`\ +`initFilters` - необязательный, массив, начальное состояние фильтров таблицы, содержит объекты вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, from: <НАЧАЛО_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>, to: <ОКОНЧАНИЕ_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>}`\ +`initOrders` - необязательный, массив, начальное состояние сортировок таблицы, содержит объекты вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, direction: }`\ +`storedArgs` - необязательный, объект, описание параметров исполняемого хранимого объекта вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"|{VALUE: "ЗНАЧЕНИЕ", SDATA_TYPE: SERV_DATA_TYPE_*}}` (если тип данных параметров не указан явно - произойдёт попытка их автоматического определения, с CLOB-параметрами это не всегда может произойти корректно)\ +`executeStoredArgs` - необязательный, объект, описание параметров функции `executeStored` (исполняющей хранимый объект) вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"}`\ +`allowDataLoad` - необязательный, функция, определяет игнорирование исполнения хранимой процедуры. Должна возвращать булево. Если возвращает `false`, то исполнение хранимой процедуры, независимо от необходимости, полностью игнорируется + +**Результат:** объект для работы с компонентом `P8PDataGrid`. Возвращаемый объект имеет следующие свойства:\ +`dataGrid` - объект, хранит информацию о таблице, загруженной с помощью хранимой процедуры\ +`isDataLoaded` - булево, признак успешной загрузки и обработки данных из хранимой процедуры\ +`isLoading` - булево, признак выполнения загрузки данных из хранимой процедуры\ +`handleFilterChanged` - функция, изменение состояния фильтров таблицы. Сигнатура функции `f({filters})`, результат функции не интерпретируется\ +`handleOrderChanged` - функция, изменение состояния сортировок таблицы. Сигнатура функции `f({orders})`, результат функции не интерпретируется\ +`handlePagesCountChanged` - функция, изменение количества отображаемых страниц данных таблицы. Сигнатура функции `f()`, результат функции не интерпретируется\ +`handlePageChange` - функция, изменение номера отображаемой страницы таблицы. Сигнатура функции `f({page})`, результат функции не интерпретируется\ +`doReload` - функция, принудительное обновление данных таблицы. Сигнатура функции `f()`, результат функции не интерпретируется + +**Особенности хранимых объектов**\ +При использовании универсального хука для хранимой процедуры существуют параметры "по умолчанию", в которые отправляются соответствующие данные:\ +`CFILTERS` - необязательный, `clob`, xml-представление фильтров таблицы\ +`CORDERS` - необязательный, `clob`, xml-представление сортировок таблицы\ +`NPAGE_NUMBER` - необязательный, `number`, номер страницы\ +`NPAGE_SIZE` - необязательный, `number`, объем выборки данных на страницу или на догрузку, значение параметра `pageSize` универсального хука `useP8PDataGrid`\ +`NINCLUDE_DEF` - необязательный, `number`, признак включения описания колонок таблицы в ответ, зависит от параметра `reloadDef` универсального хука `useP8PDataGrid`. При `reloadDef` = `true` всегда передается `1`, в ином случае при первом вызове `1`, в остальных `0` + +Данные параметры не являются обязательными. При их отсутствии в хранимой процедуре они будут проигнорированы. + **Пример** Код на стороне сервера БД (хранимая процедура в клиентском пакете `PKG_P8PANELS_SAMPLES`): @@ -1880,6 +1929,35 @@ const MyPanel = () => { `PKG_P8PANELS_VISUAL.TCHART_ADD_DATASET` - процедура, добавляет переданное объектное представление серии данных в указанный объект описания графика\ `PKG_P8PANELS_VISUAL.TCHART_TO_XML` - функция, производит сериализацию объекта, описывающего график, в специальный XML-формат, корректно интерпретируемый клиентским компонентом `P8PChart` при передаче в WEB-приложение +**Универсальный хук** + +Для компонента существует универсальный хук `useP8PChart`, который помогает автоматизировать и скрыть логику получения/обновления данных для графика. Использование универсального хука представлено в примере использования `P8PChart` ниже. + +Для использования универсального хука на панели его необходимо импортировать: + +``` +import {useP8PChart} from "../../components/p8p_chart"; + +const MyPanel = () => { + const { chart, isDataLoaded } = useP8PChart({ stored: "ХРАНИМЫЙ_ОБЪЕКТ" }); + ... +} +``` + +**Входные параметры:**\ +`stored` - обязательный, строка, имя исполняемого хранимого объекта (для пакетных - "ПАКЕТ.ОБЪЕКТ")\ +`respArg` - необязательный, строка, имя выходного параметра исполняемого объекта, значение которого необходимо вернуть как данные ответа, по умолчанию - `COUT`\ +`contentNodeName` - необязательный, строка, имя узла, содержащего информацию о графике, по умолчанию `XCHART`\ +`storedArgs` - необязательный, объект, описание параметров исполняемого хранимого объекта вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"|{VALUE: "ЗНАЧЕНИЕ", SDATA_TYPE: SERV_DATA_TYPE_*}}` (если тип данных параметров не указан явно - произойдёт попытка их автоматического определения, с CLOB-параметрами это не всегда может произойти корректно)\ +`executeStoredArgs` - необязательный, объект, описание параметров функции `executeStored` (исполняющей хранимый объект) вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"}`\ +`allowDataLoad` - необязательный, функция, определяет игнорирование исполнения хранимой процедуры. Должна возвращать булево. Если возвращает `false`, то исполнение хранимой процедуры, независимо от необходимости, полностью игнорируется + +**Результат:** объект для работы с компонентом `P8PChart`. Возвращаемый объект имеет следующие свойства:\ +`chart` - объект, хранит информацию о графике, загруженном с помощью хранимой процедуры\ +`isDataLoaded` - булево, признак успешной загрузки и обработки данных из хранимой процедуры\ +`isLoading` - булево, признак выполнения загрузки данных из хранимой процедуры\ +`doReload` - функция, принудительное обновление данных графика. Сигнатура функции `f()`, результат функции не интерпретируется + **Пример** Код на стороне сервера БД (хранимая процедура в клиентском пакете `PKG_P8PANELS_SAMPLES`): @@ -1937,37 +2015,36 @@ const MyPanel = () => { Код панели на стороне клиента (WEB-приложения): ``` -import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React +import React, { useContext } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента import { Typography, Grid, Paper } from "@mui/material"; //Интерфейсные элементы -import { P8PChart } from "../../components/p8p_chart"; //График -import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { P8PChart, useP8PChart } from "../../components/p8p_chart"; //График import { ApplicationСtx } from "../../context/application"; //Контекст приложения +//--------- +//Константы +//--------- + //Стили const STYLES = { CONTAINER: { textAlign: "center", paddingTop: "20px" }, TITLE: { paddingBottom: "15px" }, - CHART: { minWidth: "80vw", maxHeight: "80vw", display: "flex", justifyContent: "center" }, + CHART: { minWidth: "65vw", maxHeight: "65vw", display: "flex", justifyContent: "center" }, CHART_PAPER: { padding: "25px" } }; +//----------- +//Тело модуля +//----------- + //Пример: Графики "P8PChart" const Chart = ({ title }) => { //Собственное состояние - график - const [chart, setChart] = useState({ loaded: false }); - - //Подключение к контексту взаимодействия с сервером - const { executeStored } = useContext(BackEndСtx); + const { chart, isDataLoaded } = useP8PChart({ stored: "PKG_P8PANELS_SAMPLES.CHART" }); //Подключение к контексту приложения const { pOnlineShowUnit } = useContext(ApplicationСtx); - //Загрузка данных графика с сервера - const loadChart = useCallback(async () => { - const chart = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.CHART", respArg: "COUT" }); - setChart(pv => ({ ...pv, loaded: true, ...chart.XCHART })); - }, [executeStored]); - //Отработка нажатия на график const handleChartClick = ({ item }) => { pOnlineShowUnit({ @@ -1976,12 +2053,6 @@ const Chart = ({ title }) => { }); }; - //При подключении к странице - useEffect(() => { - loadChart(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - //Генерация содержимого return (
@@ -1991,7 +2062,7 @@ const Chart = ({ title }) => { - {chart.loaded ? : null} + {isDataLoaded ? : null} @@ -2093,6 +2164,35 @@ const MyPanel = () => { `PKG_P8PANELS_VISUAL.TGANTT_ADD_TASK` - процедура, добавляет, к указанному объекту описания диаграммы Ганта, новую задачу, ранее описанную через `TGANTT_TASK_MAKE`\ `PKG_P8PANELS_VISUAL.TGANTT_TO_XML` - функция, производит сериализацию объекта, описывающего диаграмму Ганта, в специальный XML-формат, корректно интерпретируемый клиентским компонентом `P8PGantt` при передаче в WEB-приложение +**Универсальный хук** + +Для компонента существует универсальный хук `useP8PGantt`, который помогает автоматизировать и скрыть логику получения/обновления данных для диаграммы Ганта. Использование универсального хука представлено в примере использования `P8PGantt` ниже. + +Для использования универсального хука на панели его необходимо импортировать: + +``` +import { useP8PGantt } from "../../components/p8p_gantt"; + +const MyPanel = () => { + const { gantt, isDataLoaded } = useP8PGantt({ stored: "ХРАНИМЫЙ_ОБЪЕКТ" }); + ... +} +``` + +**Входные параметры:**\ +`stored` - обязательный, строка, имя исполняемого хранимого объекта (для пакетных - "ПАКЕТ.ОБЪЕКТ")\ +`respArg` - необязательный, строка, имя выходного параметра исполняемого объекта, значение которого необходимо вернуть как данные ответа, по умолчанию - `COUT`\ +`contentNodeName` - необязательный, строка, имя узла, содержащего информацию о диаграмме Ганта, по умолчанию `XGANTT`\ +`storedArgs` - необязательный, объект, описание параметров исполняемого хранимого объекта вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"|{VALUE: "ЗНАЧЕНИЕ", SDATA_TYPE: SERV_DATA_TYPE_*}}` (если тип данных параметров не указан явно - произойдёт попытка их автоматического определения, с CLOB-параметрами это не всегда может произойти корректно)\ +`executeStoredArgs` - необязательный, объект, описание параметров функции `executeStored` (исполняющей хранимый объект) вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"}`\ +`allowDataLoad` - необязательный, функция, определяет игнорирование исполнения хранимой процедуры. Должна возвращать булево. Если возвращает `false`, то исполнение хранимой процедуры, независимо от необходимости, полностью игнорируется + +**Результат:** объект для работы с компонентом `P8PGantt`. Возвращаемый объект имеет следующие свойства:\ +`gantt` - объект, хранит информацию о диаграмме Ганта, загруженной с помощью хранимой процедуры\ +`isDataLoaded` - булево, признак успешной загрузки и обработки данных из хранимой процедуры\ +`isLoading` - булево, признак выполнения загрузки данных из хранимой процедуры\ +`doReload` - функция, принудительное обновление данных диаграммы Ганта. Сигнатура функции `f()`, результат функции не интерпретируется + **Пример** Код на стороне сервера БД (хранимая процедура в клиентском пакете `PKG_P8PANELS_SAMPLES`, требует наличия таблицы `P8PNL_SMPL_GANTT`, см. "db/P8PNL_SMPL_GANTT.sql"): @@ -2163,25 +2263,43 @@ const MyPanel = () => { ``` import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React -import {Typography, Grid, Stack, Icon, Box, FormControlLabel, Checkbox, Card, CardHeader, CardActions, Avatar, CardContent, Button} from "@mui/material"; //Интерфейсные элементы -import { formatDateJSONDateOnly, formatDateRF } from "../../core/utils"; //Вспомогательные функции -import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Typography, Grid, Stack, Icon, FormControlLabel, Checkbox, Card, CardHeader, CardActions, Avatar, CardContent, Button } from "@mui/material"; //Интерфейсные элементы +import { formatDateRF, hasValue } from "../../core/utils"; //Вспомогательные функции +import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы +import { P8PGantt, useP8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером -//Высота диаграммы Ганта -const GANTT_HEIGHT = "70vh"; +//--------- +//Константы +//--------- -//Ширина диаграммы Ганта -const GANTT_WIDTH = "98vw"; +//Отступ контейнера страницы от заголовка +const CONTAINER_PADDING_TOP = "20px"; + +//Высота заголовка страницы +const TITLE_HEIGHT = "47px"; + +//Высота элементов управления +const CONTROL_HEIGHT = "42px"; //Стили const STYLES = { - CONTAINER: { textAlign: "center", paddingTop: "20px" }, - TITLE: { paddingBottom: "15px" }, - GANTT_CONTAINER: { height: GANTT_HEIGHT, width: GANTT_WIDTH } + CONTAINER: { textAlign: "center", paddingTop: CONTAINER_PADDING_TOP }, + TITLE: { paddingBottom: "15px", height: TITLE_HEIGHT }, + CONTROL: { height: CONTROL_HEIGHT }, + GANTT_CONTAINER: { + height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_HEIGHT} - ${CONTROL_HEIGHT} - ${CONTAINER_PADDING_TOP})`, + width: "100vw", + paddingTop: "5px" + } }; +//--------------------------------------------- +//Вспомогательные функции форматирования данных +//--------------------------------------------- + //Формирование значения для колонки "Тип задачи" const formatTaskTypeValue = value => { const [text, icon] = value == 0 ? ["Этап проекта", "check"] : ["Работа проекта", "work_outline"]; @@ -2227,52 +2345,41 @@ const taskDialogRenderer = ({ task, close }) => { ); }; +//----------- +//Тело модуля +//----------- + //Пример: Диаграмма Ганта "P8Gantt" const Gantt = ({ title }) => { - //Собственное состояние - const [gantt, setGantt] = useState({ - init: false, - dataLoaded: false, - ident: null, - useCustomTaskDialog: false + //Собственное состояние - идентификатор данных + const [dataIdent, setDataIdent] = useState(null); + + //Собственное состояние - использование собственного диалога задачи + const [isCustomTaskDialog, setIsCustomTaskDialog] = useState(false); + + //Собственное состояние - диаграмма Ганта + const { gantt, isDataLoaded, doReload } = useP8PGantt({ + stored: "PKG_P8PANELS_SAMPLES.GANTT", + storedArgs: { NIDENT: dataIdent }, + allowDataLoad: () => hasValue(dataIdent) }); //Подключение к контексту взаимодействия с сервером const { executeStored } = useContext(BackEndСtx); - //Загрузка данных диаграммы с сервера - const loadData = useCallback(async () => { - const data = await executeStored({ - stored: "PKG_P8PANELS_SAMPLES.GANTT", - args: { NIDENT: gantt.ident }, - attributeValueProcessor: (name, val) => - name == "numb" ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val, - respArg: "COUT" - }); - setGantt(pv => ({ ...pv, dataLoaded: true, ...data.XGANTT })); - }, [gantt.ident, executeStored]); - - //Инициализация данных диаграммы - const initData = useCallback(async () => { - if (!gantt.init) { - const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_INIT", args: { NIDENT: gantt.ident } }); - setGantt(pv => ({ ...pv, init: true, ident: data.NIDENT })); - } - }, [gantt.init, gantt.ident, executeStored]); - //Изменение данных диаграммы const modifyData = useCallback( async ({ rn, start, end }) => { try { await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_MODIFY", - args: { NIDENT: gantt.ident, NRN: rn, DDATE_FROM: new Date(start), DDATE_TO: new Date(end) } + args: { NIDENT: dataIdent, NRN: rn, DDATE_FROM: new Date(start), DDATE_TO: new Date(end) } }); } finally { - loadData(); + doReload(); } }, - [gantt.ident, executeStored, loadData] + [dataIdent, executeStored, doReload] ); //Обработка измненения сроков задачи в диаграмме Гантта @@ -2280,13 +2387,13 @@ const Gantt = ({ title }) => { if (isMain) modifyData({ rn: task.rn, start, end }); }; - //При необходимости обновить данные таблицы - useEffect(() => { - if (gantt.ident) loadData(); - }, [gantt.ident, loadData]); - //При подключении компонента к странице useEffect(() => { + //Инициализация данных диаграммы Ганта + const initData = async () => { + const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_INIT", args: { NIDENT: null } }); + setDataIdent(data.NIDENT); + }; initData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -2299,19 +2406,19 @@ const Gantt = ({ title }) => { setGantt(pv => ({ ...pv, useCustomTaskDialog: !pv.useCustomTaskDialog }))} />} + control={ setIsCustomTaskDialog(!isCustomTaskDialog)} />} label="Отображать пользовательский диалог задачи" /> - {gantt.dataLoaded ? ( + {isDataLoaded ? ( ) : null} @@ -2548,6 +2655,35 @@ const MyPanel = () => { `PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK` - процедура, добавляет, к указанному объекту описания циклограммы, новую задачу, ранее описанную через `TCYCLOGRAM_TASK_MAKE`\ `PKG_P8PANELS_VISUAL.TCYCLOGRAM_TO_XML` - функция, производит сериализацию объекта, описывающего циклограмму, в специальный XML-формат, корректно интерпретируемый клиентским компонентом `P8PCyclogram` при передаче в WEB-приложение +**Универсальный хук** + +Для компонента существует универсальный хук `useP8PCyclogram`, который помогает автоматизировать и скрыть логику получения/обновления данных для циклограммы. Использование универсального хука представлено в примере использования `P8PCyclogram` ниже. + +Для использования универсального хука на панели его необходимо импортировать: + +``` +import { useP8PCyclogram } from "../../components/p8p_cyclogram"; + +const MyPanel = () => { + const { cyclogram, isDataLoaded } = useP8PCyclogram({ stored: "ХРАНИМЫЙ_ОБЪЕКТ" }); + ... +} +``` + +**Входные параметры:**\ +`stored` - обязательный, строка, имя исполняемого хранимого объекта (для пакетных - "ПАКЕТ.ОБЪЕКТ")\ +`respArg` - необязательный, строка, имя выходного параметра исполняемого объекта, значение которого необходимо вернуть как данные ответа, по умолчанию - `COUT`\ +`contentNodeName` - необязательный, строка, имя узла, содержащего информацию о циклограмме, по умолчанию `XCYCLOGRAM`\ +`storedArgs` - необязательный, объект, описание параметров исполняемого хранимого объекта вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"|{VALUE: "ЗНАЧЕНИЕ", SDATA_TYPE: SERV_DATA_TYPE_*}}` (если тип данных параметров не указан явно - произойдёт попытка их автоматического определения, с CLOB-параметрами это не всегда может произойти корректно)\ +`executeStoredArgs` - необязательный, объект, описание параметров функции `executeStored` (исполняющей хранимый объект) вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"}`\ +`allowDataLoad` - необязательный, функция, определяет игнорирование исполнения хранимой процедуры. Должна возвращать булево. Если возвращает `false`, то исполнение хранимой процедуры, независимо от необходимости, полностью игнорируется + +**Результат:** объект для работы с компонентом `P8PCyclogram`. Возвращаемый объект имеет следующие свойства:\ +`cyclogram` - объект, хранит информацию о циклограмме, загруженной с помощью хранимой процедуры\ +`isDataLoaded` - булево, признак успешной загрузки и обработки данных из хранимой процедуры\ +`isLoading` - булево, признак выполнения загрузки данных из хранимой процедуры\ +`doReload` - функция, принудительное обновление данных циклограммы. Сигнатура функции `f()`, результат функции не интерпретируется + **Пример** Код на стороне сервера БД (хранимая процедура в клиентском пакете `PKG_P8PANELS_SAMPLES`, требует наличия таблицы `P8PNL_SMPL_CYCLOGRAM`, см. "db/P8PNL_SMPL_CYCLOGRAM.sql"): @@ -2875,51 +3011,29 @@ const taskRenderer = ({ task }) => { //Пример: Циклограмма "P8PCyclogram" const Cyclogram = ({ title }) => { + //Собственное состояние - идентификатор данных + const [dataIdent, setDataIdent] = useState(null); + //Собственное состояние - const [state, setState] = useState({ - init: false, - dataLoaded: false, - reload: true, - ident: null + const { cyclogram, isDataLoaded, doReload } = useP8PCyclogram({ + stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM", + storedArgs: { NIDENT: dataIdent }, + allowDataLoad: () => hasValue(dataIdent) }); //Подключение к контексту взаимодействия с сервером const { executeStored } = useContext(BackEndСtx); - //При необходимости перезагрузки - const handleReload = () => { - setState(pv => ({ ...pv, reload: true })); - }; - - //При необходимости обновить данные таблицы - useEffect(() => { - //Загрузка данных циклограммы с сервера - const loadData = async () => { - const data = await executeStored({ - stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM", - args: { NIDENT: state.ident }, - attributeValueProcessor: (name, val) => - name === "name" ? undefined : ["ddate_start", "ddate_end"].includes(name) ? formatDateJSONDateOnly(val) : val, - respArg: "COUT" - }); - setState(pv => ({ ...pv, dataLoaded: true, ...data.XCYCLOGRAM, reload: false })); - }; - //Если указан идентификатор и требуется перезагрузить - if (state.ident && state.reload) loadData(); - }, [state.ident, state.reload, executeStored]); - //При подключении компонента к странице useEffect(() => { //Инициализация данных циклограммы - const initData = async () => { - const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_INIT", args: { NIDENT: state.ident } }); - setState(pv => ({ ...pv, init: true, ident: data.NIDENT, reload: true })); + const initCyclogram = async () => { + const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_INIT", args: { NIDENT: null } }); + setDataIdent(data.NIDENT); }; - //Если требуется проинициализировать - if (!state.init) { - initData(); - } - }, [executeStored, state.ident, state.init]); + //Инициализируем данные циклограммы + initCyclogram(); + }, [executeStored]); return ( @@ -2929,7 +3043,7 @@ const Cyclogram = ({ title }) => { - {state.dataLoaded ? ( + {isDataLoaded ? ( { `PKG_P8PANELS_VISUAL.TINDICATOR_MAKE` - функция, инициализация индикатора, возвращает объект для хранения его описания\ `PKG_P8PANELS_VISUAL.TINDICATOR_TO_XML` - функция, производит сериализацию объекта, описывающего индикатор, в специальный XML-формат, корректно интерпретируемый клиентским компонентом `P8PIndicator` при передаче в WEB-приложение +**Универсальный хук** + +Для компонента существует универсальный хук `useP8PIndicator`, который помогает автоматизировать и скрыть логику получения/обновления данных для индикатора. Использование универсального хука представлено в примере использования `P8PIndicator` ниже. + +Для использования универсального хука на панели его необходимо импортировать: + +``` +import { useP8PIndicator } from "../../components/p8p_indicator"; + +const MyPanel = () => { + const { indicator, isDataLoaded } = useP8PIndicator({ stored: "ХРАНИМЫЙ_ОБЪЕКТ" }); + ... +} +``` + +**Входные параметры:**\ +`stored` - обязательный, строка, имя исполняемого хранимого объекта (для пакетных - "ПАКЕТ.ОБЪЕКТ")\ +`respArg` - необязательный, строка, имя выходного параметра исполняемого объекта, значение которого необходимо вернуть как данные ответа, по умолчанию - `COUT`\ +`contentNodeName` - необязательный, строка, имя узла, содержащего информацию об индикаторе, по умолчанию `XINDICATOR`\ +`storedArgs` - необязательный, объект, описание параметров исполняемого хранимого объекта вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"|{VALUE: "ЗНАЧЕНИЕ", SDATA_TYPE: SERV_DATA_TYPE_*}}` (если тип данных параметров не указан явно - произойдёт попытка их автоматического определения, с CLOB-параметрами это не всегда может произойти корректно)\ +`executeStoredArgs` - необязательный, объект, описание параметров функции `executeStored` (исполняющей хранимый объект) вида `{"ПАРАМЕТР": "ЗНАЧЕНИЕ"}`\ +`allowDataLoad` - необязательный, функция, определяет игнорирование исполнения хранимой процедуры. Должна возвращать булево. Если возвращает `false`, то исполнение хранимой процедуры, независимо от необходимости, полностью игнорируется + +**Результат:** объект для работы с компонентом `P8PIndicator`. Возвращаемый объект имеет следующие свойства:\ +`indicator` - объект, хранит информацию об индикаторе, загруженном с помощью хранимой процедуры\ +`isDataLoaded` - булево, признак успешной загрузки и обработки данных из хранимой процедуры\ +`isLoading` - булево, признак выполнения загрузки данных из хранимой процедуры\ +`doReload` - функция, принудительное обновление данных индикатора. Сигнатура функции `f()`, результат функции не интерпретируется + **Пример** Полный актуальный исходный код примера можно увидеть в "app/panels/samples/indicator.js" данного репозитория. diff --git a/app/components/p8p_chart.js b/app/components/p8p_chart.js index 32c6efa..1f71328 100644 --- a/app/components/p8p_chart.js +++ b/app/components/p8p_chart.js @@ -10,6 +10,7 @@ import React, { useCallback, useEffect, useRef } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import Chart from "chart.js/auto"; //Диаграммы и графики +import { useP8PChart } from "./p8p_chart_hooks"; //Хук для графика //--------- //Константы @@ -121,4 +122,4 @@ P8PChart.propTypes = { //Интерфейс модуля //---------------- -export { P8P_CHART_TYPE, P8PChart }; +export { P8P_CHART_TYPE, P8PChart, useP8PChart }; diff --git a/app/components/p8p_chart_hooks.js b/app/components/p8p_chart_hooks.js new file mode 100644 index 0000000..dead4fe --- /dev/null +++ b/app/components/p8p_chart_hooks.js @@ -0,0 +1,136 @@ +/* + Парус 8 - Панели мониторинга + Хуки для графиков +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React +import { BackEndСtx } from "../context/backend"; //Контекст взаимодействия с сервером + +//--------- +//Константы +//--------- + +//Константы - значения по умолчанию +const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию о графике +const CHART_NODE_NAME_DEF = "XCHART"; //Наименование узла, содержащего информацию о графике + +//----------- +//Тело модуля +//----------- + +//Хук для P8PChart +const useP8PChart = ({ + stored, + respArg = RESP_ARG_DEF, + contentNodeName = CHART_NODE_NAME_DEF, + storedArgs = {}, + executeStoredArgs = {}, + allowDataLoad = () => true +}) => { + //Собственное состояние - график + const [chart, setChart] = useState({ + type: null, + title: null, + legendPosition: null, + labels: [], + datasets: [] + }); + + //Собственное состояние - признак загрузки данных + const [isDataLoaded, setIsDataLoaded] = useState(false); + + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - необходимость обновления данных + const [reload, setReload] = useState(true); + + //Собственное состояние - дополнительные агрументы + const refStoredArgs = useRef(storedArgs); + + //Собственное состояние - дополнительные параметры вызова процедуры + const refExecuteStoredArgs = useRef(executeStoredArgs); + + //Признак допустимости обновления данных + const isAllowDataLoad = useMemo(() => { + return allowDataLoad(); + }, [allowDataLoad]); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, isRespErr } = useContext(BackEndСtx); + + //Загрузка данных графика с сервера + const loadData = useCallback(async () => { + try { + setLoading(true); + const data = await executeStored({ stored, respArg, args: { ...refStoredArgs.current }, ...refExecuteStoredArgs.current }); + setChart(pv => ({ ...pv, ...data[contentNodeName] })); + //Устанавливаем признак загрузки данных с учетом возможных ошибок + setIsDataLoaded(!isRespErr(data)); + } catch (e) { + //Если произошла ошибка - данные не загружены + setIsDataLoaded(false); + } finally { + //Сбрасываем признаки загрузки и перезагрузки данных + setLoading(false); + setReload(false); + } + }, [contentNodeName, executeStored, isRespErr, respArg, stored]); + + //При необходимости обновления графика + const doReload = useCallback(() => { + setReload(true); + }, []); + + //Проверка изменений параметров + const isArgsChanged = useCallback( + (currentArgs, args) => { + //Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера) + return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args); + }, + [isLoading] + ); + + //При изменение дополнительных параметров процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refStoredArgs.current, storedArgs)) { + //Устанавливаем новые дополнительные параметры + refStoredArgs.current = storedArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [storedArgs, isArgsChanged]); + + //При изменение дополнительных параметров вызова процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) { + //Устанавливаем новые дополнительные параметры + refExecuteStoredArgs.current = executeStoredArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [executeStoredArgs, isArgsChanged]); + + //При необходимости обновить данные графика + useEffect(() => { + //Если необходимо перезагрузить данные и это допустимо + if (isAllowDataLoad && reload) { + loadData(); + } + }, [isAllowDataLoad, reload, loadData]); + + //Возвращаем данные графика + return { chart, isDataLoaded, isLoading, doReload }; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useP8PChart }; diff --git a/app/components/p8p_cyclogram.js b/app/components/p8p_cyclogram.js index bd0e3b1..bda3ca2 100644 --- a/app/components/p8p_cyclogram.js +++ b/app/components/p8p_cyclogram.js @@ -26,6 +26,7 @@ import { } from "@mui/material"; //Интерфейсные компоненты import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке import { hasValue } from "../core/utils"; //Вспомогательный функции +import { useP8PCyclogram } from "./p8p_cyclogram_hooks"; //Хук для циклограммы //--------- //Константы @@ -816,4 +817,4 @@ P8PCyclogram.propTypes = { //Интерфейс модуля //---------------- -export { P8PCyclogram }; +export { P8PCyclogram, useP8PCyclogram }; diff --git a/app/components/p8p_cyclogram_hooks.js b/app/components/p8p_cyclogram_hooks.js new file mode 100644 index 0000000..32324b5 --- /dev/null +++ b/app/components/p8p_cyclogram_hooks.js @@ -0,0 +1,145 @@ +/* + Парус 8 - Панели мониторинга + Хуки для циклограмм +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React +import { BackEndСtx } from "../context/backend"; //Контекст взаимодействия с сервером +import { formatDateJSONDateOnly } from "../core/utils"; //Вспомогательные функции + +//--------- +//Константы +//--------- + +//Константы - значения по умолчанию +const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию о циклограмме +const CG_NODE_NAME_DEF = "XCYCLOGRAM"; //Наименование узла, содержащего информацию о циклограмме + +//----------- +//Тело модуля +//----------- + +//Хук для P8PCyclogram +const useP8PCyclogram = ({ + stored, + respArg = RESP_ARG_DEF, + contentNodeName = CG_NODE_NAME_DEF, + storedArgs = {}, + executeStoredArgs = {}, + allowDataLoad = () => true +}) => { + //Собственное состояние - циклограмма + const [cyclogram, setCyclogram] = useState({ + columns: [], + groups: [], + tasks: [], + taskAttributes: [], + title: null, + lineHeight: 0, + zoom: 1, + zoomBar: true + }); + + //Собственное состояние - признак загрузки данных + const [isDataLoaded, setIsDataLoaded] = useState(false); + + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - необходимость обновления данных + const [reload, setReload] = useState(true); + + //Собственное состояние - дополнительные агрументы + const refStoredArgs = useRef(storedArgs); + + //Собственное состояние - дополнительные параметры вызова процедуры + const refExecuteStoredArgs = useRef(executeStoredArgs); + + //Признак допустимости обновления данных + const isAllowDataLoad = useMemo(() => { + return allowDataLoad(); + }, [allowDataLoad]); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, isRespErr } = useContext(BackEndСtx); + + //Загрузка данных циклограммы с сервера + const loadData = useCallback(async () => { + try { + setLoading(true); + const data = await executeStored({ + stored, + args: { ...refStoredArgs.current }, + attributeValueProcessor: (name, val) => (["ddate_start", "ddate_end"].includes(name) ? formatDateJSONDateOnly(val) : val), + respArg, + ...refExecuteStoredArgs.current + }); + setCyclogram(pv => ({ ...pv, ...data[contentNodeName] })); + //Устанавливаем признак загрузки данных с учетом возможных ошибок + setIsDataLoaded(!isRespErr(data)); + } catch (e) { + //Если произошла ошибка - данные не загружены + setIsDataLoaded(false); + } finally { + //Сбрасываем признаки загрузки и перезагрузки данных + setLoading(false); + setReload(false); + } + }, [contentNodeName, executeStored, isRespErr, respArg, stored]); + + //При необходимости обновления циклограммы + const doReload = useCallback(() => { + setReload(true); + }, []); + + //Проверка изменений параметров + const isArgsChanged = useCallback( + (currentArgs, args) => { + //Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера) + return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args); + }, + [isLoading] + ); + + //При изменение дополнительных параметров процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refStoredArgs.current, storedArgs)) { + //Устанавливаем новые дополнительные параметры + refStoredArgs.current = storedArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [storedArgs, isArgsChanged]); + + //При изменение дополнительных параметров вызова процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) { + //Устанавливаем новые дополнительные параметры + refExecuteStoredArgs.current = executeStoredArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [executeStoredArgs, isArgsChanged]); + + //При необходимости обновить данные циклограммы + useEffect(() => { + if (isAllowDataLoad && reload) { + loadData(); + } + }, [isAllowDataLoad, reload, loadData]); + + //Возвращаем данные циклограммы + return { cyclogram, isDataLoaded, isLoading, doReload }; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useP8PCyclogram }; diff --git a/app/components/p8p_data_grid.js b/app/components/p8p_data_grid.js index 5c6266e..312e5db 100644 --- a/app/components/p8p_data_grid.js +++ b/app/components/p8p_data_grid.js @@ -19,6 +19,7 @@ import { P8P_TABLE_PAGINATOR_ALIGN, P8P_TABLE_PAGINATOR_POSITION } from "./p8p_table"; //Таблица +import { useP8PDataGrid } from "./p8p_data_grid_hooks"; //Хук для таблицы данных //--------- //Константы @@ -240,5 +241,6 @@ export { P8P_DATA_GRID_FILTERS_HEIGHT, P8P_DATA_GRID_PAGINATOR_ALIGN, P8P_DATA_GRID_PAGINATOR_POSITION, - P8PDataGrid + P8PDataGrid, + useP8PDataGrid }; diff --git a/app/components/p8p_data_grid_hooks.js b/app/components/p8p_data_grid_hooks.js new file mode 100644 index 0000000..0b408cc --- /dev/null +++ b/app/components/p8p_data_grid_hooks.js @@ -0,0 +1,224 @@ +/* + Парус 8 - Панели мониторинга + Хуки для таблиц данных +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React +import { BackEndСtx } from "../context/backend"; //Контекст взаимодействия с сервером +import { object2Base64XML } from "../core/utils"; //Вспомогательные функции + +//--------- +//Константы +//--------- + +//Константы - значения по умолчанию +const DG_PAGE_SIZE_DEF = 10; //Размер страницы +const DG_NODE_NAME_DEF = "XDATA_GRID"; //Наименование узла, содержащего информацию о таблице +const FILTERS_NODE_NAME_DEF = "filters"; //Наименование узла отборов +const ORDERS_NODE_NAME_DEF = "orders"; //Наименование узла сортировок +const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию о таблице + +//----------- +//Тело модуля +//----------- + +//Хук для P8PDataGrid +const useP8PDataGrid = ({ + stored, + respArg = RESP_ARG_DEF, + contentNodeName = DG_NODE_NAME_DEF, + filtersNodeName = FILTERS_NODE_NAME_DEF, + ordersNodeName = ORDERS_NODE_NAME_DEF, + pageSize = DG_PAGE_SIZE_DEF, + reloadDef = false, + initFilters = [], + initOrders = [], + storedArgs = {}, + executeStoredArgs = {}, + 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 [isDataLoaded, setIsDataLoaded] = useState(false); + + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - необходимость обновления данных + const [reload, setReload] = useState(true); + + //Собственное состояние - дополнительные параметры процедуры + const refStoredArgs = useRef(storedArgs); + + //Собственное состояние - дополнительные параметры вызова процедуры + const refExecuteStoredArgs = useRef(executeStoredArgs); + + //Признак допустимости обновления данных + const isAllowDataLoad = useMemo(() => { + return allowDataLoad(); + }, [allowDataLoad]); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, SERV_DATA_TYPE_CLOB, isRespErr } = useContext(BackEndСtx); + + //Загрузка данных таблицы с сервера + const loadData = useCallback(async () => { + try { + setLoading(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, + NPAGE_SIZE: pageSize, + NINCLUDE_DEF: reloadDef ? 1 : dataGrid.dataLoaded ? 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)); + } catch (e) { + //Если произошла ошибка - данные не загружены + setIsDataLoaded(false); + } finally { + //Сбрасываем признаки загрузки и перезагрузки данных + setLoading(false); + setReload(false); + } + }, [ + SERV_DATA_TYPE_CLOB, + contentNodeName, + dataGrid.dataLoaded, + dataGrid.filters, + dataGrid.orders, + dataGrid.pageNumber, + executeStored, + filtersNodeName, + isRespErr, + ordersNodeName, + pageSize, + reloadDef, + respArg, + stored + ]); + + //При изменении состояния фильтра + const handleFilterChanged = useCallback(({ filters }) => { + setDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1 })); + setReload(true); + }, []); + + //При изменении состояния сортировки + const handleOrderChanged = useCallback(({ orders }) => { + setDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1 })); + setReload(true); + }, []); + + //При изменении количества отображаемых страниц + const handlePagesCountChanged = useCallback(() => { + setDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 })); + setReload(true); + }, []); + + //При изменении страницы отображения + const handlePageChange = useCallback(({ page }) => { + setDataGrid(pv => ({ ...pv, pageNumber: page })); + setReload(true); + }, []); + + //При необходимости обновления таблицы + const doReload = useCallback( + ({ returnOnFirstPage = false }) => { + //Если это не страничный вывод или установлен признак возврата на первую страницу + if (dataGrid.pagesCount <= 0 || returnOnFirstPage) { + setDataGrid(pv => ({ ...pv, pageNumber: 1 })); + } + setReload(true); + }, + [dataGrid.pagesCount] + ); + + //Проверка изменений параметров + const isArgsChanged = useCallback( + (currentArgs, args) => { + //Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера) + return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args); + }, + [isLoading] + ); + + //При изменение дополнительных параметров процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refStoredArgs.current, storedArgs)) { + //Устанавливаем новые дополнительные параметры + refStoredArgs.current = storedArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [storedArgs, isArgsChanged]); + + //При изменение дополнительных параметров вызова процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) { + //Устанавливаем новые дополнительные параметры + refExecuteStoredArgs.current = executeStoredArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [executeStoredArgs, isArgsChanged]); + + //При необходимости обновить данные таблицы + useEffect(() => { + if (isAllowDataLoad && reload) { + loadData(); + } + }, [isAllowDataLoad, reload, loadData]); + + //Возвращаем данные таблицы + return { dataGrid, isDataLoaded, isLoading, handleFilterChanged, handleOrderChanged, handlePagesCountChanged, handlePageChange, doReload }; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useP8PDataGrid }; diff --git a/app/components/p8p_gantt.js b/app/components/p8p_gantt.js index 6445096..04b335b 100644 --- a/app/components/p8p_gantt.js +++ b/app/components/p8p_gantt.js @@ -27,6 +27,7 @@ import { Link } from "@mui/material"; //Интерфейсные компоненты import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке +import { useP8PGantt } from "./p8p_gantt_hooks"; //Хук для диаграммы Ганта //--------- //Константы @@ -522,4 +523,4 @@ P8PGantt.propTypes = { //Интерфейс модуля //---------------- -export { P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE, taskLegendDesc, P8PGantt }; +export { P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE, taskLegendDesc, P8PGantt, useP8PGantt }; diff --git a/app/components/p8p_gantt_hooks.js b/app/components/p8p_gantt_hooks.js new file mode 100644 index 0000000..a64c48a --- /dev/null +++ b/app/components/p8p_gantt_hooks.js @@ -0,0 +1,148 @@ +/* + Парус 8 - Панели мониторинга + Хуки для диаграмм Ганта +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React +import { BackEndСtx } from "../context/backend"; //Контекст взаимодействия с сервером +import { formatDateJSONDateOnly } from "../core/utils"; //Вспомогательные функции + +//--------- +//Константы +//--------- + +//Константы - значения по умолчанию +const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию о диаграмме ганта +const GANTT_NODE_NAME_DEF = "XGANTT"; //Наименование узла, содержащего информацию о диаграмме ганта + +//----------- +//Тело модуля +//----------- + +//Хук для P8PGantt +const useP8PGantt = ({ + stored, + respArg = RESP_ARG_DEF, + contentNodeName = GANTT_NODE_NAME_DEF, + storedArgs = {}, + executeStoredArgs = {}, + allowDataLoad = () => true +}) => { + //Собственное состояние - диаграмма ганта + const [gantt, setGantt] = useState({ + title: null, + zoom: null, + zoomBar: null, + readOnly: false, + readOnlyDates: false, + readOnlyProgress: false, + taskAttributes: [], + tasks: [], + taskColors: [] + }); + + //Собственное состояние - признак загрузки данных + const [isDataLoaded, setIsDataLoaded] = useState(false); + + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - необходимость обновления данных + const [reload, setReload] = useState(true); + + //Собственное состояние - дополнительные агрументы + const refStoredArgs = useRef(storedArgs); + + //Собственное состояние - дополнительные параметры вызова процедуры + const refExecuteStoredArgs = useRef(executeStoredArgs); + + //Признак допустимости обновления данных + const isAllowDataLoad = useMemo(() => { + return allowDataLoad(); + }, [allowDataLoad]); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, isRespErr } = useContext(BackEndСtx); + + //Загрузка данных диаграммы ганта с сервера + const loadData = useCallback(async () => { + try { + setLoading(true); + const data = await executeStored({ + stored, + args: { ...refStoredArgs.current }, + attributeValueProcessor: (name, val) => + name == "numb" ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val, + respArg, + ...refExecuteStoredArgs.current + }); + setGantt(pv => ({ ...pv, ...data[contentNodeName] })); + //Устанавливаем признак загрузки данных с учетом возможных ошибок + setIsDataLoaded(!isRespErr(data)); + } catch (e) { + //Если произошла ошибка - данные не загружены + setIsDataLoaded(false); + } finally { + //Сбрасываем признаки загрузки и перезагрузки данных + setLoading(false); + setReload(false); + } + }, [contentNodeName, executeStored, isRespErr, respArg, stored]); + + //При необходимости обновления диаграммы ганта + const doReload = useCallback(() => { + setReload(true); + }, []); + + //Проверка изменений параметров + const isArgsChanged = useCallback( + (currentArgs, args) => { + //Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера) + return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args); + }, + [isLoading] + ); + + //При изменение дополнительных параметров процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refStoredArgs.current, storedArgs)) { + //Устанавливаем новые дополнительные параметры + refStoredArgs.current = storedArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [storedArgs, isArgsChanged]); + + //При изменение дополнительных параметров вызова процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) { + //Устанавливаем новые дополнительные параметры + refExecuteStoredArgs.current = executeStoredArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [executeStoredArgs, isArgsChanged]); + + //При необходимости обновить данные диаграммы ганта + useEffect(() => { + //Если необходимо перезагрузить данные и это допустимо + if (isAllowDataLoad && reload) { + loadData(); + } + }, [isAllowDataLoad, reload, loadData]); + + //Возвращаем данные диаграммы ганта + return { gantt, isDataLoaded, isLoading, doReload }; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useP8PGantt }; diff --git a/app/components/p8p_indicator.js b/app/components/p8p_indicator.js index 7877e34..eab245f 100644 --- a/app/components/p8p_indicator.js +++ b/app/components/p8p_indicator.js @@ -13,6 +13,7 @@ import { IconButton, Icon, Typography, Paper, Stack } from "@mui/material"; //И import { P8PHintDialog } from "./p8p_app_message"; //Диалог подсказки import { TEXTS, STATE } from "../../app.text"; //Типовые текстовые ресурсы и константы import { APP_COLORS } from "../../app.styles"; //Типовые стили +import { useP8PIndicator } from "./p8p_indicator_hooks"; //Хук для индикатора //--------- //Константы @@ -226,4 +227,4 @@ P8PIndicator.propTypes = { //Интерфейс модуля //---------------- -export { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator }; +export { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator, useP8PIndicator }; diff --git a/app/components/p8p_indicator_hooks.js b/app/components/p8p_indicator_hooks.js new file mode 100644 index 0000000..0fcdede --- /dev/null +++ b/app/components/p8p_indicator_hooks.js @@ -0,0 +1,143 @@ +/* + Парус 8 - Панели мониторинга + Хуки для индикаторов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React +import { BackEndСtx } from "../context/backend"; //Контекст взаимодействия с сервером +import { P8P_INDICATOR_STATE, P8P_INDICATOR_VARIANT } from "./p8p_indicator"; + +//--------- +//Константы +//--------- + +//Константы - значения по умолчанию +const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию об индикаторе +const INDICATOR_NODE_NAME_DEF = "XINDICATOR"; //Наименование узла, содержащего информацию об индикаторе +const ELEVATION_DEF = 3; //Высота парения + +//----------- +//Тело модуля +//----------- + +//Хук для P8PIndicator +const useP8PIndicator = ({ + stored, + respArg = RESP_ARG_DEF, + contentNodeName = INDICATOR_NODE_NAME_DEF, + storedArgs = {}, + executeStoredArgs = {}, + allowDataLoad = () => true +}) => { + //Собственное состояние - индикатор + const [indicator, setIndicator] = useState({ + caption: null, + value: null, + icon: null, + state: P8P_INDICATOR_STATE.UNDEFINED, + square: false, + elevation: ELEVATION_DEF, + variant: P8P_INDICATOR_VARIANT.ELEVATION, + hint: null, + backgroundColor: null, + color: null + }); + + //Собственное состояние - признак загрузки данных + const [isDataLoaded, setIsDataLoaded] = useState(false); + + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - необходимость обновления данных + const [reload, setReload] = useState(true); + + //Собственное состояние - дополнительные агрументы + const refStoredArgs = useRef(storedArgs); + + //Собственное состояние - дополнительные параметры вызова процедуры + const refExecuteStoredArgs = useRef(executeStoredArgs); + + //Признак допустимости обновления данных + const isAllowDataLoad = useMemo(() => { + return allowDataLoad(); + }, [allowDataLoad]); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, isRespErr } = useContext(BackEndСtx); + + //Загрузка данных индикатора с сервера + const loadData = useCallback(async () => { + try { + setLoading(true); + const data = await executeStored({ stored, respArg, args: { ...refStoredArgs.current }, ...refExecuteStoredArgs.current }); + setIndicator(pv => ({ ...pv, ...data[contentNodeName] })); + //Устанавливаем признак загрузки данных с учетом возможных ошибок + setIsDataLoaded(!isRespErr(data)); + } catch (e) { + //Если произошла ошибка - данные не загружены + setIsDataLoaded(false); + } finally { + //Сбрасываем признаки загрузки и перезагрузки данных + setLoading(false); + setReload(false); + } + }, [contentNodeName, executeStored, isRespErr, respArg, stored]); + + //При необходимости обновления индикатора + const doReload = useCallback(() => { + setReload(true); + }, []); + + //Проверка изменений параметров + const isArgsChanged = useCallback( + (currentArgs, args) => { + //Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера) + return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args); + }, + [isLoading] + ); + + //При изменение дополнительных параметров процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refStoredArgs.current, storedArgs)) { + //Устанавливаем новые дополнительные параметры + refStoredArgs.current = storedArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [storedArgs, isArgsChanged]); + + //При изменение дополнительных параметров вызова процедуры + useEffect(() => { + //Если параметры изменились + if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) { + //Устанавливаем новые дополнительные параметры + refExecuteStoredArgs.current = executeStoredArgs; + //При изменении дополнительных параметров необходимо перезагрузить данные + setReload(true); + } + }, [executeStoredArgs, isArgsChanged]); + + //При необходимости обновить данные индикатора + useEffect(() => { + //Если необходимо перезагрузить данные и это допустимо + if (isAllowDataLoad && reload) { + loadData(); + } + }, [isAllowDataLoad, reload, loadData]); + + //Возвращаем данные индикатора + return { indicator, isDataLoaded, isLoading, doReload }; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useP8PIndicator }; diff --git a/app/panels/prj_fin/projects.js b/app/panels/prj_fin/projects.js index e692926..0b2699b 100644 --- a/app/panels/prj_fin/projects.js +++ b/app/panels/prj_fin/projects.js @@ -7,15 +7,20 @@ //Подключение библиотек //--------------------- -import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React +import React, { useState, useContext } from "react"; //Классы React import { Box, Grid, Paper, Fab, Icon } from "@mui/material"; //Интерфейсные компоненты -import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы -import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_MORE_HEIGHT, P8P_DATA_GRID_FILTERS_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных +import { + P8PDataGrid, + P8P_DATA_GRID_SIZE, + P8P_DATA_GRID_MORE_HEIGHT, + P8P_DATA_GRID_FILTERS_HEIGHT, + useP8PDataGrid +} from "../../components/p8p_data_grid"; //Таблица данных import { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог -import { P8PChart } from "../../components/p8p_chart"; //График +import { P8PChart, useP8PChart } from "../../components/p8p_chart"; //График import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений @@ -49,102 +54,40 @@ const STYLES = { //Список проектов const Projects = () => { - //Собственное состояние - const [projectsDataGrid, setProjectsDataGrid] = useState({ - dataLoaded: false, - columnsDef: [], - filters: null, - orders: null, - rows: [], - reload: true, - pageNumber: 1, - morePages: true, - selectedProject: null, - stagesFilters: [] - }); + //Собственное состояние - выбранный проект + const [selectedProject, setSelectedProject] = useState(null); - //Состояния графиков - const [showCharts, setShowCharts] = useState(true); - const [problemsChart, setProblemsChart] = useState({ loaded: false, labels: [], datasets: [] }); - const [customersChart, setCustomersChart] = useState({ loaded: false, labels: [], datasets: [] }); - const [costNotesChart, setCostNotesChart] = useState({ loaded: false, labels: [], datasets: [] }); - - //Подключение к контексту взаимодействия с сервером - const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); + //Собственное состояние - фильтры этапов + const [stagesFilters, setStagesFilters] = useState([]); //Подключение к контексту приложения const { pOnlineShowDocument, pOnlineShowUnit, configSystemPageSize } = useContext(ApplicationСtx); + //Собственное состояние + const { + dataGrid: projectsDataGrid, + isDataLoaded: projectsLoaded, + handleFilterChanged, + handleOrderChanged, + handlePagesCountChanged + } = useP8PDataGrid({ + stored: "PKG_P8PANELS_PROJECTS.LIST", + pageSize: configSystemPageSize, + executeStoredArgs: { attributeValueProcessor: (name, val) => (name == "SGOVCNTRID" ? undefined : val) } + }); + + //Состояния графиков + const [showCharts, setShowCharts] = useState(true); + const { chart: problemsChart, isDataLoaded: isProblemsLoaded } = useP8PChart({ stored: "PKG_P8PANELS_PROJECTS.CHART_PROBLEMS" }); + const { chart: customersChart, isDataLoaded: isCustomersLoaded } = useP8PChart({ stored: "PKG_P8PANELS_PROJECTS.CHART_CUSTOMERS" }); + const { chart: costNotesChart, isDataLoaded: isCostNotesLoaded } = useP8PChart({ stored: "PKG_P8PANELS_PROJECTS.CHART_FCCOSTNOTES" }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + //Подключение к контексту сообщений const { showMsgErr } = useContext(MessagingСtx); - //Загрузка данных проектов с сервера - const loadProjects = useCallback(async () => { - if (projectsDataGrid.reload) { - const data = await executeStored({ - stored: "PKG_P8PANELS_PROJECTS.LIST", - args: { - CFILTERS: { VALUE: object2Base64XML(projectsDataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - CORDERS: { VALUE: object2Base64XML(projectsDataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - NPAGE_NUMBER: projectsDataGrid.pageNumber, - NPAGE_SIZE: configSystemPageSize, - NINCLUDE_DEF: projectsDataGrid.dataLoaded ? 0 : 1 - }, - attributeValueProcessor: (name, val) => (name == "SGOVCNTRID" ? undefined : val), - respArg: "COUT" - }); - setProjectsDataGrid(pv => ({ - ...pv, - ...data.XDATA_GRID, - columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, - rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])], - dataLoaded: true, - reload: false, - morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize - })); - } - }, [ - projectsDataGrid.reload, - projectsDataGrid.filters, - projectsDataGrid.orders, - projectsDataGrid.dataLoaded, - projectsDataGrid.pageNumber, - executeStored, - configSystemPageSize, - SERV_DATA_TYPE_CLOB - ]); - - //Получение данных графиков - const loadChartData = async () => { - const problemsChart = await executeStored({ - stored: "PKG_P8PANELS_PROJECTS.CHART_PROBLEMS", - respArg: "COUT" - }); - setProblemsChart(pv => ({ - ...pv, - loaded: true, - ...problemsChart.XCHART - })); - const customersChart = await executeStored({ - stored: "PKG_P8PANELS_PROJECTS.CHART_CUSTOMERS", - respArg: "COUT" - }); - setCustomersChart(pv => ({ - ...pv, - loaded: true, - ...customersChart.XCHART - })); - const costNotesChart = await executeStored({ - stored: "PKG_P8PANELS_PROJECTS.CHART_FCCOSTNOTES", - respArg: "COUT" - }); - setCostNotesChart(pv => ({ - ...pv, - loaded: true, - ...costNotesChart.XCHART - })); - }; - //Отображение журнала платежей по этапу проекта const showPayNotes = async ({ sender, direction }) => { const data = await executeStored({ @@ -166,44 +109,23 @@ const Projects = () => { }; //Отображение этапов проекта - const showStages = ({ sender, filters = [] } = {}) => - setProjectsDataGrid(pv => ({ ...pv, selectedProject: { ...sender }, stagesFilters: [...filters] })); - - //При изменении состояния фильтра - const handleFilterChanged = ({ filters }) => setProjectsDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1, reload: true })); - - //При изменении состояния сортировки - const handleOrderChanged = ({ orders }) => setProjectsDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true })); - - //При изменении количества отображаемых страниц - const handlePagesCountChanged = () => setProjectsDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); + const showStages = ({ sender, filters = [] } = {}) => { + setSelectedProject({ ...sender }); + setStagesFilters([...filters]); + }; //При закрытии списка этапов проекта - const handleStagesClose = () => setProjectsDataGrid(pv => ({ ...pv, selectedProject: null, stagesFilters: [] })); + const handleStagesClose = () => { + setSelectedProject(null); + setStagesFilters([]); + }; //Отработка нажатия на график const handleChartClick = ({ item }) => { - if (item.SFILTER && item.SFILTER_VALUE) - setProjectsDataGrid(pv => ({ - ...pv, - filters: [{ name: item.SFILTER, from: item.SFILTER_VALUE }], - pageNumber: 1, - reload: true - })); + if (item.SFILTER && item.SFILTER_VALUE) handleFilterChanged({ filters: [{ name: item.SFILTER, from: item.SFILTER_VALUE }] }); if (item.SUNITCODE == "CostNotes" && item.NYEAR && item.NMONTH) showCostNotesChartDetail({ year: item.NYEAR, month: item.NMONTH }); }; - //При необходимости обновить данные - useEffect(() => { - loadProjects(); - }, [projectsDataGrid.reload, loadProjects]); - - //При подключении к странице - useEffect(() => { - loadChartData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - //Генерация содержимого return ( @@ -212,23 +134,23 @@ const Projects = () => { <> - {problemsChart.loaded ? : null} + {isProblemsLoaded ? : null} - {customersChart.loaded ? : null} + {isCustomersLoaded ? : null} - {costNotesChart.loaded ? : null} + {isCostNotesLoaded ? : null} ) : null} - {projectsDataGrid.dataLoaded ? ( + {projectsLoaded ? ( { onPagesCountChanged={handlePagesCountChanged} /> ) : null} - {projectsDataGrid.selectedProject ? ( + {selectedProject ? ( - + ) : null} - {problemsChart.loaded || customersChart.loaded || costNotesChart.loaded ? ( + {isProblemsLoaded || isCustomersLoaded || isCostNotesLoaded ? ( setShowCharts(!showCharts)}> {showCharts ? "expand_less" : "expand_more"} diff --git a/app/panels/prj_fin/stage_arts.js b/app/panels/prj_fin/stage_arts.js index 248aa15..2ca05db 100644 --- a/app/panels/prj_fin/stage_arts.js +++ b/app/panels/prj_fin/stage_arts.js @@ -7,14 +7,19 @@ //Подключение библиотек //--------------------- -import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React +import React, { useContext } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Box } from "@mui/material"; //Интерфейсные компоненты -import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы -import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE, P8P_DATA_GRID_FILTERS_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных +import { + P8PDataGrid, + P8P_DATA_GRID_SIZE, + P8P_DATA_GRID_FILTER_SHAPE, + P8P_DATA_GRID_FILTERS_HEIGHT, + useP8PDataGrid +} from "../../components/p8p_data_grid"; //Таблица данных import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений @@ -40,16 +45,18 @@ const STYLES = { //Калькуляция этапа проекта const StageArts = ({ stage, filters }) => { //Собственное состояние - const [stageArtsDataGrid, setStageArtsDataGrid] = useState({ - dataLoaded: false, - columnsDef: [], - filters: [...filters], - rows: [], - reload: true + const { + dataGrid: stageArtsDataGrid, + isDataLoaded, + handleFilterChanged + } = useP8PDataGrid({ + stored: "PKG_P8PANELS_PROJECTS.STAGE_ARTS_LIST", + initFilters: filters, + storedArgs: { NSTAGE: stage } }); //Подключение к контексту взаимодействия с сервером - const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); + const { executeStored } = useContext(BackEndСtx); //Подключение к контексту приложения const { pOnlineShowUnit } = useContext(ApplicationСtx); @@ -57,29 +64,6 @@ const StageArts = ({ stage, filters }) => { //Подключение к контексту сообщений const { showMsgErr } = useContext(MessagingСtx); - //Загрузка данных калькуляции этапа с сервера - const loadStageArts = useCallback(async () => { - if (stageArtsDataGrid.reload) { - const data = await executeStored({ - stored: "PKG_P8PANELS_PROJECTS.STAGE_ARTS_LIST", - args: { - NSTAGE: stage, - CFILTERS: { VALUE: object2Base64XML(stageArtsDataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - NINCLUDE_DEF: stageArtsDataGrid.dataLoaded ? 0 : 1 - }, - respArg: "COUT" - }); - setStageArtsDataGrid(pv => ({ - ...pv, - ...data.XDATA_GRID, - columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, - rows: [...(data.XDATA_GRID.rows || [])], - dataLoaded: true, - reload: false - })); - } - }, [stage, stageArtsDataGrid.reload, stageArtsDataGrid.filters, stageArtsDataGrid.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB]); - //Отображение журнала затрат по статье калькуляции const showCostNotes = async ({ sender }) => { const data = await executeStored({ @@ -100,18 +84,10 @@ const StageArts = ({ stage, filters }) => { else showMsgErr(TEXTS.NO_DATA_FOUND); }; - //При изменении состояния фильтра - const handleFilterChanged = ({ filters }) => setStageArtsDataGrid(pv => ({ ...pv, filters, reload: true })); - - //При необходимости обновить данные - useEffect(() => { - loadStageArts(); - }, [stageArtsDataGrid.reload, loadStageArts]); - //Генерация содержимого return ( - {stageArtsDataGrid.dataLoaded ? ( + {isDataLoaded ? ( 0), elevation: 0 }} diff --git a/app/panels/prj_fin/stage_contracts.js b/app/panels/prj_fin/stage_contracts.js index 01ed0a9..73b7a68 100644 --- a/app/panels/prj_fin/stage_contracts.js +++ b/app/panels/prj_fin/stage_contracts.js @@ -7,10 +7,9 @@ //Подключение библиотек //--------------------- -import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React +import React, { useContext } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Box } from "@mui/material"; //Интерфейсные компоненты -import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы @@ -19,7 +18,8 @@ import { P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE, P8P_DATA_GRID_MORE_HEIGHT, - P8P_DATA_GRID_FILTERS_HEIGHT + P8P_DATA_GRID_FILTERS_HEIGHT, + useP8PDataGrid } from "../../components/p8p_data_grid"; //Таблица данных import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { ApplicationСtx } from "../../context/application"; //Контекст приложения @@ -47,68 +47,29 @@ const STYLES = { //Договоры с соисполнителями этапа проекта const StageContracts = ({ stage, filters }) => { - //Собственное состояние - const [stageContractsDataGrid, setStageContractsDataGrid] = useState({ - dataLoaded: false, - columnsDef: [], - filters: [...filters], - orders: null, - rows: [], - reload: true, - pageNumber: 1, - morePages: true - }); - - //Подключение к контексту взаимодействия с сервером - const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); - //Подключение к контексту приложения const { pOnlineShowDocument, pOnlineShowUnit, configSystemPageSize } = useContext(ApplicationСtx); + //Собственное состояние + const { + dataGrid: stageContractsDataGrid, + isDataLoaded, + handleFilterChanged, + handleOrderChanged, + handlePagesCountChanged + } = useP8PDataGrid({ + stored: "PKG_P8PANELS_PROJECTS.STAGE_CONTRACTS_LIST", + pageSize: configSystemPageSize, + storedArgs: { NSTAGE: stage }, + executeStoredArgs: { attributeValueProcessor: (name, val) => (name == "SGOVCNTRID" ? undefined : val) } + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + //Подключение к контексту сообщений const { showMsgErr } = useContext(MessagingСtx); - //Загрузка данных этапов с сервера - const loadStageContracts = useCallback(async () => { - if (stageContractsDataGrid.reload) { - const data = await executeStored({ - stored: "PKG_P8PANELS_PROJECTS.STAGE_CONTRACTS_LIST", - args: { - NSTAGE: stage, - CFILTERS: { - VALUE: object2Base64XML(stageContractsDataGrid.filters, { arrayNodeName: "filters" }), - SDATA_TYPE: SERV_DATA_TYPE_CLOB - }, - CORDERS: { VALUE: object2Base64XML(stageContractsDataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - NPAGE_NUMBER: stageContractsDataGrid.pageNumber, - NPAGE_SIZE: configSystemPageSize, - NINCLUDE_DEF: stageContractsDataGrid.dataLoaded ? 0 : 1 - }, - attributeValueProcessor: (name, val) => (name == "SGOVCNTRID" ? undefined : val), - respArg: "COUT" - }); - setStageContractsDataGrid(pv => ({ - ...pv, - ...data.XDATA_GRID, - columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, - rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])], - dataLoaded: true, - reload: false, - morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize - })); - } - }, [ - stage, - stageContractsDataGrid.reload, - stageContractsDataGrid.filters, - stageContractsDataGrid.orders, - stageContractsDataGrid.dataLoaded, - stageContractsDataGrid.pageNumber, - executeStored, - configSystemPageSize, - SERV_DATA_TYPE_CLOB - ]); - //Отображение выходящих счетов на оплату от соисполнителя этапа const showPaymentAccountsIn = async ({ sender }) => { const data = await executeStored({ @@ -139,24 +100,10 @@ const StageContracts = ({ stage, filters }) => { else showMsgErr(TEXTS.NO_DATA_FOUND); }; - //При изменении состояния фильтра - const handleFilterChanged = ({ filters }) => setStageContractsDataGrid(pv => ({ ...pv, filters, pageNumber: 1, reload: true })); - - //При изменении состояния сортировки - const handleOrderChanged = ({ orders }) => setStageContractsDataGrid(pv => ({ ...pv, orders, pageNumber: 1, reload: true })); - - //При изменении количества отображаемых страниц - const handlePagesCountChanged = () => setStageContractsDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); - - //При необходимости обновить данные - useEffect(() => { - loadStageContracts(); - }, [stageContractsDataGrid.reload, loadStageContracts]); - //Генерация содержимого return ( - {stageContractsDataGrid.dataLoaded ? ( + {isDataLoaded ? ( { - //Собственное состояние - const [stagesDataGrid, setStagesDataGrid] = useState({ - dataLoaded: false, - columnsDef: [], - filters: [...filters], - orders: null, - rows: [], - reload: true, - pageNumber: 1, - morePages: true, - selectedStageNumb: null, + //Собственное состояние - информация выбранного этапа + const [selectedStage, setSelectedStage] = useState({ + stageNumb: null, showStageArts: null, stageArtsFilters: [], showStageContracts: null, stageContractsFilters: [] }); - //Подключение к контексту взаимодействия с сервером - const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); - //Подключение к контексту приложения const { pOnlineShowDocument, pOnlineShowUnit, configSystemPageSize } = useContext(ApplicationСtx); + //Собственное состояние + const { + dataGrid: stagesDataGrid, + isDataLoaded: isStagesLoaded, + handleFilterChanged, + handleOrderChanged, + handlePagesCountChanged + } = useP8PDataGrid({ + stored: "PKG_P8PANELS_PROJECTS.STAGES_LIST", + pageSize: configSystemPageSize, + initFilters: filters, + storedArgs: { NPRN: project } + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + //Подключение к контексту сообщений const { showMsgErr } = useContext(MessagingСtx); - //Загрузка данных этапов с сервера - const loadStages = useCallback(async () => { - if (stagesDataGrid.reload) { - const data = await executeStored({ - stored: "PKG_P8PANELS_PROJECTS.STAGES_LIST", - args: { - NPRN: project, - CFILTERS: { VALUE: object2Base64XML(stagesDataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - CORDERS: { VALUE: object2Base64XML(stagesDataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - NPAGE_NUMBER: stagesDataGrid.pageNumber, - NPAGE_SIZE: configSystemPageSize, - NINCLUDE_DEF: stagesDataGrid.dataLoaded ? 0 : 1 - }, - respArg: "COUT" - }); - setStagesDataGrid(pv => ({ - ...pv, - ...data.XDATA_GRID, - columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, - rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])], - dataLoaded: true, - reload: false, - morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize - })); - } - }, [ - project, - stagesDataGrid.reload, - stagesDataGrid.filters, - stagesDataGrid.orders, - stagesDataGrid.dataLoaded, - stagesDataGrid.pageNumber, - executeStored, - configSystemPageSize, - SERV_DATA_TYPE_CLOB - ]); - //Отображение журнала платежей по этапу проекта const showPayNotes = async ({ sender, direction }) => { const data = await executeStored({ @@ -145,36 +114,22 @@ const Stages = ({ project, projectName, filters }) => { //Отображение статей калькуляции по этапу проекта const showStageArts = ({ sender, filters = [] } = {}) => - setStagesDataGrid(pv => ({ ...pv, showStageArts: sender.NRN, selectedStageNumb: sender.SNUMB, stageArtsFilters: [...filters] })); + setSelectedStage(pv => ({ ...pv, showStageArts: sender.NRN, stageNumb: sender.SNUMB, stageArtsFilters: [...filters] })); //Отображение договоров с соисполнителями по этапу проекта const showContracts = ({ sender, filters = [] } = {}) => - setStagesDataGrid(pv => ({ ...pv, showStageContracts: sender.NRN, selectedStageNumb: sender.SNUMB, stageContractsFilters: [...filters] })); - - //При изменении состояния фильтра - const handleFilterChanged = ({ filters }) => setStagesDataGrid(pv => ({ ...pv, filters, pageNumber: 1, reload: true })); - - //При изменении состояния сортировки - const handleOrderChanged = ({ orders }) => setStagesDataGrid(pv => ({ ...pv, orders, pageNumber: 1, reload: true })); - - //При изменении количества отображаемых страниц - const handlePagesCountChanged = () => setStagesDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); + setSelectedStage(pv => ({ ...pv, showStageContracts: sender.NRN, stageNumb: sender.SNUMB, stageContractsFilters: [...filters] })); //При закрытии списка договоров этапа - const handleStageContractsClose = () => setStagesDataGrid(pv => ({ ...pv, showStageContracts: null, stageContractsFilters: [] })); + const handleStageContractsClose = () => setSelectedStage(pv => ({ ...pv, showStageContracts: null, stageContractsFilters: [] })); //При закрытии калькуляции этапа - const handleStageArtsClose = () => setStagesDataGrid(pv => ({ ...pv, showStageArts: null, stageArtsFilters: [] })); - - //При необходимости обновить данные - useEffect(() => { - loadStages(); - }, [stagesDataGrid.reload, loadStages]); + const handleStageArtsClose = () => setSelectedStage(pv => ({ ...pv, showStageArts: null, stageArtsFilters: [] })); //Генерация содержимого return ( - {stagesDataGrid.dataLoaded ? ( + {isStagesLoaded ? ( { onPagesCountChanged={handlePagesCountChanged} /> ) : null} - {stagesDataGrid.showStageContracts ? ( + {selectedStage.showStageContracts ? ( - + ) : null} - {stagesDataGrid.showStageArts ? ( + {selectedStage.showStageArts ? ( - + ) : null} diff --git a/app/panels/samples/chart.js b/app/panels/samples/chart.js index 5f2adb8..c6b1253 100644 --- a/app/panels/samples/chart.js +++ b/app/panels/samples/chart.js @@ -7,11 +7,10 @@ //Подключение библиотек //--------------------- -import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React +import React, { useContext } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Typography, Grid, Paper } from "@mui/material"; //Интерфейсные элементы -import { P8PChart } from "../../components/p8p_chart"; //График -import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { P8PChart, useP8PChart } from "../../components/p8p_chart"; //График import { ApplicationСtx } from "../../context/application"; //Контекст приложения //--------- @@ -33,20 +32,11 @@ const STYLES = { //Пример: Графики "P8PChart" const Chart = ({ title }) => { //Собственное состояние - график - const [chart, setChart] = useState({ loaded: false }); - - //Подключение к контексту взаимодействия с сервером - const { executeStored } = useContext(BackEndСtx); + const { chart, isDataLoaded } = useP8PChart({ stored: "PKG_P8PANELS_SAMPLES.CHART" }); //Подключение к контексту приложения const { pOnlineShowUnit } = useContext(ApplicationСtx); - //Загрузка данных графика с сервера - const loadChart = useCallback(async () => { - const chart = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.CHART", respArg: "COUT" }); - setChart(pv => ({ ...pv, loaded: true, ...chart.XCHART })); - }, [executeStored]); - //Отработка нажатия на график const handleChartClick = ({ item }) => { pOnlineShowUnit({ @@ -55,12 +45,6 @@ const Chart = ({ title }) => { }); }; - //При подключении к странице - useEffect(() => { - loadChart(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - //Генерация содержимого return (
@@ -70,7 +54,7 @@ const Chart = ({ title }) => { - {chart.loaded ? : null} + {isDataLoaded ? : null} diff --git a/app/panels/samples/cyclogram.js b/app/panels/samples/cyclogram.js index 1ffca33..d3ef903 100644 --- a/app/panels/samples/cyclogram.js +++ b/app/panels/samples/cyclogram.js @@ -24,9 +24,9 @@ import { Stack, Icon } from "@mui/material"; //Интерфейсные элементы -import { formatDateJSONDateOnly, formatDateRF } from "../../core/utils"; //Вспомогательные функции +import { formatDateRF, hasValue } from "../../core/utils"; //Вспомогательные функции import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы -import { P8PCyclogram } from "../../components/p8p_cyclogram"; //Циклограмма +import { P8PCyclogram, useP8PCyclogram } from "../../components/p8p_cyclogram"; //Циклограмма import { P8P_CYCLOGRAM_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером @@ -70,7 +70,7 @@ const STYLES = { //--------------------------------------------- //Диалог открытия задачи -const CustomTaskDialog = ({ task, ident, handleReload, close }) => { +const CustomTaskDialog = ({ task, ident, onReload, close }) => { //Собственное состояние const [taskDates, setTaskDates] = useState({ start: task.ddate_start, end: task.ddate_end }); @@ -80,6 +80,9 @@ const CustomTaskDialog = ({ task, ident, handleReload, close }) => { //Подключение к контексту взаимодействия с сервером const { executeStored } = useContext(BackEndСtx); + //При необходимости перезагрузить данные + const handleReload = useCallback(() => onReload && onReload(), [onReload]); + //Изменение дат задачи const changeDates = useCallback(async () => { //Изменяем даты задачи @@ -182,7 +185,7 @@ const CustomTaskDialog = ({ task, ident, handleReload, close }) => { CustomTaskDialog.propTypes = { task: PropTypes.object.isRequired, ident: PropTypes.number.isRequired, - handleReload: PropTypes.func.isRequired, + onReload: PropTypes.func.isRequired, close: PropTypes.func.isRequired }; @@ -216,50 +219,29 @@ const taskRenderer = ({ task }) => { //Пример: Циклограмма "P8PCyclogram" const Cyclogram = ({ title }) => { + //Собственное состояние - идентификатор данных + const [dataIdent, setDataIdent] = useState(null); + //Собственное состояние - const [state, setState] = useState({ - init: false, - dataLoaded: false, - reload: true, - ident: null + const { cyclogram, isDataLoaded, doReload } = useP8PCyclogram({ + stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM", + storedArgs: { NIDENT: dataIdent }, + allowDataLoad: () => hasValue(dataIdent) }); //Подключение к контексту взаимодействия с сервером const { executeStored } = useContext(BackEndСtx); - //При необходимости перезагрузки - const handleReload = () => { - setState(pv => ({ ...pv, reload: true })); - }; - - //При необходимости обновить данные таблицы - useEffect(() => { - //Загрузка данных циклограммы с сервера - const loadData = async () => { - const data = await executeStored({ - stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM", - args: { NIDENT: state.ident }, - attributeValueProcessor: (name, val) => (["ddate_start", "ddate_end"].includes(name) ? formatDateJSONDateOnly(val) : val), - respArg: "COUT" - }); - setState(pv => ({ ...pv, dataLoaded: true, ...data.XCYCLOGRAM, reload: false })); - }; - //Если указан идентификатор и требуется перезагрузить - if (state.ident && state.reload) loadData(); - }, [state.ident, state.reload, executeStored]); - //При подключении компонента к странице useEffect(() => { //Инициализация данных циклограммы - const initData = async () => { - const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_INIT", args: { NIDENT: state.ident } }); - setState(pv => ({ ...pv, init: true, ident: data.NIDENT, reload: true })); + const initCyclogram = async () => { + const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_INIT", args: { NIDENT: null } }); + setDataIdent(data.NIDENT); }; - //Если требуется проинициализировать - if (!state.init) { - initData(); - } - }, [executeStored, state.ident, state.init]); + //Инициализируем данные циклограммы + initCyclogram(); + }, [executeStored]); return ( @@ -269,14 +251,14 @@ const Cyclogram = ({ title }) => { - {state.dataLoaded ? ( + {isDataLoaded ? ( ( - + )} taskRenderer={prms => taskRenderer(prms)} groupHeaderRenderer={prms => } diff --git a/app/panels/samples/data_grid.js b/app/panels/samples/data_grid.js index d9fa509..83aa3ed 100644 --- a/app/panels/samples/data_grid.js +++ b/app/panels/samples/data_grid.js @@ -7,13 +7,11 @@ //Подключение библиотек //--------------------- -import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React +import React, { useContext } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Typography, Grid, Stack, Icon, Box, Button } from "@mui/material"; //Интерфейсные элементы -import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции -import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных +import { P8PDataGrid, P8P_DATA_GRID_SIZE, useP8PDataGrid } from "../../components/p8p_data_grid"; //Таблица данных import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения -import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { APP_STYLES } from "../../../app.styles"; //Типовые стили @@ -87,78 +85,17 @@ export const groupCellRender = () => ({ cellStyle: { padding: "2px" } }); //Пример: Таблица данных "P8PDataGrid" const DataGrid = ({ title }) => { //Собственное состояние - таблица данных - const [dataGrid, setDataGrid] = useState({ - dataLoaded: false, - filters: null, - orders: null, - pageNumber: 1, - morePages: true, - expandable: true, - reloading: true + const { dataGrid, isDataLoaded, handleFilterChanged, handleOrderChanged, handlePageChange } = useP8PDataGrid({ + stored: "PKG_P8PANELS_SAMPLES.DATA_GRID", + pageSize: DATA_GRID_PAGE_SIZE }); - //Подключение к контексту взаимодействия с сервером - const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); - //Подключение к контексту приложения const { pOnlineShowDocument } = useContext(ApplicationСtx); - //Загрузка данных таблицы с сервера - const loadData = useCallback(async () => { - if (dataGrid.reloading) { - const data = await executeStored({ - stored: "PKG_P8PANELS_SAMPLES.DATA_GRID", - args: { - CFILTERS: { VALUE: object2Base64XML(dataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - CORDERS: { VALUE: object2Base64XML(dataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - NPAGE_NUMBER: dataGrid.pageNumber, - NPAGE_SIZE: DATA_GRID_PAGE_SIZE, - NINCLUDE_DEF: dataGrid.dataLoaded ? 0 : 1 - }, - respArg: "COUT" - }); - setDataGrid(pv => ({ - ...pv, - ...data.XDATA_GRID, - columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [], - rows: - data.XDATA_GRID.pagesCount > 0 || pv.pageNumber == 1 - ? [...(data.XDATA_GRID.rows || [])] - : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])], - groups: data.XDATA_GRID.groups - ? data.XDATA_GRID.pagesCount > 0 || pv.pageNumber == 1 - ? [...data.XDATA_GRID.groups] - : [...(pv.groups || []), ...data.XDATA_GRID.groups.filter(g => !pv.groups.find(pg => pg.name == g.name))] - : [...(pv.groups || [])], - dataLoaded: true, - reloading: false, - morePages: data.XDATA_GRID.morePages && (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE - })); - } - }, [dataGrid.reloading, dataGrid.filters, dataGrid.orders, dataGrid.dataLoaded, dataGrid.pageNumber, executeStored, SERV_DATA_TYPE_CLOB]); - - //При изменении состояния фильтра - const handleFilterChanged = ({ filters }) => setDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1, reloading: true })); - - //При изменении состояния сортировки - const handleOrderChanged = ({ orders }) => setDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reloading: true })); - - //При изменении количества отображаемых страниц - const handlePagesCountChanged = () => setDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reloading: true })); - - //При изменении страницы отображения - const handlePageChange = ({ page }) => { - setDataGrid(pv => ({ ...pv, pageNumber: page, reloading: true })); - }; - //При нажатии на копку контрагента const handleAgnButtonClicked = agnCode => pOnlineShowDocument({ unitCode: "AGNLIST", document: agnCode, inRnParameter: "in_AGNABBR" }); - //При необходимости обновить данные таблицы - useEffect(() => { - loadData(); - }, [dataGrid.reloading, loadData]); - //Генерация содержимого return (
@@ -168,7 +105,7 @@ const DataGrid = ({ title }) => { - {dataGrid.dataLoaded ? ( + {isDataLoaded ? ( { groupCellRender={groupCellRender} onOrderChanged={handleOrderChanged} onFilterChanged={handleFilterChanged} - onPagesCountChanged={handlePagesCountChanged} - onPageChanged={handlePageChange} rowExpandRender={({ row }) => ( )} + onPageChanged={handlePageChange} /> ) : null} diff --git a/app/panels/samples/gantt.js b/app/panels/samples/gantt.js index 392067e..092c393 100644 --- a/app/panels/samples/gantt.js +++ b/app/panels/samples/gantt.js @@ -10,9 +10,9 @@ import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Typography, Grid, Stack, Icon, FormControlLabel, Checkbox, Card, CardHeader, CardActions, Avatar, CardContent, Button } from "@mui/material"; //Интерфейсные элементы -import { formatDateJSONDateOnly, formatDateRF } from "../../core/utils"; //Вспомогательные функции +import { formatDateRF, hasValue } from "../../core/utils"; //Вспомогательные функции import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы -import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта +import { P8PGantt, useP8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером @@ -96,50 +96,35 @@ const taskDialogRenderer = ({ task, close }) => { //Пример: Диаграмма Ганта "P8Gantt" const Gantt = ({ title }) => { - //Собственное состояние - const [gantt, setGantt] = useState({ - init: false, - dataLoaded: false, - ident: null, - useCustomTaskDialog: false + //Собственное состояние - идентификатор данных + const [dataIdent, setDataIdent] = useState(null); + + //Собственное состояние - использование собственного диалога задачи + const [isCustomTaskDialog, setIsCustomTaskDialog] = useState(false); + + //Собственное состояние - диаграмма Ганта + const { gantt, isDataLoaded, doReload } = useP8PGantt({ + stored: "PKG_P8PANELS_SAMPLES.GANTT", + storedArgs: { NIDENT: dataIdent }, + allowDataLoad: () => hasValue(dataIdent) }); //Подключение к контексту взаимодействия с сервером const { executeStored } = useContext(BackEndСtx); - //Загрузка данных диаграммы с сервера - const loadData = useCallback(async () => { - const data = await executeStored({ - stored: "PKG_P8PANELS_SAMPLES.GANTT", - args: { NIDENT: gantt.ident }, - attributeValueProcessor: (name, val) => - name == "numb" ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val, - respArg: "COUT" - }); - setGantt(pv => ({ ...pv, dataLoaded: true, ...data.XGANTT })); - }, [gantt.ident, executeStored]); - - //Инициализация данных диаграммы - const initData = useCallback(async () => { - if (!gantt.init) { - const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_INIT", args: { NIDENT: gantt.ident } }); - setGantt(pv => ({ ...pv, init: true, ident: data.NIDENT })); - } - }, [gantt.init, gantt.ident, executeStored]); - //Изменение данных диаграммы const modifyData = useCallback( async ({ rn, start, end }) => { try { await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_MODIFY", - args: { NIDENT: gantt.ident, NRN: rn, DDATE_FROM: new Date(start), DDATE_TO: new Date(end) } + args: { NIDENT: dataIdent, NRN: rn, DDATE_FROM: new Date(start), DDATE_TO: new Date(end) } }); } finally { - loadData(); + doReload(); } }, - [gantt.ident, executeStored, loadData] + [dataIdent, executeStored, doReload] ); //Обработка измненения сроков задачи в диаграмме Гантта @@ -147,13 +132,13 @@ const Gantt = ({ title }) => { if (isMain) modifyData({ rn: task.rn, start, end }); }; - //При необходимости обновить данные таблицы - useEffect(() => { - if (gantt.ident) loadData(); - }, [gantt.ident, loadData]); - //При подключении компонента к странице useEffect(() => { + //Инициализация данных диаграммы Ганта + const initData = async () => { + const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_INIT", args: { NIDENT: null } }); + setDataIdent(data.NIDENT); + }; initData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -166,19 +151,19 @@ const Gantt = ({ title }) => { setGantt(pv => ({ ...pv, useCustomTaskDialog: !pv.useCustomTaskDialog }))} />} + control={ setIsCustomTaskDialog(!isCustomTaskDialog)} />} label="Отображать пользовательский диалог задачи" /> - {gantt.dataLoaded ? ( + {isDataLoaded ? ( ) : null} diff --git a/app/panels/samples/indicator.js b/app/panels/samples/indicator.js index 61bbbe5..eccb9a3 100644 --- a/app/panels/samples/indicator.js +++ b/app/panels/samples/indicator.js @@ -11,7 +11,7 @@ import React, { useContext } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Typography, Stack, Divider } from "@mui/material"; //Интерфейсные элементы import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений -import { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator } from "../../components/p8p_indicator"; //Индикатор +import { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator, useP8PIndicator } from "../../components/p8p_indicator"; //Индикатор //--------- //Константы @@ -30,6 +30,9 @@ const STYLES = { //Пример: Индикатор "P8PIndicator" const Indicator = ({ title }) => { + //Собственное состояние - индикатор данных с сервера + const { indicator, isDataLoaded } = useP8PIndicator({ stored: "PKG_P8PANELS_SAMPLES.INDICATOR" }); + //Подключение к контексту сообщений const { showMsgInfo } = useContext(MessagingСtx); @@ -140,6 +143,14 @@ const Indicator = ({ title }) => { /> ))} + Индикатор данных с сервера + + {isDataLoaded ? ( + + ) : ( + + )} +
); }; diff --git a/db/PKG_P8PANELS_SAMPLES.pck b/db/PKG_P8PANELS_SAMPLES.pck index 5cf3447..7a1438e 100644 --- a/db/PKG_P8PANELS_SAMPLES.pck +++ b/db/PKG_P8PANELS_SAMPLES.pck @@ -81,6 +81,12 @@ create or replace package PKG_P8PANELS_SAMPLES as SDATE_TO in varchar2 -- Дата окончания (в строковом представлении) ); + /* Индикатор */ + procedure INDICATOR + ( + COUT out clob -- Сериализованные данные для индикатора + ); + end PKG_P8PANELS_SAMPLES; / create or replace package body PKG_P8PANELS_SAMPLES as @@ -1313,6 +1319,26 @@ create or replace package body PKG_P8PANELS_SAMPLES as NTASK_GROUP => REC.TASK_GROUP); end loop; end CYCLOGRAM_TASK_MODIFY; + + /* Индикатор */ + procedure INDICATOR + ( + COUT out clob -- Сериализованные данные для индикатора + ) + is + RINDICATOR PKG_P8PANELS_VISUAL.TINDICATOR; -- Описание индикатора + begin + /* Формирование индикатора */ + RINDICATOR := PKG_P8PANELS_VISUAL.TINDICATOR_MAKE(SCAPTION => 'Данные загружены с помощью серверной процедуры', + SVALUE => '1000', + SICON => 'downloading', + NELEVATION => 2, + SVARIANT => PKG_P8PANELS_VISUAL.SINDICATOR_VARIANT_ELEVATION, + SBACKGROUND_COLOR => '#63a1df', + SCOLOR => 'black'); + /* Сериализуем собранные данные */ + COUT := PKG_P8PANELS_VISUAL.TINDICATOR_TO_XML(RINDICATOR => RINDICATOR); + end INDICATOR; end PKG_P8PANELS_SAMPLES; /