/*
Парус 8 - Панели мониторинга - ПУП - Работы проектов
Панель мониторинга: Корневая панель работ проектов
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useContext, useState, useCallback, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Drawer,
Fab,
Box,
Grid,
List,
ListItemButton,
ListItemText,
ListItemIcon,
Icon,
Typography,
Divider,
ListItem,
Button,
Dialog,
DialogContent,
DialogActions,
TextField,
DialogTitle
} from "@mui/material"; //Интерфейсные элементы
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта
import { formatDateRF } from "../../core/utils"; //Вспомогательные функции
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы и константы
import { ResMon } from "./res_mon"; //Монитор ресурсов
import { taskAttributeRenderer } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
//---------
//Константы
//---------
//Стили
const STYLES = {
PROJECTS_LIST_ITEM_NOJOBS: { backgroundColor: "#ff000045" },
PROJECTS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
PROJECTS_LIST_ITEM_SECONDARY: { wordWrap: "break-word", fontSize: "0.5rem", textTransform: "uppercase" },
PROJECTS_LIST_ITEM_SECONDARY_NOJOBS: { color: "red" },
PROJECTS_LIST_ITEM_SECONDARY_NOEDIT: { color: "gray" },
PROJECTS_LIST_ITEM_SECONDARY_CHANGED: { color: "green" },
PROJECTS_BUTTON: { position: "absolute", top: `calc(${APP_BAR_HEIGHT} + 16px)`, left: "16px" },
PROJECTS_DRAWER: { width: "250px", flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "250px", boxSizing: "border-box", ...APP_STYLES.SCROLL } },
GANTT_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})`, width: "100vw", paddingTop: "24px" },
GANTT_TITLE: { paddingLeft: "150px", paddingRight: "150px" },
PERIODS_BUTTON: { position: "absolute", top: `calc(${APP_BAR_HEIGHT} + 16px)`, right: "16px" },
PERIODS_DRAWER: { width: "1200px", flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "1200px", boxSizing: "border-box", ...APP_STYLES.SCROLL } }
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Диалог параметров инициализации панели
const InitPrmsDialog = ({ dateBegin, dateFact, onOk, onCancel }) => {
//Собственное состояние - значения с-по
const [values, setValues] = useState({ dateBegin: formatDateJSONDateOnly(dateBegin), dateFact: formatDateJSONDateOnly(dateFact) });
//Отработка воода значения в фильтр
const handleValueTextFieldChanged = e => setValues(prev => ({ ...prev, [e.target.name]: e.target.value }));
//Генерация содержимого
return (
);
};
//Контроль свойств - Диалог параметров инициализации панели
InitPrmsDialog.propTypes = {
dateBegin: PropTypes.instanceOf(Date).isRequired,
dateFact: PropTypes.instanceOf(Date).isRequired,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//Область параметров инициализации панели
const InitPrmsArea = ({ dateBegin, dateFact, durationMeasCode, labMeasCode, onClick }) => {
return (
Начало:
{formatDateRF(dateBegin)}
Факт на:
{formatDateRF(dateFact)}
Длительность:
{durationMeasCode}
Трудоёмкость:
{labMeasCode}
>
}
/>
);
};
//Контроль свойств - Область параметров инициализации панели
InitPrmsArea.propTypes = {
dateBegin: PropTypes.instanceOf(Date),
dateFact: PropTypes.instanceOf(Date),
durationMeasCode: PropTypes.string,
labMeasCode: PropTypes.string,
onClick: PropTypes.func
};
//Область сохранения изменений
const SaveChangesArea = ({ onClick }) => {
return (
);
};
//Контроль свойств - Область сохранения изменений
SaveChangesArea.propTypes = {
onClick: PropTypes.func
};
//Список проектов
const ProjectsList = ({ projects = [], selectedProject, onClick } = {}) => {
//Подключение к контексту сообщений
const { InlineMsgErr } = useContext(MessagingСtx);
//Генерация содержимого
return projects.length > 0 ? (
{projects.map(p => (
(onClick ? onClick(p) : null)}
>
{p.NEDITABLE == 1 ? "edit" : "edit_off"}
{p.SNAME}}
secondary={
{p.NJOBS == 1
? p.NEDITABLE == 1
? p.NCHANGED == 1
? "Изменён"
: "Не изменён"
: "Редактирование недоступно"
: "Работы не определены"}
}
/>
))}
) : (
);
};
//Контроль свойств - Список проектов
ProjectsList.propTypes = {
projects: PropTypes.array,
selectedProject: PropTypes.number,
onClick: PropTypes.func
};
//-----------
//Тело модуля
//-----------
//Корневая панель работ проектов
const PrjJobs = () => {
//Собственное состояние
let [state, setState] = useState({
needSave: false,
showProjectsList: false,
showPeriodsList: false,
init: false,
dateBegin: null,
dateFact: null,
durationMeas: null,
durationMeasCode: null,
labMeas: null,
labMeasCode: null,
resourceStatus: null,
ident: null,
projects: [],
projectsLoaded: false,
selectedProjectJobsLoaded: false,
selectedProject: null,
selectedProjectDocRn: null,
gantt: {},
showInitDialog: false
});
//Подключение к контексту приложения
const { pOnlineShowDocument } = useContext(ApplicationСtx);
//Подключение к контексту сообщений
const { InlineMsgInfo } = useContext(MessagingСtx);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Загрузка списка проектов
const loadProjects = useCallback(
async (force = false) => {
if (!state.projectsLoaded || force) {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.JB_PRJCTS_LIST",
args: { NIDENT: state.ident },
respArg: "COUT",
isArray: name => name === "XPROJECTS"
});
setState(pv => ({ ...pv, projectsLoaded: true, projects: [...(data?.XPROJECTS || [])] }));
}
},
[executeStored, state.ident, state.projectsLoaded]
);
//Загрузка списка работ проекта
const loadProjectJobs = useCallback(
async (tasksOnly = false) => {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.JB_JOBS_LIST",
args: { NIDENT: state.ident, NPRN: state.selectedProject, NINCLUDE_DEF: tasksOnly === false ? 1 : 0 },
attributeValueProcessor: (name, val) =>
name == "numb" ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val,
respArg: "COUT"
});
setState(pv => ({
...pv,
selectedProjectJobsLoaded: true,
gantt: {
...(tasksOnly === true ? { ...pv.gantt, tasks: [...data.XGANTT.tasks] } : data.XGANTT ? { ...data.XGANTT } : {})
}
}));
},
[executeStored, state.ident, state.selectedProject]
);
//Изменение работы в графике
const modifyJob = useCallback(
async (job, dateFrom, dateTo) => {
let data = null;
try {
data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.JB_JOBS_MODIFY_PERIOD",
args: { NJB_JOBS: job, DDATE_FROM: dateFrom, DDATE_TO: dateTo }
});
if (data?.NRESOURCE_STATUS != -1) {
setState(pv => ({ ...pv, resourceStatus: data.NRESOURCE_STATUS, needSave: true }));
loadProjects(true);
}
} finally {
loadProjectJobs(true);
}
},
[executeStored, loadProjectJobs, loadProjects]
);
//Сохранение буфера балансировки в проекты
const saveProjects = useCallback(async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.JB_SAVE",
args: { NIDENT: state.ident },
respArg: "COUT"
});
setState(pv => ({ ...pv, needSave: false, projects: [...(data?.XPROJECTS || [])] }));
}, [executeStored, state.ident]);
//Инициализация данных балансировки
const initJobs = useCallback(async () => {
if (!state.init) {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.JB_INIT",
args: {
DBEGIN: state.dateBegin ? state.dateBegin : null,
DFACT: state.dateFact ? state.dateFact : null,
NIDENT: state.ident
}
});
setState(pv => ({
...pv,
init: true,
reInit: false,
dateBegin: new Date(data.DBEGIN),
dateFact: new Date(data.DFACT),
durationMeas: data.NDURATION_MEAS,
durationMeasCode: data.SDURATION_MEAS,
labMeas: data.NLAB_MEAS,
labMeasCode: data.SLAB_MEAS,
resourceStatus: data.NRESOURCE_STATUS,
ident: data.NIDENT
}));
}
}, [state.init, state.dateBegin, state.dateFact, state.ident, executeStored]);
//Грузим список проектов при смене идентификатора процесса
useEffect(() => {
if (state.ident) loadProjects();
}, [state.ident, loadProjects]);
//При смене выбранного проекта
useEffect(() => {
if (state.selectedProject) loadProjectJobs(false);
}, [state.selectedProject, loadProjectJobs]);
//При изменении флага инициализации
useEffect(() => {
initJobs();
}, [state.init, initJobs]);
//Выбор проекта
const selectPoject = (project, projectDocRn) => {
setState(pv => ({
...pv,
selectedProject: project,
selectedProjectDocRn: projectDocRn,
selectedProjectJobsLoaded: false,
gantt: {},
showProjectsList: false
}));
};
//Сброс выбора проекта
const unselectProject = () =>
setState(pv => ({
...pv,
selectedProjectJobsLoaded: false,
selectedProject: null,
selectedProjectDocRn: null,
gantt: {},
showProjectsList: false
}));
//Обработка нажатия на элемент в списке проектов
const handleProjectClick = project => {
if (state.selectedProject != project.NRN) selectPoject(project.NRN, project.NPROJECT);
else unselectProject();
};
//Отработка нажатия на заголовок плана-графика
const handleTitleClick = () =>
state.selectedProjectDocRn ? pOnlineShowDocument({ unitCode: "Projects", document: state.selectedProjectDocRn }) : null;
//Обработка измненения сроков задачи в диаграмме Гантта
const handleTaskDatesChange = ({ task, start, end, isMain }) => {
if (isMain) modifyJob(task.rn, new Date(start), new Date(end));
};
//Отработка нажатия на отображения диалога параметров инициализации панели
const handleShowInitDialogClick = () => setState(pv => ({ ...pv, showInitDialog: true }));
//Отработка нажатия на "ОК" в диалоге параметров инициализации панели
const handleOKInitDialogClick = values =>
setState(pv => ({ ...pv, dateBegin: values.dateBegin, dateFact: values.dateFact, showInitDialog: false, init: false }));
//Отработка нажатия на "Отмена" в диалоге параметров инициализации панели
const handleCancelInitDialogClick = () => setState(pv => ({ ...pv, showInitDialog: false }));
//Обработка нажатия на сохранение данных в проект
const handleSaveToProjectsClick = () => saveProjects();
//Обработка нажатия на проект в таблице детализации трудоёмкости по плану-графику монитора ресурсов
const handlePlanJobsDtlProjectClick = ({ sender }) => {
setState(pv => ({ ...pv, showPeriodsList: false }));
if (state.selectedProject != sender.NJB_PRJCTS) selectPoject(sender.NJB_PRJCTS, sender.NPROJECT);
};
//Генерация содержимого
return (
{state.showInitDialog ? (
) : null}
setState(pv => ({ ...pv, showProjectsList: !pv.showProjectsList }))}>
Проекты
{state.needSave ? (
<>
save
>
) : null}
setState(pv => ({ ...pv, showProjectsList: false }))}
sx={STYLES.PROJECTS_DRAWER}
>
{state.projectsLoaded ? (
<>
{state.needSave ? (
<>
>
) : null}
>
) : null}
setState(pv => ({ ...pv, showPeriodsList: !pv.showPeriodsList }))}>
Ресурсы
{[0, 1].includes(state.resourceStatus) ? (
<>
{state.resourceStatus === 0 ? "done" : "error"}
>
) : null}
setState(pv => ({ ...pv, showPeriodsList: false }))}
sx={STYLES.PERIODS_DRAWER}
>
{state.ident ? : null}
{state.init == true ? (
{state.selectedProjectJobsLoaded ? (
) : !state.selectedProject ? (
) : null}
) : null}
);
};
//----------------
//Интерфейс модуля
//----------------
export { PrjJobs };