forked from CITKParus/P8-Panels
ЦИТК-904 - Добавлен компонент "Циклограмма"
This commit is contained in:
parent
86b1dcd717
commit
cf76c80519
497
README.md
497
README.md
@ -2366,6 +2366,503 @@ const Svg = ({ title }) => {
|
||||
|
||||
Полные актуальные исходные коды примера можно увидеть в "app/panels/samples/svg.js" данного репозитория соответственно.
|
||||
|
||||
##### Циклограмма "P8PCyclogram"
|
||||
|
||||
Компонент предназначен для отображения данных в виде циклограммы. Поддерживается:
|
||||
|
||||
- Группировка задач с отображением описания группы при наведении
|
||||
- Форматирование цвета заливки задачи, текста задачи и цвета при наведении на задачу/группу
|
||||
- Дополнение задачи произвольными учётными атрибутами
|
||||
- Диалоговый редактор задачи, отображающий её дополнительные атрибуты с возможностью настройки их форматирования
|
||||
- Отображение произвольного пользовательского диалога в качестве карточки задачи/редактора задачи
|
||||
- Масштабирование визуального представления
|
||||
|
||||

|
||||

|
||||
|
||||
**Подключение**
|
||||
|
||||
Клиентская часть циклограммы реализована в компоненте `P8PCyclogram`, объявленном в "app/components/p8p_cyclogram". Для использования компонента на панели его необходимо импортировать:
|
||||
|
||||
```
|
||||
import { P8PCyclogram } from "../../components/p8p_cyclogram";
|
||||
|
||||
const MyPanel = () => {
|
||||
return (
|
||||
<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. Тем не менее, при разработке пользовательских интерфейсов панелей важно придерживаться предложенных ниже правил. Это позволит создавать их в едином ключе и упростит работу конечного пользователя при их освоении.
|
||||
|
819
app/components/p8p_cyclogram.js
Normal file
819
app/components/p8p_cyclogram.js
Normal file
@ -0,0 +1,819 @@
|
||||
/*
|
||||
Парус 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,6 +76,14 @@ const P8P_GANTT_CONFIG_PROPS = {
|
||||
cancelTaskEditorBtnCaption: BUTTONS.CANCEL
|
||||
};
|
||||
|
||||
//Конфигурируемые свойства "Циклограммы" (P8PCyclogram)
|
||||
const P8P_CYCLOGRAM_CONFIG_PROPS = {
|
||||
noDataFoundText: TEXTS.NO_DATA_FOUND,
|
||||
nameTaskEditorCaption: CAPTIONS.NAME,
|
||||
okTaskEditorBtnCaption: BUTTONS.OK,
|
||||
cancelTaskEditorBtnCaption: BUTTONS.CANCEL
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
@ -132,6 +140,7 @@ export {
|
||||
P8P_DATA_GRID_SIZE,
|
||||
P8P_DATA_GRID_FILTER_SHAPE,
|
||||
P8P_GANTT_CONFIG_PROPS,
|
||||
P8P_CYCLOGRAM_CONFIG_PROPS,
|
||||
P8P_GANTT_TASK_SHAPE,
|
||||
P8P_GANTT_TASK_ATTRIBUTE_SHAPE,
|
||||
P8P_GANTT_TASK_COLOR_SHAPE,
|
||||
|
302
app/panels/samples/cyclogram.js
Normal file
302
app/panels/samples/cyclogram.js
Normal file
@ -0,0 +1,302 @@
|
||||
/*
|
||||
Парус 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,6 +18,7 @@ import { DataGrid } from "./data_grid"; //Пример: Таблица данн
|
||||
import { Chart } from "./chart"; //Пример: Графики "P8PChart"
|
||||
import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt"
|
||||
import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG"
|
||||
import { Cyclogram } from "./cyclogram"; //Пример: Циклограмма "P8PCyclogram"
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -32,7 +33,8 @@ const MODES = {
|
||||
DATAGRID: { name: "DATAGRID", caption: 'Таблица данных "P8PDataGrid"', component: DataGrid },
|
||||
CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart },
|
||||
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 }
|
||||
};
|
||||
|
||||
//Стили
|
||||
|
18
db/P8PNL_SMPL_CYCLOGRAM.sql
Normal file
18
db/P8PNL_SMPL_CYCLOGRAM.sql
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Парус 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,10 +59,35 @@ create or replace package PKG_P8PANELS_SAMPLES as
|
||||
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;
|
||||
/
|
||||
create or replace package body PKG_P8PANELS_SAMPLES as
|
||||
|
||||
/* Константы для циклограммы */
|
||||
NCG_MULTIPLIER constant PKG_STD.TNUMBER := 5; -- Множитель для ширины отображения
|
||||
|
||||
/* Получение списка контрагентов */
|
||||
procedure AGNLIST_GET
|
||||
(
|
||||
@ -205,6 +230,7 @@ create or replace package body PKG_P8PANELS_SAMPLES as
|
||||
NCONTACT_METHOD => null,
|
||||
SMF_ID => null,
|
||||
SOKOGU => null,
|
||||
NJURPERS_SUBDIV => null,
|
||||
NRN => NRN);
|
||||
end AGNLIST_INSERT;
|
||||
|
||||
@ -667,5 +693,532 @@ create or replace package body PKG_P8PANELS_SAMPLES as
|
||||
end loop;
|
||||
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;
|
||||
/
|
||||
|
BIN
docs/img/72.png
Normal file
BIN
docs/img/72.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
docs/img/73.png
Normal file
BIN
docs/img/73.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
Loading…
x
Reference in New Issue
Block a user