WEB APP: P8PGantt - управление видимостью атрибута задачи, поддержка пользовательских карточек задач
This commit is contained in:
parent
30c75fb079
commit
69cb24ed6e
@ -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,117 +164,121 @@ const P8PGanttTaskEditor = ({
|
|||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<Dialog open onClose={handleCancel}>
|
<Dialog open onClose={handleCancel}>
|
||||||
<DialogContent>
|
{taskDialogRenderer ? (
|
||||||
<List sx={STYLES.TASK_EDITOR_LIST}>
|
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
|
||||||
<ListItem alignItems="flex-start">
|
) : (
|
||||||
<ListItemText primary={numbCaption} secondary={task.numb} />
|
<>
|
||||||
</ListItem>
|
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
|
||||||
<Divider component="li" />
|
<List sx={STYLES.TASK_EDITOR_LIST}>
|
||||||
<ListItem alignItems="flex-start">
|
<ListItem alignItems="flex-start">
|
||||||
<ListItemText primary={nameCaption} secondary={task.fullName} />
|
<ListItemText primary={numbCaption} secondary={task.numb} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider component="li" />
|
<Divider component="li" />
|
||||||
<ListItem alignItems="flex-start">
|
<ListItem alignItems="flex-start">
|
||||||
<ListItemText
|
<ListItemText primary={nameCaption} secondary={task.fullName} />
|
||||||
secondaryTypographyProps={{ component: "span" }}
|
</ListItem>
|
||||||
primary={startCaption}
|
<Divider component="li" />
|
||||||
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"
|
|
||||||
size="small"
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
<Divider component="li" />
|
|
||||||
<ListItem alignItems="flex-start">
|
|
||||||
<ListItemText
|
|
||||||
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"
|
|
||||||
size="small"
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
<Divider component="li" />
|
|
||||||
{hasValue(task.progress) ? (
|
|
||||||
<>
|
|
||||||
<ListItem alignItems="flex-start">
|
<ListItem alignItems="flex-start">
|
||||||
<ListItemText
|
<ListItemText
|
||||||
secondaryTypographyProps={{ component: "span" }}
|
secondaryTypographyProps={{ component: "span" }}
|
||||||
primary={`${progressCaption}${
|
primary={startCaption}
|
||||||
task.readOnly === true || task.readOnlyProgress === true ? ` (${task.progress}%)` : ""
|
|
||||||
}`}
|
|
||||||
secondary={
|
secondary={
|
||||||
<Slider
|
<TextField
|
||||||
disabled={task.readOnly === true || task.readOnlyProgress === true}
|
error={!state.start}
|
||||||
defaultValue={task.progress}
|
disabled={task.readOnly === true || task.readOnlyDates === true}
|
||||||
valueLabelDisplay="auto"
|
name="start"
|
||||||
onChange={handleProgressChanged}
|
fullWidth
|
||||||
|
required
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type={"date"}
|
||||||
|
value={state.start}
|
||||||
|
onChange={handlePeriodChanged}
|
||||||
|
variant="standard"
|
||||||
|
size="small"
|
||||||
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider component="li" />
|
<Divider component="li" />
|
||||||
</>
|
<ListItem alignItems="flex-start">
|
||||||
) : null}
|
<ListItemText
|
||||||
{legend ? (
|
secondaryTypographyProps={{ component: "span" }}
|
||||||
<>
|
primary={endCaption}
|
||||||
<ListItem alignItems="flex-start">{legend}</ListItem>
|
secondary={
|
||||||
<Divider component="li" />
|
<TextField
|
||||||
</>
|
error={!state.end}
|
||||||
) : null}
|
disabled={task.readOnly === true || task.readOnlyDates === true}
|
||||||
{Array.isArray(taskAttributes) && taskAttributes.length > 0
|
name="end"
|
||||||
? taskAttributes
|
fullWidth
|
||||||
.filter(attr => hasValue(task[attr.name]))
|
required
|
||||||
.map((attr, i) => {
|
InputLabelProps={{ shrink: true }}
|
||||||
const defaultView = task[attr.name];
|
type={"date"}
|
||||||
const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null;
|
value={state.end}
|
||||||
return (
|
onChange={handlePeriodChanged}
|
||||||
<React.Fragment key={i}>
|
variant="standard"
|
||||||
<ListItem alignItems="flex-start">
|
size="small"
|
||||||
<ListItemText
|
margin="normal"
|
||||||
primary={attr.caption}
|
/>
|
||||||
secondaryTypographyProps={{ component: "span" }}
|
}
|
||||||
secondary={customView ? customView : defaultView}
|
/>
|
||||||
/>
|
</ListItem>
|
||||||
</ListItem>
|
{hasValue(task.progress) || legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
|
||||||
{i < taskAttributes.length - 1 ? <Divider component="li" /> : null}
|
{hasValue(task.progress) ? (
|
||||||
</React.Fragment>
|
<>
|
||||||
);
|
<ListItem alignItems="flex-start">
|
||||||
})
|
<ListItemText
|
||||||
: null}
|
secondaryTypographyProps={{ component: "span" }}
|
||||||
</List>
|
primary={`${progressCaption}${
|
||||||
</DialogContent>
|
task.readOnly === true || task.readOnlyProgress === true ? ` (${task.progress}%)` : ""
|
||||||
<DialogActions>
|
}`}
|
||||||
<Button disabled={!state.start || !state.end || task.readOnly} onClick={handleOk}>
|
secondary={
|
||||||
{okBtnCaption}
|
<Slider
|
||||||
</Button>
|
disabled={task.readOnly === true || task.readOnlyProgress === true}
|
||||||
<Button onClick={handleCancel}>{cancelBtnCaption}</Button>
|
defaultValue={task.progress}
|
||||||
</DialogActions>
|
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
|
||||||
|
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 disabled={!state.start || !state.end || task.readOnly} onClick={handleOk}>
|
||||||
|
{okBtnCaption}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCancel}>{cancelBtnCaption}</Button>
|
||||||
|
</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,
|
||||||
|
@ -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">
|
||||||
|
Это пользовательский диалог с данными о задаче. Вы можете формировать такие указав свой функциональный компонент в качестве
|
||||||
|
свойства "taskDialogRenderer" компонента "P8PGantt".
|
||||||
|
</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}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user