ЦИТК-1037 - Добавлены универсальные хуки для компонентов высокого уровня #43
351
README.md
351
README.md
@ -1459,6 +1459,55 @@ const MyPanel = () => {
|
||||
`PKG_P8PANELS_VISUAL.TDG_FILTERS_SET_QUERY` - процедура, вспомогательная утилита, производит вызов указанной серверной процедуры отбора с учётом переданных переменных окружения и значений в `RFILTERS`\
|
||||
`PKG_P8PANELS_VISUAL.UTL_ROWS_LIMITS_CALC` - процедура, вспомогательная утилита, служит для конвертации номера страницы данных и размера страницы данных в границы диапазона строк выборки (как правило, клиентскому приложению удобнее прислать на сервер текущий номер страницы и её размер, в то время к в запросах, для выборки, удобнее применять границы диапазонов строк)
|
||||
|
||||
**Универсальный хук**
|
||||
|
||||
Для компонента существует универсальный хук `useP8PDataGrid`, который помогает автоматизировать и скрыть логику получения/обновления данных для таблицы. Использование универсального хука представлено в примере использования `P8PDataGrid` ниже.
|
||||
|
||||
Для использования универсального хука на панели его необходимо импортировать:
|
||||
|
||||
```
|
||||
import {useP8PDataGrid} from "../../components/p8p_data_grid";
|
||||
|
||||
const MyPanel = () => {
|
||||
const { dataGrid, isDataLoaded } = useP8PDataGrid({ stored: "ХРАНИМЫЙ_ОБЪЕКТ" });
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Входные параметры:**\
|
||||
`stored` - обязательный, строка, имя исполняемого хранимого объекта (для пакетных - "ПАКЕТ.ОБЪЕКТ")\
|
||||
`respArg` - необязательный, строка, имя выходного параметра исполняемого объекта, значение которого необходимо вернуть как данные ответа, по умолчанию - `COUT`\
|
||||
`contentNodeName` - необязательный, строка, имя узла, содержащего информацию о таблице, по умолчанию `XDATA_GRID`\
|
||||
`filtersNodeName` - необязательный, строка, имя узла, содержащего информацию о фильтрах, отправляемых на сервер, по умолчанию `filters`. При использовании в хранимом объекте стандартной функции `PKG_P8PANELS_VISUAL.TDG_FILTERS_FROM_XML` для десериализации данных важно, чтобы данный параметр имел значение `filters`\
|
||||
`ordersNodeName` - необязательный, строка, имя узла, содержащего информацию о сортировках, отправляемых на сервер, по умолчанию `orders`. При использовании в хранимом объекте стандартной функции `PKG_P8PANELS_VISUAL.TDG_ORDERS_FROM_XML` для десериализации данных важно, чтобы данный параметр имел значение `orders`\
|
||||
`pageSize` - необязательный, число, объем выборки данных на страницу или на догрузку, по умолчанию `10`. Передается в параметр `NPAGE_SIZE` хранимого объекта из переданного свойства `stored`\
|
||||
`reloadDef` - необязательный, булево, определяет необходимость включения описания колонок таблицы в ответ, по умолчанию `false`. Передается в параметр `NINCLUDE_DEF` хранимого объекта из переданного свойства `stored`. При значении `true` соответствующий параметр `NINCLUDE_DEF` будет всегда равен `1`, в ином случае при первом обращении к хранимому объекту передается `1`, а в последующих `0`\
|
||||
`initFilters` - необязательный, массив, начальное состояние фильтров таблицы, содержит объекты вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, from: <НАЧАЛО_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>, to: <ОКОНЧАНИЕ_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>}`\
|
||||
`initOrders` - необязательный, массив, начальное состояние сортировок таблицы, содержит объекты вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, direction: <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" данного репозитория.
|
||||
|
||||
@ -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 };
|
||||
|
||||
136
app/components/p8p_chart_hooks.js
Normal file
136
app/components/p8p_chart_hooks.js
Normal 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 };
|
||||
@ -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 };
|
||||
|
||||
145
app/components/p8p_cyclogram_hooks.js
Normal file
145
app/components/p8p_cyclogram_hooks.js
Normal 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 };
|
||||
@ -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
|
||||
};
|
||||
|
||||
224
app/components/p8p_data_grid_hooks.js
Normal file
224
app/components/p8p_data_grid_hooks.js
Normal 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 };
|
||||
@ -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 };
|
||||
|
||||
148
app/components/p8p_gantt_hooks.js
Normal file
148
app/components/p8p_gantt_hooks.js
Normal 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 };
|
||||
@ -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 };
|
||||
|
||||
143
app/components/p8p_indicator_hooks.js
Normal file
143
app/components/p8p_indicator_hooks.js
Normal 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 };
|
||||
@ -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>
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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={{
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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} />}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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;
|
||||
/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user