diff --git a/app/panels/prj_jobs/lab_fact_rpt_dtl.js b/app/panels/prj_jobs/lab_fact_rpt_dtl.js new file mode 100644 index 0000000..7716c3c --- /dev/null +++ b/app/panels/prj_jobs/lab_fact_rpt_dtl.js @@ -0,0 +1,125 @@ +/* + Парус 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"; //Таблица данных +import { factRptDtlValueFormatter, factRptDtlHeadCellRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов + +//----------- +//Тело модуля +//----------- + +//Детализация фактической трудоёмкости по "Планам и отчетам подразделений" +const LabFactRptDtl = ({ periodId, title, onHide }) => { + //Состояние таблицы детализации плановой трудоёмкости по графику + const [factRptDtl, setFactRptDtl] = 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 loadFactRptDtl = useCallback(async () => { + if (factRptDtl.reload) { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.JB_PERIODS_LIST_FACT_RPT", + args: { + NJB_PERIODS: periodId, + CORDERS: { VALUE: object2Base64XML(factRptDtl.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + NPAGE_NUMBER: factRptDtl.pageNumber, + NPAGE_SIZE: configSystemPageSize, + NINCLUDE_DEF: factRptDtl.dataLoaded ? 0 : 1 + }, + respArg: "COUT" + }); + setFactRptDtl(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, + factRptDtl.reload, + factRptDtl.orders, + factRptDtl.dataLoaded, + factRptDtl.pageNumber, + executeStored, + configSystemPageSize, + SERV_DATA_TYPE_CLOB + ]); + + //При изменении состояния сортировки в детализации факта по "Планам и отчетам в подразделении" + const handlePlanJobsDtlDGOrderChanged = ({ orders }) => setFactRptDtl(pv => ({ ...pv, orders, pageNumber: 1, reload: true })); + + //При изменении количества отображаемых страниц в факта по "Планам и отчетам в подразделении" + const handlePlanJobsDtlDGPagesCountChanged = () => setFactRptDtl(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); + + //При необходимости обновить данные + useEffect(() => { + loadFactRptDtl(); + }, [factRptDtl.reload, loadFactRptDtl]); + + //Генерация содержимого + return factRptDtl.dataLoaded ? ( + + {title} + + + + + + + + ) : null; +}; + +//Контроль свойств - Детализация фактической трудоёмкости по "Планам и отчетам подразделений" +LabFactRptDtl.propTypes = { + periodId: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + onHide: PropTypes.func.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { LabFactRptDtl }; diff --git a/app/panels/prj_jobs/lab_plan_jobs_dtl.js b/app/panels/prj_jobs/lab_plan_jobs_dtl.js index 5f829c3..1c77aa7 100644 --- a/app/panels/prj_jobs/lab_plan_jobs_dtl.js +++ b/app/panels/prj_jobs/lab_plan_jobs_dtl.js @@ -9,87 +9,19 @@ 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 { Dialog, DialogContent, DialogActions, Button, DialogTitle } from "@mui/material"; //Интерфейсные элементы import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { ApplicationСtx } from "../../context/application"; //Контекст приложения -import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции +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"; //Таблица данных +import { planJobsDtlValueFormatter, planJobsDtlHeadCellRender, planJobsDtlDataCellRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов //------------------------------------ //Вспомогательные функции и компоненты //------------------------------------ -//Формирование значения для колонки "Состояние" этапа -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 ( - - {icon} - - ); -}; - -//Форматирование значений колонок в таблице детализации трудоёмкости по графику -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] ? ( - (onProjectClick ? onProjectClick({ sender: row }) : null)} - > - {row[columnDef.name]} - - ) : ( - row[columnDef.name] - ) - }; - case "NSTATE": - return { - cellProps: { align: "center" }, - data: formatJobStatusValue(row[columnDef.name]) - }; - } -}; - //----------- //Тело модуля //----------- @@ -113,8 +45,8 @@ const LabPlanJobsDtl = ({ periodId, title, onHide, onProjectClick }) => { //Подключение к контексту взаимодействия с сервером const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); - //Загрузка детализации плановой трудоёмкости по ФОТ для ресурса - const loadPlanFOTDtl = useCallback(async () => { + //Загрузка детализации плановой трудоёмкости по графику для ресурса + const loadPlanJobsDtl = useCallback(async () => { if (planJobsDtl.reload) { const data = await executeStored({ stored: "PKG_P8PANELS_PROJECTS.JB_PERIODS_LIST_PLAN_JOBS", @@ -147,19 +79,19 @@ const LabPlanJobsDtl = ({ periodId, title, onHide, onProjectClick }) => { 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]); + loadPlanJobsDtl(); + }, [planJobsDtl.reload, loadPlanJobsDtl]); //Генерация содержимого return planJobsDtl.dataLoaded ? ( diff --git a/app/panels/prj_jobs/layouts.js b/app/panels/prj_jobs/layouts.js new file mode 100644 index 0000000..646839c --- /dev/null +++ b/app/panels/prj_jobs/layouts.js @@ -0,0 +1,187 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Экономика проектов + Дополнительная разметка и вёрстка клиентских элементов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import { Icon, Stack, Link } from "@mui/material"; //Интерфейсные компоненты +import { formatDateRF } from "../../core/utils"; //Вспомогательные функции + +//----------- +//Тело модуля +//----------- + +//Формирование значения для колонки "Состояние" этапа +const formatStageStatusValue = value => { + const [text, icon] = + value == 0 + ? ["Зарегистрирован", "app_registration"] + : value == 1 + ? ["Открыт", "lock_open"] + : value == 2 + ? ["Закрыт", "lock_outline"] + : value == 3 + ? ["Согласован", "thumb_up_alt"] + : value == 4 + ? ["Исполнение прекращено", "block"] + : ["Остановлен", "do_not_disturb_on"]; + return ( + + {icon} + {text} + + ); +}; + +//Формирование значения для колонки "Состояние" работы +const formatJobStatusValue = (value, addText = false, justifyContent = null) => { + const [text, icon] = + value == 0 + ? ["Не начата", "not_started"] + : value == 1 + ? ["Выполняется", "loop"] + : value == 2 + ? ["Выполнена", "task_alt"] + : value == 3 + ? ["Остановлена", "do_not_disturb_on"] + : ["Отменена", "cancel"]; + return ( + + {icon} + {addText == true ? text : null} + + ); +}; + +//Генерация кастомных представлений атрибутов задачи в редакторе +export const taskAttributeRenderer = ({ task, attribute }) => { + switch (attribute.name) { + case "type": + return task.type === 1 ? "Этап проекта" : "Работа проекта"; + case "state": + return task.type === 1 ? formatStageStatusValue(task[attribute.name]) : formatJobStatusValue(task[attribute.name], true); + default: + return null; + } +}; + +//Форматирование значений колонок в таблице детализации трудоёмкости по графику +export const planJobsDtlValueFormatter = ({ value, columnDef }) => { + switch (columnDef.name) { + case "NJOB_STATE": + return formatJobStatusValue(value, false, "center"); + case "DJOB_BEG": + case "DJOB_END": + return formatDateRF(value); + } + return value; +}; + +//Генерация представления ячейки заголовка в таблице детализации трудоёмкости по графику +export const planJobsDtlHeadCellRender = ({ columnDef }) => { + switch (columnDef.name) { + case "NJOB_STATE": + return { + stackProps: { justifyContent: "center" }, + cellProps: { align: "center" } + }; + } +}; + +//Генерация представления ячейки c данными в таблице детализации трудоёмкости по графику +export const planJobsDtlDataCellRender = ({ row, columnDef, onProjectClick }) => { + switch (columnDef.name) { + case "SPRJ": + return { + data: row[columnDef.name] ? ( + (onProjectClick ? onProjectClick({ sender: row }) : null)} + > + {row[columnDef.name]} + + ) : ( + row[columnDef.name] + ) + }; + } +}; + +//Форматирование значений колонок в таблице детализации трудоёмкости по отчетам +export const factRptDtlValueFormatter = ({ value, columnDef }) => { + switch (columnDef.name) { + case "NJOB_STATE": + return formatJobStatusValue(value, false, "center"); + case "DJOB_BEG": + case "DJOB_END": + return formatDateRF(value); + } + return value; +}; + +//Генерация представления ячейки заголовка в таблице детализации трудоёмкости по отчетам +export const factRptDtlHeadCellRender = ({ columnDef }) => { + switch (columnDef.name) { + case "NJOB_STATE": + return { + stackProps: { justifyContent: "center" }, + cellProps: { align: "center" } + }; + } +}; + +//Генерация представления ячейки c данными в таблице периодов балансировки +export const periodsDataCellRender = ({ row, columnDef, onLabPlanFOTClick, onLabFactRptClick, onLabPlanJobsClick }) => { + switch (columnDef.name) { + case "NLAB_PLAN_FOT": + case "NLAB_FACT_RPT": + case "NLAB_PLAN_JOBS": + return { + data: row[columnDef.name] ? ( + + columnDef.name === "NLAB_PLAN_FOT" + ? onLabPlanFOTClick + ? onLabPlanFOTClick({ sender: row }) + : null + : columnDef.name === "NLAB_FACT_RPT" + ? onLabFactRptClick + ? onLabFactRptClick({ sender: row }) + : null + : columnDef.name === "NLAB_PLAN_JOBS" + ? onLabPlanJobsClick + ? onLabPlanJobsClick({ sender: row }) + : null + : null + } + > + {row[columnDef.name]} + + ) : ( + row[columnDef.name] + ) + }; + case "NLAB_DIFF_RPT_FOT": + return { data:
{row[columnDef.name]}
}; + case "NLAB_DIFF_JOBS_FOT": + return { + data: ( + +
{row[columnDef.name]}
+ {row[columnDef.name] <= 0 ? "done" : "error"} +
+ ) + }; + } +}; diff --git a/app/panels/prj_jobs/prj_jobs.js b/app/panels/prj_jobs/prj_jobs.js index f9c4937..696d436 100644 --- a/app/panels/prj_jobs/prj_jobs.js +++ b/app/panels/prj_jobs/prj_jobs.js @@ -9,7 +9,7 @@ import React, { useContext, useState, useCallback, useEffect } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента -import { Drawer, Fab, Box, Grid, List, ListItemButton, ListItemText, ListItemIcon, Icon, Typography, Stack } from "@mui/material"; //Интерфейсные элементы +import { Drawer, Fab, Box, Grid, List, ListItemButton, ListItemText, ListItemIcon, Icon, Typography } from "@mui/material"; //Интерфейсные элементы import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений import { ApplicationСtx } from "../../context/application"; //Контекст приложения @@ -17,6 +17,7 @@ import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогат import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8PGantt } from "../../components/p8p_gantt"; //Диаграмма Ганта import { ResMon } from "./res_mon"; //Монитор ресурсов +import { taskAttributeRenderer } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов //--------- //Константы @@ -42,55 +43,13 @@ const STYLES = { GANTT_CONTAINER: { height: GANTT_HEIGHT, width: GANTT_WIDTH }, 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" } } + PERIODS_DRAWER: { width: "1200px", flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "1200px", boxSizing: "border-box" } } }; //------------------------------------ //Вспомогательные функции и компоненты //------------------------------------ -//Формирование значения для колонки "Состояние" этапа -const formatStageStatusValue = value => { - const [text, icon] = - value == 0 - ? ["Зарегистрирован", "app_registration"] - : value == 1 - ? ["Открыт", "lock_open"] - : value == 2 - ? ["Закрыт", "lock_outline"] - : value == 3 - ? ["Согласован", "thumb_up_alt"] - : value == 4 - ? ["Исполнение прекращено", "block"] - : ["Остановлен", "do_not_disturb_on"]; - return ( - - {icon} - {text} - - ); -}; - -//Формирование значения для колонки "Состояние" работы -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 ( - - {icon} - {text} - - ); -}; - //Список проектов const ProjectsList = ({ projects = [], selectedProject, onClick } = {}) => { //Подключение к контексту сообщений @@ -333,18 +292,6 @@ const PrjJobs = () => { if (isMain) modifyJob(task.rn, new Date(start), new Date(end), new Date(state.dateBegin), new Date(state.dateFact), state.durationMeas); }; - //Генерация кастомных представлений атрибутов задачи в редакторе - const taskAttributeRenderer = ({ task, attribute }) => { - switch (attribute.name) { - case "type": - return task.type === 1 ? "Этап проекта" : "Работа проекта"; - case "state": - return task.type === 1 ? formatStageStatusValue(task[attribute.name]) : formatJobStatusValue(task[attribute.name]); - default: - return null; - } - }; - //Обработка нажатия на сохранение данных в проект const handleSaveToProjectsClick = () => saveProjects(); diff --git a/app/panels/prj_jobs/res_mon.js b/app/panels/prj_jobs/res_mon.js index cf9d095..8d61d15 100644 --- a/app/panels/prj_jobs/res_mon.js +++ b/app/panels/prj_jobs/res_mon.js @@ -9,67 +9,15 @@ 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 { LabFactRptDtl } from "./lab_fact_rpt_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] ? ( - (onLabPlanFOTClick ? onLabPlanFOTClick({ sender: row }) : null)} - > - {row[columnDef.name]} - - ) : ( - row[columnDef.name] - ) - }; - case "NLAB_DIFF_RPT_FOT": - return { data:
{row[columnDef.name]}
}; - case "NLAB_PLAN_JOBS": - return { - data: row[columnDef.name] ? ( - (onLabPlanJobsClick ? onLabPlanJobsClick({ sender: row }) : null)} - > - {row[columnDef.name]} - - ) : ( - row[columnDef.name] - ) - }; - case "NLAB_DIFF_JOBS_FOT": - return { - data: ( - -
{row[columnDef.name]}
- {row[columnDef.name] <= 0 ? "done" : "error"} -
- ) - }; - } -}; +import { periodsDataCellRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов //----------- //Тело модуля @@ -78,7 +26,14 @@ const periodsDataCellRender = ({ row, columnDef, onLabPlanFOTClick, onLabPlanJob //Монитор ресурсов const ResMon = ({ ident, onPlanJobsDtlProjectClick }) => { //Собственное состояние - const [state, setState] = useState({ displayPlanFOTDtl: null, titlePlanFOTDtl: null, displayPlanJobsDtl: null, titlePlanJobsDtl: null }); + const [state, setState] = useState({ + displayPlanFOTDtl: null, + titlePlanFOTDtl: null, + displayFactRptDtl: null, + titleFactRptDtl: null, + displayPlanJobsDtl: null, + titlePlanJobsDtl: null + }); //Состояние таблицы периодов монитора ресурсов const [peridos, setPeriods] = useState({ @@ -125,7 +80,15 @@ const ResMon = ({ ident, onPlanJobsDtlProjectClick }) => { //При сокрытии детализации const handleHideDtl = () => - setState(pv => ({ ...pv, displayPlanFOTDtl: null, titlePlanFOTDtl: null, displayPlanJobsDtl: null, titlePlanJobsDtl: null })); + setState(pv => ({ + ...pv, + displayPlanFOTDtl: null, + titlePlanFOTDtl: null, + displayFactRptDtl: null, + titleFactRptDtl: null, + displayPlanJobsDtl: null, + titlePlanJobsDtl: null + })); //При нажатии на плановую трудоёмкость по ФОТ const handleLabPlanFOTClick = ({ sender }) => @@ -135,6 +98,14 @@ const ResMon = ({ ident, onPlanJobsDtlProjectClick }) => { titlePlanFOTDtl: `${sender.SPERIOD} - ${sender.SINS_DEPARTMENT} - ${sender.SFCMANPOWER} - ${sender.NLAB_PLAN_FOT}` })); + //При нажатии на фактическую трудоёмкость по отчетам + const handleLabFactRptClick = ({ sender }) => + setState(pv => ({ + ...pv, + displayFactRptDtl: sender.NRN, + titleFactRptDtl: `${sender.SPERIOD} - ${sender.SINS_DEPARTMENT} - ${sender.SFCMANPOWER} - ${sender.NLAB_FACT_RPT}` + })); + //При нажатии на проект в списке детализации плановой трудоёмкости по графику const handleLabPlanJobsClick = ({ sender }) => setState(pv => ({ @@ -171,13 +142,21 @@ const ResMon = ({ ident, onPlanJobsDtlProjectClick }) => { onOrderChanged={handlePeriodsOrderChanged} onPagesCountChanged={handlePeriodsPagesCountChanged} dataCellRender={prms => - periodsDataCellRender({ ...prms, onLabPlanFOTClick: handleLabPlanFOTClick, onLabPlanJobsClick: handleLabPlanJobsClick }) + periodsDataCellRender({ + ...prms, + onLabPlanFOTClick: handleLabPlanFOTClick, + onLabFactRptClick: handleLabFactRptClick, + onLabPlanJobsClick: handleLabPlanJobsClick + }) } /> ) : null} {state.displayPlanFOTDtl ? ( ) : null} + {state.displayFactRptDtl ? ( + + ) : null} {state.displayPlanJobsDtl ? (