diff --git a/README.md b/README.md
index c6a6461..58cc08f 100644
--- a/README.md
+++ b/README.md
@@ -2366,6 +2366,503 @@ const Svg = ({ title }) => {
Полные актуальные исходные коды примера можно увидеть в "app/panels/samples/svg.js" данного репозитория соответственно.
+##### Циклограмма "P8PCyclogram"
+
+Компонент предназначен для отображения данных в виде циклограммы. Поддерживается:
+
+- Группировка задач с отображением описания группы при наведении
+- Форматирование цвета заливки задачи, текста задачи и цвета при наведении на задачу/группу
+- Дополнение задачи произвольными учётными атрибутами
+- Диалоговый редактор задачи, отображающий её дополнительные атрибуты с возможностью настройки их форматирования
+- Отображение произвольного пользовательского диалога в качестве карточки задачи/редактора задачи
+- Масштабирование визуального представления
+
+
+
+
+**Подключение**
+
+Клиентская часть циклограммы реализована в компоненте `P8PCyclogram`, объявленном в "app/components/p8p_cyclogram". Для использования компонента на панели его необходимо импортировать:
+
+```
+import { P8PCyclogram } from "../../components/p8p_cyclogram";
+
+const MyPanel = () => {
+ return (
+
+
+
+ );
+}
+```
+
+**Свойства**
+
+`containerStyle` - необязательный, объект, стили, которые будут применены к компонету `div`, являющемуся контейнером циклограммы\
+`lineHeight` - необязательный, число, высота строки в пикселях (по умолчанию 20)\
+`title` - необязательный, строка, заголовок циклограммы (если не указан - не отображается)\
+`titleStyle` - необязательный, объект, стили, которые будут применены к компонету `Typography` заголовка циклограммы\
+`onTitleClick` - необязательный, функция, будет вызвана при нажатии пользователем на заголовок (если указана - заголовок формируется в виде гиперссылки), сигнатура функции `f()`, результат функции не интерпретируется\
+`zoomBar` - необязательный, логический, признак отображения панели управления масштабом (по умолчанию - не отображается)\
+`zoom` - необязательный, число, масштаб циклограммы\
+`columns` - обязательный, массив, колонки, отображаемые на циклограмме, должен состоять из объектов вида `{name: <НАИМЕНОВАНИЕ>, start: <ПОЗИЦИЯ_НАЧАЛА_КОЛОНКИ>, end: <ПОЗИЦИЯ_ОКОНЧАНИЯ_КОЛОНКИ>}` (см. константу `P8P_CYCLOGRAM_COLUMN_SHAPE` в коде компонента)\
+`columnRenderer` - необязательный, функция формирования представления колонки (если не указана - отображение по умолчанию). Сигнатура функции: `f({column})`. Будет вызвана для каждой колонки циклограммы, в функцию будет передан объект, в поле `column` которого будет содержаться описание текущей генерируемой колонки (элемент массива `columns`, см. выше описание полей). Должна возвращать значение или React-компонент.\
+`groups` - необязательный, массив, группы задач, которые отображаются на циклограмме, должен состоять из объектов вида `{name: <НАИМЕНОВАНИЕ>, height: <ВЫСОТА_ОТОБРАЖЕНИЯ_ГРУППЫ>, width: <ШИРИНА_ОТОБРАЖЕНИЯ_ГРУППЫ>, visible: <ПРИЗНАК_ОТОБРАЖЕНИЯ_ГРУППЫ>}` (см. константу `P8P_CYCLOGRAM_GROUP_SHAPE` в коде компонента). Группа отображается только при наведении на соответствующую задачу, которая относится к данной группе.\
+`groupHeaderRenderer` - необязательный, функция формирования представления всплывающей информации о группе (если не указана - отображение по умолчанию). Сигнатура функции: `f({group})`. Будет вызвана для каждой группы циклограммы, в функцию будет передан объект, в поле `group` которого будет содержаться описание текущей генерируемой группы (элемент массив `groups`, см. выше описание полей). Должна возвращать значение или React-компонент.\
+`tasks` - обязательный, массив, задачи, отображаемые на циклограмме, должен состоять из объектов вида `{id: <УНИКАЛЬНЫЙ_ИДЕНТИФИКАТОР>, rn: <ССЫЛКА_НА_ЗАПИСЬ_В_СИСТЕМЕ>, name: <НАИМЕНОВАНИЕ>, fullName: <ПОЛНОЕ_НАИМЕНОВАНИЕ>, lineNumb: <НОМЕР_СТРОКИ_ЗАДАЧИ>, start: <ПОЗИЦИЯ_НАЧАЛА_ЗАДАЧИ>, end: <ПОЗИЦИЯ_ОКОНЧАНИЯ_ЗАДАЧИ>, group: <НАИМЕНОВАНИЕ_ГРУППЫ>, bgColor: <ЦВЕТ_ЗАЛИВКИ>, textColor: <ЦВЕТ_ТЕКСТА>, highlightColor: <ЦВЕТ_НАВЕДЕНИЯ>, [<ИМЯ_ДОПОЛНИТЕЛЬНОГО_АТРИБУТА1>:<ЗНАЧЕНИЕ1>, <ИМЯ_ДОПОЛНИТЕЛЬНОГО_АТРИБУТА2>:<ЗНАЧЕНИЕ2>,...]}` (см. константу `P8P_CYCLOGRAM_TASK_SHAPE` в коде компонента).\
+`taskRenderer` - необязательный, функция формирования представления задачи на циклограмме (если не указана - отображение по умолчанию). Сигнатура функции: `f({task, taskHeight, taskWidth})`. Будет вызвана для каждой задачи циклограммы, в функцию будет передан объект, в поле `task` которого будет содержаться описание текущей генерируемой задаче (элемент массив `tasks`, см. выше описание полей), в поле `taskHeight` описание высоты задачи, в поле `taskWidth` описание ширины задачи. Должна возвращать объект вида `{taskStyle: <СТИЛИ_ДЛЯ_ЭЛЕМЕНТА_ЗАДАЧИ>, taskProps: <СВОЙСТВА_ДЛЯ_ЭЛЕМЕНТА_ЗАДАЧИ>, data: <ЗНАЧЕНИЕ_ИЛИ_КОМПОНЕНТ_React_ДЛЯ_СОДЕРЖИМОГО_ЭЛЕМЕНТА_ЗАДАЧИ>}` или `undefined`, если для задачи не предполагается специального представления.\
+`taskAttributes` - необязательный, массив, состав (не значения) дополнительных атрибутутов задач, должен состоять из объектов вида `{name: <ИМЯ_ДОПОЛНИТЕЛЬНОГО_АТРИБУТА>, caption: <ЗАГОЛОВОК_ДОПОЛНИТЕЛЬНОГО_АТРИБУТА>, visible: <ПРИЗНАК_ОТОБРАЖЕНИЯ_ДОПОЛНИТЕЛЬНОГО_АТРИБУТА - true|false>}` (см. константу `P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE` в коде компонента)\
+`taskAttributeRenderer` - необязательный, функция, если указана - будет вызвана при отображении диалога редактора здачи, результат функции будет применён для отображения области дополнительных атрибутов задачи в диалоге редактора, если не указана - дополнительные атрибуты будут отображены с форматированием по умолчанию. Сигнатура функции - `f({task, attribute})`, в функцию будет передан объект в поле `task`, которого, будет содержаться описание задачи для которой отображается редактор (элемент массива `tasks`, см. выше описание полей), в поле `attribute` - описание дополнительного атрибута формируемого в диалоге редактора (элемент массива `taskAttributes`, см. выше описание полей). Должна возвращать значение или React-компонент.\
+`taskDialogRenderer` - необязательный, функция, если указана - будет вызвана до отображения диалога редактора задачи. Результат функции будет показан в качестве содержимого диалога редактора, вместо типовой формы. Сигнатура функции - `f({task, taskAttributes, close})`, в функцию будет передан объект в поле `task`, которого, будет содержаться описание задачи для которой отображается редактор (элемент массива `tasks`, см. выше описание полей), в поле `taskAttributes` - массив `taskAttributes` (см. выше описание полей), описывающий состав полей задачи, в поле `close` - функция закрытия диалога задачи, может быть вызвана возвращаемым Reac-компонентом для сокрытия диалога. Должна возвращать значение или React-компонент.\
+`noDataFoundText` - обязательный, строка, текст для отображения ошибки об отсутствии данных\
+`nameTaskEditorCaption` - обязательный, строка, подпись стандартного атрибута `name` в диалоге редактора задачи\
+`okTaskEditorBtnCaption` - обязательный, строка, подпись кнопки "ОК" диалога редактора задачи\
+`cancelTaskEditorBtnCaption` - обязательный, строка, подпись кнопки "ОТМЕНА" диалога редактора задачи
+
+Некоторые параметры циклограммы вынесены в свойства компонента `P8PCyclogram` для минимизации его связи с фреймворком и поддержания возможности стороннего использования (например, свойства `noDataFoundText`, `okTaskEditorBtnCaption`, `cancelTaskEditorBtnCaption` и т.п.) . Тем не менее, в настройках фреймворка и его окружении уже есть реализации для данных свойств. Например, в "app.text.js" уже содержатся объявления типовых констант для текстов подписей кнопок и пунктов меню. Поэтому, в "app/config_wrapper.js" для привязки свойств `P8PCyclogram` к контексту фреймворка реализованы специальные декораторы и объекты-шаблоны, облегчающие подключение экземпляра `P8PCyclogram` к панели и снимающие с разработчика необходимость указывать некоторые из перечисленных выше обязательных свойств. В предложенном ниже примере, из модуля "config_wrapper" в панель импортируется объект `P8P_CYCLOGRAM_CONFIG_PROPS`, который уже содержт преднастроенное описание свойств `noDataFoundText`,`nameTaskEditorCaption`, `okTaskEditorBtnCaption` и `cancelTaskEditorBtnCaption`, полученное из окружения фреймворка. Таким образом, прикладной разработчик может не указывать их значения при использовании `P8PCyclogram` (если по каким-то причинам не хочет их переопределить, конечно).
+
+```
+import { P8PCyclogram } from "../../components/p8p_cyclogram";
+import { P8P_CYCLOGRAM_CONFIG_PROPS } from "../../config_wrapper";
+
+const MyPanel = () => {
+ return (
+
+
+
+ );
+}
+```
+
+**API на сервере БД**
+
+Компонент `P8PCyclogram` требует от разработчика передачи данных в определённом формате. С целью снижения трудозатрат на приведение собранных хранимым объектом данных Системы к форматам, потребляемым `P8PCyclogram`, реализован специальный API на стороне сервера БД.
+
+Для циклограммы это (см. детальные описания программных интерфейсов в пакете `PKG_P8PANELS_VISUAL`):
+`PKG_P8PANELS_VISUAL.TCYCLOGRAM_MAKE` - функция, инициализация циклограммы, возвращает объект для хранения её описания\
+`PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK_ATTR` - процедура, добавляет, к указанному объекту описания циклограммы, описатель дополнительного атрибута задачи\
+`PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_MAKE` - функция, инициализирует и возвращает объект для описания задачи в циклограмме (поставщик данных для `TCYCLOGRAM_ADD_TASK`)\
+`PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL` - процедура, добавляет, к указанному объекту описания задачи, значение дополнительного атрибута\
+`PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_COLUMN` - процедура, добавляет, к указанному объекту описания циклограммы, новую колонку\
+`PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_GROUP` - процедура, добавляет, к указанному объекту описания циклограммы, новую группу\
+`PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK` - процедура, добавляет, к указанному объекту описания циклограммы, новую задачу, ранее описанную через `TCYCLOGRAM_TASK_MAKE`\
+`PKG_P8PANELS_VISUAL.TCYCLOGRAM_TO_XML` - функция, производит сериализацию объекта, описывающего циклограмму, в специальный XML-формат, корректно интерпретируемый клиентским компонентом `P8PCyclogram` при передаче в WEB-приложение
+
+**Пример**
+
+Код на стороне сервера БД (хранимая процедура в клиентском пакете `PKG_P8PANELS_SAMPLES`, требует наличия таблицы `P8PNL_SMPL_CYCLOGRAM`, см. "db/P8PNL_SMPL_CYCLOGRAM.sql"):
+
+```
+ procedure CYCLOGRAM
+ (
+ NIDENT in number, -- Идентификатор процесса
+ COUT out clob -- Сериализованные данные для циклограммы
+ )
+ is
+ CG PKG_P8PANELS_VISUAL.TCYCLOGRAM; -- Описание циклограммы
+ RTASK PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK; -- Описание задачи циклограммы
+ NLINE_NUMB PKG_STD.TNUMBER := 0; -- Номер строки
+ NLINE_NUMB_TMP PKG_STD.TNUMBER := 0; -- Номер строки (буфер)
+ DTASK_DATE_START PKG_STD.TLDATE; -- Дата начала этапа
+ DTASK_DATE_END PKG_STD.TLDATE; -- Дата окончания этапа
+ NTASK_START PKG_STD.TNUMBER := 0; -- Позиция начала этапа
+ NTASK_END PKG_STD.TNUMBER := 0; -- Позиция окончания этапа
+ STASK_NAME PKG_STD.TSTRING; -- Наименование задачи
+ SCOLOR_WHITE PKG_STD.TSTRING := 'white'; -- Цвет - белый
+ SBG_TASK_COLOR_W_GRP PKG_STD.TSTRING := '#6bc982'; -- Цвет задачи с группой
+ SHL_TASK_COLOR_W_GRP PKG_STD.TSTRING := '#7dd592'; -- Цвет наведения задачи с группой
+ SBG_TASK_COLOR_WO_GRP PKG_STD.TSTRING := '#e36d6d'; -- Цвет задачи без группы
+ STEXT_COLOR_TASK_WO_GRP PKG_STD.TSTRING := '#e5e5e5'; -- Цвет текста задачи без группы
+ SBG_TASK_COLOR_GRP PKG_STD.TSTRING := 'cadetblue'; -- Цвет групповой задачи
+ SHL_TASK_COLOR_GRP PKG_STD.TSTRING := '#6fadaf'; -- Цвет наведения групповой задачи
+
+ /* Считывание значений группирующей задачи */
+ procedure GROUP_TASK_GET
+ (
+ NIDENT in number, -- Идентификатор процесса
+ NGROUP in number := null, -- Рег. номер группы
+ NFLAG_WO_GROUP in number := 0, -- Признак отбора задач без групп (0 - нет, 1 - да)
+ DTASK_DATE_START out date, -- Дата начала этапа
+ DTASK_DATE_END out date, -- Дата окончания этапа
+ NTASK_START out number, -- Позиция начала этапа
+ NTASK_END out number -- Позиция окончания этапа
+ )
+ is
+ begin
+ ...
+ end GROUP_TASK_GET;
+ begin
+ /* Инициализируем циклограмму */
+ CG := PKG_P8PANELS_VISUAL.TCYCLOGRAM_MAKE(STITLE => 'Задачи на ' || TO_CHAR(EXTRACT(year from sysdate)) || ' год');
+ /* Добавляем атрибуты */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK_ATTR(RCYCLOGRAM => CG,
+ SNAME => 'ddate_start',
+ SCAPTION => 'Дата начала',
+ BVISIBLE => true,
+ BCLEAR => true);
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK_ATTR(RCYCLOGRAM => CG,
+ SNAME => 'ddate_end',
+ SCAPTION => 'Дата окончания',
+ BVISIBLE => true);
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK_ATTR(RCYCLOGRAM => CG,
+ SNAME => 'type',
+ SCAPTION => 'Тип',
+ BVISIBLE => false);
+ /* Обходим колонки */
+ for CLMN in (select T.NAME,
+ T.POS_START,
+ T.POS_END
+ from P8PNL_SMPL_CYCLOGRAM T
+ where T.IDENT = NIDENT
+ and T.TYPE = 0)
+ loop
+ /* Добавляем колонку */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_COLUMN(RCYCLOGRAM => CG,
+ SNAME => CLMN.NAME,
+ NSTART => CLMN.POS_START,
+ NEND => CLMN.POS_END);
+ end loop;
+ /* Считываем значения для задач проекта */
+ GROUP_TASK_GET(NIDENT => NIDENT,
+ NFLAG_WO_GROUP => 0,
+ DTASK_DATE_START => DTASK_DATE_START,
+ DTASK_DATE_END => DTASK_DATE_END,
+ NTASK_START => NTASK_START,
+ NTASK_END => NTASK_END);
+ /* Формируем задачу (этап) */
+ RTASK := PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_MAKE(NRN => 1,
+ SCAPTION => 'Задачи проекта',
+ SNAME => 'Задачи проекта',
+ NLINE_NUMB => NLINE_NUMB,
+ NSTART => NTASK_START,
+ NEND => NTASK_END,
+ SBG_COLOR => SCOLOR_WHITE);
+ /* Добавляем атрибуты */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
+ RTASK => RTASK,
+ SNAME => 'ddate_start',
+ SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => DTASK_DATE_START),
+ BCLEAR => true);
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
+ RTASK => RTASK,
+ SNAME => 'ddate_end',
+ SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => DTASK_DATE_END));
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG, RTASK => RTASK, SNAME => 'type', SVALUE => 0);
+ /* Добавляем задачу в циклограмму */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK(RCYCLOGRAM => CG, RTASK => RTASK);
+ /* Указываем следующую строку */
+ NLINE_NUMB := NLINE_NUMB + 1;
+ /* Обходим группы */
+ for GRP in (select T.RN,
+ T.NAME,
+ ROWNUM RNUM
+ from P8PNL_SMPL_CYCLOGRAM T
+ where T.IDENT = NIDENT
+ and T.TYPE = 1
+ order by T.RN asc)
+ loop
+ ...
+ /* Добавляем группу */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_GROUP(RCYCLOGRAM => CG,
+ SNAME => GRP.NAME,
+ NHEADER_HEIGHT => 30,
+ NHEADER_WIDTH => 200);
+ /* Считываем значения этапа группы */
+ GROUP_TASK_GET(NIDENT => NIDENT,
+ NGROUP => GRP.RN,
+ DTASK_DATE_START => DTASK_DATE_START,
+ DTASK_DATE_END => DTASK_DATE_END,
+ NTASK_START => NTASK_START,
+ NTASK_END => NTASK_END);
+ /* Формируем задачу (этап) */
+ RTASK := PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_MAKE(NRN => GRP.RN,
+ SCAPTION => 'Этап ' || TO_CHAR(GRP.RNUM),
+ SNAME => 'Этап ' || TO_CHAR(GRP.RNUM),
+ NLINE_NUMB => NLINE_NUMB,
+ NSTART => NTASK_START,
+ NEND => NTASK_END,
+ SBG_COLOR => SBG_TASK_COLOR_GRP,
+ STEXT_COLOR => SCOLOR_WHITE,
+ SHIGHLIGHT_COLOR => SHL_TASK_COLOR_GRP);
+ /* Добавляем атрибуты */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
+ RTASK => RTASK,
+ SNAME => 'ddate_start',
+ SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => DTASK_DATE_START),
+ BCLEAR => true);
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
+ RTASK => RTASK,
+ SNAME => 'ddate_end',
+ SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => DTASK_DATE_END));
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG, RTASK => RTASK, SNAME => 'type', SVALUE => 1);
+ /* Добавляем задачу в циклограмму */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK(RCYCLOGRAM => CG, RTASK => RTASK);
+ /* Обходим задачи группы */
+ for TASK in (select T.RN,
+ T.NAME,
+ T.POS_START,
+ T.POS_END,
+ T.DATE_FROM,
+ T.DATE_TO,
+ ROWNUM RNUM
+ from P8PNL_SMPL_CYCLOGRAM T
+ where T.IDENT = NIDENT
+ and T.TYPE = 2
+ and T.TASK_GROUP = GRP.RN)
+ loop
+ ...
+ end loop;
+ /* Указываем следующую строку */
+ NLINE_NUMB := NLINE_NUMB + 1;
+ end loop;
+ /* Указываем следующую строку */
+ NLINE_NUMB := NLINE_NUMB + 1;
+ /* Считываем значения для обособленных задач */
+ GROUP_TASK_GET(NIDENT => NIDENT,
+ NFLAG_WO_GROUP => 1,
+ DTASK_DATE_START => DTASK_DATE_START,
+ DTASK_DATE_END => DTASK_DATE_END,
+ NTASK_START => NTASK_START,
+ NTASK_END => NTASK_END);
+ /* Формируем задачу (этап) */
+ RTASK := PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_MAKE(NRN => 1,
+ SCAPTION => 'Обособленные задачи',
+ SNAME => 'Обособленные задачи',
+ NLINE_NUMB => NLINE_NUMB,
+ NSTART => NTASK_START,
+ NEND => NTASK_END,
+ SBG_COLOR => SCOLOR_WHITE);
+ /* Добавляем атрибуты */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
+ RTASK => RTASK,
+ SNAME => 'ddate_start',
+ SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => DTASK_DATE_START),
+ BCLEAR => true);
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
+ RTASK => RTASK,
+ SNAME => 'ddate_end',
+ SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => DTASK_DATE_END));
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG, RTASK => RTASK, SNAME => 'type', SVALUE => 0);
+ /* Добавляем задачу в циклограмму */
+ PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK(RCYCLOGRAM => CG, RTASK => RTASK);
+ /* Указываем следующую строку */
+ NLINE_NUMB := NLINE_NUMB + 1;
+ /* Цикл по обособленным задачам */
+ for REC in (select T.RN,
+ T.NAME,
+ T.POS_START,
+ T.POS_END,
+ T.DATE_FROM,
+ T.DATE_TO,
+ ROWNUM RNUM
+ from P8PNL_SMPL_CYCLOGRAM T
+ where T.IDENT = NIDENT
+ and T.TYPE = 2
+ and T.TASK_GROUP is null)
+ loop
+ ...
+ end loop;
+ /* Формируем список */
+ COUT := PKG_P8PANELS_VISUAL.TCYCLOGRAM_TO_XML(RCYCLOGRAM => CG);
+ end CYCLOGRAM;
+```
+
+Код панели на стороне клиента (WEB-приложения):
+
+```
+/*
+ Парус 8 - Панели мониторинга - Примеры для разработчиков
+ Пример: Циклограмма "P8PCyclogram"
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import {
+ Typography,
+ Grid,
+ Button,
+ Box,
+ DialogContent,
+ List,
+ ListItem,
+ ListItemText,
+ Divider,
+ TextField,
+ DialogActions,
+ Stack,
+ Icon
+} from "@mui/material"; //Интерфейсные элементы
+import { formatDateJSONDateOnly, formatDateRF } from "../../core/utils"; //Вспомогательные функции
+import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
+import { P8PCyclogram } from "../../components/p8p_cyclogram"; //Циклограмма
+import { P8P_CYCLOGRAM_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
+import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
+
+//---------
+//Константы
+//---------
+
+//Отступ контейнера страницы от заголовка
+const CONTAINER_PADDING_TOP = "20px";
+
+//Высота заголовка страницы
+const TITLE_HEIGHT = "47px";
+
+//Высота строк
+const LINE_HEIGHT = 30;
+
+//Стили
+const STYLES = {
+ CONTAINER: { textAlign: "center", paddingTop: CONTAINER_PADDING_TOP },
+ TITLE: { paddingBottom: "15px", height: TITLE_HEIGHT },
+ GANTT_CONTAINER: {
+ height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_HEIGHT} - ${CONTAINER_PADDING_TOP})`,
+ width: "100vw",
+ paddingTop: "5px"
+ },
+ TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
+ TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" },
+ GROUP_HEADER: height => ({
+ border: "1px solid",
+ backgroundColor: "#ecf8fb",
+ height: height,
+ borderRadius: "10px",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-around"
+ })
+};
+
+//---------------------------------------------
+//Вспомогательные функции форматирования данных
+//---------------------------------------------
+
+//Диалог открытия задачи
+const CustomTaskDialog = ({ task, ident, handleReload, close }) => {
+ ...
+};
+
+//Контроль свойств - Диалог открытия задачи
+CustomTaskDialog.propTypes = {
+ task: PropTypes.object.isRequired,
+ ident: PropTypes.number.isRequired,
+ handleReload: PropTypes.func.isRequired,
+ close: PropTypes.func.isRequired
+};
+
+//Заголовок группы
+const CustomGroupHeader = ({ group }) => {
+ ...
+};
+
+//Контроль свойств - Заголовок группы
+CustomGroupHeader.propTypes = {
+ group: PropTypes.object.isRequired
+};
+
+//Отображение задачи
+const taskRenderer = ({ task }) => {
+ ...
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Пример: Циклограмма "P8PCyclogram"
+const Cyclogram = ({ title }) => {
+ //Собственное состояние
+ const [state, setState] = useState({
+ init: false,
+ dataLoaded: false,
+ reload: true,
+ ident: null
+ });
+
+ //Подключение к контексту взаимодействия с сервером
+ 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 }));
+ };
+ //Если требуется проинициализировать
+ if (!state.init) {
+ initData();
+ }
+ }, [executeStored, state.ident, state.init]);
+
+ return (
+
+