P8-Panels/app/panels/prj_fin/projects.js

291 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Парус 8 - Панели мониторинга - ПУП - Экономика проектов
Панель мониторинга: Список проктов
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React
import { Box, Grid, Paper, Fab, Icon } from "@mui/material"; //Интерфейсные компоненты
import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_MORE_HEIGHT, P8P_DATA_GRID_FILTERS_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог
import { P8PChart } from "../../components/p8p_chart"; //График
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { COMMON_PROJECTS_STYLES, PANEL_UNITS, headCellRender, dataCellRender, valueFormatter, rowExpandRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
import { Stages } from "./stages"; //Список этапов проекта
//---------
//Константы
//---------
//Высота графиков
const CHART_HEIGHT = "300px";
//Стили
const STYLES = {
TABLE_PROJECTS: (showCharts, morePages, filters) => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${showCharts ? CHART_HEIGHT : "0px"} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - ${
filters ? P8P_DATA_GRID_FILTERS_HEIGHT : "0px"
} - 25px)`,
...APP_STYLES.SCROLL
}),
CHART: { maxHeight: CHART_HEIGHT, display: "flex", justifyContent: "center" },
CHART_PAPER: { height: "100%" },
CHART_FAB: { position: "absolute", top: 80, left: 16 }
};
//-----------
//Тело модуля
//-----------
//Список проектов
const Projects = () => {
//Собственное состояние
const [projectsDataGrid, setProjectsDataGrid] = useState({
dataLoaded: false,
columnsDef: [],
filters: null,
orders: null,
rows: [],
reload: true,
pageNumber: 1,
morePages: true,
selectedProject: null,
stagesFilters: []
});
//Состояния графиков
const [showCharts, setShowCharts] = useState(true);
const [problemsChart, setProblemsChart] = useState({ loaded: false, labels: [], datasets: [] });
const [customersChart, setCustomersChart] = useState({ loaded: false, labels: [], datasets: [] });
const [costNotesChart, setCostNotesChart] = useState({ loaded: false, labels: [], datasets: [] });
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//Подключение к контексту приложения
const { pOnlineShowDocument, pOnlineShowUnit, configSystemPageSize } = useContext(ApplicationСtx);
//Подключение к контексту сообщений
const { showMsgErr } = useContext(MessagingСtx);
//Загрузка данных проектов с сервера
const loadProjects = useCallback(async () => {
if (projectsDataGrid.reload) {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.LIST",
args: {
CFILTERS: { VALUE: object2Base64XML(projectsDataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
CORDERS: { VALUE: object2Base64XML(projectsDataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: projectsDataGrid.pageNumber,
NPAGE_SIZE: configSystemPageSize,
NINCLUDE_DEF: projectsDataGrid.dataLoaded ? 0 : 1
},
attributeValueProcessor: (name, val) => (name == "SGOVCNTRID" ? undefined : val),
respArg: "COUT"
});
setProjectsDataGrid(pv => ({
...pv,
...data.XDATA_GRID,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true,
reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize
}));
}
}, [
projectsDataGrid.reload,
projectsDataGrid.filters,
projectsDataGrid.orders,
projectsDataGrid.dataLoaded,
projectsDataGrid.pageNumber,
executeStored,
configSystemPageSize,
SERV_DATA_TYPE_CLOB
]);
//Получение данных графиков
const loadChartData = async () => {
const problemsChart = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.CHART_PROBLEMS",
respArg: "COUT"
});
setProblemsChart(pv => ({
...pv,
loaded: true,
...problemsChart.XCHART
}));
const customersChart = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.CHART_CUSTOMERS",
respArg: "COUT"
});
setCustomersChart(pv => ({
...pv,
loaded: true,
...customersChart.XCHART
}));
const costNotesChart = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.CHART_FCCOSTNOTES",
respArg: "COUT"
});
setCostNotesChart(pv => ({
...pv,
loaded: true,
...costNotesChart.XCHART
}));
};
//Отображение журнала платежей по этапу проекта
const showPayNotes = async ({ sender, direction }) => {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.SELECT_FIN",
args: { NRN: sender.NRN, NDIRECTION: direction }
});
if (data.NIDENT) pOnlineShowUnit({ unitCode: "PayNotes", inputParameters: [{ name: "in_SelectList_Ident", value: data.NIDENT }] });
else showMsgErr(TEXTS.NO_DATA_FOUND);
};
//Отображение детализации точки графика затрат
const showCostNotesChartDetail = async ({ year, month }) => {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.CHART_FCCOSTNOTES_SELECT_COST",
args: { NYEAR: year, NMONTH: month }
});
if (data.NIDENT) pOnlineShowUnit({ unitCode: "CostNotes", inputParameters: [{ name: "in_IDENT", value: data.NIDENT }] });
else showMsgErr(TEXTS.NO_DATA_FOUND);
};
//Отображение этапов проекта
const showStages = ({ sender, filters = [] } = {}) =>
setProjectsDataGrid(pv => ({ ...pv, selectedProject: { ...sender }, stagesFilters: [...filters] }));
//При изменении состояния фильтра
const handleFilterChanged = ({ filters }) => setProjectsDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1, reload: true }));
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setProjectsDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setProjectsDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
//При закрытии списка этапов проекта
const handleStagesClose = () => setProjectsDataGrid(pv => ({ ...pv, selectedProject: null, stagesFilters: [] }));
//Отработка нажатия на график
const handleChartClick = ({ item }) => {
if (item.SFILTER && item.SFILTER_VALUE)
setProjectsDataGrid(pv => ({
...pv,
filters: [{ name: item.SFILTER, from: item.SFILTER_VALUE }],
pageNumber: 1,
reload: true
}));
if (item.SUNITCODE == "CostNotes" && item.NYEAR && item.NMONTH) showCostNotesChartDetail({ year: item.NYEAR, month: item.NMONTH });
};
//При необходимости обновить данные
useEffect(() => {
loadProjects();
}, [projectsDataGrid.reload, loadProjects]);
//При подключении к странице
useEffect(() => {
loadChartData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Генерация содержимого
return (
<Box p={1}>
<Grid container spacing={1}>
{showCharts ? (
<>
<Grid item xs={4}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{problemsChart.loaded ? <P8PChart {...problemsChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
</Paper>
</Grid>
<Grid item xs={4}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{customersChart.loaded ? <P8PChart {...customersChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
</Paper>
</Grid>
<Grid item xs={4}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{costNotesChart.loaded ? <P8PChart {...costNotesChart} onClick={handleChartClick} style={STYLES.CHART} /> : null}
</Paper>
</Grid>
</>
) : null}
<Grid item xs={12}>
{projectsDataGrid.dataLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{
sx: STYLES.TABLE_PROJECTS(showCharts, projectsDataGrid.morePages, (projectsDataGrid.filters || []).length > 0)
}}
columnsDef={projectsDataGrid.columnsDef}
rows={projectsDataGrid.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
filtersInitial={projectsDataGrid.filters}
morePages={projectsDataGrid.morePages}
reloading={projectsDataGrid.reload}
fixedHeader={true}
expandable={true}
headCellRender={headCellRender}
dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECTS, showStages })}
rowExpandRender={prms =>
rowExpandRender({
...prms,
panelUnit: PANEL_UNITS.PROJECTS,
pOnlineShowDocument,
showPayNotes,
showStages
})
}
valueFormatter={prms => valueFormatter({ ...prms, panelUnit: PANEL_UNITS.PROJECTS })}
onOrderChanged={handleOrderChanged}
onFilterChanged={handleFilterChanged}
onPagesCountChanged={handlePagesCountChanged}
/>
) : null}
{projectsDataGrid.selectedProject ? (
<P8PFullScreenDialog
title={`Этапы проекта "${projectsDataGrid.selectedProject.SNAME_USL}"`}
onClose={handleStagesClose}
contentProps={{ sx: COMMON_PROJECTS_STYLES.FULL_SCREEN_DIALOG_CONTENT }}
>
<Stages
project={projectsDataGrid.selectedProject.NRN}
projectName={projectsDataGrid.selectedProject.SNAME_USL}
filters={projectsDataGrid.stagesFilters}
/>
</P8PFullScreenDialog>
) : null}
</Grid>
</Grid>
{problemsChart.loaded || customersChart.loaded || costNotesChart.loaded ? (
<Fab size="small" color="secondary" sx={STYLES.CHART_FAB} onClick={() => setShowCharts(!showCharts)}>
<Icon>{showCharts ? "expand_less" : "expand_more"}</Icon>
</Fab>
) : null}
</Box>
);
};
//----------------
//Интерфейс модуля
//----------------
export { Projects };