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 };
 |