WEB APP: Панель "Работы проектов" - мониторинг ресурсов, сохранение изменений работ (начало)

This commit is contained in:
Mikhail Chechnev 2023-10-25 18:57:43 +03:00
parent 3cf0b7f41e
commit 6c0d021a65
4 changed files with 663 additions and 47 deletions

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

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

View File

@ -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 ? (
<>
&nbsp;&nbsp;
<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) ? (
<>
&nbsp;&nbsp;
<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}>

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