P8-Panels/app/components/p8p_gantt/p8p_gantt_task_editor.js

268 lines
13 KiB
JavaScript

/*
Парус 8 - Панели мониторинга - Диаграмма Ганта
Компонент: Редактор задачи
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogActions, DialogContent, TextField, Button, List, ListItem, ListItemText, Divider, Slider } from "@mui/material"; //Интерфейсные компоненты
import { P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE } from "./p8p_gantt_constants"; //Константы диаграммы Ганта
import { P8P_DIALOG_CONTENT_VARIANT } from "../../theme/variants/p8p_dialog_content_variants"; //Варианты диалогов (содержимое)
import { P8P_LIST_VARIANT } from "../../theme/variants/p8p_list_variants"; //Варианты списков
import { P8P_LIST_ITEM_TEXT_VARIANT } from "../../theme/variants/p8p_list_item_text_variants"; //Варианты значений списков
import { P8P_TEXT_FIELD_VARIANT } from "../../theme/variants/p8p_text_field_variants"; //Варианты полей ввода
import { P8P_BUTTON_VARIANT } from "../../theme/variants/p8p_button_variants"; //Варианты кнопок
//--------------------------------
//Вспомогательные классы и функции
//--------------------------------
//Проверка существования значения
const hasValue = value => typeof value !== "undefined" && value !== null && value !== "";
//Формирование описания для легенды
const taskLegendDesc = ({ task, taskColors }) => {
if (Array.isArray(taskColors) && taskColors.length > 0) {
const colorDesc = taskColors.find(
color => task.bgColor === color.bgColor && task.textColor === color.textColor && task.bgProgressColor === color.bgProgressColor
);
if (colorDesc)
return {
text: colorDesc.desc,
style: {
...(colorDesc.bgProgressColor
? {
background: `linear-gradient(to right, ${colorDesc.bgProgressColor} ,${
colorDesc.bgColor ? colorDesc.bgColor : "transparent"
})`
}
: colorDesc.bgColor
? { backgroundColor: colorDesc.bgColor }
: {}),
...(colorDesc.textColor ? { color: colorDesc.textColor } : {})
}
};
else return null;
} else return null;
};
//-----------
//Тело модуля
//-----------
//Редактор задачи
const P8PGanttTaskEditor = ({
task,
taskAttributes,
taskColors,
onOk,
onCancel,
taskAttributeRenderer,
taskDialogRenderer,
taskDialogProps,
numbCaption,
nameCaption,
startCaption,
endCaption,
progressCaption,
legendCaption,
okBtnCaption,
cancelBtnCaption
}) => {
//Собственное состояние
const [state, setState] = useState({
start: task.start,
end: task.end,
progress: task.progress
});
//Отображаемые атрибуты
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({ task, start: state.start, end: state.end, progress: state.progress }) : null);
//При отмене
const handleCancel = () => (onCancel ? onCancel() : null);
//При изменении сроков
const handlePeriodChanged = e => setState(prev => ({ ...prev, [e.target.name]: e.target.value }));
//При изменении прогресса
const handleProgressChanged = (e, newValue) => setState(prev => ({ ...prev, progress: newValue }));
//Описание легенды для задачи
const legendDesc = taskLegendDesc({ task, taskColors });
let legend = legendDesc ? (
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
secondaryTypographyProps={{
p: 1,
sx: legendDesc.style
}}
primary={legendCaption}
secondary={legendDesc.text}
/>
) : null;
//Генерация содержимого
return (
<Dialog open onClose={handleCancel} {...(taskDialogProps ? taskDialogProps : {})}>
{taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
) : (
<>
<DialogContent variant={P8P_DIALOG_CONTENT_VARIANT.TASK}>
<List variant={P8P_LIST_VARIANT.GANTT_TASK}>
<ListItem alignItems="flex-start">
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={numbCaption} secondary={task.numb} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={nameCaption} secondary={task.fullName} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
secondaryTypographyProps={{ component: "span" }}
primary={startCaption}
secondary={
<TextField
error={!state.start}
disabled={task.readOnly === true || task.readOnlyDates === true}
name="start"
fullWidth
required
InputLabelProps={{ shrink: true }}
type={"date"}
value={state.start}
onChange={handlePeriodChanged}
variant="standard"
data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY}
size="small"
margin="normal"
/>
}
/>
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
secondaryTypographyProps={{ component: "span" }}
primary={endCaption}
secondary={
<TextField
error={!state.end}
disabled={task.readOnly === true || task.readOnlyDates === true}
name="end"
fullWidth
required
InputLabelProps={{ shrink: true }}
type={"date"}
value={state.end}
onChange={handlePeriodChanged}
variant="standard"
data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY}
size="small"
margin="normal"
/>
}
/>
</ListItem>
{hasValue(task.progress) || legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
{hasValue(task.progress) ? (
<>
<ListItem alignItems="flex-start">
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
secondaryTypographyProps={{ component: "span" }}
primary={`${progressCaption}${
task.readOnly === true || task.readOnlyProgress === true ? ` (${task.progress}%)` : ""
}`}
secondary={
<Slider
disabled={task.readOnly === true || task.readOnlyProgress === true}
defaultValue={task.progress}
valueLabelDisplay="auto"
onChange={handleProgressChanged}
/>
}
/>
</ListItem>
{legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
</>
) : null}
{legend ? (
<>
<ListItem alignItems="flex-start">{legend}</ListItem>
{dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
</>
) : 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
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
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 variant={P8P_BUTTON_VARIANT.SECONDARY} onClick={handleCancel}>
{cancelBtnCaption}
</Button>
<Button variant={P8P_BUTTON_VARIANT.PRIMARY} disabled={!state.start || !state.end || task.readOnly} onClick={handleOk}>
{okBtnCaption}
</Button>
</DialogActions>
</>
)}
</Dialog>
);
};
//Контроль свойств - Редактор задачи
P8PGanttTaskEditor.propTypes = {
task: P8P_GANTT_TASK_SHAPE,
taskAttributes: PropTypes.arrayOf(P8P_GANTT_TASK_ATTRIBUTE_SHAPE),
taskColors: PropTypes.arrayOf(P8P_GANTT_TASK_COLOR_SHAPE),
onOk: PropTypes.func,
onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
numbCaption: PropTypes.string.isRequired,
nameCaption: PropTypes.string.isRequired,
startCaption: PropTypes.string.isRequired,
endCaption: PropTypes.string.isRequired,
progressCaption: PropTypes.string.isRequired,
legendCaption: PropTypes.string.isRequired,
okBtnCaption: PropTypes.string.isRequired,
cancelBtnCaption: PropTypes.string.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { P8PGanttTaskEditor, taskLegendDesc };