P8-Panels/app/components/p8p_gantt.js

238 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Парус 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 };