forked from CITKParus/P8-Panels
302 lines
12 KiB
JavaScript
302 lines
12 KiB
JavaScript
/*
|
||
Парус 8 - Панели мониторинга - Примеры для разработчиков
|
||
Пример: Циклограмма "P8PCyclogram"
|
||
*/
|
||
|
||
//---------------------
|
||
//Подключение библиотек
|
||
//---------------------
|
||
|
||
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
|
||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||
import {
|
||
Typography,
|
||
Grid,
|
||
Button,
|
||
Box,
|
||
DialogContent,
|
||
List,
|
||
ListItem,
|
||
ListItemText,
|
||
Divider,
|
||
TextField,
|
||
DialogActions,
|
||
Stack,
|
||
Icon
|
||
} from "@mui/material"; //Интерфейсные элементы
|
||
import { formatDateJSONDateOnly, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
||
import { P8PCyclogram } from "../../components/p8p_cyclogram"; //Циклограмма
|
||
import { P8P_CYCLOGRAM_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||
|
||
//---------
|
||
//Константы
|
||
//---------
|
||
|
||
//Отступ контейнера страницы от заголовка
|
||
const CONTAINER_PADDING_TOP = "20px";
|
||
|
||
//Высота заголовка страницы
|
||
const TITLE_HEIGHT = "47px";
|
||
|
||
//Высота строк
|
||
const LINE_HEIGHT = 30;
|
||
|
||
//Стили
|
||
const STYLES = {
|
||
CONTAINER: { textAlign: "center", paddingTop: CONTAINER_PADDING_TOP },
|
||
TITLE: { paddingBottom: "15px", height: TITLE_HEIGHT },
|
||
CYCLOGRAM_CONTAINER: {
|
||
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_HEIGHT} - ${CONTAINER_PADDING_TOP})`,
|
||
width: "100vw",
|
||
paddingTop: "5px"
|
||
},
|
||
TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
|
||
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" },
|
||
GROUP_HEADER: height => ({
|
||
border: "1px solid",
|
||
backgroundColor: "#ecf8fb",
|
||
height: height,
|
||
borderRadius: "10px",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "space-around"
|
||
})
|
||
};
|
||
|
||
//---------------------------------------------
|
||
//Вспомогательные функции форматирования данных
|
||
//---------------------------------------------
|
||
|
||
//Диалог открытия задачи
|
||
const CustomTaskDialog = ({ task, ident, handleReload, close }) => {
|
||
//Собственное состояние
|
||
const [taskDates, setTaskDates] = useState({ start: task.ddate_start, end: task.ddate_end });
|
||
|
||
//Тип проекта
|
||
const textType = task.type === 0 ? "Задачи проекта" : task.type === 1 ? "Этап проекта" : "Работа проекта";
|
||
|
||
//Подключение к контексту взаимодействия с сервером
|
||
const { executeStored } = useContext(BackEndСtx);
|
||
|
||
//Изменение дат задачи
|
||
const changeDates = useCallback(async () => {
|
||
//Изменяем даты задачи
|
||
await executeStored({
|
||
stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_TASK_MODIFY",
|
||
args: {
|
||
NIDENT: ident,
|
||
NRN: task.rn,
|
||
SDATE_FROM: formatDateRF(taskDates.start),
|
||
SDATE_TO: formatDateRF(taskDates.end)
|
||
}
|
||
});
|
||
handleReload();
|
||
close();
|
||
}, [close, executeStored, handleReload, ident, task.rn, taskDates.end, taskDates.start]);
|
||
|
||
//При нажатии OK
|
||
const handleOk = () => {
|
||
//Изменяем даты задачи
|
||
changeDates();
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
|
||
<List sx={STYLES.TASK_EDITOR_LIST}>
|
||
<ListItem alignItems="flex-start">
|
||
<ListItemText primary={"Наименование"} secondary={task.fullName} />
|
||
</ListItem>
|
||
<Divider component="li" />
|
||
<ListItem alignItems="flex-start">
|
||
<ListItemText
|
||
secondaryTypographyProps={{ component: "span" }}
|
||
primary={"Начало"}
|
||
secondary={
|
||
<TextField
|
||
error={!taskDates.start}
|
||
disabled={task.type !== 2}
|
||
name="start"
|
||
fullWidth
|
||
required
|
||
InputLabelProps={{ shrink: true }}
|
||
type={"date"}
|
||
value={taskDates.start}
|
||
onChange={e => setTaskDates(pv => ({ ...pv, start: e.target.value }))}
|
||
variant="standard"
|
||
size="small"
|
||
margin="normal"
|
||
></TextField>
|
||
}
|
||
/>
|
||
</ListItem>
|
||
<Divider component="li" />
|
||
<ListItem alignItems="flex-start">
|
||
<ListItemText
|
||
secondaryTypographyProps={{ component: "span" }}
|
||
primary={"Окончание"}
|
||
secondary={
|
||
<TextField
|
||
error={!taskDates.end}
|
||
disabled={task.type !== 2}
|
||
name="end"
|
||
fullWidth
|
||
required
|
||
InputLabelProps={{ shrink: true }}
|
||
type={"date"}
|
||
value={taskDates.end}
|
||
onChange={e => setTaskDates(pv => ({ ...pv, end: e.target.value }))}
|
||
variant="standard"
|
||
size="small"
|
||
margin="normal"
|
||
></TextField>
|
||
}
|
||
/>
|
||
</ListItem>
|
||
<Divider component="li" />
|
||
<ListItem alignItems="flex-start">
|
||
<ListItemText
|
||
primary={"Тип"}
|
||
secondaryTypographyProps={{ component: "span" }}
|
||
secondary={
|
||
<Stack direction="row" gap={0.5}>
|
||
<Icon title={textType}>{task.type === 0 ? "description" : task.type === 1 ? "check" : "work_outline"}</Icon>
|
||
{textType}
|
||
</Stack>
|
||
}
|
||
/>
|
||
</ListItem>
|
||
</List>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Button onClick={handleOk}>ОК</Button>
|
||
<Button onClick={close}>Отмена</Button>
|
||
</DialogActions>
|
||
</>
|
||
);
|
||
};
|
||
|
||
//Контроль свойств - Диалог открытия задачи
|
||
CustomTaskDialog.propTypes = {
|
||
task: PropTypes.object.isRequired,
|
||
ident: PropTypes.number.isRequired,
|
||
handleReload: PropTypes.func.isRequired,
|
||
close: PropTypes.func.isRequired
|
||
};
|
||
|
||
//Заголовок группы
|
||
const CustomGroupHeader = ({ group }) => {
|
||
return (
|
||
<Box sx={STYLES.GROUP_HEADER(group.height)}>
|
||
<Typography variant="body2">{group.name}</Typography>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
//Контроль свойств - Заголовок группы
|
||
CustomGroupHeader.propTypes = {
|
||
group: PropTypes.object.isRequired
|
||
};
|
||
|
||
//Отображение задачи
|
||
const taskRenderer = ({ task }) => {
|
||
//Если это задачи проекта
|
||
if (task.type === 0) {
|
||
return {
|
||
taskStyle: { border: "3px solid #ebe058" }
|
||
};
|
||
}
|
||
};
|
||
|
||
//-----------
|
||
//Тело модуля
|
||
//-----------
|
||
|
||
//Пример: Циклограмма "P8PCyclogram"
|
||
const Cyclogram = ({ title }) => {
|
||
//Собственное состояние
|
||
const [state, setState] = useState({
|
||
init: false,
|
||
dataLoaded: false,
|
||
reload: true,
|
||
ident: null
|
||
});
|
||
|
||
//Подключение к контексту взаимодействия с сервером
|
||
const { executeStored } = useContext(BackEndСtx);
|
||
|
||
//При необходимости перезагрузки
|
||
const handleReload = () => {
|
||
setState(pv => ({ ...pv, reload: true }));
|
||
};
|
||
|
||
//При необходимости обновить данные таблицы
|
||
useEffect(() => {
|
||
//Загрузка данных циклограммы с сервера
|
||
const loadData = async () => {
|
||
const data = await executeStored({
|
||
stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM",
|
||
args: { NIDENT: state.ident },
|
||
attributeValueProcessor: (name, val) => (["ddate_start", "ddate_end"].includes(name) ? formatDateJSONDateOnly(val) : val),
|
||
respArg: "COUT"
|
||
});
|
||
setState(pv => ({ ...pv, dataLoaded: true, ...data.XCYCLOGRAM, reload: false }));
|
||
};
|
||
//Если указан идентификатор и требуется перезагрузить
|
||
if (state.ident && state.reload) loadData();
|
||
}, [state.ident, state.reload, executeStored]);
|
||
|
||
//При подключении компонента к странице
|
||
useEffect(() => {
|
||
//Инициализация данных циклограммы
|
||
const initData = async () => {
|
||
const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_INIT", args: { NIDENT: state.ident } });
|
||
setState(pv => ({ ...pv, init: true, ident: data.NIDENT, reload: true }));
|
||
};
|
||
//Если требуется проинициализировать
|
||
if (!state.init) {
|
||
initData();
|
||
}
|
||
}, [executeStored, state.ident, state.init]);
|
||
|
||
return (
|
||
<Box>
|
||
<div style={STYLES.CONTAINER}>
|
||
<Typography sx={STYLES.TITLE} variant={"h6"}>
|
||
{title}
|
||
</Typography>
|
||
<Grid container direction="column" alignItems="center">
|
||
<Grid item xs={12}>
|
||
{state.dataLoaded ? (
|
||
<P8PCyclogram
|
||
{...P8P_CYCLOGRAM_CONFIG_PROPS}
|
||
{...state}
|
||
containerStyle={STYLES.CYCLOGRAM_CONTAINER}
|
||
lineHeight={LINE_HEIGHT}
|
||
taskDialogRenderer={prms => (
|
||
<CustomTaskDialog task={prms.task} ident={state.ident} handleReload={handleReload} close={prms.close} />
|
||
)}
|
||
taskRenderer={prms => taskRenderer(prms)}
|
||
groupHeaderRenderer={prms => <CustomGroupHeader group={prms.group} />}
|
||
/>
|
||
) : null}
|
||
</Grid>
|
||
</Grid>
|
||
</div>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
//Контроль свойств - Пример: Циклограмма "P8PCyclogram"
|
||
Cyclogram.propTypes = {
|
||
title: PropTypes.string.isRequired
|
||
};
|
||
|
||
//----------------
|
||
//Интерфейс модуля
|
||
//----------------
|
||
|
||
export { Cyclogram };
|