Compare commits
No commits in common. "db66f0c96bd3b749c342b95a8d79b350617a7654" and "a8c9bf2fecc22097aba91b2faf91221c0e392a61" have entirely different histories.
db66f0c96b
...
a8c9bf2fec
497
README.md
497
README.md
@ -2366,503 +2366,6 @@ const Svg = ({ title }) => {
|
|||||||
|
|
||||||
Полные актуальные исходные коды примера можно увидеть в "app/panels/samples/svg.js" данного репозитория соответственно.
|
Полные актуальные исходные коды примера можно увидеть в "app/panels/samples/svg.js" данного репозитория соответственно.
|
||||||
|
|
||||||
##### Циклограмма "P8PCyclogram"
|
|
||||||
|
|
||||||
Компонент предназначен для отображения данных в виде циклограммы. Поддерживается:
|
|
||||||
|
|
||||||
- Группировка задач с отображением описания группы при наведении
|
|
||||||
- Форматирование цвета заливки задачи, текста задачи и цвета при наведении на задачу/группу
|
|
||||||
- Дополнение задачи произвольными учётными атрибутами
|
|
||||||
- Диалоговый редактор задачи, отображающий её дополнительные атрибуты с возможностью настройки их форматирования
|
|
||||||
- Отображение произвольного пользовательского диалога в качестве карточки задачи/редактора задачи
|
|
||||||
- Масштабирование визуального представления
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
**Подключение**
|
|
||||||
|
|
||||||
Клиентская часть циклограммы реализована в компоненте `P8PCyclogram`, объявленном в "app/components/p8p_cyclogram". Для использования компонента на панели его необходимо импортировать:
|
|
||||||
|
|
||||||
```
|
|
||||||
import { P8PCyclogram } from "../../components/p8p_cyclogram";
|
|
||||||
|
|
||||||
const MyPanel = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<P8PCyclogram .../>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Свойства**
|
|
||||||
|
|
||||||
`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 (
|
|
||||||
<div>
|
|
||||||
<P8PCyclogram {...P8P_CYCLOGRAM_CONFIG_PROPS} .../>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**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 (
|
|
||||||
<Box>
|
|
||||||
<div style={STYLES.CONTAINER}>
|
|
||||||
<Typography sx={STYLES.TITLE} variant={"h6"}>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
<Grid container direction="column" alignItems="center">
|
|
||||||
<Grid item xs={12}>
|
|
||||||
{state.dataLoaded ? (
|
|
||||||
<P8PCyclogram
|
|
||||||
{...P8P_CYCLOGRAM_CONFIG_PROPS}
|
|
||||||
{...state}
|
|
||||||
containerStyle={STYLES.GANTT_CONTAINER}
|
|
||||||
lineHeight={LINE_HEIGHT}
|
|
||||||
taskDialogRenderer={prms => (
|
|
||||||
<CustomTaskDialog task={prms.task} ident={state.ident} handleReload={handleReload} close={prms.close} />
|
|
||||||
)}
|
|
||||||
taskRenderer={prms => taskRenderer(prms)}
|
|
||||||
groupHeaderRenderer={prms => <CustomGroupHeader group={prms.group} />}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Пример: Циклограмма "P8PCyclogram"
|
|
||||||
Cyclogram.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { Cyclogram };
|
|
||||||
```
|
|
||||||
|
|
||||||
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/cyclogram.js" данного репозитория соответственно.
|
|
||||||
|
|
||||||
### Ограничения дизайна пользовательского интерфейса
|
### Ограничения дизайна пользовательского интерфейса
|
||||||
|
|
||||||
Фреймворк позволяет реализовать любые пользовательские интерфейсы, вёрстка которых не противоречит возможностям современного HTML. Тем не менее, при разработке пользовательских интерфейсов панелей важно придерживаться предложенных ниже правил. Это позволит создавать их в едином ключе и упростит работу конечного пользователя при их освоении.
|
Фреймворк позволяет реализовать любые пользовательские интерфейсы, вёрстка которых не противоречит возможностям современного HTML. Тем не менее, при разработке пользовательских интерфейсов панелей важно придерживаться предложенных ниже правил. Это позволит создавать их в едином ключе и упростит работу конечного пользователя при их освоении.
|
||||||
|
@ -1,819 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга
|
|
||||||
Компонент: Циклограмма
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useEffect, useState, useRef } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Typography,
|
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
Button,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemText,
|
|
||||||
Link,
|
|
||||||
Divider,
|
|
||||||
IconButton,
|
|
||||||
Icon
|
|
||||||
} from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
|
|
||||||
import { hasValue } from "../core/utils"; //Вспомогательный функции
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Уровни масштаба
|
|
||||||
const P8P_CYCLOGRAM_ZOOM = [0.2, 0.4, 0.7, 1, 1.5, 2, 2.5];
|
|
||||||
|
|
||||||
//Параметры элементов циклограммы
|
|
||||||
const NDEFAULT_LINE_HEIGHT = 20;
|
|
||||||
const NDEFAULT_HEADER_HEIGHT = 35;
|
|
||||||
|
|
||||||
//Высота заголовка
|
|
||||||
const TITLE_HEIGHT = "44px";
|
|
||||||
|
|
||||||
//Высота панели масштабирования
|
|
||||||
const ZOOM_HEIGHT = "56px";
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CYCLOGRAM_TITLE: { height: TITLE_HEIGHT },
|
|
||||||
CYCLOGRAM_ZOOM: { height: ZOOM_HEIGHT },
|
|
||||||
HEADER_COLUMN: {
|
|
||||||
fontSize: "12px",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
overflow: "hidden",
|
|
||||||
whiteSpace: "pre",
|
|
||||||
textAlign: "center",
|
|
||||||
lineHeight: "3",
|
|
||||||
padding: "0px 5px"
|
|
||||||
},
|
|
||||||
CYCLOGRAM_BOX: (noData, title, zoomBar) => ({
|
|
||||||
position: "relative",
|
|
||||||
overflow: "auto",
|
|
||||||
padding: "0px 8px",
|
|
||||||
height: `calc(100% - ${zoomBar ? ZOOM_HEIGHT : "0px"} - ${title ? TITLE_HEIGHT : "0px"})`,
|
|
||||||
display: noData ? "none" : ""
|
|
||||||
}),
|
|
||||||
GRID_ROW: index => (index % 2 === 0 ? { backgroundColor: "#ffffff" } : { backgroundColor: "#f5f5f5" }),
|
|
||||||
GROUP_HEADER_BOX: {
|
|
||||||
border: "1px solid",
|
|
||||||
backgroundColor: "#ebebeb",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center"
|
|
||||||
},
|
|
||||||
GROUP_HEADER: {
|
|
||||||
fontSize: "14px",
|
|
||||||
textAlign: "center",
|
|
||||||
wordWrap: "break-word"
|
|
||||||
},
|
|
||||||
TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
|
|
||||||
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" },
|
|
||||||
TASK_BOX: (lineHeight, bgColor, textColor, highlightColor) => ({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: bgColor ? bgColor : "#b4b9bf",
|
|
||||||
...(textColor ? { color: textColor } : {}),
|
|
||||||
height: lineHeight,
|
|
||||||
"&:hover": {
|
|
||||||
...(highlightColor
|
|
||||||
? { backgroundColor: `${highlightColor} !important`, filter: "brightness(1) !important" }
|
|
||||||
: { filter: "brightness(1.25) !important" }),
|
|
||||||
cursor: "pointer !important"
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TASK: lineHeight => {
|
|
||||||
const availableLines = Math.floor(lineHeight / 18);
|
|
||||||
return {
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "12px",
|
|
||||||
overflowWrap: "break-word",
|
|
||||||
wordBreak: "break-all",
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
display: "-webkit-box",
|
|
||||||
lineHeight: "18px",
|
|
||||||
maxHeight: lineHeight,
|
|
||||||
WebkitLineClamp: availableLines < 1 ? 1 : availableLines,
|
|
||||||
WebkitBoxOrient: "vertical"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Структура колонки
|
|
||||||
const P8P_CYCLOGRAM_COLUMN_SHAPE = PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
start: PropTypes.number.isRequired,
|
|
||||||
end: PropTypes.number.isRequired
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура группы
|
|
||||||
const P8P_CYCLOGRAM_GROUP_SHAPE = PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
height: PropTypes.number.isRequired,
|
|
||||||
width: PropTypes.number.isRequired,
|
|
||||||
visible: PropTypes.bool.isRequired
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура задачи
|
|
||||||
const P8P_CYCLOGRAM_TASK_SHAPE = PropTypes.shape({
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
rn: PropTypes.number.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
fullName: PropTypes.string.isRequired,
|
|
||||||
lineNumb: PropTypes.number.isRequired,
|
|
||||||
start: PropTypes.number.isRequired,
|
|
||||||
end: PropTypes.number.isRequired,
|
|
||||||
group: PropTypes.string,
|
|
||||||
bgColor: PropTypes.string,
|
|
||||||
textColor: PropTypes.string,
|
|
||||||
highlightColor: PropTypes.string
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура динамического атрибута задачи
|
|
||||||
const P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE = PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
caption: PropTypes.string.isRequired,
|
|
||||||
visible: PropTypes.bool.isRequired
|
|
||||||
});
|
|
||||||
|
|
||||||
//--------------------------------
|
|
||||||
//Вспомогательные классы и функции
|
|
||||||
//--------------------------------
|
|
||||||
|
|
||||||
//Определение сдвига для максимальной ширины колонок
|
|
||||||
const getShift = (columns, currentColumnsMaxWidth, maxCyclogramWidth) => {
|
|
||||||
//Определяем доступное пространство для расширения
|
|
||||||
let maxWidthDiff = maxCyclogramWidth - currentColumnsMaxWidth;
|
|
||||||
//Инициализируем значение сдвига
|
|
||||||
let shift = 1;
|
|
||||||
//Если доступно больше ширины и есть пространство для расширения
|
|
||||||
if (maxCyclogramWidth > currentColumnsMaxWidth && maxCyclogramWidth - maxWidthDiff > columns.length) {
|
|
||||||
//Определяем доступный сдвиг колонок
|
|
||||||
shift = maxCyclogramWidth / currentColumnsMaxWidth;
|
|
||||||
}
|
|
||||||
//Возвращаем сдвиг
|
|
||||||
return shift;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Формирование стилей для группы
|
|
||||||
const getGroupStyles = (indexGrp, highlightColor) => {
|
|
||||||
return `.main .TaskGrp${indexGrp}:hover .TaskGrp${indexGrp} {
|
|
||||||
${highlightColor ? `background: ${highlightColor};` : `filter: brightness(1.15);`}
|
|
||||||
}
|
|
||||||
.main:has(.TaskGrp${indexGrp}:hover) .TaskGrpHeader${indexGrp} {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
//cursor: pointer;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Фон строк таблицы
|
|
||||||
const P8PCyclogramRowsGrid = ({ rows, maxWidth, lineHeight }) => {
|
|
||||||
return (
|
|
||||||
<g>
|
|
||||||
{rows.map((el, index) => (
|
|
||||||
<foreignObject x="0" y={NDEFAULT_HEADER_HEIGHT + index * lineHeight} width={maxWidth} height={lineHeight} key={index}>
|
|
||||||
<Box sx={STYLES.GRID_ROW(index)} height={lineHeight} />
|
|
||||||
</foreignObject>
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Фон строк таблицы
|
|
||||||
P8PCyclogramRowsGrid.propTypes = {
|
|
||||||
rows: PropTypes.array.isRequired,
|
|
||||||
maxWidth: PropTypes.number.isRequired,
|
|
||||||
lineHeight: PropTypes.number.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Линии строк таблицы
|
|
||||||
const P8PCyclogramRowsLines = ({ rows, maxWidth, lineHeight }) => {
|
|
||||||
return (
|
|
||||||
<g>
|
|
||||||
{rows.map((el, index) => (
|
|
||||||
<line
|
|
||||||
x1="0"
|
|
||||||
y1={NDEFAULT_HEADER_HEIGHT + lineHeight + index * lineHeight}
|
|
||||||
x2={maxWidth}
|
|
||||||
y2={NDEFAULT_HEADER_HEIGHT + lineHeight + index * lineHeight}
|
|
||||||
key={index}
|
|
||||||
></line>
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Линии строк таблицы
|
|
||||||
P8PCyclogramRowsLines.propTypes = {
|
|
||||||
rows: PropTypes.array.isRequired,
|
|
||||||
maxWidth: PropTypes.number.isRequired,
|
|
||||||
lineHeight: PropTypes.number.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Линии колонок таблицы
|
|
||||||
const P8PCyclogramColumnsLines = ({ columns, shift, y1, y2 }) => {
|
|
||||||
//Инициализируем старт текущей колонки
|
|
||||||
let prevColumnEnd = 0;
|
|
||||||
return (
|
|
||||||
<g>
|
|
||||||
{columns.map((column, index) => {
|
|
||||||
//Аккумулируем окончание последней колонки с учетом сдвига
|
|
||||||
prevColumnEnd = index !== 0 ? prevColumnEnd + (columns[index - 1].end - columns[index - 1].start) * shift : 0;
|
|
||||||
return <line x1={prevColumnEnd} y1={y1} x2={prevColumnEnd} y2={y2} stroke="#e0e0e0" key={index} />;
|
|
||||||
})}
|
|
||||||
<line
|
|
||||||
x1={prevColumnEnd + (columns[columns.length - 1].end - columns[columns.length - 1].start) * shift}
|
|
||||||
y1={y1}
|
|
||||||
x2={prevColumnEnd + (columns[columns.length - 1].end - columns[columns.length - 1].start) * shift}
|
|
||||||
y2={y2}
|
|
||||||
stroke="#e0e0e0"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Линии колонок таблицы
|
|
||||||
P8PCyclogramColumnsLines.propTypes = {
|
|
||||||
columns: PropTypes.array.isRequired,
|
|
||||||
shift: PropTypes.number.isRequired,
|
|
||||||
y1: PropTypes.number.isRequired,
|
|
||||||
y2: PropTypes.number.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Фон таблицы циклограммы
|
|
||||||
const P8PCyclogramGrid = ({ tasks, columns, shift, maxWidth, maxHeight, lineHeight }) => {
|
|
||||||
//Формируем массив строк исходя из максимального значения строки задачи
|
|
||||||
const rows = Array.from(Array(Math.max(...tasks.map(o => o.lineNumb)) + 1).keys());
|
|
||||||
return (
|
|
||||||
<g className="grid">
|
|
||||||
<rect x="0" y="0" width={maxWidth} height={maxHeight}></rect>
|
|
||||||
<P8PCyclogramRowsGrid rows={rows} maxWidth={maxWidth} lineHeight={lineHeight} />
|
|
||||||
<P8PCyclogramRowsLines rows={rows} maxWidth={maxWidth} lineHeight={lineHeight} />
|
|
||||||
<P8PCyclogramColumnsLines columns={columns} shift={shift} y1={NDEFAULT_HEADER_HEIGHT} y2={maxHeight} />
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Фон таблицы циклограммы
|
|
||||||
P8PCyclogramGrid.propTypes = {
|
|
||||||
tasks: PropTypes.array.isRequired,
|
|
||||||
columns: PropTypes.array.isRequired,
|
|
||||||
shift: PropTypes.number.isRequired,
|
|
||||||
maxWidth: PropTypes.number.isRequired,
|
|
||||||
maxHeight: PropTypes.number.isRequired,
|
|
||||||
lineHeight: PropTypes.number.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Колонка заголовка циклограммы
|
|
||||||
const P8PCyclogramHeaderColumn = ({ column, start, shift, columnRenderer }) => {
|
|
||||||
//Рассчитываем ширину колонки
|
|
||||||
const columnWidth = column.end - column.start;
|
|
||||||
//Формируем собственное отображение, если требуется
|
|
||||||
const customView = columnRenderer ? columnRenderer({ column }) : null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<foreignObject x={start} y="0" width={columnWidth * shift} height={NDEFAULT_HEADER_HEIGHT}>
|
|
||||||
{customView ? (
|
|
||||||
customView
|
|
||||||
) : (
|
|
||||||
<Typography sx={{ ...STYLES.HEADER_COLUMN, height: NDEFAULT_HEADER_HEIGHT }} title={column.name}>
|
|
||||||
{column.name}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</foreignObject>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Колонка заголовка циклограммы
|
|
||||||
P8PCyclogramHeaderColumn.propTypes = {
|
|
||||||
column: PropTypes.object.isRequired,
|
|
||||||
start: PropTypes.number.isRequired,
|
|
||||||
shift: PropTypes.number.isRequired,
|
|
||||||
maxHeight: PropTypes.number.isRequired,
|
|
||||||
lastElement: PropTypes.bool,
|
|
||||||
columnRenderer: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//Заголовок циклограммы
|
|
||||||
const P8PCyclogramHeader = ({ columns, shift, maxWidth, maxHeight, columnRenderer, headerBlock }) => {
|
|
||||||
//Инициализируем старт текущей колонки
|
|
||||||
let prevColumnEnd = 0;
|
|
||||||
return (
|
|
||||||
<g className="header" ref={headerBlock}>
|
|
||||||
<rect x="0" y="0" width={maxWidth} height={NDEFAULT_HEADER_HEIGHT} fill="#ffffff" stroke="#e0e0e0" strokeWidth="1.4"></rect>
|
|
||||||
{columns.map((column, index) => {
|
|
||||||
//Аккумулируем окончание последней колонки с учетом сдвига
|
|
||||||
prevColumnEnd = index !== 0 ? prevColumnEnd + (columns[index - 1].end - columns[index - 1].start) * shift : 0;
|
|
||||||
return (
|
|
||||||
<P8PCyclogramHeaderColumn
|
|
||||||
column={column}
|
|
||||||
shift={shift}
|
|
||||||
start={prevColumnEnd}
|
|
||||||
maxHeight={maxHeight}
|
|
||||||
lastElement={columns.length - 1 === index}
|
|
||||||
columnRenderer={columnRenderer}
|
|
||||||
key={index}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<g className="columnsDividers">
|
|
||||||
<P8PCyclogramColumnsLines columns={columns} shift={shift} y1={0} y2={NDEFAULT_HEADER_HEIGHT} />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Заголовок циклограммы
|
|
||||||
P8PCyclogramHeader.propTypes = {
|
|
||||||
columns: PropTypes.array.isRequired,
|
|
||||||
shift: PropTypes.number.isRequired,
|
|
||||||
maxWidth: PropTypes.number.isRequired,
|
|
||||||
maxHeight: PropTypes.number.isRequired,
|
|
||||||
columnRenderer: PropTypes.func,
|
|
||||||
headerBlock: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
//Задача циклограммы
|
|
||||||
const P8PCyclogramTask = ({ task, indexGrp, shift, lineHeight, openTaskEditor, taskRenderer }) => {
|
|
||||||
//Рассчитываем ширину задачи
|
|
||||||
const width = task.end !== 0 ? (task.end - task.start) * shift : 0;
|
|
||||||
//Формируем собственное отображение, если требуется
|
|
||||||
const customView = taskRenderer ? taskRenderer({ task, taskHeight: lineHeight, taskWidth: width }) || {} : {};
|
|
||||||
return (
|
|
||||||
<foreignObject
|
|
||||||
x={task.start !== 0 ? task.start * shift : 0}
|
|
||||||
y={NDEFAULT_HEADER_HEIGHT + task.lineNumb * lineHeight}
|
|
||||||
width={width}
|
|
||||||
height={lineHeight}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
className={hasValue(indexGrp) ? `TaskGrp${indexGrp}` : null}
|
|
||||||
sx={{ ...STYLES.TASK_BOX(lineHeight, task.bgColor, task.textColor, task.highlightColor), ...customView.taskStyle }}
|
|
||||||
{...customView.taskProps}
|
|
||||||
onClick={() => openTaskEditor(task)}
|
|
||||||
>
|
|
||||||
{customView.data ? (
|
|
||||||
customView.data
|
|
||||||
) : (
|
|
||||||
<Typography sx={STYLES.TASK(lineHeight)} title={task.name}>
|
|
||||||
{task.name}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</foreignObject>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Группы циклограммы
|
|
||||||
P8PCyclogramTask.propTypes = {
|
|
||||||
task: PropTypes.object.isRequired,
|
|
||||||
indexGrp: PropTypes.number,
|
|
||||||
shift: PropTypes.number.isRequired,
|
|
||||||
lineHeight: PropTypes.number.isRequired,
|
|
||||||
openTaskEditor: PropTypes.func.isRequired,
|
|
||||||
taskRenderer: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//Основная информация циклограммы
|
|
||||||
const P8PCyclogramMain = ({
|
|
||||||
columns,
|
|
||||||
groups,
|
|
||||||
tasks,
|
|
||||||
shift,
|
|
||||||
lineHeight,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
openTaskEditor,
|
|
||||||
groupHeaderRenderer,
|
|
||||||
taskRenderer,
|
|
||||||
columnRenderer,
|
|
||||||
headerBlock
|
|
||||||
}) => {
|
|
||||||
//Инициализируем коллекцию тасков с группами
|
|
||||||
const tasksWithGroup = tasks.filter(task => hasValue(task.groupName));
|
|
||||||
//Инициализируем коллекцию тасков без групп
|
|
||||||
const tasksWithoutGroup = tasks.filter(task => !hasValue(task.groupName));
|
|
||||||
//Инициализируем коллекцию отображаемых групп
|
|
||||||
const visibleGroups = groups ? groups.filter(group => group.visible) : [];
|
|
||||||
return (
|
|
||||||
<g className="main">
|
|
||||||
<g className="tasks">
|
|
||||||
{visibleGroups.length !== 0
|
|
||||||
? visibleGroups.map((grp, indexGrp) => {
|
|
||||||
//Считываем задачи группы
|
|
||||||
let groupTasks = tasksWithGroup.filter(task => task.groupName === grp.name);
|
|
||||||
//Если по данной группе нет тасков - ничего не выводим
|
|
||||||
if (groupTasks.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<g className={`TaskGrp${indexGrp}`} key={indexGrp}>
|
|
||||||
{groupTasks.map((task, index) => (
|
|
||||||
<P8PCyclogramTask
|
|
||||||
task={task}
|
|
||||||
indexGrp={indexGrp}
|
|
||||||
shift={shift}
|
|
||||||
lineHeight={lineHeight}
|
|
||||||
openTaskEditor={openTaskEditor}
|
|
||||||
taskRenderer={taskRenderer}
|
|
||||||
key={index}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<style>{getGroupStyles(indexGrp, grp.highlightColor)}</style>
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
<g className={`TasksWithoutGroups`}>
|
|
||||||
{tasksWithoutGroup.map((task, index) => {
|
|
||||||
return (
|
|
||||||
<P8PCyclogramTask
|
|
||||||
task={task}
|
|
||||||
shift={shift}
|
|
||||||
lineHeight={lineHeight}
|
|
||||||
openTaskEditor={openTaskEditor}
|
|
||||||
taskRenderer={taskRenderer}
|
|
||||||
key={index}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<P8PCyclogramHeader
|
|
||||||
columns={columns}
|
|
||||||
shift={shift}
|
|
||||||
maxWidth={maxWidth}
|
|
||||||
maxHeight={maxHeight}
|
|
||||||
columnRenderer={columnRenderer}
|
|
||||||
headerBlock={headerBlock}
|
|
||||||
/>
|
|
||||||
{visibleGroups.length !== 0 ? (
|
|
||||||
<g className="groups">
|
|
||||||
{visibleGroups.map((grp, indexGrp) => {
|
|
||||||
//Инициализируем параметры группы
|
|
||||||
let defaultView = null;
|
|
||||||
let customView = null;
|
|
||||||
let groupHeaderX = 0;
|
|
||||||
let groupHeaderY = 0;
|
|
||||||
let groupTasks = tasksWithGroup.filter(task => task.groupName === grp.name);
|
|
||||||
//Если по данной группе нет тасков - ничего не выводим
|
|
||||||
if (groupTasks.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
//Если требуется отображать заголовок группы
|
|
||||||
if (grp.visible) {
|
|
||||||
//Формируем отображение по умолчанию
|
|
||||||
defaultView = (
|
|
||||||
<Box sx={{ ...STYLES.GROUP_HEADER_BOX, height: grp.height }}>
|
|
||||||
<Typography sx={{ ...STYLES.GROUP_HEADER, maxWidth: grp.width, maxHeight: grp.height }}>{grp.name}</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
//Формируем собственное отображение, если требуется
|
|
||||||
customView = groupHeaderRenderer ? groupHeaderRenderer({ group: grp }) : null;
|
|
||||||
//Рассчитываем координаты заголовка группы
|
|
||||||
groupHeaderX = Math.min(...groupTasks.map(o => o.start)) * shift;
|
|
||||||
groupHeaderY = NDEFAULT_HEADER_HEIGHT + Math.min(...groupTasks.map(o => o.lineNumb)) * lineHeight - grp.height - 5;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<foreignObject
|
|
||||||
x={groupHeaderX}
|
|
||||||
y={groupHeaderY}
|
|
||||||
width={grp.width}
|
|
||||||
height={grp.height}
|
|
||||||
className={`TaskGrpHeader${indexGrp}`}
|
|
||||||
display="none"
|
|
||||||
key={indexGrp}
|
|
||||||
>
|
|
||||||
{customView ? customView : defaultView}
|
|
||||||
</foreignObject>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</g>
|
|
||||||
) : null}
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Основная информация циклограммы
|
|
||||||
P8PCyclogramMain.propTypes = {
|
|
||||||
columns: PropTypes.array.isRequired,
|
|
||||||
groups: PropTypes.array,
|
|
||||||
tasks: PropTypes.array.isRequired,
|
|
||||||
shift: PropTypes.number.isRequired,
|
|
||||||
lineHeight: PropTypes.number.isRequired,
|
|
||||||
maxWidth: PropTypes.number.isRequired,
|
|
||||||
maxHeight: PropTypes.number.isRequired,
|
|
||||||
openTaskEditor: PropTypes.func.isRequired,
|
|
||||||
groupHeaderRenderer: PropTypes.func,
|
|
||||||
taskRenderer: PropTypes.func,
|
|
||||||
columnRenderer: PropTypes.func,
|
|
||||||
headerBlock: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
//Редактор задачи
|
|
||||||
const P8PCyclogramTaskEditor = ({
|
|
||||||
task,
|
|
||||||
taskAttributes,
|
|
||||||
onOk,
|
|
||||||
onCancel,
|
|
||||||
taskAttributeRenderer,
|
|
||||||
taskDialogRenderer,
|
|
||||||
nameCaption,
|
|
||||||
okBtnCaption,
|
|
||||||
cancelBtnCaption
|
|
||||||
}) => {
|
|
||||||
//Собственное состояние
|
|
||||||
const [state, setState] = useState({
|
|
||||||
start: task.start,
|
|
||||||
end: task.end
|
|
||||||
});
|
|
||||||
|
|
||||||
//Отображаемые атрибуты
|
|
||||||
const dispTaskAttributes =
|
|
||||||
Array.isArray(taskAttributes) && taskAttributes.length > 0 ? taskAttributes.filter(attr => attr.visible && hasValue(task[attr.name])) : [];
|
|
||||||
|
|
||||||
//При сохранении
|
|
||||||
const handleOk = () => (onOk && state.start && state.end ? onOk() : null);
|
|
||||||
|
|
||||||
//При отмене
|
|
||||||
const handleCancel = () => (onCancel ? onCancel() : null);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Dialog open onClose={handleCancel}>
|
|
||||||
{taskDialogRenderer ? (
|
|
||||||
taskDialogRenderer({ task, taskAttributes, close: handleCancel })
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
|
|
||||||
<List sx={STYLES.TASK_EDITOR_LIST}>
|
|
||||||
<ListItem alignItems="flex-start">
|
|
||||||
<ListItemText primary={nameCaption} secondary={task.fullName} />
|
|
||||||
</ListItem>
|
|
||||||
{dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
|
|
||||||
{dispTaskAttributes.length > 0
|
|
||||||
? dispTaskAttributes.map((attr, i) => {
|
|
||||||
const defaultView = task[attr.name];
|
|
||||||
const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null;
|
|
||||||
return (
|
|
||||||
<React.Fragment key={i}>
|
|
||||||
<ListItem alignItems="flex-start">
|
|
||||||
<ListItemText
|
|
||||||
primary={attr.caption}
|
|
||||||
secondaryTypographyProps={{ component: "span" }}
|
|
||||||
secondary={customView ? customView : defaultView}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
{i < dispTaskAttributes.length - 1 ? <Divider component="li" /> : null}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</List>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={handleOk}>{okBtnCaption}</Button>
|
|
||||||
<Button onClick={handleCancel}>{cancelBtnCaption}</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Редактор задачи
|
|
||||||
P8PCyclogramTaskEditor.propTypes = {
|
|
||||||
task: P8P_CYCLOGRAM_TASK_SHAPE,
|
|
||||||
taskAttributes: PropTypes.arrayOf(P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE),
|
|
||||||
onOk: PropTypes.func,
|
|
||||||
onCancel: PropTypes.func,
|
|
||||||
taskAttributeRenderer: PropTypes.func,
|
|
||||||
taskDialogRenderer: PropTypes.func,
|
|
||||||
nameCaption: PropTypes.string.isRequired,
|
|
||||||
okBtnCaption: PropTypes.string.isRequired,
|
|
||||||
cancelBtnCaption: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Циклограмма
|
|
||||||
const P8PCyclogram = ({
|
|
||||||
containerStyle,
|
|
||||||
lineHeight,
|
|
||||||
title,
|
|
||||||
titleStyle,
|
|
||||||
onTitleClick,
|
|
||||||
zoomBar,
|
|
||||||
zoom,
|
|
||||||
columns,
|
|
||||||
columnRenderer,
|
|
||||||
groups,
|
|
||||||
groupHeaderRenderer,
|
|
||||||
tasks,
|
|
||||||
taskRenderer,
|
|
||||||
taskAttributes,
|
|
||||||
taskAttributeRenderer,
|
|
||||||
taskDialogRenderer,
|
|
||||||
noDataFoundText,
|
|
||||||
nameTaskEditorCaption,
|
|
||||||
okTaskEditorBtnCaption,
|
|
||||||
cancelTaskEditorBtnCaption
|
|
||||||
}) => {
|
|
||||||
//Хук основного блока (для последующего определения доступной ширины)
|
|
||||||
const mainBlock = useRef(null);
|
|
||||||
//Хук для заголовка таблицы
|
|
||||||
const headerBlock = useRef(null);
|
|
||||||
//Собственное состояние
|
|
||||||
const [state, setState] = useState({
|
|
||||||
noData: true,
|
|
||||||
loaded: false,
|
|
||||||
lineHeight: NDEFAULT_LINE_HEIGHT,
|
|
||||||
maxWidth: 0,
|
|
||||||
maxHeight: 0,
|
|
||||||
shift: 0,
|
|
||||||
zoom: P8P_CYCLOGRAM_ZOOM.includes(zoom) ? zoom : 1,
|
|
||||||
tasks: [],
|
|
||||||
editTask: null
|
|
||||||
});
|
|
||||||
|
|
||||||
//Обновление масштаба циклограммы
|
|
||||||
const handleZoomChange = direction => {
|
|
||||||
//Считываем текущий индекс
|
|
||||||
const currentIndex = P8P_CYCLOGRAM_ZOOM.indexOf(state.zoom);
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
zoom:
|
|
||||||
currentIndex + direction !== P8P_CYCLOGRAM_ZOOM.length && currentIndex + direction !== -1
|
|
||||||
? P8P_CYCLOGRAM_ZOOM[currentIndex + direction]
|
|
||||||
: pv.zoom
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
//Открытие редактора задачи
|
|
||||||
const openTaskEditor = task => setState(pv => ({ ...pv, editTask: { ...task } }));
|
|
||||||
|
|
||||||
//При сохранении задачи в редакторе
|
|
||||||
const handleTaskEditorSave = () => {
|
|
||||||
setState(pv => ({ ...pv, editTask: null }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При закрытии редактора задачи без сохранения
|
|
||||||
const handleTaskEditorCancel = () => setState(pv => ({ ...pv, editTask: null }));
|
|
||||||
|
|
||||||
//При скролле блока
|
|
||||||
const handleScroll = e => {
|
|
||||||
//Изменяем позицию заголовка таблицы относительно скролла
|
|
||||||
headerBlock.current.setAttribute("transform", "translate(0," + e.currentTarget.scrollTop + ")");
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении данных
|
|
||||||
useEffect(() => {
|
|
||||||
//Если есть колонки и задачи
|
|
||||||
if (Array.isArray(columns) && columns.length > 0 && Array.isArray(tasks) && tasks.length > 0) {
|
|
||||||
//Определяем текущую максимальную ширину колонок
|
|
||||||
let currentColumnsMaxWidth = Math.max(...columns.map(o => o.end));
|
|
||||||
//Определяем доступный сдвиг для ширины колонок (16 - паддинг по бокам)
|
|
||||||
let columnShift = getShift(columns, currentColumnsMaxWidth, mainBlock.current.offsetWidth - 16) * state.zoom;
|
|
||||||
//Устанавливаем значения исходя из колонок/задач
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
loaded: true,
|
|
||||||
lineHeight: lineHeight ? lineHeight : NDEFAULT_LINE_HEIGHT,
|
|
||||||
maxWidth: columnShift !== 0 ? currentColumnsMaxWidth * columnShift : currentColumnsMaxWidth,
|
|
||||||
maxHeight: NDEFAULT_HEADER_HEIGHT + (Math.max(...tasks.map(o => o.lineNumb)) + 1) * (lineHeight ? lineHeight : NDEFAULT_LINE_HEIGHT),
|
|
||||||
shift: columnShift,
|
|
||||||
tasks: tasks,
|
|
||||||
noData: false
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
//Устанавливаем значения исходя из колонок/задач
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
noData: true
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}, [columns, lineHeight, state.zoom, tasks]);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div ref={mainBlock} style={{ ...(containerStyle ? containerStyle : {}) }}>
|
|
||||||
{state.noData ? <P8PAppInlineError text={noDataFoundText} /> : null}
|
|
||||||
{state.loaded ? (
|
|
||||||
<>
|
|
||||||
{title ? (
|
|
||||||
<Typography
|
|
||||||
p={1}
|
|
||||||
sx={{ ...STYLES.CYCLOGRAM_TITLE, ...(titleStyle ? titleStyle : {}) }}
|
|
||||||
align="center"
|
|
||||||
color="textSecondary"
|
|
||||||
variant="subtitle1"
|
|
||||||
>
|
|
||||||
{onTitleClick ? (
|
|
||||||
<Link component="button" variant="body2" underline="hover" onClick={() => onTitleClick()}>
|
|
||||||
{title}
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
title
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
) : null}
|
|
||||||
{zoomBar ? (
|
|
||||||
<Box p={1} sx={STYLES.CYCLOGRAM_ZOOM}>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => handleZoomChange(1)}
|
|
||||||
disabled={state.zoom == P8P_CYCLOGRAM_ZOOM[P8P_CYCLOGRAM_ZOOM.length - 1]}
|
|
||||||
>
|
|
||||||
<Icon>zoom_in</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton onClick={() => handleZoomChange(-1)} disabled={state.zoom == P8P_CYCLOGRAM_ZOOM[0]}>
|
|
||||||
<Icon>zoom_out</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
) : null}
|
|
||||||
<Box className="scroll" sx={STYLES.CYCLOGRAM_BOX(state.noData, title, zoomBar)} onScroll={handleScroll}>
|
|
||||||
<svg id="cyclogram" width={state.maxWidth} height={state.maxHeight}>
|
|
||||||
<P8PCyclogramGrid
|
|
||||||
tasks={state.tasks}
|
|
||||||
columns={columns}
|
|
||||||
shift={state.shift}
|
|
||||||
maxWidth={state.maxWidth}
|
|
||||||
maxHeight={state.maxHeight}
|
|
||||||
lineHeight={state.lineHeight}
|
|
||||||
/>
|
|
||||||
<P8PCyclogramMain
|
|
||||||
columns={columns}
|
|
||||||
groups={groups}
|
|
||||||
tasks={state.tasks}
|
|
||||||
shift={state.shift}
|
|
||||||
lineHeight={state.lineHeight}
|
|
||||||
maxWidth={state.maxWidth}
|
|
||||||
maxHeight={state.maxHeight}
|
|
||||||
groupHeaderRenderer={groupHeaderRenderer}
|
|
||||||
openTaskEditor={openTaskEditor}
|
|
||||||
taskRenderer={taskRenderer}
|
|
||||||
columnRenderer={columnRenderer}
|
|
||||||
headerBlock={headerBlock}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
{state.editTask ? (
|
|
||||||
<P8PCyclogramTaskEditor
|
|
||||||
task={state.editTask}
|
|
||||||
taskAttributes={taskAttributes}
|
|
||||||
onOk={handleTaskEditorSave}
|
|
||||||
onCancel={handleTaskEditorCancel}
|
|
||||||
taskAttributeRenderer={taskAttributeRenderer}
|
|
||||||
taskDialogRenderer={taskDialogRenderer}
|
|
||||||
nameCaption={nameTaskEditorCaption}
|
|
||||||
okBtnCaption={okTaskEditorBtnCaption}
|
|
||||||
cancelBtnCaption={cancelTaskEditorBtnCaption}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Циклограмма
|
|
||||||
P8PCyclogram.propTypes = {
|
|
||||||
containerStyle: PropTypes.object,
|
|
||||||
lineHeight: PropTypes.number,
|
|
||||||
title: PropTypes.string,
|
|
||||||
titleStyle: PropTypes.object,
|
|
||||||
onTitleClick: PropTypes.func,
|
|
||||||
zoomBar: PropTypes.bool,
|
|
||||||
zoom: PropTypes.number,
|
|
||||||
columns: PropTypes.arrayOf(P8P_CYCLOGRAM_COLUMN_SHAPE).isRequired,
|
|
||||||
columnRenderer: PropTypes.func,
|
|
||||||
groups: PropTypes.arrayOf(P8P_CYCLOGRAM_GROUP_SHAPE),
|
|
||||||
groupHeaderRenderer: PropTypes.func,
|
|
||||||
tasks: PropTypes.arrayOf(P8P_CYCLOGRAM_TASK_SHAPE).isRequired,
|
|
||||||
taskRenderer: PropTypes.func,
|
|
||||||
taskAttributes: PropTypes.arrayOf(P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE),
|
|
||||||
taskAttributeRenderer: PropTypes.func,
|
|
||||||
taskDialogRenderer: PropTypes.func,
|
|
||||||
noDataFoundText: PropTypes.string.isRequired,
|
|
||||||
nameTaskEditorCaption: PropTypes.string.isRequired,
|
|
||||||
okTaskEditorBtnCaption: PropTypes.string.isRequired,
|
|
||||||
cancelTaskEditorBtnCaption: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PCyclogram };
|
|
@ -76,14 +76,6 @@ const P8P_GANTT_CONFIG_PROPS = {
|
|||||||
cancelTaskEditorBtnCaption: BUTTONS.CANCEL
|
cancelTaskEditorBtnCaption: BUTTONS.CANCEL
|
||||||
};
|
};
|
||||||
|
|
||||||
//Конфигурируемые свойства "Циклограммы" (P8PCyclogram)
|
|
||||||
const P8P_CYCLOGRAM_CONFIG_PROPS = {
|
|
||||||
noDataFoundText: TEXTS.NO_DATA_FOUND,
|
|
||||||
nameTaskEditorCaption: CAPTIONS.NAME,
|
|
||||||
okTaskEditorBtnCaption: BUTTONS.OK,
|
|
||||||
cancelTaskEditorBtnCaption: BUTTONS.CANCEL
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------------------
|
//-----------------------
|
||||||
//Вспомогательные функции
|
//Вспомогательные функции
|
||||||
//-----------------------
|
//-----------------------
|
||||||
@ -140,7 +132,6 @@ export {
|
|||||||
P8P_DATA_GRID_SIZE,
|
P8P_DATA_GRID_SIZE,
|
||||||
P8P_DATA_GRID_FILTER_SHAPE,
|
P8P_DATA_GRID_FILTER_SHAPE,
|
||||||
P8P_GANTT_CONFIG_PROPS,
|
P8P_GANTT_CONFIG_PROPS,
|
||||||
P8P_CYCLOGRAM_CONFIG_PROPS,
|
|
||||||
P8P_GANTT_TASK_SHAPE,
|
P8P_GANTT_TASK_SHAPE,
|
||||||
P8P_GANTT_TASK_ATTRIBUTE_SHAPE,
|
P8P_GANTT_TASK_ATTRIBUTE_SHAPE,
|
||||||
P8P_GANTT_TASK_COLOR_SHAPE,
|
P8P_GANTT_TASK_COLOR_SHAPE,
|
||||||
|
@ -1,302 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 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 }) => {
|
|
||||||
//Собственное состояние
|
|
||||||
const [taskDates, setTaskDates] = useState({ start: task.ddate_start, end: task.ddate_end });
|
|
||||||
|
|
||||||
//Тип проекта
|
|
||||||
const textType = task.type === 0 ? "Задачи проекта" : task.type === 1 ? "Этап проекта" : "Работа проекта";
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//Изменение дат задачи
|
|
||||||
const changeDates = useCallback(async () => {
|
|
||||||
//Изменяем даты задачи
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_TASK_MODIFY",
|
|
||||||
args: {
|
|
||||||
NIDENT: ident,
|
|
||||||
NRN: task.rn,
|
|
||||||
SDATE_FROM: formatDateRF(taskDates.start),
|
|
||||||
SDATE_TO: formatDateRF(taskDates.end)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
handleReload();
|
|
||||||
close();
|
|
||||||
}, [close, executeStored, handleReload, ident, task.rn, taskDates.end, taskDates.start]);
|
|
||||||
|
|
||||||
//При нажатии OK
|
|
||||||
const handleOk = () => {
|
|
||||||
//Изменяем даты задачи
|
|
||||||
changeDates();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
|
|
||||||
<List sx={STYLES.TASK_EDITOR_LIST}>
|
|
||||||
<ListItem alignItems="flex-start">
|
|
||||||
<ListItemText primary={"Наименование"} secondary={task.fullName} />
|
|
||||||
</ListItem>
|
|
||||||
<Divider component="li" />
|
|
||||||
<ListItem alignItems="flex-start">
|
|
||||||
<ListItemText
|
|
||||||
secondaryTypographyProps={{ component: "span" }}
|
|
||||||
primary={"Начало"}
|
|
||||||
secondary={
|
|
||||||
<TextField
|
|
||||||
error={!taskDates.start}
|
|
||||||
disabled={task.type !== 2}
|
|
||||||
name="start"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type={"date"}
|
|
||||||
value={taskDates.start}
|
|
||||||
onChange={e => setTaskDates(pv => ({ ...pv, start: e.target.value }))}
|
|
||||||
variant="standard"
|
|
||||||
size="small"
|
|
||||||
margin="normal"
|
|
||||||
></TextField>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
<Divider component="li" />
|
|
||||||
<ListItem alignItems="flex-start">
|
|
||||||
<ListItemText
|
|
||||||
secondaryTypographyProps={{ component: "span" }}
|
|
||||||
primary={"Окончание"}
|
|
||||||
secondary={
|
|
||||||
<TextField
|
|
||||||
error={!taskDates.end}
|
|
||||||
disabled={task.type !== 2}
|
|
||||||
name="end"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type={"date"}
|
|
||||||
value={taskDates.end}
|
|
||||||
onChange={e => setTaskDates(pv => ({ ...pv, end: e.target.value }))}
|
|
||||||
variant="standard"
|
|
||||||
size="small"
|
|
||||||
margin="normal"
|
|
||||||
></TextField>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
<Divider component="li" />
|
|
||||||
<ListItem alignItems="flex-start">
|
|
||||||
<ListItemText
|
|
||||||
primary={"Тип"}
|
|
||||||
secondaryTypographyProps={{ component: "span" }}
|
|
||||||
secondary={
|
|
||||||
<Stack direction="row" gap={0.5}>
|
|
||||||
<Icon title={textType}>{task.type === 0 ? "description" : task.type === 1 ? "check" : "work_outline"}</Icon>
|
|
||||||
{textType}
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={handleOk}>ОК</Button>
|
|
||||||
<Button onClick={close}>Отмена</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Диалог открытия задачи
|
|
||||||
CustomTaskDialog.propTypes = {
|
|
||||||
task: PropTypes.object.isRequired,
|
|
||||||
ident: PropTypes.number.isRequired,
|
|
||||||
handleReload: PropTypes.func.isRequired,
|
|
||||||
close: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Заголовок группы
|
|
||||||
const CustomGroupHeader = ({ group }) => {
|
|
||||||
return (
|
|
||||||
<Box sx={STYLES.GROUP_HEADER(group.height)}>
|
|
||||||
<Typography variant="body2">{group.name}</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Заголовок группы
|
|
||||||
CustomGroupHeader.propTypes = {
|
|
||||||
group: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Отображение задачи
|
|
||||||
const taskRenderer = ({ task }) => {
|
|
||||||
//Если это задачи проекта
|
|
||||||
if (task.type === 0) {
|
|
||||||
return {
|
|
||||||
taskStyle: { border: "3px solid #ebe058" }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Пример: Циклограмма "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 (
|
|
||||||
<Box>
|
|
||||||
<div style={STYLES.CONTAINER}>
|
|
||||||
<Typography sx={STYLES.TITLE} variant={"h6"}>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
<Grid container direction="column" alignItems="center">
|
|
||||||
<Grid item xs={12}>
|
|
||||||
{state.dataLoaded ? (
|
|
||||||
<P8PCyclogram
|
|
||||||
{...P8P_CYCLOGRAM_CONFIG_PROPS}
|
|
||||||
{...state}
|
|
||||||
containerStyle={STYLES.GANTT_CONTAINER}
|
|
||||||
lineHeight={LINE_HEIGHT}
|
|
||||||
taskDialogRenderer={prms => (
|
|
||||||
<CustomTaskDialog task={prms.task} ident={state.ident} handleReload={handleReload} close={prms.close} />
|
|
||||||
)}
|
|
||||||
taskRenderer={prms => taskRenderer(prms)}
|
|
||||||
groupHeaderRenderer={prms => <CustomGroupHeader group={prms.group} />}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Пример: Циклограмма "P8PCyclogram"
|
|
||||||
Cyclogram.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { Cyclogram };
|
|
@ -18,7 +18,6 @@ import { DataGrid } from "./data_grid"; //Пример: Таблица данн
|
|||||||
import { Chart } from "./chart"; //Пример: Графики "P8PChart"
|
import { Chart } from "./chart"; //Пример: Графики "P8PChart"
|
||||||
import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt"
|
import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt"
|
||||||
import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG"
|
import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG"
|
||||||
import { Cyclogram } from "./cyclogram"; //Пример: Циклограмма "P8PCyclogram"
|
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -33,8 +32,7 @@ const MODES = {
|
|||||||
DATAGRID: { name: "DATAGRID", caption: 'Таблица данных "P8PDataGrid"', component: DataGrid },
|
DATAGRID: { name: "DATAGRID", caption: 'Таблица данных "P8PDataGrid"', component: DataGrid },
|
||||||
CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart },
|
CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart },
|
||||||
GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt },
|
GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt },
|
||||||
SVG: { name: "SVG", caption: 'Интерактивные изображения "P8PSVG"', component: Svg },
|
SVG: { name: "SVG", caption: 'Интерактивные изображения "P8PSVG"', component: Svg }
|
||||||
CYCLOGRAM: { name: "CYCLOGRAM", caption: 'Циклограмма "P8PCyclogram"', component: Cyclogram }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Примеры
|
|
||||||
Буфер для циклограммы
|
|
||||||
*/
|
|
||||||
create table P8PNL_SMPL_CYCLOGRAM
|
|
||||||
(
|
|
||||||
RN number( 17 ) not null,
|
|
||||||
IDENT number( 17 ) not null,
|
|
||||||
TYPE number( 1 ) not null,
|
|
||||||
NAME varchar2( 200 ) not null,
|
|
||||||
POS_START number( 17 ) default null,
|
|
||||||
POS_END number( 17 ) default null,
|
|
||||||
DATE_FROM date default null,
|
|
||||||
DATE_TO date default null,
|
|
||||||
TASK_GROUP number( 17 ) default null,
|
|
||||||
constraint C_P8PNL_SMPL_CYCLOGRAM_RN_PK primary key (RN),
|
|
||||||
constraint C_P8PNL_SMPL_CYCLOGRAM_TP_VAL check (TYPE in (0, 1, 2))
|
|
||||||
);
|
|
@ -59,35 +59,10 @@ create or replace package PKG_P8PANELS_SAMPLES as
|
|||||||
DDATE_TO in date -- Дата окончания задачи
|
DDATE_TO in date -- Дата окончания задачи
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Инициализация буфера данных для диаграммы Ганта */
|
|
||||||
procedure CYCLOGRAM_INIT
|
|
||||||
(
|
|
||||||
NIDENT in out number -- Идентификатор буфера сформированных данных (null - сгенерировать новый, !null - удалить старые данные и пересоздать с указанным идентификатором)
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Сбор данных для отображения циклограммы */
|
|
||||||
procedure CYCLOGRAM
|
|
||||||
(
|
|
||||||
NIDENT in number, -- Идентификатор процесса
|
|
||||||
COUT out clob -- Сериализованные данные для циклограммы
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Изменение задачи циклограммы */
|
|
||||||
procedure CYCLOGRAM_TASK_MODIFY
|
|
||||||
(
|
|
||||||
NIDENT in number, -- Идентификатор буфера
|
|
||||||
NRN in number, -- Рег. номер записи
|
|
||||||
SDATE_FROM in varchar2, -- Дата начала (в строковом представлении)
|
|
||||||
SDATE_TO in varchar2 -- Дата окончания (в строковом представлении)
|
|
||||||
);
|
|
||||||
|
|
||||||
end PKG_P8PANELS_SAMPLES;
|
end PKG_P8PANELS_SAMPLES;
|
||||||
/
|
/
|
||||||
create or replace package body PKG_P8PANELS_SAMPLES as
|
create or replace package body PKG_P8PANELS_SAMPLES as
|
||||||
|
|
||||||
/* Константы для циклограммы */
|
|
||||||
NCG_MULTIPLIER constant PKG_STD.TNUMBER := 5; -- Множитель для ширины отображения
|
|
||||||
|
|
||||||
/* Получение списка контрагентов */
|
/* Получение списка контрагентов */
|
||||||
procedure AGNLIST_GET
|
procedure AGNLIST_GET
|
||||||
(
|
(
|
||||||
@ -230,7 +205,6 @@ create or replace package body PKG_P8PANELS_SAMPLES as
|
|||||||
NCONTACT_METHOD => null,
|
NCONTACT_METHOD => null,
|
||||||
SMF_ID => null,
|
SMF_ID => null,
|
||||||
SOKOGU => null,
|
SOKOGU => null,
|
||||||
NJURPERS_SUBDIV => null,
|
|
||||||
NRN => NRN);
|
NRN => NRN);
|
||||||
end AGNLIST_INSERT;
|
end AGNLIST_INSERT;
|
||||||
|
|
||||||
@ -692,533 +666,6 @@ create or replace package body PKG_P8PANELS_SAMPLES as
|
|||||||
DDATE_TO => TRUNC(DDATE_TO));
|
DDATE_TO => TRUNC(DDATE_TO));
|
||||||
end loop;
|
end loop;
|
||||||
end GANTT_MODIFY;
|
end GANTT_MODIFY;
|
||||||
|
|
||||||
/* Очистка буфера данных для циклограммы */
|
|
||||||
procedure CYCLOGRAM_BASE_CLEAN
|
|
||||||
(
|
|
||||||
NIDENT in number -- Идентификатор буфера
|
|
||||||
)
|
|
||||||
is
|
|
||||||
begin
|
|
||||||
/* Удалим из буфера всё с указанным идентификатором */
|
|
||||||
delete from P8PNL_SMPL_CYCLOGRAM T where T.IDENT = NIDENT;
|
|
||||||
end CYCLOGRAM_BASE_CLEAN;
|
|
||||||
|
|
||||||
/* Добавление данных в буфер циклограммы */
|
|
||||||
procedure CYCLOGRAM_BASE_INSERT
|
|
||||||
(
|
|
||||||
NIDENT in number, -- Идентификатор буфера
|
|
||||||
NTYPE in number, -- Тип (0 - колонка, 1 - группа, 2 - задача)
|
|
||||||
SNAME in varchar2, -- Наименование
|
|
||||||
NPOS_START in number := null, -- Позиция начала элемента
|
|
||||||
NPOS_END in number := null, -- Позиция окончания элемента
|
|
||||||
DDATE_FROM in date := null, -- Дата начала
|
|
||||||
DDATE_TO in date := null, -- Дата окончания
|
|
||||||
NTASK_GROUP in number := null, -- Рег. номер группы
|
|
||||||
NRN out number -- Рег. номер записи
|
|
||||||
)
|
|
||||||
is
|
|
||||||
begin
|
|
||||||
/* Генерируем рег. номер записи */
|
|
||||||
NRN := GEN_ID();
|
|
||||||
/* Добавим запись */
|
|
||||||
insert into P8PNL_SMPL_CYCLOGRAM
|
|
||||||
(RN, IDENT, type, name, POS_START, POS_END, DATE_FROM, DATE_TO, TASK_GROUP)
|
|
||||||
values
|
|
||||||
(NRN, NIDENT, NTYPE, SNAME, NPOS_START, NPOS_END, DDATE_FROM, DDATE_TO, NTASK_GROUP);
|
|
||||||
end CYCLOGRAM_BASE_INSERT;
|
|
||||||
|
|
||||||
/* Исправление данных в буфере циклограммы */
|
|
||||||
procedure CYCLOGRAM_BASE_UPDATE
|
|
||||||
(
|
|
||||||
NIDENT in number, -- Идентификатор буфера
|
|
||||||
NRN in number, -- Рег. номер записи
|
|
||||||
NTYPE in number, -- Тип задачи (0 - этап/веха, 1 - работа)
|
|
||||||
SNAME in varchar2, -- Наименование
|
|
||||||
NPOS_START in number, -- Позиция начала
|
|
||||||
NPOS_END in number, -- Позиция окончания
|
|
||||||
DDATE_FROM in date, -- Дата начала
|
|
||||||
DDATE_TO in date, -- Дата окончания
|
|
||||||
NTASK_GROUP in number -- Рег. номер группы
|
|
||||||
)
|
|
||||||
is
|
|
||||||
begin
|
|
||||||
/* Изменим запись */
|
|
||||||
update P8PNL_SMPL_CYCLOGRAM T
|
|
||||||
set T.TYPE = NTYPE,
|
|
||||||
T.NAME = SNAME,
|
|
||||||
T.POS_START = NPOS_START,
|
|
||||||
T.POS_END = NPOS_END,
|
|
||||||
T.DATE_FROM = DDATE_FROM,
|
|
||||||
T.DATE_TO = DDATE_TO,
|
|
||||||
T.TASK_GROUP = NTASK_GROUP
|
|
||||||
where T.RN = NRN
|
|
||||||
and T.IDENT = NIDENT;
|
|
||||||
end CYCLOGRAM_BASE_UPDATE;
|
|
||||||
|
|
||||||
/* Инициализация буфера данных для диаграммы Ганта */
|
|
||||||
procedure CYCLOGRAM_INIT
|
|
||||||
(
|
|
||||||
NIDENT in out number -- Идентификатор буфера сформированных данных (null - сгенерировать новый, !null - удалить старые данные и пересоздать с указанным идентификатором)
|
|
||||||
)
|
|
||||||
is
|
|
||||||
NYEAR PKG_STD.TNUMBER; -- Текущий год
|
|
||||||
DCYCLOGRAM_START PKG_STD.TLDATE; -- Дата начала циклограммы
|
|
||||||
DMONTH_CUR PKG_STD.TLDATE; -- Текущий месяц (для расчетов)
|
|
||||||
DMONTH_START PKG_STD.TLDATE; -- Начало месяца (для расчетов)
|
|
||||||
DMONTH_END PKG_STD.TLDATE; -- Окончание месяца (для расчетов)
|
|
||||||
NMONTH_DAYS PKG_STD.TNUMBER; -- Количество дней месяца
|
|
||||||
NSTART PKG_STD.TNUMBER := 0; -- Позиция начала элемента
|
|
||||||
NEND PKG_STD.TNUMBER := 0; -- Позиция окончания элемента
|
|
||||||
NGROUP PKG_STD.TNUMBER; -- Рег. номер группы
|
|
||||||
NDUMMY PKG_STD.TNUMBER; -- Буфер для рег. номера
|
|
||||||
NMONTH PKG_STD.TNUMBER; -- Месяц даты
|
|
||||||
|
|
||||||
/* Инициализация группы */
|
|
||||||
procedure INIT_GROUP
|
|
||||||
(
|
|
||||||
NIDENT in number, -- Идентификатор буфера
|
|
||||||
DDATE in date, -- Текущая обрабатываемая дата
|
|
||||||
NRN in out number -- Рег. номер группы
|
|
||||||
)
|
|
||||||
is
|
|
||||||
NMONTH PKG_STD.TNUMBER; -- Месяц даты
|
|
||||||
begin
|
|
||||||
/* Считываем текущий месяц */
|
|
||||||
NMONTH := D_MONTH(DDATE => DDATE);
|
|
||||||
/* Исходим от даты (формируем группу на начало каждого квартала) */
|
|
||||||
case NMONTH
|
|
||||||
/* Первый квартал */
|
|
||||||
when 1 then
|
|
||||||
/* Добавляем группу */
|
|
||||||
CYCLOGRAM_BASE_INSERT(NIDENT => NIDENT, NTYPE => 1, SNAME => 'I группа', NRN => NRN);
|
|
||||||
/* Второй квартал */
|
|
||||||
when 4 then
|
|
||||||
/* Добавляем группу */
|
|
||||||
CYCLOGRAM_BASE_INSERT(NIDENT => NIDENT, NTYPE => 1, SNAME => 'II группа', NRN => NRN);
|
|
||||||
/* Третий квартал */
|
|
||||||
when 7 then
|
|
||||||
/* Добавляем группу */
|
|
||||||
CYCLOGRAM_BASE_INSERT(NIDENT => NIDENT, NTYPE => 1, SNAME => 'III группа', NRN => NRN);
|
|
||||||
/* Четвертый квартал */
|
|
||||||
when 10 then
|
|
||||||
/* ДОбавляем группу */
|
|
||||||
CYCLOGRAM_BASE_INSERT(NIDENT => NIDENT, NTYPE => 1, SNAME => 'IV группа', NRN => NRN);
|
|
||||||
else
|
|
||||||
null;
|
|
||||||
end case;
|
|
||||||
end INIT_GROUP;
|
|
||||||
begin
|
|
||||||
/* Удаляем старые данные из буфера */
|
|
||||||
if (NIDENT is not null) then
|
|
||||||
CYCLOGRAM_BASE_CLEAN(NIDENT => NIDENT);
|
|
||||||
else
|
|
||||||
/* Илиформируем новый идентификатор, если не задан */
|
|
||||||
NIDENT := GEN_IDENT();
|
|
||||||
end if;
|
|
||||||
/* Фиксируем текущий год */
|
|
||||||
NYEAR := EXTRACT(year from sysdate);
|
|
||||||
/* Фиксируем дату начала циклограммы */
|
|
||||||
DCYCLOGRAM_START := TO_DATE('01.01.' || NYEAR, 'dd.mm.yyyy');
|
|
||||||
/* Добавляем колонки и групповые задачи (месяцы года) */
|
|
||||||
for I in 0 .. 11
|
|
||||||
loop
|
|
||||||
/* Рассчитываем текущий месяц */
|
|
||||||
DMONTH_CUR := ADD_MONTHS(DCYCLOGRAM_START, I);
|
|
||||||
/* Считываем первый и последний день месяца */
|
|
||||||
P_FIRST_LAST_DAY(DCALCDATE => DMONTH_CUR, DBGNDATE => DMONTH_START, DENDDATE => DMONTH_END);
|
|
||||||
/* Рассчитываем количество дней месяца */
|
|
||||||
NMONTH_DAYS := DMONTH_END - DMONTH_START + 1;
|
|
||||||
/* Рассчитываем позицию окончания элемента */
|
|
||||||
NEND := NSTART + (NMONTH_DAYS * NCG_MULTIPLIER);
|
|
||||||
/* Определяем номер месяца */
|
|
||||||
NMONTH := D_MONTH(DDATE => DMONTH_CUR);
|
|
||||||
/* Добавляем колонку в таблицу */
|
|
||||||
CYCLOGRAM_BASE_INSERT(NIDENT => NIDENT,
|
|
||||||
NTYPE => 0,
|
|
||||||
SNAME => TO_CHAR(NMONTH),
|
|
||||||
NPOS_START => NSTART,
|
|
||||||
NPOS_END => NEND,
|
|
||||||
NRN => NDUMMY);
|
|
||||||
/* Инициализируем группу */
|
|
||||||
INIT_GROUP(NIDENT => NIDENT, DDATE => DMONTH_CUR, NRN => NGROUP);
|
|
||||||
/* Добавляем задачу */
|
|
||||||
CYCLOGRAM_BASE_INSERT(NIDENT => NIDENT,
|
|
||||||
NTYPE => 2,
|
|
||||||
SNAME => 'Работа ' || TO_CHAR(I + 1),
|
|
||||||
NPOS_START => NSTART,
|
|
||||||
NPOS_END => NEND,
|
|
||||||
DDATE_FROM => DMONTH_START,
|
|
||||||
DDATE_TO => DMONTH_END,
|
|
||||||
NTASK_GROUP => NGROUP,
|
|
||||||
NRN => NDUMMY);
|
|
||||||
/* Если это февраль, май, август или ноябрь - добавляем особосбленную задачу */
|
|
||||||
if (NMONTH in (2, 5, 8, 11)) then
|
|
||||||
/* Добавляем обособленную задачу */
|
|
||||||
CYCLOGRAM_BASE_INSERT(NIDENT => NIDENT,
|
|
||||||
NTYPE => 2,
|
|
||||||
SNAME => 'Обособленная работа ' || NMONTH,
|
|
||||||
NPOS_START => NSTART,
|
|
||||||
NPOS_END => NEND,
|
|
||||||
DDATE_FROM => DMONTH_START,
|
|
||||||
DDATE_TO => DMONTH_END,
|
|
||||||
NRN => NDUMMY);
|
|
||||||
end if;
|
|
||||||
/* Рассчитываем начало следующего месяца */
|
|
||||||
NSTART := NEND;
|
|
||||||
end loop;
|
|
||||||
end CYCLOGRAM_INIT;
|
|
||||||
|
|
||||||
/* Сбор данных для отображения циклограммы */
|
|
||||||
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
|
|
||||||
/* Считываем начало и окончание этапа */
|
|
||||||
begin
|
|
||||||
select min(T.DATE_FROM),
|
|
||||||
max(T.DATE_TO),
|
|
||||||
min(T.POS_START),
|
|
||||||
max(T.POS_END)
|
|
||||||
into DTASK_DATE_START,
|
|
||||||
DTASK_DATE_END,
|
|
||||||
NTASK_START,
|
|
||||||
NTASK_END
|
|
||||||
from P8PNL_SMPL_CYCLOGRAM T
|
|
||||||
where T.IDENT = NIDENT
|
|
||||||
and (((NFLAG_WO_GROUP = 1) and (T.TASK_GROUP is null)) or ((NFLAG_WO_GROUP = 0) and (T.TASK_GROUP is not null)))
|
|
||||||
and ((NGROUP is null) or ((NGROUP is not null) and (T.TASK_GROUP = NGROUP)))
|
|
||||||
and T.TYPE = 2;
|
|
||||||
end;
|
|
||||||
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
|
|
||||||
/* Если это вторая группа - сохраним номер строки */
|
|
||||||
if (GRP.RNUM = 2) then
|
|
||||||
NLINE_NUMB_TMP := NLINE_NUMB;
|
|
||||||
end if;
|
|
||||||
/* Если это третья группа - вернемся на уровень второй группы */
|
|
||||||
if (GRP.RNUM = 3) then
|
|
||||||
NLINE_NUMB := NLINE_NUMB_TMP;
|
|
||||||
end if;
|
|
||||||
/* Добавляем группу */
|
|
||||||
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
|
|
||||||
/* Указываем следующую строку */
|
|
||||||
NLINE_NUMB := NLINE_NUMB + 1;
|
|
||||||
/* Формируем наименование задачи */
|
|
||||||
STASK_NAME := 'Работа ' || TO_CHAR(TASK.RNUM) || ' этапа ' || TO_CHAR(GRP.RNUM);
|
|
||||||
/* Формируем задачу */
|
|
||||||
RTASK := PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_MAKE(NRN => TASK.RN,
|
|
||||||
SCAPTION => STASK_NAME,
|
|
||||||
SNAME => STASK_NAME,
|
|
||||||
NLINE_NUMB => NLINE_NUMB,
|
|
||||||
NSTART => TASK.POS_START,
|
|
||||||
NEND => TASK.POS_END,
|
|
||||||
SGROUP => GRP.NAME,
|
|
||||||
SBG_COLOR => SBG_TASK_COLOR_W_GRP,
|
|
||||||
STEXT_COLOR => SCOLOR_WHITE,
|
|
||||||
SHIGHLIGHT_COLOR => SHL_TASK_COLOR_W_GRP);
|
|
||||||
/* Добавляем атрибуты */
|
|
||||||
PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
|
|
||||||
RTASK => RTASK,
|
|
||||||
SNAME => 'ddate_start',
|
|
||||||
SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => TASK.DATE_FROM),
|
|
||||||
BCLEAR => true);
|
|
||||||
PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
|
|
||||||
RTASK => RTASK,
|
|
||||||
SNAME => 'ddate_end',
|
|
||||||
SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => TASK.DATE_TO));
|
|
||||||
PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
|
|
||||||
RTASK => RTASK,
|
|
||||||
SNAME => 'type',
|
|
||||||
SVALUE => 2);
|
|
||||||
/* Добавляем задачу в циклограмму */
|
|
||||||
PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK(RCYCLOGRAM => CG, RTASK => RTASK);
|
|
||||||
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
|
|
||||||
/* Формируем наименование задачи */
|
|
||||||
STASK_NAME := 'Работа ' || TO_CHAR(REC.RNUM) || ' без этапа ';
|
|
||||||
/* Формируем задачу */
|
|
||||||
RTASK := PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_MAKE(NRN => REC.RN,
|
|
||||||
SCAPTION => STASK_NAME,
|
|
||||||
SNAME => STASK_NAME,
|
|
||||||
NLINE_NUMB => NLINE_NUMB,
|
|
||||||
NSTART => REC.POS_START,
|
|
||||||
NEND => REC.POS_END,
|
|
||||||
SBG_COLOR => SBG_TASK_COLOR_WO_GRP,
|
|
||||||
STEXT_COLOR => STEXT_COLOR_TASK_WO_GRP);
|
|
||||||
/* Добавляем атрибуты */
|
|
||||||
PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
|
|
||||||
RTASK => RTASK,
|
|
||||||
SNAME => 'ddate_start',
|
|
||||||
SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => REC.DATE_FROM),
|
|
||||||
BCLEAR => true);
|
|
||||||
PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG,
|
|
||||||
RTASK => RTASK,
|
|
||||||
SNAME => 'ddate_end',
|
|
||||||
SVALUE => PKG_XCONVERT.DATE_TO_XML(DVALUE => REC.DATE_TO));
|
|
||||||
PKG_P8PANELS_VISUAL.TCYCLOGRAM_TASK_ADD_ATTR_VAL(RCYCLOGRAM => CG, RTASK => RTASK, SNAME => 'type', SVALUE => 2);
|
|
||||||
/* Добавляем задачу в циклограмму */
|
|
||||||
PKG_P8PANELS_VISUAL.TCYCLOGRAM_ADD_TASK(RCYCLOGRAM => CG, RTASK => RTASK);
|
|
||||||
end loop;
|
|
||||||
/* Формируем список */
|
|
||||||
COUT := PKG_P8PANELS_VISUAL.TCYCLOGRAM_TO_XML(RCYCLOGRAM => CG);
|
|
||||||
end CYCLOGRAM;
|
|
||||||
|
|
||||||
/* Изменение задачи циклограммы */
|
|
||||||
procedure CYCLOGRAM_TASK_MODIFY
|
|
||||||
(
|
|
||||||
NIDENT in number, -- Идентификатор буфера
|
|
||||||
NRN in number, -- Рег. номер записи
|
|
||||||
SDATE_FROM in varchar2, -- Дата начала (в строковом представлении)
|
|
||||||
SDATE_TO in varchar2 -- Дата окончания (в строковом представлении)
|
|
||||||
)
|
|
||||||
is
|
|
||||||
DDATE_FROM PKG_STD.TLDATE; -- Дата начала
|
|
||||||
DDATE_TO PKG_STD.TLDATE; -- Дата окончания
|
|
||||||
NYEAR PKG_STD.TNUMBER; -- Текущий год
|
|
||||||
DCYCLOGRAM_START PKG_STD.TLDATE; -- Дата начала циклограммы
|
|
||||||
NSTART PKG_STD.TNUMBER; -- Позиция начала элемента
|
|
||||||
NEND PKG_STD.TNUMBER; -- Позиция окончания элемента
|
|
||||||
begin
|
|
||||||
/* Фиксируем текущий год */
|
|
||||||
NYEAR := EXTRACT(year from sysdate);
|
|
||||||
/* Переводим даты */
|
|
||||||
DDATE_FROM := TO_DATE(SDATE_FROM, 'dd.mm.yyyy');
|
|
||||||
DDATE_TO := TO_DATE(SDATE_TO, 'dd.mm.yyyy');
|
|
||||||
/* Если дата начала выходит за границы года */
|
|
||||||
if (D_YEAR(DDATE => DDATE_FROM) <> NYEAR) then
|
|
||||||
P_EXCEPTION(0,
|
|
||||||
'Дата начала задачи выходит за границы текущего года (%s).',
|
|
||||||
NYEAR);
|
|
||||||
end if;
|
|
||||||
/* Если дата окончания выходит за границы года */
|
|
||||||
if (D_YEAR(DDATE => DDATE_TO) <> NYEAR) then
|
|
||||||
P_EXCEPTION(0,
|
|
||||||
'Дата окончания задачи выходит за границы текущего года (%s).',
|
|
||||||
NYEAR);
|
|
||||||
end if;
|
|
||||||
/* Дата окончания не может быть меньше даты начала */
|
|
||||||
if (DDATE_TO < DDATE_FROM) then
|
|
||||||
P_EXCEPTION(0, 'Дата окончания не может быть меньше даты начала.');
|
|
||||||
end if;
|
|
||||||
/* Фиксируем дату начала циклограммы */
|
|
||||||
DCYCLOGRAM_START := TO_DATE('01.01.' || NYEAR, 'dd.mm.yyyy');
|
|
||||||
/* Рассчитываем новую позицию начала */
|
|
||||||
NSTART := (DDATE_FROM - DCYCLOGRAM_START) * NCG_MULTIPLIER;
|
|
||||||
/* Рассчитываем новую позицию окончания */
|
|
||||||
NEND := NSTART + ((DDATE_TO - DDATE_FROM + 1) * NCG_MULTIPLIER);
|
|
||||||
/* Считываем запись */
|
|
||||||
for REC in (select T.*
|
|
||||||
from P8PNL_SMPL_CYCLOGRAM T
|
|
||||||
where T.RN = NRN
|
|
||||||
and T.IDENT = NIDENT)
|
|
||||||
loop
|
|
||||||
/* Обновляем запись циклограммы */
|
|
||||||
CYCLOGRAM_BASE_UPDATE(NIDENT => REC.IDENT,
|
|
||||||
NRN => REC.RN,
|
|
||||||
NTYPE => REC.TYPE,
|
|
||||||
SNAME => REC.NAME,
|
|
||||||
NPOS_START => NSTART,
|
|
||||||
NPOS_END => NEND,
|
|
||||||
DDATE_FROM => DDATE_FROM,
|
|
||||||
DDATE_TO => DDATE_TO,
|
|
||||||
NTASK_GROUP => REC.TASK_GROUP);
|
|
||||||
end loop;
|
|
||||||
end CYCLOGRAM_TASK_MODIFY;
|
|
||||||
|
|
||||||
end PKG_P8PANELS_SAMPLES;
|
end PKG_P8PANELS_SAMPLES;
|
||||||
/
|
/
|
||||||
|
BIN
docs/img/72.png
BIN
docs/img/72.png
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
BIN
docs/img/73.png
BIN
docs/img/73.png
Binary file not shown.
Before Width: | Height: | Size: 58 KiB |
Loading…
x
Reference in New Issue
Block a user