forked from CITKParus/P8-Panels
259 lines
12 KiB
JavaScript
259 lines
12 KiB
JavaScript
/*
|
||
Парус 8 - Панели мониторинга
|
||
Компонент: Циклограмма
|
||
*/
|
||
|
||
//---------------------
|
||
//Подключение библиотек
|
||
//---------------------
|
||
|
||
import React, { useEffect, useState, useRef } from "react"; //Классы React
|
||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||
import { Box, Typography, Link, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
|
||
import { useP8PCyclogram } from "./p8p_cyclogram_hooks"; //Хук для циклограммы
|
||
import { P8PCyclogramTaskEditor } from "./p8p_cyclogram/p8p_cyclogram_task_editor"; //Редактор задачи
|
||
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
|
||
import { P8P_COMPONENT_HEIGHT } from "../theme/styles/common"; //Стили - общие
|
||
import { P8P_BOX_CYCLOGRAM } from "../theme/styles/box"; //Стили контейнеров
|
||
import { getShift } from "./p8p_cyclogram/p8p_cyclogram_utils"; //Вспомогательные функции циклограммы
|
||
import {
|
||
P8P_CYCLOGRAM_ZOOM,
|
||
NDEFAULT_LINE_HEIGHT,
|
||
NDEFAULT_HEADER_HEIGHT,
|
||
TITLE_HEIGHT,
|
||
ZOOM_HEIGHT,
|
||
P8P_CYCLOGRAM_COLUMN_SHAPE,
|
||
P8P_CYCLOGRAM_GROUP_SHAPE,
|
||
P8P_CYCLOGRAM_TASK_SHAPE,
|
||
P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE
|
||
} from "./p8p_cyclogram/p8p_cyclogram_constants"; //Константы циклограммы
|
||
import { P8PCyclogramGrid } from "./p8p_cyclogram/p8p_cyclogram_grid"; //Фон таблицы циклограммы
|
||
import { P8PCyclogramView } from "./p8p_cyclogram/p8p_cyclogram_view"; //Представление циклограммы
|
||
import { P8P_TYPOGRAPHY_TITLE } from "../theme/styles/typography"; //Стили текста
|
||
|
||
//-----------
|
||
//Тело модуля
|
||
//-----------
|
||
|
||
//Циклограмма
|
||
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={{ ...P8P_TYPOGRAPHY_TITLE, ...(titleStyle ? titleStyle : {}) }}
|
||
align="center"
|
||
color="textSecondary"
|
||
variant={P8P_TYPOGRAPHY_VARIANT.TITLE}
|
||
component="h6"
|
||
>
|
||
{onTitleClick ? (
|
||
<Link component="button" variant={P8P_TYPOGRAPHY_VARIANT.BODY3} underline="hover" onClick={() => onTitleClick()}>
|
||
{title}
|
||
</Link>
|
||
) : (
|
||
title
|
||
)}
|
||
</Typography>
|
||
) : null}
|
||
{zoomBar ? (
|
||
<Box p={1} sx={P8P_COMPONENT_HEIGHT({ height: ZOOM_HEIGHT })}>
|
||
<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)}
|
||
sx={P8P_BOX_CYCLOGRAM({
|
||
noData: state.noData,
|
||
zoomBarHeight: zoomBar ? ZOOM_HEIGHT : null,
|
||
titleHeight: title ? TITLE_HEIGHT : null
|
||
})}
|
||
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}
|
||
/>
|
||
<P8PCyclogramView
|
||
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, useP8PCyclogram };
|