forked from CITKParus/P8-Panels
WEB APP: Панель "Работы проектов" - мониторинг ресурсов, сохранение изменений работ (начало)
This commit is contained in:
parent
3cf0b7f41e
commit
6c0d021a65
122
app/panels/prj_jobs/lab_plan_fot_dtl.js
Normal file
122
app/panels/prj_jobs/lab_plan_fot_dtl.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - ПУП - Работы проектов
|
||||||
|
Компонент панели: Детализация плановой трудоёмкости по ФОТ
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useContext, useState, useCallback, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Dialog, DialogContent, DialogActions, Button, DialogTitle } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
|
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
|
||||||
|
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы
|
||||||
|
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||||
|
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Детализация плановой трудоёмкости по ФОТ
|
||||||
|
const LabPlanFOTDtl = ({ periodId, title, onHide }) => {
|
||||||
|
//Состояние таблицы детализации плановой трудоёмкости по ФОТ ресурса
|
||||||
|
const [planFOTDtl, setPlanFOTDtl] = useState({
|
||||||
|
dataLoaded: false,
|
||||||
|
columnsDef: [],
|
||||||
|
orders: [],
|
||||||
|
rows: [],
|
||||||
|
reload: true,
|
||||||
|
pageNumber: 1,
|
||||||
|
morePages: true
|
||||||
|
});
|
||||||
|
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { configSystemPageSize } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//Загрузка детализации плановой трудоёмкости по ФОТ для ресурса
|
||||||
|
const loadPlanFOTDtl = useCallback(async () => {
|
||||||
|
if (planFOTDtl.reload) {
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PROJECTS.JB_PERIODS_LIST_PLAN_FOT",
|
||||||
|
args: {
|
||||||
|
NJB_PERIODS: periodId,
|
||||||
|
CORDERS: { VALUE: object2Base64XML(planFOTDtl.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||||
|
NPAGE_NUMBER: planFOTDtl.pageNumber,
|
||||||
|
NPAGE_SIZE: configSystemPageSize,
|
||||||
|
NINCLUDE_DEF: planFOTDtl.dataLoaded ? 0 : 1
|
||||||
|
},
|
||||||
|
respArg: "COUT"
|
||||||
|
});
|
||||||
|
setPlanFOTDtl(pv => ({
|
||||||
|
...pv,
|
||||||
|
columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
|
||||||
|
rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
|
||||||
|
dataLoaded: true,
|
||||||
|
reload: false,
|
||||||
|
morePages: (data.XROWS || []).length >= configSystemPageSize
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
periodId,
|
||||||
|
planFOTDtl.reload,
|
||||||
|
planFOTDtl.orders,
|
||||||
|
planFOTDtl.dataLoaded,
|
||||||
|
planFOTDtl.pageNumber,
|
||||||
|
executeStored,
|
||||||
|
configSystemPageSize,
|
||||||
|
SERV_DATA_TYPE_CLOB
|
||||||
|
]);
|
||||||
|
|
||||||
|
//При изменении состояния сортировки в детализации плана ФОТ по строке ресурса
|
||||||
|
const handlePlanFOTDtlDGOrderChanged = ({ orders }) => setPlanFOTDtl(pv => ({ ...pv, orders, pageNumber: 1, reload: true }));
|
||||||
|
|
||||||
|
//При изменении количества отображаемых страниц в в детализации плана ФОТ по строке ресурса
|
||||||
|
const handlePlanFOTDtlDGPagesCountChanged = () => setPlanFOTDtl(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
|
||||||
|
|
||||||
|
//При необходимости обновить данные
|
||||||
|
useEffect(() => {
|
||||||
|
loadPlanFOTDtl();
|
||||||
|
}, [planFOTDtl.reload, loadPlanFOTDtl]);
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return planFOTDtl.dataLoaded ? (
|
||||||
|
<Dialog open onClose={onHide}>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<P8PDataGrid
|
||||||
|
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||||
|
columnsDef={planFOTDtl.columnsDef}
|
||||||
|
rows={planFOTDtl.rows}
|
||||||
|
size={P8P_DATA_GRID_SIZE.SMALL}
|
||||||
|
morePages={planFOTDtl.morePages}
|
||||||
|
reloading={planFOTDtl.reload}
|
||||||
|
onOrderChanged={handlePlanFOTDtlDGOrderChanged}
|
||||||
|
onPagesCountChanged={handlePlanFOTDtlDGPagesCountChanged}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onHide}>{BUTTONS.CLOSE}</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Детализация плановой трудоёмкости по ФОТ
|
||||||
|
LabPlanFOTDtl.propTypes = {
|
||||||
|
periodId: PropTypes.number.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
onHide: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { LabPlanFOTDtl };
|
202
app/panels/prj_jobs/lab_plan_jobs_dtl.js
Normal file
202
app/panels/prj_jobs/lab_plan_jobs_dtl.js
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - ПУП - Работы проектов
|
||||||
|
Компонент панели: Детализация плановой трудоёмкости по графику
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useContext, useState, useCallback, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Dialog, DialogContent, DialogActions, Button, DialogTitle, Stack, Icon, Link } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
|
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
||||||
|
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы
|
||||||
|
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||||
|
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Формирование значения для колонки "Состояние" этапа
|
||||||
|
const formatJobStatusValue = value => {
|
||||||
|
const [text, icon] =
|
||||||
|
value == 0
|
||||||
|
? ["Не начата", "not_started"]
|
||||||
|
: value == 1
|
||||||
|
? ["Выполняется", "loop"]
|
||||||
|
: value == 2
|
||||||
|
? ["Выполнена", "task_alt"]
|
||||||
|
: value == 3
|
||||||
|
? ["Остановлена", "do_not_disturb_on"]
|
||||||
|
: ["Отменена", "cancel"];
|
||||||
|
return (
|
||||||
|
<Stack direction="row" gap={0.5} alignItems="center" justifyContent="center">
|
||||||
|
<Icon title={text}>{icon}</Icon>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Форматирование значений колонок в таблице детализации трудоёмкости по графику
|
||||||
|
const planJobsDtlValueFormatter = ({ value, columnDef }) => {
|
||||||
|
switch (columnDef.name) {
|
||||||
|
case "NJOB_STATE":
|
||||||
|
return formatJobStatusValue(value);
|
||||||
|
case "DJOB_BEG":
|
||||||
|
case "DJOB_END":
|
||||||
|
return formatDateRF(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Генерация представления ячейки заголовка в таблице детализации трудоёмкости по графику
|
||||||
|
const planJobsDtlHeadCellRender = ({ columnDef }) => {
|
||||||
|
switch (columnDef.name) {
|
||||||
|
case "NJOB_STATE":
|
||||||
|
return {
|
||||||
|
stackProps: { justifyContent: "center" },
|
||||||
|
cellProps: { align: "center" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Генерация представления ячейки c данными в таблице детализации трудоёмкости по графику
|
||||||
|
const planJobsDtlDataCellRender = ({ row, columnDef, onProjectClick }) => {
|
||||||
|
switch (columnDef.name) {
|
||||||
|
case "SPRJ":
|
||||||
|
return {
|
||||||
|
data: row[columnDef.name] ? (
|
||||||
|
<Link
|
||||||
|
component="button"
|
||||||
|
variant="body2"
|
||||||
|
align="left"
|
||||||
|
underline="hover"
|
||||||
|
onClick={() => (onProjectClick ? onProjectClick({ sender: row }) : null)}
|
||||||
|
>
|
||||||
|
{row[columnDef.name]}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
row[columnDef.name]
|
||||||
|
)
|
||||||
|
};
|
||||||
|
case "NSTATE":
|
||||||
|
return {
|
||||||
|
cellProps: { align: "center" },
|
||||||
|
data: formatJobStatusValue(row[columnDef.name])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Детализация плановой трудоёмкости по графику
|
||||||
|
const LabPlanJobsDtl = ({ periodId, title, onHide, onProjectClick }) => {
|
||||||
|
//Состояние таблицы детализации плановой трудоёмкости по графику
|
||||||
|
const [planJobsDtl, setPlanJobsDtl] = useState({
|
||||||
|
dataLoaded: false,
|
||||||
|
columnsDef: [],
|
||||||
|
orders: [],
|
||||||
|
rows: [],
|
||||||
|
reload: true,
|
||||||
|
pageNumber: 1,
|
||||||
|
morePages: true
|
||||||
|
});
|
||||||
|
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { configSystemPageSize } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//Загрузка детализации плановой трудоёмкости по ФОТ для ресурса
|
||||||
|
const loadPlanFOTDtl = useCallback(async () => {
|
||||||
|
if (planJobsDtl.reload) {
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PROJECTS.JB_PERIODS_LIST_PLAN_JOBS",
|
||||||
|
args: {
|
||||||
|
NJB_PERIODS: periodId,
|
||||||
|
CORDERS: { VALUE: object2Base64XML(planJobsDtl.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||||
|
NPAGE_NUMBER: planJobsDtl.pageNumber,
|
||||||
|
NPAGE_SIZE: configSystemPageSize,
|
||||||
|
NINCLUDE_DEF: planJobsDtl.dataLoaded ? 0 : 1
|
||||||
|
},
|
||||||
|
respArg: "COUT"
|
||||||
|
});
|
||||||
|
setPlanJobsDtl(pv => ({
|
||||||
|
...pv,
|
||||||
|
columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
|
||||||
|
rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
|
||||||
|
dataLoaded: true,
|
||||||
|
reload: false,
|
||||||
|
morePages: (data.XROWS || []).length >= configSystemPageSize
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
periodId,
|
||||||
|
planJobsDtl.reload,
|
||||||
|
planJobsDtl.orders,
|
||||||
|
planJobsDtl.dataLoaded,
|
||||||
|
planJobsDtl.pageNumber,
|
||||||
|
executeStored,
|
||||||
|
configSystemPageSize,
|
||||||
|
SERV_DATA_TYPE_CLOB
|
||||||
|
]);
|
||||||
|
|
||||||
|
//При изменении состояния сортировки в детализации плана ФОТ по строке ресурса
|
||||||
|
const handlePlanJobsDtlDGOrderChanged = ({ orders }) => setPlanJobsDtl(pv => ({ ...pv, orders, pageNumber: 1, reload: true }));
|
||||||
|
|
||||||
|
//При изменении количества отображаемых страниц в в детализации плана ФОТ по строке ресурса
|
||||||
|
const handlePlanJobsDtlDGPagesCountChanged = () => setPlanJobsDtl(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
|
||||||
|
|
||||||
|
//При нажатии на проект в таблице детализацц
|
||||||
|
const handleProjectClick = ({ sender }) => (onProjectClick ? onProjectClick({ sender }) : null);
|
||||||
|
|
||||||
|
//При необходимости обновить данные
|
||||||
|
useEffect(() => {
|
||||||
|
loadPlanFOTDtl();
|
||||||
|
}, [planJobsDtl.reload, loadPlanFOTDtl]);
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return planJobsDtl.dataLoaded ? (
|
||||||
|
<Dialog open onClose={onHide} fullWidth maxWidth="xl">
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<P8PDataGrid
|
||||||
|
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||||
|
columnsDef={planJobsDtl.columnsDef}
|
||||||
|
rows={planJobsDtl.rows}
|
||||||
|
size={P8P_DATA_GRID_SIZE.SMALL}
|
||||||
|
morePages={planJobsDtl.morePages}
|
||||||
|
reloading={planJobsDtl.reload}
|
||||||
|
valueFormatter={planJobsDtlValueFormatter}
|
||||||
|
headCellRender={planJobsDtlHeadCellRender}
|
||||||
|
dataCellRender={prms => planJobsDtlDataCellRender({ ...prms, onProjectClick: handleProjectClick })}
|
||||||
|
onOrderChanged={handlePlanJobsDtlDGOrderChanged}
|
||||||
|
onPagesCountChanged={handlePlanJobsDtlDGPagesCountChanged}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onHide}>{BUTTONS.CLOSE}</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Детализация плановой трудоёмкости по графику
|
||||||
|
LabPlanJobsDtl.propTypes = {
|
||||||
|
periodId: PropTypes.number.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
onHide: PropTypes.func.isRequired,
|
||||||
|
onProjectClick: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { LabPlanJobsDtl };
|
@ -13,9 +13,10 @@ import { Drawer, Fab, Box, Grid, List, ListItemButton, ListItemText, ListItemIco
|
|||||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
|
import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции
|
||||||
import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||||
import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта
|
import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта
|
||||||
import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции
|
import { ResMon } from "./res_mon"; //Монитор ресурсов
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -29,6 +30,7 @@ const GANTT_WIDTH = "98vw";
|
|||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
|
PROJECTS_LIST_SAVE_BUTTON: { backgroundColor: "orange" },
|
||||||
PROJECTS_LIST_ITEM_NOJOBS: { backgroundColor: "#ff000045" },
|
PROJECTS_LIST_ITEM_NOJOBS: { backgroundColor: "#ff000045" },
|
||||||
PROJECTS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
|
PROJECTS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
|
||||||
PROJECTS_LIST_ITEM_SECONDARY: { wordWrap: "break-word", fontSize: "0.5rem", textTransform: "uppercase" },
|
PROJECTS_LIST_ITEM_SECONDARY: { wordWrap: "break-word", fontSize: "0.5rem", textTransform: "uppercase" },
|
||||||
@ -38,7 +40,9 @@ const STYLES = {
|
|||||||
PROJECTS_BUTTON: { position: "absolute" },
|
PROJECTS_BUTTON: { position: "absolute" },
|
||||||
PROJECTS_DRAWER: { width: "250px", flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "250px", boxSizing: "border-box" } },
|
PROJECTS_DRAWER: { width: "250px", flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "250px", boxSizing: "border-box" } },
|
||||||
GANTT_CONTAINER: { height: GANTT_HEIGHT, width: GANTT_WIDTH },
|
GANTT_CONTAINER: { height: GANTT_HEIGHT, width: GANTT_WIDTH },
|
||||||
GANTT_TITLE: { paddingLeft: "100px", paddingRight: "100px" }
|
GANTT_TITLE: { paddingLeft: "100px", paddingRight: "120px" },
|
||||||
|
PERIODS_BUTTON: { position: "absolute", right: "20px" },
|
||||||
|
PERIODS_DRAWER: { width: "1000px", flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "1000px", boxSizing: "border-box" } }
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
@ -153,8 +157,15 @@ ProjectsList.propTypes = {
|
|||||||
const PrjJobs = () => {
|
const PrjJobs = () => {
|
||||||
//Собственное состояние
|
//Собственное состояние
|
||||||
let [state, setState] = useState({
|
let [state, setState] = useState({
|
||||||
|
needSave: false,
|
||||||
showProjectsList: false,
|
showProjectsList: false,
|
||||||
|
showPeriodsList: false,
|
||||||
init: false,
|
init: false,
|
||||||
|
dateBegin: null,
|
||||||
|
dateFact: null,
|
||||||
|
durationMeas: null,
|
||||||
|
labMeas: null,
|
||||||
|
resourceStatus: null,
|
||||||
ident: null,
|
ident: null,
|
||||||
projects: [],
|
projects: [],
|
||||||
projectsLoaded: false,
|
projectsLoaded: false,
|
||||||
@ -175,18 +186,21 @@ const PrjJobs = () => {
|
|||||||
const { executeStored } = useContext(BackEndСtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Загрузка списка проектов
|
//Загрузка списка проектов
|
||||||
const loadProjects = useCallback(async () => {
|
const loadProjects = useCallback(
|
||||||
if (!state.projectsLoaded) {
|
async (force = false) => {
|
||||||
const data = await executeStored({
|
if (!state.projectsLoaded || force) {
|
||||||
stored: "PKG_P8PANELS_PROJECTS.JB_PRJCTS_LIST",
|
const data = await executeStored({
|
||||||
args: {
|
stored: "PKG_P8PANELS_PROJECTS.JB_PRJCTS_LIST",
|
||||||
NIDENT: state.ident
|
args: {
|
||||||
},
|
NIDENT: state.ident
|
||||||
respArg: "COUT"
|
},
|
||||||
});
|
respArg: "COUT"
|
||||||
setState(pv => ({ ...pv, projectsLoaded: true, projects: [...(data?.XPROJECTS || [])] }));
|
});
|
||||||
}
|
setState(pv => ({ ...pv, projectsLoaded: true, projects: [...(data?.XPROJECTS || [])] }));
|
||||||
}, [executeStored, state.ident, state.projectsLoaded]);
|
}
|
||||||
|
},
|
||||||
|
[executeStored, state.ident, state.projectsLoaded]
|
||||||
|
);
|
||||||
|
|
||||||
//Загрузка списка работ проекта
|
//Загрузка списка работ проекта
|
||||||
const loadProjectJobs = useCallback(
|
const loadProjectJobs = useCallback(
|
||||||
@ -212,25 +226,55 @@ const PrjJobs = () => {
|
|||||||
[executeStored, state.ident, state.selectedProject]
|
[executeStored, state.ident, state.selectedProject]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//Изменение работы в графике
|
||||||
|
const modifyJob = useCallback(
|
||||||
|
async (job, dateFrom, dateTo) => {
|
||||||
|
try {
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PROJECTS.JB_JOBS_MODIFY_PERIOD",
|
||||||
|
args: {
|
||||||
|
NJB_JOBS: job,
|
||||||
|
DDATE_FROM: new Date(dateFrom),
|
||||||
|
DDATE_TO: new Date(dateTo),
|
||||||
|
DBEGIN: new Date(state.dateBegin)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setState(pv => ({ ...pv, resourceStatus: data.NRESOURCE_STATUS, needSave: true }));
|
||||||
|
loadProjects(true);
|
||||||
|
} finally {
|
||||||
|
loadProjectJobs(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[executeStored, loadProjectJobs, loadProjects, state.dateBegin]
|
||||||
|
);
|
||||||
|
|
||||||
//Инициализация данных балансировки
|
//Инициализация данных балансировки
|
||||||
const initJobs = useCallback(async () => {
|
const initJobs = useCallback(async () => {
|
||||||
if (!state.init) {
|
if (!state.init) {
|
||||||
const data = await executeStored({
|
const data = await executeStored({
|
||||||
stored: "PKG_P8PANELS_PROJECTS.JB_INIT",
|
stored: "PKG_P8PANELS_PROJECTS.JB_INIT",
|
||||||
args: {
|
args: {
|
||||||
DBEGIN: null,
|
DBEGIN: state.dateBegin ? new Date(state.dateBegin) : null,
|
||||||
DFACT: null,
|
DFACT: state.dateFact ? new Date(state.dateFact) : null,
|
||||||
NDURATION_MEAS: 0,
|
NDURATION_MEAS: state.durationMeas,
|
||||||
SLAB_MEAS: null,
|
SLAB_MEAS: state.labMeas,
|
||||||
NINCLUDE_DEF: null,
|
|
||||||
NIDENT: state.ident
|
NIDENT: state.ident
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setState(pv => ({ ...pv, init: true, ident: data.NIDENT }));
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
init: true,
|
||||||
|
dateBegin: data.DBEGIN,
|
||||||
|
dateFact: data.DFACT,
|
||||||
|
durationMeas: data.NDURATION_MEAS,
|
||||||
|
labMeas: data.SLAB_MEAS,
|
||||||
|
resourceStatus: data.NRESOURCE_STATUS,
|
||||||
|
ident: data.NIDENT
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}, [state.init, state.ident, executeStored]);
|
}, [state.init, state.dateBegin, state.dateFact, state.durationMeas, state.labMeas, state.ident, executeStored]);
|
||||||
|
|
||||||
//При смене идентификатора процесса
|
//Грузим список проектов при смене идентификатора процесса
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.ident) loadProjects();
|
if (state.ident) loadProjects();
|
||||||
}, [state.ident, loadProjects]);
|
}, [state.ident, loadProjects]);
|
||||||
@ -246,28 +290,35 @@ const PrjJobs = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
//Выбор проекта
|
||||||
|
const selectPoject = (project, projectDocRn) => {
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
selectedProject: project,
|
||||||
|
selectedProjectDocRn: projectDocRn,
|
||||||
|
selectedProjectJobsLoaded: false,
|
||||||
|
selectedProjectTasks: [],
|
||||||
|
selectedProjectGanttDef: {},
|
||||||
|
showProjectsList: false
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Сброс выбора проекта
|
||||||
|
const unselectProject = () =>
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
selectedProjectJobsLoaded: false,
|
||||||
|
selectedProject: null,
|
||||||
|
selectedProjectDocRn: null,
|
||||||
|
selectedProjectTasks: [],
|
||||||
|
selectedProjectGanttDef: {},
|
||||||
|
showProjectsList: false
|
||||||
|
}));
|
||||||
|
|
||||||
//Обработка нажатия на элемент в списке проектов
|
//Обработка нажатия на элемент в списке проектов
|
||||||
const handleProjectClick = project => {
|
const handleProjectClick = project => {
|
||||||
if (state.selectedProject != project.NRN) {
|
if (state.selectedProject != project.NRN) selectPoject(project.NRN, project.NPROJECT);
|
||||||
setState(pv => ({
|
else unselectProject();
|
||||||
...pv,
|
|
||||||
selectedProject: project.NRN,
|
|
||||||
selectedProjectDocRn: project.NPROJECT,
|
|
||||||
selectedProjectJobsLoaded: false,
|
|
||||||
selectedProjectTasks: [],
|
|
||||||
selectedProjectGanttDef: {},
|
|
||||||
showProjectsList: false
|
|
||||||
}));
|
|
||||||
} else
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
selectedProjectJobsLoaded: false,
|
|
||||||
selectedProject: null,
|
|
||||||
selectedProjectDocRn: null,
|
|
||||||
selectedProjectTasks: [],
|
|
||||||
selectedProjectGanttDef: {},
|
|
||||||
showProjectsList: false
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Отработка нажатия на заголовок плана-графика
|
//Отработка нажатия на заголовок плана-графика
|
||||||
@ -280,10 +331,7 @@ const PrjJobs = () => {
|
|||||||
console.log(task);
|
console.log(task);
|
||||||
console.log(start);
|
console.log(start);
|
||||||
console.log(end);
|
console.log(end);
|
||||||
if (isMain) {
|
if (isMain) modifyJob(task.rn, start, end);
|
||||||
console.log("ЭТО - ГЛАВНОЕ. ПОЙДЁМ НА СЕРВЕР...");
|
|
||||||
loadProjectJobs(true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Обработка изменения прогресса задачи в диаграмме Гантта
|
//Обработка изменения прогресса задачи в диаграмме Гантта
|
||||||
@ -305,11 +353,23 @@ const PrjJobs = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Обработка нажатия на проект в таблице детализации трудоёмкости по плану-графику монитора ресурсов
|
||||||
|
const handlePlanJobsDtlProjectClick = ({ sender }) => {
|
||||||
|
setState(pv => ({ ...pv, showPeriodsList: false }));
|
||||||
|
if (state.selectedProject != sender.NJB_PRJCTS) selectPoject(sender.NJB_PRJCTS, sender.NPROJECT);
|
||||||
|
};
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<Box p={2}>
|
<Box p={2}>
|
||||||
<Fab variant="extended" sx={STYLES.PROJECTS_BUTTON} onClick={() => setState(pv => ({ ...pv, showProjectsList: !pv.showProjectsList }))}>
|
<Fab variant="extended" sx={STYLES.PROJECTS_BUTTON} onClick={() => setState(pv => ({ ...pv, showProjectsList: !pv.showProjectsList }))}>
|
||||||
Проекты
|
Проекты
|
||||||
|
{state.needSave ? (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<Icon sx={{ color: "orange" }}>save</Icon>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</Fab>
|
</Fab>
|
||||||
<Drawer
|
<Drawer
|
||||||
anchor={"left"}
|
anchor={"left"}
|
||||||
@ -318,9 +378,38 @@ const PrjJobs = () => {
|
|||||||
sx={STYLES.PROJECTS_DRAWER}
|
sx={STYLES.PROJECTS_DRAWER}
|
||||||
>
|
>
|
||||||
{state.projectsLoaded ? (
|
{state.projectsLoaded ? (
|
||||||
<ProjectsList projects={state.projects} selectedProject={state.selectedProject} onClick={handleProjectClick} />
|
<>
|
||||||
|
{state.needSave ? (
|
||||||
|
<List>
|
||||||
|
<ListItemButton sx={STYLES.PROJECTS_LIST_SAVE_BUTTON}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon>save</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Сохранить" secondary="Перенсти изменения в проекты" />
|
||||||
|
</ListItemButton>
|
||||||
|
</List>
|
||||||
|
) : null}
|
||||||
|
<ProjectsList projects={state.projects} selectedProject={state.selectedProject} onClick={handleProjectClick} />
|
||||||
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
<Fab variant="extended" sx={STYLES.PERIODS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPeriodsList: !pv.showPeriodsList }))}>
|
||||||
|
Ресурсы
|
||||||
|
{[0, 1].includes(state.resourceStatus) ? (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<Icon sx={{ color: state.resourceStatus === 0 ? "green" : "red" }}>{state.resourceStatus === 0 ? "done" : "error"}</Icon>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Fab>
|
||||||
|
<Drawer
|
||||||
|
anchor={"right"}
|
||||||
|
open={state.showPeriodsList}
|
||||||
|
onClose={() => setState(pv => ({ ...pv, showPeriodsList: false }))}
|
||||||
|
sx={STYLES.PERIODS_DRAWER}
|
||||||
|
>
|
||||||
|
{state.ident ? <ResMon ident={state.ident} onPlanJobsDtlProjectClick={handlePlanJobsDtlProjectClick} /> : null}
|
||||||
|
</Drawer>
|
||||||
{state.init == true ? (
|
{state.init == true ? (
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
203
app/panels/prj_jobs/res_mon.js
Normal file
203
app/panels/prj_jobs/res_mon.js
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - ПУП - Работы проектов
|
||||||
|
Компонент панели: Монитор ресурсов
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useContext, useState, useCallback, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Icon, Stack, Link } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
|
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
|
||||||
|
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||||
|
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
||||||
|
import { LabPlanFOTDtl } from "./lab_plan_fot_dtl"; //Детализация плановой трудоёмкости по ФОТ
|
||||||
|
import { LabPlanJobsDtl } from "./lab_plan_jobs_dtl"; //Детализация плановой трудоёмкости по графику
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Генерация представления ячейки c данными в таблице периодов балансировки
|
||||||
|
const periodsDataCellRender = ({ row, columnDef, onLabPlanFOTClick, onLabPlanJobsClick }) => {
|
||||||
|
switch (columnDef.name) {
|
||||||
|
case "NLAB_PLAN_FOT":
|
||||||
|
return {
|
||||||
|
data: row[columnDef.name] ? (
|
||||||
|
<Link
|
||||||
|
component="button"
|
||||||
|
variant="body2"
|
||||||
|
align="left"
|
||||||
|
underline="hover"
|
||||||
|
onClick={() => (onLabPlanFOTClick ? onLabPlanFOTClick({ sender: row }) : null)}
|
||||||
|
>
|
||||||
|
{row[columnDef.name]}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
row[columnDef.name]
|
||||||
|
)
|
||||||
|
};
|
||||||
|
case "NLAB_DIFF_RPT_FOT":
|
||||||
|
return { data: <div style={{ color: row[columnDef.name] <= 0 ? "green" : "red" }}>{row[columnDef.name]}</div> };
|
||||||
|
case "NLAB_PLAN_JOBS":
|
||||||
|
return {
|
||||||
|
data: row[columnDef.name] ? (
|
||||||
|
<Link
|
||||||
|
component="button"
|
||||||
|
variant="body2"
|
||||||
|
align="left"
|
||||||
|
underline="hover"
|
||||||
|
onClick={() => (onLabPlanJobsClick ? onLabPlanJobsClick({ sender: row }) : null)}
|
||||||
|
>
|
||||||
|
{row[columnDef.name]}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
row[columnDef.name]
|
||||||
|
)
|
||||||
|
};
|
||||||
|
case "NLAB_DIFF_JOBS_FOT":
|
||||||
|
return {
|
||||||
|
data: (
|
||||||
|
<Stack direction="row" gap={0.5} alignItems="center" justifyContent="right">
|
||||||
|
<div style={{ color: row[columnDef.name] <= 0 ? "green" : "red" }}>{row[columnDef.name]}</div>
|
||||||
|
<Icon sx={{ color: row[columnDef.name] <= 0 ? "green" : "red" }}>{row[columnDef.name] <= 0 ? "done" : "error"}</Icon>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Монитор ресурсов
|
||||||
|
const ResMon = ({ ident, onPlanJobsDtlProjectClick }) => {
|
||||||
|
//Собственное состояние
|
||||||
|
const [state, setState] = useState({ displayPlanFOTDtl: null, titlePlanFOTDtl: null, displayPlanJobsDtl: null, titlePlanJobsDtl: null });
|
||||||
|
|
||||||
|
//Состояние таблицы периодов монитора ресурсов
|
||||||
|
const [peridos, setPeriods] = useState({
|
||||||
|
dataLoaded: false,
|
||||||
|
columnsDef: [],
|
||||||
|
orders: [],
|
||||||
|
rows: [],
|
||||||
|
reload: true,
|
||||||
|
pageNumber: 1,
|
||||||
|
morePages: true
|
||||||
|
});
|
||||||
|
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { configSystemPageSize } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//Загрузка данных монитора балансировки периодов с сервера
|
||||||
|
const loadPeriods = useCallback(async () => {
|
||||||
|
if (peridos.reload) {
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PROJECTS.JB_PERIODS_LIST",
|
||||||
|
args: {
|
||||||
|
NIDENT: ident,
|
||||||
|
CORDERS: { VALUE: object2Base64XML(peridos.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||||
|
NPAGE_NUMBER: peridos.pageNumber,
|
||||||
|
NPAGE_SIZE: configSystemPageSize,
|
||||||
|
NINCLUDE_DEF: peridos.dataLoaded ? 0 : 1
|
||||||
|
},
|
||||||
|
attributeValueProcessor: (name, val) => (name == "SPERIOD" ? undefined : val),
|
||||||
|
respArg: "COUT"
|
||||||
|
});
|
||||||
|
setPeriods(pv => ({
|
||||||
|
...pv,
|
||||||
|
columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
|
||||||
|
rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
|
||||||
|
dataLoaded: true,
|
||||||
|
reload: false,
|
||||||
|
morePages: (data.XROWS || []).length >= configSystemPageSize
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [ident, peridos.reload, peridos.orders, peridos.dataLoaded, peridos.pageNumber, executeStored, configSystemPageSize, SERV_DATA_TYPE_CLOB]);
|
||||||
|
|
||||||
|
//При сокрытии детализации
|
||||||
|
const handleHideDtl = () =>
|
||||||
|
setState(pv => ({ ...pv, displayPlanFOTDtl: null, titlePlanFOTDtl: null, displayPlanJobsDtl: null, titlePlanJobsDtl: null }));
|
||||||
|
|
||||||
|
//При нажатии на плановую трудоёмкость по ФОТ
|
||||||
|
const handleLabPlanFOTClick = ({ sender }) =>
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
displayPlanFOTDtl: sender.NRN,
|
||||||
|
titlePlanFOTDtl: `${sender.SPERIOD} - ${sender.SINS_DEPARTMENT} - ${sender.SFCMANPOWER} - ${sender.NLAB_PLAN_FOT}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
//При нажатии на проект в списке детализации плановой трудоёмкости по графику
|
||||||
|
const handleLabPlanJobsClick = ({ sender }) =>
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
displayPlanJobsDtl: sender.NRN,
|
||||||
|
titlePlanJobsDtl: `${sender.SPERIOD} - ${sender.SINS_DEPARTMENT} - ${sender.SFCMANPOWER} - ${sender.NLAB_PLAN_JOBS}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
//При изменении состояния сортировки в таблице периодов балансировки
|
||||||
|
const handlePeriodsOrderChanged = ({ orders }) => setPeriods(pv => ({ ...pv, orders, pageNumber: 1, reload: true }));
|
||||||
|
|
||||||
|
//При изменении количества отображаемых страниц в таблице периодов балансировки
|
||||||
|
const handlePeriodsPagesCountChanged = () => setPeriods(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
|
||||||
|
|
||||||
|
//При нажатии на проект в таблице детализации трудоёмкости по плану-графику
|
||||||
|
const handlePlanJobsDtlProjectClick = ({ sender }) => (onPlanJobsDtlProjectClick ? onPlanJobsDtlProjectClick({ sender }) : null);
|
||||||
|
|
||||||
|
//При необходимости обновить данные
|
||||||
|
useEffect(() => {
|
||||||
|
loadPeriods();
|
||||||
|
}, [peridos.reload, loadPeriods]);
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{peridos.dataLoaded ? (
|
||||||
|
<P8PDataGrid
|
||||||
|
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||||
|
columnsDef={peridos.columnsDef}
|
||||||
|
rows={peridos.rows}
|
||||||
|
size={P8P_DATA_GRID_SIZE.SMALL}
|
||||||
|
morePages={peridos.morePages}
|
||||||
|
reloading={peridos.reload}
|
||||||
|
onOrderChanged={handlePeriodsOrderChanged}
|
||||||
|
onPagesCountChanged={handlePeriodsPagesCountChanged}
|
||||||
|
dataCellRender={prms =>
|
||||||
|
periodsDataCellRender({ ...prms, onLabPlanFOTClick: handleLabPlanFOTClick, onLabPlanJobsClick: handleLabPlanJobsClick })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{state.displayPlanFOTDtl ? (
|
||||||
|
<LabPlanFOTDtl periodId={state.displayPlanFOTDtl} title={state.titlePlanFOTDtl} onHide={handleHideDtl} />
|
||||||
|
) : null}
|
||||||
|
{state.displayPlanJobsDtl ? (
|
||||||
|
<LabPlanJobsDtl
|
||||||
|
periodId={state.displayPlanJobsDtl}
|
||||||
|
title={state.titlePlanJobsDtl}
|
||||||
|
onHide={handleHideDtl}
|
||||||
|
onProjectClick={handlePlanJobsDtlProjectClick}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Монитор ресурсов
|
||||||
|
ResMon.propTypes = {
|
||||||
|
ident: PropTypes.number.isRequired,
|
||||||
|
onPlanJobsDtlProjectClick: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { ResMon };
|
Loading…
x
Reference in New Issue
Block a user