WEB APP: P8PGantt - управление видимостью атрибута задачи, поддержка пользовательских карточек задач

This commit is contained in:
Mikhail Chechnev 2024-03-31 23:04:33 +03:00
parent 30c75fb079
commit 69cb24ed6e
2 changed files with 163 additions and 105 deletions

View File

@ -79,6 +79,7 @@ const P8P_GANTT_TASK_COLOR_SHAPE = PropTypes.shape({
//Стили //Стили
const STYLES = { const STYLES = {
TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" } TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" }
}; };
@ -97,6 +98,7 @@ const P8PGanttTaskEditor = ({
onOk, onOk,
onCancel, onCancel,
taskAttributeRenderer, taskAttributeRenderer,
taskDialogRenderer,
numbCaption, numbCaption,
nameCaption, nameCaption,
startCaption, startCaption,
@ -113,6 +115,10 @@ const P8PGanttTaskEditor = ({
progress: task.progress 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 handleOk = () => (onOk && state.start && state.end ? onOk({ task, start: state.start, end: state.end, progress: state.progress }) : null);
@ -158,7 +164,11 @@ const P8PGanttTaskEditor = ({
//Генерация содержимого //Генерация содержимого
return ( return (
<Dialog open onClose={handleCancel}> <Dialog open onClose={handleCancel}>
<DialogContent> {taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
) : (
<>
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
<List sx={STYLES.TASK_EDITOR_LIST}> <List sx={STYLES.TASK_EDITOR_LIST}>
<ListItem alignItems="flex-start"> <ListItem alignItems="flex-start">
<ListItemText primary={numbCaption} secondary={task.numb} /> <ListItemText primary={numbCaption} secondary={task.numb} />
@ -213,7 +223,7 @@ const P8PGanttTaskEditor = ({
} }
/> />
</ListItem> </ListItem>
<Divider component="li" /> {hasValue(task.progress) || legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
{hasValue(task.progress) ? ( {hasValue(task.progress) ? (
<> <>
<ListItem alignItems="flex-start"> <ListItem alignItems="flex-start">
@ -232,19 +242,17 @@ const P8PGanttTaskEditor = ({
} }
/> />
</ListItem> </ListItem>
<Divider component="li" /> {legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
</> </>
) : null} ) : null}
{legend ? ( {legend ? (
<> <>
<ListItem alignItems="flex-start">{legend}</ListItem> <ListItem alignItems="flex-start">{legend}</ListItem>
<Divider component="li" /> {dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
</> </>
) : null} ) : null}
{Array.isArray(taskAttributes) && taskAttributes.length > 0 {dispTaskAttributes.length > 0
? taskAttributes ? dispTaskAttributes.map((attr, i) => {
.filter(attr => hasValue(task[attr.name]))
.map((attr, i) => {
const defaultView = task[attr.name]; const defaultView = task[attr.name];
const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null; const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null;
return ( return (
@ -256,7 +264,7 @@ const P8PGanttTaskEditor = ({
secondary={customView ? customView : defaultView} secondary={customView ? customView : defaultView}
/> />
</ListItem> </ListItem>
{i < taskAttributes.length - 1 ? <Divider component="li" /> : null} {i < dispTaskAttributes.length - 1 ? <Divider component="li" /> : null}
</React.Fragment> </React.Fragment>
); );
}) })
@ -269,6 +277,8 @@ const P8PGanttTaskEditor = ({
</Button> </Button>
<Button onClick={handleCancel}>{cancelBtnCaption}</Button> <Button onClick={handleCancel}>{cancelBtnCaption}</Button>
</DialogActions> </DialogActions>
</>
)}
</Dialog> </Dialog>
); );
}; };
@ -281,6 +291,7 @@ P8PGanttTaskEditor.propTypes = {
onOk: PropTypes.func, onOk: PropTypes.func,
onCancel: PropTypes.func, onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func, taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
numbCaption: PropTypes.string.isRequired, numbCaption: PropTypes.string.isRequired,
nameCaption: PropTypes.string.isRequired, nameCaption: PropTypes.string.isRequired,
startCaption: PropTypes.string.isRequired, startCaption: PropTypes.string.isRequired,
@ -312,6 +323,7 @@ const P8PGantt = ({
onTaskDatesChange, onTaskDatesChange,
onTaskProgressChange, onTaskProgressChange,
taskAttributeRenderer, taskAttributeRenderer,
taskDialogRenderer,
noDataFoundText, noDataFoundText,
numbTaskEditorCaption, numbTaskEditorCaption,
nameTaskEditorCaption, nameTaskEditorCaption,
@ -417,6 +429,7 @@ const P8PGantt = ({
onOk={handleTaskEditorSave} onOk={handleTaskEditorSave}
onCancel={handleTaskEditorCancel} onCancel={handleTaskEditorCancel}
taskAttributeRenderer={taskAttributeRenderer} taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={taskDialogRenderer}
numbCaption={numbTaskEditorCaption} numbCaption={numbTaskEditorCaption}
nameCaption={nameTaskEditorCaption} nameCaption={nameTaskEditorCaption}
startCaption={startTaskEditorCaption} startCaption={startTaskEditorCaption}
@ -451,6 +464,7 @@ P8PGantt.propTypes = {
onTaskDatesChange: PropTypes.func, onTaskDatesChange: PropTypes.func,
onTaskProgressChange: PropTypes.func, onTaskProgressChange: PropTypes.func,
taskAttributeRenderer: PropTypes.func, taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
noDataFoundText: PropTypes.string.isRequired, noDataFoundText: PropTypes.string.isRequired,
numbTaskEditorCaption: PropTypes.string.isRequired, numbTaskEditorCaption: PropTypes.string.isRequired,
nameTaskEditorCaption: PropTypes.string.isRequired, nameTaskEditorCaption: PropTypes.string.isRequired,

View File

@ -9,8 +9,22 @@
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { Typography, Grid, Stack, Icon, Box } from "@mui/material"; //Интерфейсные элементы import {
import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции Typography,
Grid,
Stack,
Icon,
Box,
FormControlLabel,
Checkbox,
Card,
CardHeader,
CardActions,
Avatar,
CardContent,
Button
} from "@mui/material"; //Интерфейсные элементы
import { formatDateJSONDateOnly, formatDateRF } from "../../core/utils"; //Вспомогательные функции
import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта
import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
@ -57,6 +71,30 @@ const taskAttributeRenderer = ({ task, attribute }) => {
} }
}; };
//Генерация кастомного диалога задачи
const taskDialogRenderer = ({ task, close }) => {
return (
<Card>
<CardHeader
avatar={<Avatar sx={{ bgcolor: task.bgColor }}>{task.type == 0 ? "Эт" : "Ра"}</Avatar>}
title={task.name}
subheader={`с ${formatDateRF(task.start)} по ${formatDateRF(task.end)}`}
/>
<CardContent>
<Typography variant="body2" color="text.secondary">
Это пользовательский диалог с данными о задаче. Вы можете формировать такие указав свой функциональный компонент в качестве
свойства &quot;taskDialogRenderer&quot; компонента &quot;P8PGantt&quot;.
</Typography>
</CardContent>
<CardActions disableSpacing>
<Button size="small" onClick={close}>
Закрыть
</Button>
</CardActions>
</Card>
);
};
//----------- //-----------
//Тело модуля //Тело модуля
//----------- //-----------
@ -69,7 +107,8 @@ const Gantt = ({ title }) => {
dataLoaded: false, dataLoaded: false,
ident: null, ident: null,
ganttDef: {}, ganttDef: {},
ganttTasks: [] ganttTasks: [],
useCustomTaskDialog: false
}); });
//Подключение к контексту взаимодействия с сервером //Подключение к контексту взаимодействия с сервером
@ -132,6 +171,10 @@ const Gantt = ({ title }) => {
<Typography sx={STYLES.TITLE} variant={"h6"}> <Typography sx={STYLES.TITLE} variant={"h6"}>
{title} {title}
</Typography> </Typography>
<FormControlLabel
control={<Checkbox onChange={() => setState(pv => ({ ...pv, useCustomTaskDialog: !pv.useCustomTaskDialog }))} />}
label="Отображать пользовательский диалог задачи"
/>
<Grid container spacing={0} direction="column" alignItems="center"> <Grid container spacing={0} direction="column" alignItems="center">
<Grid item xs={12}> <Grid item xs={12}>
{state.dataLoaded ? ( {state.dataLoaded ? (
@ -143,6 +186,7 @@ const Gantt = ({ title }) => {
tasks={state.ganttTasks} tasks={state.ganttTasks}
onTaskDatesChange={handleTaskDatesChange} onTaskDatesChange={handleTaskDatesChange}
taskAttributeRenderer={taskAttributeRenderer} taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={state.useCustomTaskDialog ? taskDialogRenderer : null}
/> />
</Box> </Box>
) : null} ) : null}