forked from CITKParus/P8-Panels
238 lines
9.7 KiB
JavaScript
238 lines
9.7 KiB
JavaScript
/*
|
||
Парус 8 - Панели мониторинга
|
||
Компонент: Диаграмма Ганта
|
||
*/
|
||
|
||
//---------------------
|
||
//Подключение библиотек
|
||
//---------------------
|
||
|
||
import React, { useEffect, useState, useCallback, useRef } from "react"; //Классы React
|
||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||
import { Box, IconButton, Icon, Typography, Link } from "@mui/material"; //Интерфейсные компоненты
|
||
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
|
||
import { useP8PGantt } from "./p8p_gantt/p8p_gantt_hooks"; //Хук для диаграммы Ганта
|
||
import {
|
||
P8P_GANTT_ZOOM,
|
||
P8P_GANTT_ZOOM_VIEW_MODES,
|
||
P8P_GANTT_TASK_SHAPE,
|
||
P8P_GANTT_TASK_ATTRIBUTE_SHAPE,
|
||
P8P_GANTT_TASK_COLOR_SHAPE,
|
||
TITLE_HEIGHT,
|
||
ZOOM_HEIGHT
|
||
} from "./p8p_gantt/p8p_gantt_constants"; //Константы диаграммы Ганта
|
||
import { P8PGanttTaskEditor, taskLegendDesc } from "./p8p_gantt/p8p_gantt_task_editor"; //Редактор задачи
|
||
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
|
||
import { P8P_COMPONENT_HEIGHT } from "../theme/styles/common"; //Стили - общие
|
||
import { P8P_BOX_GANTT } from "../theme/styles/box"; //Стили контейнеров
|
||
import { P8P_TYPOGRAPHY_TITLE } from "../theme/styles/typography"; //Стили текста
|
||
|
||
//-----------
|
||
//Тело модуля
|
||
//-----------
|
||
|
||
//Диаграмма Ганта
|
||
const P8PGantt = ({
|
||
containerStyle,
|
||
title,
|
||
titleStyle,
|
||
onTitleClick,
|
||
zoomBar,
|
||
readOnly,
|
||
readOnlyDates,
|
||
readOnlyProgress,
|
||
zoom,
|
||
tasks,
|
||
taskAttributes,
|
||
taskColors,
|
||
onTaskDatesChange,
|
||
onTaskProgressChange,
|
||
taskAttributeRenderer,
|
||
taskDialogRenderer,
|
||
taskDialogProps,
|
||
noDataFoundText,
|
||
numbTaskEditorCaption,
|
||
nameTaskEditorCaption,
|
||
startTaskEditorCaption,
|
||
endTaskEditorCaption,
|
||
progressTaskEditorCaption,
|
||
legendTaskEditorCaption,
|
||
okTaskEditorBtnCaption,
|
||
cancelTaskEditorBtnCaption,
|
||
zoomBarStyle,
|
||
zoomBarHeight
|
||
}) => {
|
||
//Собственное состояние
|
||
const [state, setState] = useState({
|
||
noData: true,
|
||
gantt: null,
|
||
zoom: P8P_GANTT_ZOOM.includes(zoom) ? zoom : 3,
|
||
editTask: null
|
||
});
|
||
|
||
//Ссылки на DOM
|
||
const svgContainerRef = useRef(null);
|
||
|
||
//Отображение диаграммы
|
||
const showGantt = useCallback(() => {
|
||
if (!state.gantt) {
|
||
// eslint-disable-next-line no-undef
|
||
const gantt = new Gantt("#__gantt__", tasks, {
|
||
view_mode: P8P_GANTT_ZOOM_VIEW_MODES[state.zoom],
|
||
date_format: "YYYY-MM-DD",
|
||
language: "ru",
|
||
readOnly,
|
||
readOnlyDates,
|
||
readOnlyProgress,
|
||
on_date_change: (task, start, end, isMain) => (onTaskDatesChange ? onTaskDatesChange({ task, start, end, isMain }) : null),
|
||
on_progress_change: (task, progress) => (onTaskProgressChange ? onTaskProgressChange({ task, progress }) : null),
|
||
on_click: openTaskEditor
|
||
});
|
||
setState(pv => ({ ...pv, gantt, noData: false }));
|
||
} else {
|
||
state.gantt.refresh(tasks);
|
||
setState(pv => ({ ...pv, noData: false }));
|
||
}
|
||
}, [state.gantt, state.zoom, readOnly, readOnlyDates, readOnlyProgress, tasks, onTaskDatesChange, onTaskProgressChange]);
|
||
|
||
//Обновление масштаба диаграммы
|
||
const handleZoomChange = direction =>
|
||
setState(pv => ({
|
||
...pv,
|
||
zoom: pv.zoom + direction < 0 ? 0 : pv.zoom + direction >= P8P_GANTT_ZOOM.length ? P8P_GANTT_ZOOM.length - 1 : pv.zoom + direction
|
||
}));
|
||
|
||
//Открытие редактора задачи
|
||
const openTaskEditor = task => setState(pv => ({ ...pv, editTask: { ...task } }));
|
||
|
||
//При сохранении задачи в редакторе
|
||
const handleTaskEditorSave = ({ task, start, end, progress }) => {
|
||
setState(pv => ({ ...pv, editTask: null }));
|
||
if (onTaskDatesChange && (task.start != start || task.end != end)) onTaskDatesChange({ task, start, end, isMain: true });
|
||
if (onTaskProgressChange && task.progress != progress) onTaskProgressChange({ task, progress });
|
||
};
|
||
|
||
//При закрытии редактора задачи без сохранения
|
||
const handleTaskEditorCancel = () => setState(pv => ({ ...pv, editTask: null }));
|
||
|
||
//При изменении масштаба
|
||
useEffect(() => {
|
||
if (state.gantt) state.gantt.change_view_mode(P8P_GANTT_ZOOM_VIEW_MODES[state.zoom]);
|
||
}, [state.gantt, state.zoom]);
|
||
|
||
//При изменении списка задач
|
||
useEffect(() => {
|
||
if (Array.isArray(tasks) && tasks.length > 0) showGantt();
|
||
else setState(pv => ({ ...pv, noData: true }));
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [tasks]);
|
||
|
||
//При подключении компонента к старице
|
||
useEffect(() => {
|
||
svgContainerRef.current.children[0].classList.add("scroll");
|
||
}, []);
|
||
|
||
//Генерация содержимого
|
||
return (
|
||
<div style={{ ...(containerStyle ? containerStyle : {}) }}>
|
||
{state.gantt && state.noData ? <P8PAppInlineError text={noDataFoundText} /> : null}
|
||
{state.gantt && !state.noData && 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}
|
||
{state.gantt && !state.noData && zoomBar ? (
|
||
<Box p={1} sx={zoomBarStyle ? zoomBarStyle : P8P_COMPONENT_HEIGHT({ height: ZOOM_HEIGHT })}>
|
||
<IconButton onClick={() => handleZoomChange(-1)} disabled={state.zoom == 0}>
|
||
<Icon>zoom_in</Icon>
|
||
</IconButton>
|
||
<IconButton onClick={() => handleZoomChange(1)} disabled={state.zoom == P8P_GANTT_ZOOM.length - 1}>
|
||
<Icon>zoom_out</Icon>
|
||
</IconButton>
|
||
</Box>
|
||
) : null}
|
||
{state.editTask ? (
|
||
<P8PGanttTaskEditor
|
||
task={state.editTask}
|
||
taskAttributes={taskAttributes}
|
||
taskColors={taskColors}
|
||
onOk={handleTaskEditorSave}
|
||
onCancel={handleTaskEditorCancel}
|
||
taskAttributeRenderer={taskAttributeRenderer}
|
||
taskDialogRenderer={taskDialogRenderer}
|
||
taskDialogProps={taskDialogProps}
|
||
numbCaption={numbTaskEditorCaption}
|
||
nameCaption={nameTaskEditorCaption}
|
||
startCaption={startTaskEditorCaption}
|
||
endCaption={endTaskEditorCaption}
|
||
progressCaption={progressTaskEditorCaption}
|
||
legendCaption={legendTaskEditorCaption}
|
||
okBtnCaption={okTaskEditorBtnCaption}
|
||
cancelBtnCaption={cancelTaskEditorBtnCaption}
|
||
/>
|
||
) : null}
|
||
<div
|
||
style={P8P_BOX_GANTT({
|
||
noData: state.noData,
|
||
zoomBarHeight: zoomBar ? (zoomBarHeight ? zoomBarHeight : ZOOM_HEIGHT) : null,
|
||
titleHeight: title ? TITLE_HEIGHT : null
|
||
})}
|
||
ref={svgContainerRef}
|
||
>
|
||
<svg id="__gantt__" width="100%"></svg>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
//Контроль свойств - Диаграмма Ганта
|
||
P8PGantt.propTypes = {
|
||
containerStyle: PropTypes.object,
|
||
title: PropTypes.string,
|
||
titleStyle: PropTypes.object,
|
||
onTitleClick: PropTypes.func,
|
||
zoomBar: PropTypes.bool,
|
||
readOnly: PropTypes.bool,
|
||
readOnlyDates: PropTypes.bool,
|
||
readOnlyProgress: PropTypes.bool,
|
||
zoom: PropTypes.number,
|
||
tasks: PropTypes.arrayOf(P8P_GANTT_TASK_SHAPE).isRequired,
|
||
taskAttributes: PropTypes.arrayOf(P8P_GANTT_TASK_ATTRIBUTE_SHAPE),
|
||
taskColors: PropTypes.arrayOf(P8P_GANTT_TASK_COLOR_SHAPE),
|
||
onTaskDatesChange: PropTypes.func,
|
||
onTaskProgressChange: PropTypes.func,
|
||
taskAttributeRenderer: PropTypes.func,
|
||
taskDialogRenderer: PropTypes.func,
|
||
taskDialogProps: PropTypes.object,
|
||
noDataFoundText: PropTypes.string.isRequired,
|
||
numbTaskEditorCaption: PropTypes.string.isRequired,
|
||
nameTaskEditorCaption: PropTypes.string.isRequired,
|
||
startTaskEditorCaption: PropTypes.string.isRequired,
|
||
endTaskEditorCaption: PropTypes.string.isRequired,
|
||
progressTaskEditorCaption: PropTypes.string.isRequired,
|
||
legendTaskEditorCaption: PropTypes.string.isRequired,
|
||
okTaskEditorBtnCaption: PropTypes.string.isRequired,
|
||
cancelTaskEditorBtnCaption: PropTypes.string.isRequired,
|
||
zoomBarStyle: PropTypes.object,
|
||
zoomBarHeight: PropTypes.string
|
||
};
|
||
|
||
//----------------
|
||
//Интерфейс модуля
|
||
//----------------
|
||
|
||
export { P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE, taskLegendDesc, P8PGantt, useP8PGantt };
|