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 { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции
|
||||
import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
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 = {
|
||||
PROJECTS_LIST_SAVE_BUTTON: { backgroundColor: "orange" },
|
||||
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" },
|
||||
@ -38,7 +40,9 @@ const STYLES = {
|
||||
PROJECTS_BUTTON: { position: "absolute" },
|
||||
PROJECTS_DRAWER: { width: "250px", flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "250px", boxSizing: "border-box" } },
|
||||
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 = () => {
|
||||
//Собственное состояние
|
||||
let [state, setState] = useState({
|
||||
needSave: false,
|
||||
showProjectsList: false,
|
||||
showPeriodsList: false,
|
||||
init: false,
|
||||
dateBegin: null,
|
||||
dateFact: null,
|
||||
durationMeas: null,
|
||||
labMeas: null,
|
||||
resourceStatus: null,
|
||||
ident: null,
|
||||
projects: [],
|
||||
projectsLoaded: false,
|
||||
@ -175,18 +186,21 @@ const PrjJobs = () => {
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Загрузка списка проектов
|
||||
const loadProjects = useCallback(async () => {
|
||||
if (!state.projectsLoaded) {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PROJECTS.JB_PRJCTS_LIST",
|
||||
args: {
|
||||
NIDENT: state.ident
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
setState(pv => ({ ...pv, projectsLoaded: true, projects: [...(data?.XPROJECTS || [])] }));
|
||||
}
|
||||
}, [executeStored, state.ident, state.projectsLoaded]);
|
||||
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"
|
||||
});
|
||||
setState(pv => ({ ...pv, projectsLoaded: true, projects: [...(data?.XPROJECTS || [])] }));
|
||||
}
|
||||
},
|
||||
[executeStored, state.ident, state.projectsLoaded]
|
||||
);
|
||||
|
||||
//Загрузка списка работ проекта
|
||||
const loadProjectJobs = useCallback(
|
||||
@ -212,25 +226,55 @@ const PrjJobs = () => {
|
||||
[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 () => {
|
||||
if (!state.init) {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PROJECTS.JB_INIT",
|
||||
args: {
|
||||
DBEGIN: null,
|
||||
DFACT: null,
|
||||
NDURATION_MEAS: 0,
|
||||
SLAB_MEAS: null,
|
||||
NINCLUDE_DEF: null,
|
||||
DBEGIN: state.dateBegin ? new Date(state.dateBegin) : null,
|
||||
DFACT: state.dateFact ? new Date(state.dateFact) : null,
|
||||
NDURATION_MEAS: state.durationMeas,
|
||||
SLAB_MEAS: state.labMeas,
|
||||
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(() => {
|
||||
if (state.ident) loadProjects();
|
||||
}, [state.ident, loadProjects]);
|
||||
@ -246,28 +290,35 @@ const PrjJobs = () => {
|
||||
// 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 => {
|
||||
if (state.selectedProject != project.NRN) {
|
||||
setState(pv => ({
|
||||
...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
|
||||
}));
|
||||
if (state.selectedProject != project.NRN) selectPoject(project.NRN, project.NPROJECT);
|
||||
else unselectProject();
|
||||
};
|
||||
|
||||
//Отработка нажатия на заголовок плана-графика
|
||||
@ -280,10 +331,7 @@ const PrjJobs = () => {
|
||||
console.log(task);
|
||||
console.log(start);
|
||||
console.log(end);
|
||||
if (isMain) {
|
||||
console.log("ЭТО - ГЛАВНОЕ. ПОЙДЁМ НА СЕРВЕР...");
|
||||
loadProjectJobs(true);
|
||||
}
|
||||
if (isMain) modifyJob(task.rn, start, end);
|
||||
};
|
||||
|
||||
//Обработка изменения прогресса задачи в диаграмме Гантта
|
||||
@ -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 (
|
||||
<Box p={2}>
|
||||
<Fab variant="extended" sx={STYLES.PROJECTS_BUTTON} onClick={() => setState(pv => ({ ...pv, showProjectsList: !pv.showProjectsList }))}>
|
||||
Проекты
|
||||
{state.needSave ? (
|
||||
<>
|
||||
|
||||
<Icon sx={{ color: "orange" }}>save</Icon>
|
||||
</>
|
||||
) : null}
|
||||
</Fab>
|
||||
<Drawer
|
||||
anchor={"left"}
|
||||
@ -318,9 +378,38 @@ const PrjJobs = () => {
|
||||
sx={STYLES.PROJECTS_DRAWER}
|
||||
>
|
||||
{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}
|
||||
</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 ? (
|
||||
<Grid container spacing={1}>
|
||||
<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