diff --git a/README.md b/README.md
index e904e76..e458503 100644
--- a/README.md
+++ b/README.md
@@ -792,8 +792,8 @@ const Mui = ({ title }) => {
{title}
-
-
+
+
{
```
import { P8PDataGrid } from "../../components/p8p_data_grid";
-import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
+import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper";
const MyPanel = () => {
return (
@@ -1558,8 +1558,8 @@ import { ApplicationСtx } from "../../context/application"; //Контекст
const STYLES = {
CONTAINER: { textAlign: "center", paddingTop: "20px" },
TITLE: { paddingBottom: "15px" },
- CHART: { maxHeight: "500px", display: "flex", justifyContent: "center" },
- CHART_PAPER: { height: "100%", padding: "5px" }
+ CHART: { minWidth: "80vw", maxHeight: "80vw", display: "flex", justifyContent: "center" },
+ CHART_PAPER: { padding: "25px" }
};
//Пример: Графики "P8PChart"
@@ -1599,14 +1599,12 @@ const Chart = ({ title }) => {
{title}
-
-
-
-
+
+
+
{chart.loaded ? : null}
-
);
@@ -1617,12 +1615,273 @@ const Chart = ({ title }) => {
##### Диаграмма ганта "P8PGantt"
-Раздел в разработке.
+Компонент предназначен для отображения данных в виде диаграммы Ганта. Основан на библиотеке [Frappe-Gantt](https://frappe.io/gantt). Поддерживается:
+
+- Редактирование сроков задачи перетаскиванием
+- Отображение и редактирование прогресса задачи
+- Установка признаков "только для чтения" для всей диаграммы и отдельной задачи (для сроков и прогресса задачи - независимо)
+- Форматирование цвета заливки и текста задачи
+- Дополнение задачи произвольными учётными атрибутами
+- Диалоговый редактор задачи, отображающий её дополнительные атрибуты с возможностью настройки их форматирования
+
+
+
**Подключение**
+Клиентская часть диаграммы реализована в компоненте `P8PGantt`, объявленном в "app/components/p8p_gantt". Для использования компонента на панели его необходимо импортировать:
+
+```
+import { P8PGantt } from "../../components/p8p_gantt";
+
+const MyPanel = () => {
+ return (
+
+ );
+}
+```
+
**Свойства**
+`height` - обязательный, число\
+`title` - необязательный, строка\
+`titleStyle` - необязательный, объект\
+`onTitleClick` - необязательный, функция\
+`zoomBar` - необязательный, логический\
+`readOnly` - необязательный, логический\
+`readOnlyDates` - необязательный, логический\
+`readOnlyProgress` - необязательный, логический\
+`zoom` - необязательный, число\
+`tasks` - обязательный, массив\
+`taskAttributes` - необязательный, массив\
+`taskColors` - необязательный, массив\
+`onTaskDatesChange` - необязательный, функция\
+`onTaskProgressChange` - необязательный, функция\
+`taskAttributeRenderer` - необязательный, функция\
+`noDataFoundText` - обязательный, строка\
+`numbTaskEditorCaption` - обязательный, строка\
+`nameTaskEditorCaption` - обязательный, строка\
+`startTaskEditorCaption` - обязательный, строка\
+`endTaskEditorCaption` - обязательный, строка\
+`progressTaskEditorCaption` - обязательный, строка\
+`legendTaskEditorCaption` - обязательный, строка\
+`okTaskEditorBtnCaption` - обязательный, строка\
+`cancelTaskEditorBtnCaption` - обязательный, строка
+
+Некоторые параметры диаграммы Ганта вынесены в свойства компонента `P8PGantt` для минимизации его связи с фреймворком и поддержания возможности стороннего использования (например, свойства `noDataFoundText`, `okTaskEditorBtnCaption`, `cancelTaskEditorBtnCaption` и т.п.) . Тем не менее, в настройках фреймворка и его окружении уже есть реализации для данных свойств. Например, в "app.text.js" уже содержатся объявления типовых констант для текстов подписей кнопок и пунктов меню. Поэтому, в "app/config_wrapper.js" для привязки свойств `P8PGantt` к контексту фреймворка реализованы специальные декораторы и объекты-шаблоны, облегчающие подключение экземпляра `P8PGantt` к панели и снимающие с разработчика необходимость указывать некоторые из перечисленных выше обязательных свойств. В предложенном ниже примере, из модуля "config_wrapper" в панель импортируется объект `P8P_GANTT_CONFIG_PROPS`, который уже содержт преднастроенное описание свойств `noDataFoundText`, `numbTaskEditorCaption`, `nameTaskEditorCaption`, `startTaskEditorCaption`, `endTaskEditorCaption`, `progressTaskEditorCaption`, `legendTaskEditorCaption`, `okTaskEditorBtnCaption` и `cancelTaskEditorBtnCaption`, полученное из окружения фреймворка. Таким образом, прикладной разработчик может не указывать их значения при использовании `P8PGantt` (если по каким-то причинам не хочет их переопределить, конечно).
+
+```
+import { P8PGantt } from "../../components/p8p_gantt";
+import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper";
+
+const MyPanel = () => {
+ return (
+
+ );
+}
+```
+
**API на сервере БД**
**Пример**
+
+Код на стороне сервера БД (хранимая процедура в клиентском пакете `PKG_P8PANELS_SAMPLES`, требует наличия таблицы `P8PNL_SMPL_GANTT`, см. "db/P8PNL_SMPL_GANTT.sql"):
+
+```
+ procedure GANTT
+ (
+ NIDENT in number, -- Идентификатор процесса
+ COUT out clob -- Сериализованные данные для диаграммы Ганта
+ )
+ is
+ /* Константы */
+ SBG_COLOR_STAGE constant PKG_STD.TSTRING := 'cadetblue'; -- Цвет заливки этапов
+ SBG_COLOR_JOB constant PKG_STD.TSTRING := 'lightgreen'; -- Цвет заливки работ
+ /* Переменные */
+ RG PKG_P8PANELS_VISUAL.TGANTT; -- Описание диаграммы Ганта
+ RGT PKG_P8PANELS_VISUAL.TGANTT_TASK; -- Описание задачи для диаграммы
+ STASK_BG_COLOR PKG_STD.TSTRING; -- Цвет фона задачи
+ begin
+ /* Инициализируем диаграмму Ганта */
+ RG := PKG_P8PANELS_VISUAL.TGANTT_MAKE(STITLE => 'Задачи на ' || TO_CHAR(EXTRACT(year from sysdate)) || ' год',
+ NZOOM => PKG_P8PANELS_VISUAL.NGANTT_ZOOM_MONTH);
+ /* Добавим динамические атрибуты к задачам */
+ PKG_P8PANELS_VISUAL.TGANTT_ADD_TASK_ATTR(RGANTT => RG, SNAME => 'type', SCAPTION => 'Тип');
+ /* Добавим описание цветов задач */
+ PKG_P8PANELS_VISUAL.TGANTT_ADD_TASK_COLOR(RGANTT => RG,
+ SBG_COLOR => SBG_COLOR_JOB,
+ SDESC => 'Этот цвет для задач.');
+ PKG_P8PANELS_VISUAL.TGANTT_ADD_TASK_COLOR(RGANTT => RG,
+ SBG_COLOR => SBG_COLOR_STAGE,
+ SDESC => 'Этим цветом выделены этапы.');
+ /* Обходим буфер */
+ for C in (select T.* from P8PNL_SMPL_GANTT T where T.IDENT = NIDENT order by T.RN)
+ loop
+ /* Определимся с форматированием */
+ if (C.TYPE = 0) then
+ STASK_BG_COLOR := SBG_COLOR_STAGE;
+ else
+ STASK_BG_COLOR := SBG_COLOR_JOB;
+ end if;
+ /* Сформируем задачу */
+ RGT := PKG_P8PANELS_VISUAL.TGANTT_TASK_MAKE(NRN => C.RN,
+ SNUMB => C.NUMB,
+ SCAPTION => C.NUMB || ' - ' || C.NAME,
+ SNAME => C.NAME,
+ DSTART => C.DATE_FROM,
+ DEND => C.DATE_TO,
+ SBG_COLOR => STASK_BG_COLOR);
+ PKG_P8PANELS_VISUAL.TGANTT_TASK_ADD_ATTR_VAL(RGANTT => RG,
+ RTASK => RGT,
+ SNAME => 'type',
+ SVALUE => C.TYPE,
+ BCLEAR => true);
+ /* Добавляем задачу в диаграмму */
+ PKG_P8PANELS_VISUAL.TGANTT_ADD_TASK(RGANTT => RG, RTASK => RGT);
+ end loop;
+ /* Сериализуем собранные данные */
+ COUT := PKG_P8PANELS_VISUAL.TGANTT_TO_XML(RGANTT => RG);
+ end GANTT;
+```
+
+Код панели на стороне клиента (WEB-приложения):
+
+```
+import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
+import { Typography, Grid, Stack, Icon, Box } from "@mui/material"; //Интерфейсные элементы
+import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции
+import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта
+import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
+import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
+
+//Высота диаграммы Ганта
+const GANTT_HEIGHT = "600px";
+
+//Ширина диаграммы Ганта
+const GANTT_WIDTH = "98vw";
+
+//Стили
+const STYLES = {
+ CONTAINER: { textAlign: "center", paddingTop: "20px" },
+ TITLE: { paddingBottom: "15px" },
+ GANTT_CONTAINER: { height: GANTT_HEIGHT, width: GANTT_WIDTH }
+};
+
+//Формирование значения для колонки "Тип задачи"
+const formatTaskTypeValue = value => {
+ const [text, icon] = value == 0 ? ["Этап проекта", "check"] : ["Работа проекта", "work_outline"];
+ return (
+
+ {icon}
+ {text}
+
+ );
+};
+
+//Генерация кастомных представлений атрибутов задачи в редакторе
+const taskAttributeRenderer = ({ task, attribute }) => {
+ switch (attribute.name) {
+ case "type":
+ return formatTaskTypeValue(task.type);
+ default:
+ return null;
+ }
+};
+
+//Пример: Диаграмма Ганта "P8Gantt"
+const Gantt = ({ title }) => {
+ //Собственное состояние
+ const [state, setState] = useState({
+ init: false,
+ dataLoaded: false,
+ ident: null,
+ ganttDef: {},
+ ganttTasks: []
+ });
+
+ //Подключение к контексту взаимодействия с сервером
+ const { executeStored } = useContext(BackEndСtx);
+
+ //Загрузка данных диаграммы с сервера
+ const loadData = useCallback(async () => {
+ const data = await executeStored({
+ stored: "PKG_P8PANELS_SAMPLES.GANTT",
+ args: { NIDENT: state.ident },
+ attributeValueProcessor: (name, val) =>
+ name == "numb" ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val,
+ respArg: "COUT"
+ });
+ setState(pv => ({ ...pv, dataLoaded: true, ganttDef: { ...data.XGANTT_DEF }, ganttTasks: [...data.XGANTT_TASKS] }));
+ }, [state.ident, executeStored]);
+
+ //Инициализация данных диаграммы
+ const initData = useCallback(async () => {
+ if (!state.init) {
+ const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_INIT", args: { NIDENT: state.ident } });
+ setState(pv => ({ ...pv, init: true, ident: data.NIDENT }));
+ }
+ }, [state.init, state.ident, executeStored]);
+
+ //Изменение данных диаграммы
+ const modifyData = useCallback(
+ async ({ rn, start, end }) => {
+ try {
+ await executeStored({
+ stored: "PKG_P8PANELS_SAMPLES.GANTT_MODIFY",
+ args: { NIDENT: state.ident, NRN: rn, DDATE_FROM: new Date(start), DDATE_TO: new Date(end) }
+ });
+ } finally {
+ loadData();
+ }
+ },
+ [state.ident, executeStored, loadData]
+ );
+
+ //Обработка измненения сроков задачи в диаграмме Гантта
+ const handleTaskDatesChange = ({ task, start, end, isMain }) => {
+ if (isMain) modifyData({ rn: task.rn, start, end });
+ };
+
+ //При необходимости обновить данные таблицы
+ useEffect(() => {
+ if (state.ident) loadData();
+ }, [state.ident, loadData]);
+
+ //При подключении компонента к странице
+ useEffect(() => {
+ initData();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ //Генерация содержимого
+ return (
+
+
+ {title}
+
+
+
+ {state.dataLoaded ? (
+
+
+
+ ) : null}
+
+
+
+ );
+};
+```
+
+Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/gantt.js" данного репозитория соответственно.
diff --git a/docs/img/68.png b/docs/img/68.png
new file mode 100644
index 0000000..21c3ed2
Binary files /dev/null and b/docs/img/68.png differ
diff --git a/docs/img/69.png b/docs/img/69.png
new file mode 100644
index 0000000..312e603
Binary files /dev/null and b/docs/img/69.png differ