ЦИТК-1037 - Добавлены универсальные хуки для компонентов высокого уровня #43

Merged
Mim merged 1 commits from Dollerok/P8-Panels:main into main 2026-03-28 00:35:58 +03:00
21 changed files with 1275 additions and 610 deletions

351
README.md
View File

@ -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: <ASC|DESC>}`\
`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 (
<div style={STYLES.CONTAINER}>
@ -1991,7 +2062,7 @@ const Chart = ({ title }) => {
<Grid container spacing={0} pt={5} direction="column" alignItems="center">
<Grid item xs={12}>
<Paper elevation={6} sx={STYLES.CHART_PAPER}>
{chart.loaded ? <P8PChart {...chart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
{isDataLoaded ? <P8PChart {...chart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
</Paper>
</Grid>
</Grid>
@ -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 }) => {
</Typography>
<FormControlLabel
sx={STYLES.CONTROL}
control={<Checkbox onChange={() => setGantt(pv => ({ ...pv, useCustomTaskDialog: !pv.useCustomTaskDialog }))} />}
control={<Checkbox onChange={() => setIsCustomTaskDialog(!isCustomTaskDialog)} />}
label="Отображать пользовательский диалог задачи"
/>
<Grid container direction="column" alignItems="center">
<Grid item xs={12}>
{gantt.dataLoaded ? (
{isDataLoaded ? (
<P8PGantt
{...P8P_GANTT_CONFIG_PROPS}
{...gantt}
containerStyle={STYLES.GANTT_CONTAINER}
onTaskDatesChange={handleTaskDatesChange}
taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={gantt.useCustomTaskDialog ? taskDialogRenderer : null}
taskDialogRenderer={isCustomTaskDialog ? taskDialogRenderer : null}
/>
) : null}
</Grid>
@ -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 (
<Box>
@ -2929,7 +3043,7 @@ const Cyclogram = ({ title }) => {
</Typography>
<Grid container direction="column" alignItems="center">
<Grid item xs={12}>
{state.dataLoaded ? (
{isDataLoaded ? (
<P8PCyclogram
{...P8P_CYCLOGRAM_CONFIG_PROPS}
{...state}
@ -3014,6 +3128,35 @@ const MyPanel = () => {
`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" данного репозитория.

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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
};

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 (
<Box p={1}>
@ -212,23 +134,23 @@ const Projects = () => {
<>
<Grid item xs={4}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{problemsChart.loaded ? <P8PChart {...problemsChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
{isProblemsLoaded ? <P8PChart {...problemsChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
</Paper>
</Grid>
<Grid item xs={4}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{customersChart.loaded ? <P8PChart {...customersChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
{isCustomersLoaded ? <P8PChart {...customersChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
</Paper>
</Grid>
<Grid item xs={4}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{costNotesChart.loaded ? <P8PChart {...costNotesChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
{isCostNotesLoaded ? <P8PChart {...costNotesChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
</Paper>
</Grid>
</>
) : null}
<Grid item xs={12}>
{projectsDataGrid.dataLoaded ? (
{projectsLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{
@ -259,22 +181,18 @@ const Projects = () => {
onPagesCountChanged={handlePagesCountChanged}
/>
) : null}
{projectsDataGrid.selectedProject ? (
{selectedProject ? (
<P8PFullScreenDialog
title={`Этапы проекта "${projectsDataGrid.selectedProject.SNAME_USL}"`}
title={`Этапы проекта "${selectedProject.SNAME_USL}"`}
onClose={handleStagesClose}
contentProps={{ sx: COMMON_PROJECTS_STYLES.FULL_SCREEN_DIALOG_CONTENT }}
>
<Stages
project={projectsDataGrid.selectedProject.NRN}
projectName={projectsDataGrid.selectedProject.SNAME_USL}
filters={projectsDataGrid.stagesFilters}
/>
<Stages project={selectedProject.NRN} projectName={selectedProject.SNAME_USL} filters={stagesFilters} />
</P8PFullScreenDialog>
) : null}
</Grid>
</Grid>
{problemsChart.loaded || customersChart.loaded || costNotesChart.loaded ? (
{isProblemsLoaded || isCustomersLoaded || isCostNotesLoaded ? (
<Fab size="small" color="secondary" sx={STYLES.CHART_FAB} onClick={() => setShowCharts(!showCharts)}>
<Icon>{showCharts ? "expand_less" : "expand_more"}</Icon>
</Fab>

View File

@ -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 (
<Box pt={2}>
{stageArtsDataGrid.dataLoaded ? (
{isDataLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STYLES.TABLE_ARTS((stageArtsDataGrid.filters || []).length > 0), elevation: 0 }}

View File

@ -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 (
<Box pt={2}>
{stageContractsDataGrid.dataLoaded ? (
{isDataLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{

View File

@ -7,10 +7,9 @@
//Подключение библиотек
//---------------------
import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React
import React, { useState, 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 { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог
import { StageArts } from "./stage_arts"; //Калькуляция этапа проекта
@ -50,69 +50,38 @@ const STYLES = {
//Список этапов проекта
const Stages = ({ project, projectName, filters }) => {
//Собственное состояние
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 (
<Box pt={2}>
{stagesDataGrid.dataLoaded ? (
{isStagesLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{
@ -210,22 +165,22 @@ const Stages = ({ project, projectName, filters }) => {
onPagesCountChanged={handlePagesCountChanged}
/>
) : null}
{stagesDataGrid.showStageContracts ? (
{selectedStage.showStageContracts ? (
<P8PFullScreenDialog
title={`Договоры этапа "${stagesDataGrid.selectedStageNumb}" проекта "${projectName}"`}
title={`Договоры этапа "${selectedStage.stageNumb}" проекта "${projectName}"`}
onClose={handleStageContractsClose}
contentProps={{ sx: COMMON_PROJECTS_STYLES.FULL_SCREEN_DIALOG_CONTENT }}
>
<StageContracts stage={stagesDataGrid.showStageContracts} filters={stagesDataGrid.stageContractsFilters} />
<StageContracts stage={selectedStage.showStageContracts} filters={selectedStage.stageContractsFilters} />
</P8PFullScreenDialog>
) : null}
{stagesDataGrid.showStageArts ? (
{selectedStage.showStageArts ? (
<P8PFullScreenDialog
title={`Калькуляция этапа "${stagesDataGrid.selectedStageNumb}" проекта "${projectName}"`}
title={`Калькуляция этапа "${selectedStage.stageNumb}" проекта "${projectName}"`}
onClose={handleStageArtsClose}
contentProps={{ sx: COMMON_PROJECTS_STYLES.FULL_SCREEN_DIALOG_CONTENT }}
>
<StageArts stage={stagesDataGrid.showStageArts} filters={stagesDataGrid.stageArtsFilters} />
<StageArts stage={selectedStage.showStageArts} filters={selectedStage.stageArtsFilters} />
</P8PFullScreenDialog>
) : null}
</Box>

View File

@ -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 (
<div style={STYLES.CONTAINER}>
@ -70,7 +54,7 @@ const Chart = ({ title }) => {
<Grid container spacing={0} pt={5} direction="column" alignItems="center">
<Grid item xs={12}>
<Paper elevation={6} sx={STYLES.CHART_PAPER}>
{chart.loaded ? <P8PChart {...chart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
{isDataLoaded ? <P8PChart {...chart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
</Paper>
</Grid>
</Grid>

View File

@ -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 (
<Box>
@ -269,14 +251,14 @@ const Cyclogram = ({ title }) => {
</Typography>
<Grid container direction="column" alignItems="center">
<Grid item xs={12}>
{state.dataLoaded ? (
{isDataLoaded ? (
<P8PCyclogram
{...P8P_CYCLOGRAM_CONFIG_PROPS}
{...state}
{...cyclogram}
containerStyle={STYLES.CYCLOGRAM_CONTAINER}
lineHeight={LINE_HEIGHT}
taskDialogRenderer={prms => (
<CustomTaskDialog task={prms.task} ident={state.ident} handleReload={handleReload} close={prms.close} />
<CustomTaskDialog task={prms.task} ident={dataIdent} onReload={doReload} close={prms.close} />
)}
taskRenderer={prms => taskRenderer(prms)}
groupHeaderRenderer={prms => <CustomGroupHeader group={prms.group} />}

View File

@ -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 (
<div style={STYLES.CONTAINER}>
@ -168,7 +105,7 @@ const DataGrid = ({ title }) => {
<Grid container spacing={1} pt={5}>
<Grid item xs={12}>
<Box p={5} display="flex" justifyContent="center" alignItems="center">
{dataGrid.dataLoaded ? (
{isDataLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...dataGrid}
@ -180,11 +117,10 @@ const DataGrid = ({ title }) => {
groupCellRender={groupCellRender}
onOrderChanged={handleOrderChanged}
onFilterChanged={handleFilterChanged}
onPagesCountChanged={handlePagesCountChanged}
onPageChanged={handlePageChange}
rowExpandRender={({ row }) => (
<Button onClick={() => handleAgnButtonClicked(row.SAGNABBR)}>Показать в разделе</Button>
)}
onPageChanged={handlePageChange}
/>
) : null}
</Box>

View File

@ -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 }) => {
</Typography>
<FormControlLabel
sx={STYLES.CONTROL}
control={<Checkbox onChange={() => setGantt(pv => ({ ...pv, useCustomTaskDialog: !pv.useCustomTaskDialog }))} />}
control={<Checkbox onChange={() => setIsCustomTaskDialog(!isCustomTaskDialog)} />}
label="Отображать пользовательский диалог задачи"
/>
<Grid container direction="column" alignItems="center">
<Grid item xs={12}>
{gantt.dataLoaded ? (
{isDataLoaded ? (
<P8PGantt
{...P8P_GANTT_CONFIG_PROPS}
{...gantt}
containerStyle={STYLES.GANTT_CONTAINER}
onTaskDatesChange={handleTaskDatesChange}
taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={gantt.useCustomTaskDialog ? taskDialogRenderer : null}
taskDialogRenderer={isCustomTaskDialog ? taskDialogRenderer : null}
/>
) : null}
</Grid>

View File

@ -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 }) => {
/>
))}
</Stack>
<Divider>Индикатор данных с сервера</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{isDataLoaded ? (
<P8PIndicator {...indicator} />
) : (
<P8PIndicator caption={"Ошибка загрузки данных с сервера"} value={0} state={P8P_INDICATOR_STATE.WARN} />
)}
</Stack>
</div>
);
};

View File

@ -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;
/