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 = {
TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" }
};
@ -97,6 +98,7 @@ const P8PGanttTaskEditor = ({
onOk,
onCancel,
taskAttributeRenderer,
taskDialogRenderer,
numbCaption,
nameCaption,
startCaption,
@ -113,6 +115,10 @@ const P8PGanttTaskEditor = ({
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);
@ -158,117 +164,121 @@ const P8PGanttTaskEditor = ({
//Генерация содержимого
return (
<Dialog open onClose={handleCancel}>
<DialogContent>
<List sx={STYLES.TASK_EDITOR_LIST}>
<ListItem alignItems="flex-start">
<ListItemText primary={numbCaption} secondary={task.numb} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText primary={nameCaption} secondary={task.fullName} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
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"
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) ? (
<>
{taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
) : (
<>
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
<List sx={STYLES.TASK_EDITOR_LIST}>
<ListItem alignItems="flex-start">
<ListItemText primary={numbCaption} secondary={task.numb} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText primary={nameCaption} secondary={task.fullName} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
secondaryTypographyProps={{ component: "span" }}
primary={`${progressCaption}${
task.readOnly === true || task.readOnlyProgress === true ? ` (${task.progress}%)` : ""
}`}
primary={startCaption}
secondary={
<Slider
disabled={task.readOnly === true || task.readOnlyProgress === true}
defaultValue={task.progress}
valueLabelDisplay="auto"
onChange={handleProgressChanged}
<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" />
</>
) : null}
{legend ? (
<>
<ListItem alignItems="flex-start">{legend}</ListItem>
<Divider component="li" />
</>
) : null}
{Array.isArray(taskAttributes) && taskAttributes.length > 0
? taskAttributes
.filter(attr => hasValue(task[attr.name]))
.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 < taskAttributes.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>
<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>
{hasValue(task.progress) || legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
{hasValue(task.progress) ? (
<>
<ListItem alignItems="flex-start">
<ListItemText
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
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>
);
};
@ -281,6 +291,7 @@ P8PGanttTaskEditor.propTypes = {
onOk: PropTypes.func,
onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
numbCaption: PropTypes.string.isRequired,
nameCaption: PropTypes.string.isRequired,
startCaption: PropTypes.string.isRequired,
@ -312,6 +323,7 @@ const P8PGantt = ({
onTaskDatesChange,
onTaskProgressChange,
taskAttributeRenderer,
taskDialogRenderer,
noDataFoundText,
numbTaskEditorCaption,
nameTaskEditorCaption,
@ -417,6 +429,7 @@ const P8PGantt = ({
onOk={handleTaskEditorSave}
onCancel={handleTaskEditorCancel}
taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={taskDialogRenderer}
numbCaption={numbTaskEditorCaption}
nameCaption={nameTaskEditorCaption}
startCaption={startTaskEditorCaption}
@ -451,6 +464,7 @@ P8PGantt.propTypes = {
onTaskDatesChange: PropTypes.func,
onTaskProgressChange: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
noDataFoundText: PropTypes.string.isRequired,
numbTaskEditorCaption: PropTypes.string.isRequired,
nameTaskEditorCaption: PropTypes.string.isRequired,

View File

@ -9,8 +9,22 @@
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Typography, Grid, Stack, Icon, Box } from "@mui/material"; //Интерфейсные элементы
import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции
import {
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 { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
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,
ident: null,
ganttDef: {},
ganttTasks: []
ganttTasks: [],
useCustomTaskDialog: false
});
//Подключение к контексту взаимодействия с сервером
@ -132,6 +171,10 @@ const Gantt = ({ title }) => {
<Typography sx={STYLES.TITLE} variant={"h6"}>
{title}
</Typography>
<FormControlLabel
control={<Checkbox onChange={() => setState(pv => ({ ...pv, useCustomTaskDialog: !pv.useCustomTaskDialog }))} />}
label="Отображать пользовательский диалог задачи"
/>
<Grid container spacing={0} direction="column" alignItems="center">
<Grid item xs={12}>
{state.dataLoaded ? (
@ -143,6 +186,7 @@ const Gantt = ({ title }) => {
tasks={state.ganttTasks}
onTaskDatesChange={handleTaskDatesChange}
taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={state.useCustomTaskDialog ? taskDialogRenderer : null}
/>
</Box>
) : null}