WEBAPP: Новая панель - "Информация о проектах"
This commit is contained in:
parent
72aa5bc89c
commit
3eba0a52f0
170
app/panels/prj_info/filter.js
Normal file
170
app/panels/prj_info/filter.js
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Фильтр
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Grid, Chip, Stack, Input, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||
import { FILTER_INITIAL, FILTER_ITEMS, PRICE_STRUCT_STATUS, PROJECT_STATE, FilterDialog } from "./filter_dialog"; //Компонент "Диалог фильтра"
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { paddingTop: "10px" },
|
||||
FILTER: { maxWidth: "99vw" },
|
||||
SEARCH_GRID_ITEM: { paddingRight: "15px" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Элемент фильтра
|
||||
const FilterItem = ({ caption, value, defaultValue, onClick, onDelete }) => {
|
||||
//При нажатии на элемент
|
||||
const handleClick = () => (onClick ? onClick() : null);
|
||||
|
||||
//При нажатии на удаление элемента
|
||||
const handleDelete = () => (onDelete ? onDelete() : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Chip
|
||||
label={
|
||||
<Stack direction={"row"} alignItems={"center"}>
|
||||
<strong>{caption}</strong>: {value || defaultValue}
|
||||
</Stack>
|
||||
}
|
||||
variant="outlined"
|
||||
onClick={handleClick}
|
||||
onDelete={onDelete ? handleDelete : null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Элемент фильтра
|
||||
FilterItem.propTypes = {
|
||||
caption: PropTypes.string.isRequired,
|
||||
value: PropTypes.any,
|
||||
defaultValue: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Фильтр
|
||||
const Filter = ({ values, onChange }) => {
|
||||
//Собственное состояние - отображение диалога ввода значений фильтра
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
//Собственное состояние - строка поиска
|
||||
const [search, setSearch] = useState(values.search);
|
||||
|
||||
//Передача сообщения об измении фильтра родителю
|
||||
const notifyChange = values => (onChange ? onChange(values) : null);
|
||||
|
||||
//При закрытии диалога с сохранением значений
|
||||
const handleFilterDialogOk = values => {
|
||||
setIsOpen(false);
|
||||
notifyChange(values);
|
||||
};
|
||||
|
||||
//При закрытии диалога без сохранения значений
|
||||
const handleFilterDialogCancel = () => setIsOpen(false);
|
||||
|
||||
//При нажатии на фильтр
|
||||
const handleClick = () => setIsOpen(true);
|
||||
|
||||
//При выполнении поиска
|
||||
const handleDoSearch = (clear = false) => {
|
||||
if (clear === true) setSearch("");
|
||||
notifyChange({ ...values, search: clear === true ? "" : search });
|
||||
};
|
||||
|
||||
//При изменении значения в строке поиска
|
||||
const handleSearchChange = e => setSearch(e.target.value);
|
||||
|
||||
//При нажатии клавиши в строке поиска
|
||||
const handleSearchKeyPress = e => ([13, 27].includes(e.keyCode) ? handleDoSearch(e.keyCode == 27) : null);
|
||||
|
||||
//Формирование функции обработки очистки элемента фильтар
|
||||
const buildFilterItemClearHandler = сode =>
|
||||
values[сode] != FILTER_INITIAL[сode] ? () => notifyChange({ ...values, [сode]: FILTER_INITIAL[сode] }) : null;
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Grid container sx={STYLES.CONTAINER}>
|
||||
<Grid xs={10} item>
|
||||
{isOpen ? <FilterDialog valuesInitial={values} onOk={handleFilterDialogOk} onCancel={handleFilterDialogCancel} /> : null}
|
||||
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.FILTER} onClick={handleClick}>
|
||||
<FilterItem
|
||||
caption={"Тип заказа"}
|
||||
value={values.prjType}
|
||||
defaultValue={"Любой"}
|
||||
onDelete={buildFilterItemClearHandler("prjType")}
|
||||
/>
|
||||
<FilterItem
|
||||
caption={"Подразделение-ответственный"}
|
||||
defaultValue={"Любое"}
|
||||
value={values.insDep}
|
||||
onDelete={buildFilterItemClearHandler("insDep")}
|
||||
/>
|
||||
<FilterItem
|
||||
caption={"Статус структуры цены"}
|
||||
defaultValue={"Неподдерживаемое значение"}
|
||||
value={PRICE_STRUCT_STATUS.find(item => item.value == values.priceStructStatus)?.name}
|
||||
onDelete={buildFilterItemClearHandler("priceStructStatus")}
|
||||
/>
|
||||
<FilterItem
|
||||
caption={"Состояние проекта"}
|
||||
defaultValue={"Неподдерживаемое значение"}
|
||||
value={PROJECT_STATE.find(item => item.value == values.prjState)?.name}
|
||||
onDelete={buildFilterItemClearHandler("prjState")}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid xs={2} item sx={STYLES.SEARCH_GRID_ITEM}>
|
||||
<Input
|
||||
fullWidth
|
||||
placeholder="Поиск..."
|
||||
value={search}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleDoSearch(true)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleDoSearch}>
|
||||
<Icon>search</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
onKeyDown={handleSearchKeyPress}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Фильтр
|
||||
Filter.propTypes = {
|
||||
values: FILTER_ITEMS.isRequired,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { FILTER_INITIAL, Filter };
|
147
app/panels/prj_info/filter_dialog.js
Normal file
147
app/panels/prj_info/filter_dialog.js
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Диалог фильтра
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Button, Dialog, DialogTitle, DialogContent, DialogActions } from "@mui/material"; //Интерфейсные элементы
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { BUTTONS } from "../../../app.text"; //Типовые тексты
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { FormField } from "./layouts"; //Общие компоненты панели
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIALOG_CONTENT: { overflowY: "auto", ...APP_STYLES.SCROLL }
|
||||
};
|
||||
|
||||
//Структура фильтра
|
||||
const FILTER_ITEMS = PropTypes.shape({
|
||||
prjType: PropTypes.string,
|
||||
insDep: PropTypes.string,
|
||||
priceStructStatus: PropTypes.number.isRequired,
|
||||
prjState: PropTypes.number.isRequired,
|
||||
search: PropTypes.string
|
||||
});
|
||||
|
||||
//Начальное состояние фильтра
|
||||
const FILTER_INITIAL = { prjType: "", insDep: "", priceStructStatus: 0, prjState: 0, search: "" };
|
||||
|
||||
//Статусы структуры цены
|
||||
const PRICE_STRUCT_STATUS = [
|
||||
{ value: 0, name: "Все" },
|
||||
{ value: 1, name: "Есть статьи с расходом больше 90%" },
|
||||
{ value: 2, name: "Есть статьи с перерасходом" }
|
||||
];
|
||||
|
||||
//Состояния проекта
|
||||
const PROJECT_STATE = [
|
||||
{ value: 0, name: "Все" },
|
||||
{ value: 1, name: "Открытые" },
|
||||
{ value: 2, name: "Неоткрытые" }
|
||||
];
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог фильтра
|
||||
const FilterDialog = ({ valuesInitial, onOk, onCancel }) => {
|
||||
//Собственное состояние элементов фильтра
|
||||
const [values, setValues] = useState({ ...valuesInitial });
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Изменение элемента формы фильтра
|
||||
const handleValueChanged = (name, value) => setValues(pv => ({ ...pv, [name]: value }));
|
||||
|
||||
//Сброс настроек фильтра
|
||||
const handleResetClick = () => setValues({ ...FILTER_INITIAL });
|
||||
|
||||
//Сохранение фильтра
|
||||
const handleOkClick = () => (onOk ? onOk(values) : null);
|
||||
|
||||
//Отмена фильтра
|
||||
const handleCancelClick = () => (onCancel ? onCancel() : null);
|
||||
|
||||
//Выбор значения элемента формы из словаря
|
||||
const selectFromDictionary = (unitCode, name, applyValue) => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode,
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_CODE", value: values[name] }],
|
||||
callBack: res => applyValue(res.success ? [{ name, value: res.outParameters.out_CODE }] : null)
|
||||
});
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Dialog open onClose={handleCancelClick} fullWidth maxWidth={"md"}>
|
||||
<DialogTitle>Фильтр отбора</DialogTitle>
|
||||
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
||||
<FormField
|
||||
elementCode={"prjType"}
|
||||
elementValue={values.prjType}
|
||||
labelText={"Тип заказа"}
|
||||
onChange={handleValueChanged}
|
||||
dictionary={applyValue => selectFromDictionary("ProjectTypes", "prjType", applyValue)}
|
||||
/>
|
||||
<FormField
|
||||
elementCode={"insDep"}
|
||||
elementValue={values.insDep}
|
||||
labelText={"Подразделение-ответственный"}
|
||||
onChange={handleValueChanged}
|
||||
dictionary={applyValue => selectFromDictionary("INS_DEPARTMENT", "insDep", applyValue)}
|
||||
/>
|
||||
<FormField
|
||||
elementCode={"priceStructStatus"}
|
||||
elementValue={values.priceStructStatus}
|
||||
labelText={"Статус структуры цены"}
|
||||
onChange={handleValueChanged}
|
||||
list={PRICE_STRUCT_STATUS}
|
||||
/>
|
||||
<FormField
|
||||
elementCode={"prjState"}
|
||||
elementValue={values.prjState}
|
||||
labelText={"Состояние проекта"}
|
||||
onChange={handleValueChanged}
|
||||
list={PROJECT_STATE}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
|
||||
<Button variant="text" onClick={handleOkClick}>
|
||||
{BUTTONS.OK}
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleResetClick}>
|
||||
{BUTTONS.CLEAR}
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleCancelClick}>
|
||||
{BUTTONS.CANCEL}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалог фильтра
|
||||
FilterDialog.propTypes = {
|
||||
valuesInitial: FILTER_ITEMS.isRequired,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { FILTER_ITEMS, FILTER_INITIAL, PRICE_STRUCT_STATUS, PROJECT_STATE, FilterDialog };
|
16
app/panels/prj_info/index.js
Normal file
16
app/panels/prj_info/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Панель мониторинга: точка входа
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { PrjInfo } from "./prj_info"; //Корневая панель информации о проектах
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export const RootClass = PrjInfo;
|
168
app/panels/prj_info/layouts.js
Normal file
168
app/panels/prj_info/layouts.js
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Общие дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Icon, Input, InputAdornment, FormControl, Select, InputLabel, MenuItem, IconButton, Typography, Switch, Stack } from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
STATE: value => ({ color: value === 1 ? "green" : "black" }),
|
||||
COST_STATUS: color => ({ color, verticalAlign: "middle" }),
|
||||
COST_READY: value => ({ color: value <= 30 ? "red" : value >= 80 ? "green" : "#e2af00" }),
|
||||
TOGGLE_COLOR: checked => ({ color: checked ? "#006dd9" : "lightgrey" })
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Поле ввода формы
|
||||
const FormField = ({ elementCode, elementValue, labelText, onChange, dictionary, list, type, ...other }) => {
|
||||
//Значение элемента
|
||||
const [value, setValue] = useState(elementValue);
|
||||
|
||||
//При получении нового значения из вне
|
||||
useEffect(() => {
|
||||
setValue(elementValue);
|
||||
}, [elementValue]);
|
||||
|
||||
//Выбор значения из словаря
|
||||
const handleDictionaryClick = () =>
|
||||
dictionary ? dictionary(res => (res ? res.map(i => handleChange({ target: { name: i.name, value: i.value } })) : null)) : null;
|
||||
|
||||
//Изменение значения элемента (по событию)
|
||||
const handleChange = e => {
|
||||
setValue(e.target.value);
|
||||
if (onChange) onChange(e.target.name, e.target.value);
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box p={1}>
|
||||
<FormControl variant="standard" fullWidth {...other}>
|
||||
{list ? (
|
||||
<>
|
||||
<InputLabel id={`${elementCode}Lable`} shrink>
|
||||
{labelText}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`${elementCode}Lable`}
|
||||
id={elementCode}
|
||||
name={elementCode}
|
||||
label={labelText}
|
||||
value={value || value == 0 ? value : ""}
|
||||
onChange={handleChange}
|
||||
displayEmpty
|
||||
>
|
||||
{list.map((item, i) => (
|
||||
<MenuItem key={i} value={item.value || item.value == 0 ? item.value : ""}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<InputLabel {...(type == "date" ? { shrink: true } : {})} htmlFor={elementCode}>
|
||||
{labelText}
|
||||
</InputLabel>
|
||||
<Input
|
||||
id={elementCode}
|
||||
name={elementCode}
|
||||
value={value || value == 0 ? value : ""}
|
||||
endAdornment={
|
||||
dictionary ? (
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}
|
||||
{...(type ? { type } : {})}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Поле ввода формы
|
||||
FormField.propTypes = {
|
||||
elementCode: PropTypes.string.isRequired,
|
||||
elementValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.instanceOf(Date)]),
|
||||
labelText: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
dictionary: PropTypes.func,
|
||||
list: PropTypes.array,
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
//Переключатель
|
||||
const Toggle = ({ labels, checked, onChange }) => {
|
||||
//Обработка переключения
|
||||
const handleChange = event => (onChange ? onChange(event.target.checked) : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Stack direction={"row"} spacing={1} alignItems={"center"} justifyContent={"center"}>
|
||||
<Typography sx={STYLES.TOGGLE_COLOR(!checked)}>{labels[0]}</Typography>
|
||||
<Switch checked={checked} size="small" onChange={handleChange} />
|
||||
<Typography sx={STYLES.TOGGLE_COLOR(checked)}>{labels[1]}</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Переключатель
|
||||
Toggle.propTypes = {
|
||||
labels: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
checked: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//Формирование значения для колонки "Статус структуры цены"
|
||||
const formatCostStatusValue = ({ value, onClick, type = 1 }) => {
|
||||
const [text, color] =
|
||||
value == 0
|
||||
? ["Без отклонений", "lightgreen"]
|
||||
: value == 1
|
||||
? [type == 1 ? "Есть статьи с расходом более 90%" : "Расход более 90%", "#ffdf71"]
|
||||
: value == 2
|
||||
? [type == 1 ? "Есть статьи с перерасходом" : "Перерасход", "#eb6b6b"]
|
||||
: ["Не определено", "lightgray"];
|
||||
return onClick ? (
|
||||
<IconButton onClick={onClick}>
|
||||
<Icon sx={STYLES.COST_STATUS(color)} title={`${text}\nНажмите для детальной информации`}>
|
||||
circle
|
||||
</Icon>
|
||||
</IconButton>
|
||||
) : (
|
||||
<Icon sx={STYLES.COST_STATUS(color)} title={text}>
|
||||
circle
|
||||
</Icon>
|
||||
);
|
||||
};
|
||||
|
||||
//Формирование значения для колонки "Готов (%, зтраты)"
|
||||
const formatCostReadyValue = value => {
|
||||
return <span style={STYLES.COST_READY(value)}>{value}</span>;
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { STYLES as COMMON_STYLES, FormField, Toggle, formatCostStatusValue, formatCostReadyValue };
|
27
app/panels/prj_info/prj_info.js
Normal file
27
app/panels/prj_info/prj_info.js
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Корневой компонент панели
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import { Projects } from "./projects"; //Список проектов
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Корневой компонент панели "Информация о проектах"
|
||||
const PrjInfo = () => {
|
||||
//Генерация содержимого
|
||||
return <Projects />;
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PrjInfo };
|
70
app/panels/prj_info/projects.js
Normal file
70
app/panels/prj_info/projects.js
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Список проектов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import { P8PDataGrid } from "../../components/p8p_data_grid"; //Таблица данных
|
||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { useProjectsDataGrid } from "./projects_hooks"; //Хуки списка проектов
|
||||
import { FILTER_INITIAL, Filter } from "./filter"; //Компонент "Фильтр"
|
||||
import { PROJECTS_STYLES, projectDataCellRender, projectRowExpandRender } from "./projects_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Список проектов
|
||||
const Projects = () => {
|
||||
//Собственное состояние
|
||||
const [projects, setProjects] = useState({ pageNumber: 1, orders: [], filter: { ...FILTER_INITIAL } });
|
||||
|
||||
//Состояние таблицы проектов
|
||||
const [projectsDataGrid] = useProjectsDataGrid({ ...projects.filter, pageNumber: projects.pageNumber, orders: projects.orders });
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDocument } = useContext(ApplicationСtx);
|
||||
|
||||
//Отображение записи проекта в штатном разделе
|
||||
const showProject = async rn => pOnlineShowDocument({ unitCode: "Projects", document: rn, modal: false });
|
||||
|
||||
//При изменении количества отображаемых страниц
|
||||
const handlePagesCountChanged = () => setProjects(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
|
||||
|
||||
//При изменении состояния сортировки
|
||||
const handleOrderChanged = ({ orders }) => setProjects(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));
|
||||
|
||||
//При изменении фильтра
|
||||
const handleFilterChanged = values => setProjects(pv => ({ ...pv, filter: { ...values }, pageNumber: 1 }));
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<>
|
||||
<Filter values={projects.filter} onChange={handleFilterChanged} />
|
||||
{projectsDataGrid.init ? (
|
||||
<P8PDataGrid
|
||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||
{...projectsDataGrid}
|
||||
containerComponentProps={{ sx: PROJECTS_STYLES.DATA_GRID_CONTAINER(projectsDataGrid.morePages), elevation: 0 }}
|
||||
expandable={true}
|
||||
fixedHeader={true}
|
||||
onPagesCountChanged={handlePagesCountChanged}
|
||||
onOrderChanged={handleOrderChanged}
|
||||
dataCellRender={prms => projectDataCellRender({ ...prms, showProject })}
|
||||
rowExpandRender={projectRowExpandRender}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { Projects };
|
82
app/panels/prj_info/projects_hooks.js
Normal file
82
app/panels/prj_info/projects_hooks.js
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Список проектов: пользовательские хуки для взаимодействия с сервером
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
||||
import config from "../../../app.config"; //Настройки приложения
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Размер страницы данных
|
||||
const DATA_GRID_PAGE_SIZE = config.SYSTEM.PAGE_SIZE;
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Получение данных проектов с сервера
|
||||
const useProjectsDataGrid = ({ prjType, insDep, priceStructStatus, prjState, search, pageNumber, orders }) => {
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - таблица данных
|
||||
const [data, setData] = useState({ init: false, morePages: true });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости обновить данные таблицы
|
||||
useEffect(() => {
|
||||
//Загрузка данных таблицы с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PROJECTS.INFO_PROJECTS_DG",
|
||||
args: {
|
||||
SPRJ_TYPE: prjType,
|
||||
SINS_DEPARTMENT: insDep,
|
||||
NCOST_STATUS: priceStructStatus,
|
||||
NSTATE: prjState,
|
||||
SSEARCH: search,
|
||||
CORDERS: { VALUE: object2Base64XML(orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||
NPAGE_NUMBER: pageNumber,
|
||||
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
|
||||
NINCLUDE_DEF: pageNumber == 1 ? 1 : 0
|
||||
},
|
||||
respArg: "COUT",
|
||||
loader: true,
|
||||
attributeValueProcessor: (name, val) => (["DBEGPLAN", "DENDPLAN"].includes(name) ? formatDateRF(val) : val)
|
||||
});
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [],
|
||||
rows: pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])],
|
||||
morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE,
|
||||
init: true
|
||||
}));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
loadData();
|
||||
}, [prjType, insDep, priceStructStatus, prjState, search, pageNumber, orders, executeStored, SERV_DATA_TYPE_CLOB]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, isLoading];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useProjectsDataGrid };
|
96
app/panels/prj_info/projects_layouts.js
Normal file
96
app/panels/prj_info/projects_layouts.js
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Список проектов: дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import { Icon, Stack, Paper, Link } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8P_DATA_GRID_MORE_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
|
||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { COMMON_STYLES, formatCostStatusValue, formatCostReadyValue } from "./layouts"; //Общие стили и разметка панели
|
||||
import { Stages } from "./stages"; //Компонент "Этапы проекта"
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Высота фильтра (пиксели)
|
||||
const FILTER_HEIGHT = "60px";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DATA_GRID_CONTAINER: morePages => ({
|
||||
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${FILTER_HEIGHT} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - 8px)`,
|
||||
...APP_STYLES.SCROLL
|
||||
})
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Формирование значения для колонки "Состояние" проекта
|
||||
const formatPrjStateValue = value => {
|
||||
const [text, icon] =
|
||||
value == 0
|
||||
? ["Зарегистрирован", "app_registration"]
|
||||
: value == 1
|
||||
? ["Открыт", "lock_open"]
|
||||
: value == 2
|
||||
? ["Остановлен", "do_not_disturb_on"]
|
||||
: value == 3
|
||||
? ["Закрыт", "lock_outline"]
|
||||
: value == 4
|
||||
? ["Согласован", "thumb_up_alt"]
|
||||
: ["Исполнение прекращено", "block"];
|
||||
return (
|
||||
<Stack direction="row" gap={0.5} alignItems="center" justifyContent="center">
|
||||
<Icon title={text} sx={COMMON_STYLES.STATE(value)}>
|
||||
{icon}
|
||||
</Icon>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
//Форматирование ячеек таблицы "Проекты"
|
||||
const projectDataCellRender = ({ row, columnDef, showProject }) => {
|
||||
//Формирование представлений
|
||||
switch (columnDef.name) {
|
||||
case "NCOST_STATUS":
|
||||
return { cellProps: { align: "center" }, data: formatCostStatusValue({ value: row[columnDef.name] }) };
|
||||
case "NCOST_READY":
|
||||
return { cellProps: { align: "center" }, data: formatCostReadyValue(row[columnDef.name]) };
|
||||
case "NSTATE":
|
||||
return { cellProps: { align: "center" }, data: formatPrjStateValue(row[columnDef.name]) };
|
||||
case "SCODE":
|
||||
return {
|
||||
data: (
|
||||
<Link component="button" align="left" underline="hover" onClick={() => showProject(row["NRN"])}>
|
||||
{row[columnDef.name]}
|
||||
</Link>
|
||||
)
|
||||
};
|
||||
default:
|
||||
return { data: row[columnDef.name] };
|
||||
}
|
||||
};
|
||||
|
||||
//Генерация представления расширения строки таблицы "Проектов"
|
||||
const projectRowExpandRender = ({ row }) => {
|
||||
return (
|
||||
<Paper elevation={6}>
|
||||
<Stages projectRn={row.NRN} projectCode={row.SCODE} />
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { STYLES as PROJECTS_STYLES, projectDataCellRender, projectRowExpandRender };
|
173
app/panels/prj_info/stage_detail.js
Normal file
173
app/panels/prj_info/stage_detail.js
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Детальная информация об этапе проекта
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Grid, Box, Typography, Paper, Drawer, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
|
||||
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
||||
import { P8PChart } from "../../components/p8p_chart"; //График
|
||||
import { P8PAppInlineError } from "../../components/p8p_app_message"; //Встраиваемое сообщение об ошибке
|
||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
import { useStageDetailInfoDataGrid, useStageDetailArtsDataGrid, useStageDetailArtsChart } from "./stage_detail_hooks"; //Хуки детализации этапов проекта
|
||||
import { Toggle } from "./layouts"; //Общая разметка и компоненты панели
|
||||
import {
|
||||
STAGE_DETAIL_STYLES,
|
||||
stageDetailInfoHeadCellRender,
|
||||
stageDetailInfoDataCellRender,
|
||||
stageDetailArtsHeadCellRender,
|
||||
stageDetailArtsDataCellRender
|
||||
} from "./stage_detail_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Данные этапа
|
||||
const StageDetailData = ({ stageRn }) => {
|
||||
//Собственное состояние
|
||||
const [state, setState] = useState({ artsDisplayType: 0, artsChartType: 0 });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowUnit } = useContext(ApplicationСtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgErr } = useContext(MessagingСtx);
|
||||
|
||||
//Отображение журнала затрат (фактического, по рег. номеру ЛС и статьи затрат)
|
||||
const showCostNotesFact = async ({ faceAccRn, artclRn }) => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PROJECTS.INFO_FCCOSTNOTES_FACT_SELECT",
|
||||
args: { NFACEACC: faceAccRn, NFPDARTCL: artclRn }
|
||||
});
|
||||
if (data.NIDENT) pOnlineShowUnit({ unitCode: "CostNotes", inputParameters: [{ name: "in_IDENT", value: data.NIDENT }] });
|
||||
else showMsgErr(TEXTS.NO_DATA_FOUND);
|
||||
};
|
||||
|
||||
//Состояние таблицы с информацией об этапе
|
||||
const [stageDeatilInfoDataGrid] = useStageDetailInfoDataGrid({ stageRn });
|
||||
|
||||
//Состояние таблицы с данными структуры цены
|
||||
const [stageDeatilArtsDataGrid] = useStageDetailArtsDataGrid({ stageRn });
|
||||
|
||||
//Состояние графика с данными структуры цены
|
||||
const [stageDeatilArtsChart] = useStageDetailArtsChart({ stageRn, display: state.artsDisplayType == 1, type: state.artsChartType });
|
||||
|
||||
//При изменении способа отображения структуры цены
|
||||
const handleArtsDisplayTypeChange = checked => setState(pv => ({ ...pv, artsDisplayType: checked ? 1 : 0 }));
|
||||
|
||||
//При изменении типа данных графика структуры цены
|
||||
const handleArtsChartTypeChange = checked => setState(pv => ({ ...pv, artsChartType: checked ? 1 : 0 }));
|
||||
|
||||
//Отработка нажатия на график
|
||||
const handleChartClick = ({ item }) =>
|
||||
state.artsChartType === 1 && item.NFACEACC && item.NFPDARTCL
|
||||
? showCostNotesFact({ faceAccRn: item.NFACEACC, artclRn: item.NFPDARTCL })
|
||||
: null;
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Grid container spacing={2} sx={STAGE_DETAIL_STYLES.DATA_AREA_CONTAINER}>
|
||||
<Grid item xs={5}>
|
||||
<Typography variant={"h6"} sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER}>
|
||||
Сведения
|
||||
</Typography>
|
||||
{stageDeatilInfoDataGrid.init ? (
|
||||
<P8PDataGrid
|
||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||
containerComponentProps={{ sx: STAGE_DETAIL_STYLES.DATA_AREA, elevation: 6 }}
|
||||
{...stageDeatilInfoDataGrid}
|
||||
size={P8P_DATA_GRID_SIZE.SMALL}
|
||||
fixedHeader={true}
|
||||
headCellRender={stageDetailInfoHeadCellRender}
|
||||
dataCellRender={stageDetailInfoDataCellRender}
|
||||
/>
|
||||
) : null}
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Box sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER_CONTAINER}>
|
||||
<Typography variant={"h6"} sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER}>
|
||||
Структура цены
|
||||
</Typography>
|
||||
<Toggle labels={["Таблица", "График"]} checked={state.artsDisplayType === 1} onChange={handleArtsDisplayTypeChange} />
|
||||
</Box>
|
||||
{state.artsDisplayType === 0 ? (
|
||||
stageDeatilArtsDataGrid.init ? (
|
||||
<P8PDataGrid
|
||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||
containerComponentProps={{ sx: STAGE_DETAIL_STYLES.DATA_AREA, elevation: 6 }}
|
||||
{...stageDeatilArtsDataGrid}
|
||||
size={P8P_DATA_GRID_SIZE.SMALL}
|
||||
fixedHeader={true}
|
||||
headCellRender={stageDetailArtsHeadCellRender}
|
||||
dataCellRender={prms => stageDetailArtsDataCellRender({ ...prms, showCostNotesFact })}
|
||||
/>
|
||||
) : null
|
||||
) : (
|
||||
<Paper elevation={6} sx={STAGE_DETAIL_STYLES.DATA_AREA}>
|
||||
<Box sx={STAGE_DETAIL_STYLES.CHART_CONTAINER}>
|
||||
<Toggle labels={["План", "Факт"]} checked={state.artsChartType === 1} onChange={handleArtsChartTypeChange} />
|
||||
{stageDeatilArtsDataGrid?.rows?.length > 0 ? (
|
||||
stageDeatilArtsChart.init ? (
|
||||
<P8PChart style={STAGE_DETAIL_STYLES.CHART} {...stageDeatilArtsChart} onClick={handleChartClick} />
|
||||
) : null
|
||||
) : (
|
||||
<P8PAppInlineError text={TEXTS.NO_DATA_FOUND} />
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Данные этапа
|
||||
StageDetailData.propTypes = {
|
||||
stageRn: PropTypes.number
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Детальная информация об этапе проекта
|
||||
const StageDetail = ({ stageRn, stageName, isOpen, onClose }) => {
|
||||
return (
|
||||
<Drawer anchor={"right"} open={isOpen} onClose={onClose} sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_DRAWER}>
|
||||
<Box sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_HEADER}>
|
||||
<IconButton sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_CLOSE_BUTTON} size={"small"} onClick={onClose}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<Typography variant={"h6"} color={"white"} pl={2}>{`Этап: ${stageName}`}</Typography>
|
||||
</Box>
|
||||
<StageDetailData stageRn={stageRn} />
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Детальная информация об этапе проекта
|
||||
StageDetail.propTypes = {
|
||||
stageRn: PropTypes.number,
|
||||
stageName: PropTypes.string,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { StageDetail };
|
126
app/panels/prj_info/stage_detail_hooks.js
Normal file
126
app/panels/prj_info/stage_detail_hooks.js
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Детальная информация об этапе проекта: пользовательские хуки для взаимодействия с сервером
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Детали этапа проекта - информация об этапе
|
||||
const useStageDetailInfoDataGrid = ({ stageRn }) => {
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - таблица данных
|
||||
const [data, setData] = useState({ init: false });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости обновить данные таблицы
|
||||
useEffect(() => {
|
||||
//Загрузка данных таблицы с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_DTL_DG",
|
||||
args: { NPROJECTSTAGE: stageRn },
|
||||
respArg: "COUT",
|
||||
loader: true
|
||||
});
|
||||
setData(pv => ({ ...pv, ...data.XDATA_GRID, init: true }));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (stageRn) loadData();
|
||||
}, [stageRn, executeStored]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, isLoading];
|
||||
};
|
||||
|
||||
//Детали этапа проекта - структура цены - таблица данных
|
||||
const useStageDetailArtsDataGrid = ({ stageRn }) => {
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - таблица данных
|
||||
const [data, setData] = useState({ init: false });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости обновить данные таблицы
|
||||
useEffect(() => {
|
||||
//Загрузка данных таблицы с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const artsData = await executeStored({
|
||||
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_ARTS_DG",
|
||||
args: { NPROJECTSTAGE: stageRn },
|
||||
respArg: "COUT",
|
||||
loader: true
|
||||
});
|
||||
setData(pv => ({ ...pv, ...artsData.XDATA_GRID, init: true }));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (stageRn) loadData();
|
||||
}, [stageRn, executeStored, SERV_DATA_TYPE_CLOB]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, isLoading];
|
||||
};
|
||||
|
||||
//Детали этапа проекта - структура цены - график
|
||||
const useStageDetailArtsChart = ({ stageRn, display, type }) => {
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - график
|
||||
const [data, setData] = useState({ init: false, currentType: null });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости обновить данные таблицы
|
||||
useEffect(() => {
|
||||
//Загрузка данных таблицы с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_ARTS_CHART",
|
||||
args: { NPROJECTSTAGE: stageRn, NTYPE: type },
|
||||
respArg: "COUT",
|
||||
loader: true
|
||||
});
|
||||
setData(pv => ({ ...pv, ...data.XCHART, currentType: type, init: true }));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (stageRn && display && data.currentType != type) loadData();
|
||||
}, [stageRn, display, type, data.currentType, executeStored]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, isLoading];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useStageDetailInfoDataGrid, useStageDetailArtsDataGrid, useStageDetailArtsChart };
|
148
app/panels/prj_info/stage_detail_layouts.js
Normal file
148
app/panels/prj_info/stage_detail_layouts.js
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Детальная информация об этапе проекта: дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import { Link } from "@mui/material"; //Интерфейсные элементы
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
||||
import { formatNumberRFCurrency } from "../../core/utils"; //Вспомогательные функции
|
||||
import { formatCostStatusValue } from "./layouts"; //Общие стили и разметка панели
|
||||
import { formatStageStatusValue } from "./stages_layouts"; //Cтили и разметка списка этапов проекта
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Высота заголовка информационного блока
|
||||
const DATA_AREA_HEADER_HEIGHT = "52px";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
STAGE_DETAIL_DRAWER: { flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "70%", boxSizing: "border-box", ...APP_STYLES.SCROLL } },
|
||||
STAGE_DETAIL_HEADER: {
|
||||
height: APP_BAR_HEIGHT,
|
||||
paddingLeft: "24px",
|
||||
backgroundColor: "#1976d2",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start"
|
||||
},
|
||||
STAGE_DETAIL_CLOSE_BUTTON: { color: "white", marginBottom: "3px" },
|
||||
DATA_AREA_CONTAINER: { paddingLeft: "10px", paddingRight: "10px" },
|
||||
DATA_AREA: { height: `calc(100vh - ${APP_BAR_HEIGHT} - ${DATA_AREA_HEADER_HEIGHT} - 10px)`, overflowY: "auto", ...APP_STYLES.SCROLL },
|
||||
DATA_AREA_HEADER_CONTAINER: { display: "flex", justifyContent: "space-between" },
|
||||
DATA_AREA_HEADER: { paddingTop: "10px", paddingBottom: "10px" },
|
||||
DATA_GRID_HEADER: { fontSize: "10pt", padding: "6px 10px" },
|
||||
DATA_GRID_CELL: value => ({ fontSize: "9pt", padding: "6px 10px", ...(value ? { color: value > 0 ? "green" : "red" } : {}) }),
|
||||
CHART_CONTAINER: { paddingTop: "20px" },
|
||||
CHART: { maxHeight: "60vh", display: "flex", justifyContent: "center" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Форматирование заголовков колонок таблицы "Сведения"
|
||||
const stageDetailInfoHeadCellRender = ({ columnDef }) => {
|
||||
//Инициализируем общий стиль ячеек
|
||||
let cellStyle = STYLES.DATA_GRID_HEADER;
|
||||
//Формирование представлений
|
||||
switch (columnDef.name) {
|
||||
case "SATTR":
|
||||
return { cellStyle, stackProps: { justifyContent: "left" } };
|
||||
case "SVALUE":
|
||||
return { cellStyle, stackProps: { justifyContent: "right" } };
|
||||
default:
|
||||
return { cellStyle: cellStyle };
|
||||
}
|
||||
};
|
||||
|
||||
//Форматирование ячеек строк таблицы "Сведения"
|
||||
const stageDetailInfoDataCellRender = ({ row, columnDef }) => {
|
||||
//Инициализируем общий стиль ячеек
|
||||
let cellStyle = STYLES.DATA_GRID_CELL();
|
||||
//Формирование представлений
|
||||
switch (columnDef.name) {
|
||||
case "SATTR":
|
||||
return { cellStyle: { ...cellStyle, color: "#1976d2" }, cellProps: { align: "left" } };
|
||||
case "SVALUE": {
|
||||
const res = { cellStyle, cellProps: { align: "right" } };
|
||||
if (["NCOST_SUM", "NSTAGE_COST_SUM"].includes(row["SCODE"]))
|
||||
res.data = row["SVALUE"] || row["SVALUE"] === 0 ? formatNumberRFCurrency(row["SVALUE"]) : "-";
|
||||
if (row["SCODE"] == "NSTATE")
|
||||
res.data = formatStageStatusValue({ value: parseInt(row["SVALUE"]), addText: true, justifyContent: "right" });
|
||||
return res;
|
||||
}
|
||||
default:
|
||||
return { cellStyle };
|
||||
}
|
||||
};
|
||||
|
||||
//Форматирование заголовков колонок таблицы "Структура затрат"
|
||||
const stageDetailArtsHeadCellRender = ({ columnDef }) => {
|
||||
//Инициализируем общий стиль ячеек
|
||||
let cellStyle = STYLES.DATA_GRID_HEADER;
|
||||
//Формирование представлений
|
||||
switch (columnDef.name) {
|
||||
case "NSTATE":
|
||||
return { cellStyle: { ...cellStyle, justifyContent: "center" }, stackStyle: { justifyContent: "center" } };
|
||||
default:
|
||||
return { cellStyle: cellStyle };
|
||||
}
|
||||
};
|
||||
|
||||
//Форматирование ячеек строк таблицы "Структура затрат"
|
||||
const stageDetailArtsDataCellRender = ({ row, columnDef, showCostNotesFact }) => {
|
||||
//Инициализируем общий стиль ячеек
|
||||
let cellStyle = STYLES.DATA_GRID_CELL;
|
||||
//Формирование представлений
|
||||
switch (columnDef.name) {
|
||||
case "NCOST_STATUS":
|
||||
return {
|
||||
cellProps: { align: "center" },
|
||||
data: formatCostStatusValue({ value: row[columnDef.name], type: 0 })
|
||||
};
|
||||
case "NPLAN_SUM":
|
||||
case "NPLAN_FACT_SUM":
|
||||
return {
|
||||
cellStyle: cellStyle(columnDef.name == "NPLAN_FACT_SUM" ? row[columnDef.name] : null),
|
||||
data: row[columnDef.name] || row[columnDef.name] === 0 ? formatNumberRFCurrency(row[columnDef.name]) : "-"
|
||||
};
|
||||
case "NFACT_SUM":
|
||||
return {
|
||||
cellStyle: cellStyle(),
|
||||
data:
|
||||
row[columnDef.name] || row[columnDef.name] === 0 ? (
|
||||
row[columnDef.name] > 0 ? (
|
||||
<Link component="button" onClick={() => showCostNotesFact({ faceAccRn: row["NFACEACC"], artclRn: row["NFPDARTCL"] })}>
|
||||
{formatNumberRFCurrency(row[columnDef.name])}
|
||||
</Link>
|
||||
) : (
|
||||
formatNumberRFCurrency(row[columnDef.name])
|
||||
)
|
||||
) : (
|
||||
"-"
|
||||
)
|
||||
};
|
||||
default:
|
||||
return { cellStyle: cellStyle(), data: row[columnDef.name] };
|
||||
}
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export {
|
||||
STYLES as STAGE_DETAIL_STYLES,
|
||||
stageDetailInfoHeadCellRender,
|
||||
stageDetailInfoDataCellRender,
|
||||
stageDetailArtsHeadCellRender,
|
||||
stageDetailArtsDataCellRender
|
||||
};
|
95
app/panels/prj_info/stages.js
Normal file
95
app/panels/prj_info/stages.js
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Список этапов проекта
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Typography } from "@mui/material"; //Интерфейсные элементы
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { P8PDataGrid } from "../../components/p8p_data_grid"; //Таблица данных
|
||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
import { useStagesDataGrid } from "./stages_hooks"; //Хуки списка этапов проекта
|
||||
import { STAGES_STYLES, projectStageDataCellRender } from "./stages_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
import { StageDetail } from "./stage_detail"; //Компонент "Информация об этапе проекта"
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Список этапов проекта
|
||||
const Stages = ({ projectRn, projectCode }) => {
|
||||
//Собственное состояние
|
||||
const [stages, setStages] = useState({ pageNumber: 1, orders: [] });
|
||||
|
||||
//Состояние таблицы этапов
|
||||
const [stagesDataGrid] = useStagesDataGrid({ ...stages, projectRn });
|
||||
|
||||
//Состояние информации о этапе
|
||||
const [stageInfo, setStageInfo] = useState({ showInfo: false, stage: null, sFaceAcc: null });
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowUnit } = useContext(ApplicationСtx);
|
||||
|
||||
//Отображение записи этапа проекта в штатном разделе
|
||||
const showProjectStage = (prn, rn) => {
|
||||
pOnlineShowUnit({
|
||||
unitCode: "Projects",
|
||||
inputParameters: [
|
||||
{ name: "in_RN", value: prn },
|
||||
{ name: "in_STAGE_RN", value: rn }
|
||||
],
|
||||
modal: false
|
||||
});
|
||||
};
|
||||
|
||||
//Отображение деталей этапа
|
||||
const showStageDetails = stage => setStageInfo(pv => ({ ...pv, showInfo: true, stage: stage["NRN"], sFaceAcc: stage["SFACEACC"] }));
|
||||
|
||||
//При изменении количества отображаемых страниц
|
||||
const handlePagesCountChanged = () => setStages(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
|
||||
|
||||
//При изменении состояния сортировки
|
||||
const handleOrderChanged = ({ orders }) => setStages(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));
|
||||
|
||||
//Генерация содержимого
|
||||
return stagesDataGrid.init ? (
|
||||
<>
|
||||
<div style={STAGES_STYLES.CONTAINER}>
|
||||
<Typography variant={"subtitle2"} sx={STAGES_STYLES.TITLE}>
|
||||
{`Этапы проекта "${projectCode}"`}
|
||||
</Typography>
|
||||
<P8PDataGrid
|
||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||
{...stagesDataGrid}
|
||||
containerComponentProps={{ sx: STAGES_STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
|
||||
onPagesCountChanged={handlePagesCountChanged}
|
||||
onOrderChanged={handleOrderChanged}
|
||||
dataCellRender={prms => projectStageDataCellRender({ ...prms, showProjectStage, showStageDetails })}
|
||||
/>
|
||||
</div>
|
||||
<StageDetail
|
||||
stageRn={stageInfo.stage}
|
||||
stageName={stageInfo.sFaceAcc}
|
||||
isOpen={stageInfo.showInfo}
|
||||
onClose={() => setStageInfo(pv => ({ ...pv, showInfo: false, stage: null, sFaceAcc: null }))}
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Список этапов проекта
|
||||
Stages.propTypes = {
|
||||
projectRn: PropTypes.number.isRequired,
|
||||
projectCode: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { Stages };
|
78
app/panels/prj_info/stages_hooks.js
Normal file
78
app/panels/prj_info/stages_hooks.js
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Список этапов проекта: пользовательские хуки для взаимодействия с сервером
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
||||
import config from "../../../app.config"; //Настройки приложения
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Размер страницы данных
|
||||
const DATA_GRID_PAGE_SIZE = config.SYSTEM.PAGE_SIZE;
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Этапы проекта
|
||||
const useStagesDataGrid = ({ projectRn, pageNumber, orders }) => {
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - таблица данных
|
||||
const [data, setData] = useState({ init: false, morePages: true });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости обновить данные таблицы
|
||||
useEffect(() => {
|
||||
//Загрузка данных таблицы с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGES_DG",
|
||||
args: {
|
||||
NPROJECT: projectRn,
|
||||
CORDERS: { VALUE: object2Base64XML(orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||
NPAGE_NUMBER: pageNumber,
|
||||
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
|
||||
NINCLUDE_DEF: pageNumber == 1 ? 1 : 0
|
||||
},
|
||||
respArg: "COUT",
|
||||
loader: true,
|
||||
attributeValueProcessor: (name, val) => (["DBEGPLAN", "DENDPLAN"].includes(name) ? formatDateRF(val) : val)
|
||||
});
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [],
|
||||
rows: pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])],
|
||||
morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE,
|
||||
init: true
|
||||
}));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (projectRn) loadData();
|
||||
}, [projectRn, orders, pageNumber, executeStored, SERV_DATA_TYPE_CLOB]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, isLoading];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useStagesDataGrid };
|
86
app/panels/prj_info/stages_layouts.js
Normal file
86
app/panels/prj_info/stages_layouts.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
||||
Список этапов проекта: дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import { Icon, Stack, Link } from "@mui/material"; //Интерфейсные элементы
|
||||
import { formatNumberRFCurrency } from "../../core/utils"; //Спомогательные функции
|
||||
import { COMMON_STYLES, formatCostStatusValue, formatCostReadyValue } from "./layouts"; //Общие стили и разметка панели
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { textAlign: "center", paddingTop: "10px", backgroundColor: "lightcyan" },
|
||||
TITLE: { fontSize: "13pt", paddingBottom: "10px" },
|
||||
DATA_GRID_CONTAINER: { backgroundColor: "lightcyan" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Формирование значения для колонки "Состояние" этапа
|
||||
const formatStageStatusValue = ({ value, addText = false, justifyContent = "center" }) => {
|
||||
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 (
|
||||
<Stack direction="row" gap={0.5} alignItems={"center"} justifyContent={justifyContent || "center"}>
|
||||
<Icon title={text} sx={COMMON_STYLES.STATE(value)}>
|
||||
{icon}
|
||||
</Icon>
|
||||
{addText == true ? text : null}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
//Форматирование ячеек таблицы "Этапы проекта"
|
||||
const projectStageDataCellRender = ({ row, columnDef, showProjectStage, showStageDetails }) => {
|
||||
//Формирование представлений
|
||||
switch (columnDef.name) {
|
||||
case "NCOST_STATUS":
|
||||
return {
|
||||
cellProps: { align: "center" },
|
||||
data: formatCostStatusValue({ value: row[columnDef.name], onClick: () => showStageDetails(row) })
|
||||
};
|
||||
case "NCOST_READY":
|
||||
return { cellProps: { align: "center" }, data: formatCostReadyValue(row[columnDef.name]) };
|
||||
case "NSTATE":
|
||||
return { cellProps: { align: "center" }, data: formatStageStatusValue({ value: row[columnDef.name] }) };
|
||||
case "SFACEACC":
|
||||
return {
|
||||
data: (
|
||||
<Link component="button" align="left" underline="hover" onClick={() => showProjectStage(row["NPRN"], row["NRN"])}>
|
||||
{row[columnDef.name]}
|
||||
</Link>
|
||||
)
|
||||
};
|
||||
case "NCOST_SUM":
|
||||
return { data: formatNumberRFCurrency(row[columnDef.name]) };
|
||||
default:
|
||||
return { data: row[columnDef.name] };
|
||||
}
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { STYLES as STAGES_STYLES, projectStageDataCellRender, formatStageStatusValue };
|
File diff suppressed because it is too large
Load Diff
BIN
img/prj_info.jpg
Normal file
BIN
img/prj_info.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 134 KiB |
@ -8,6 +8,7 @@
|
||||
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelFin" caption="Экономика проектов" panelName="PrjFin"/>
|
||||
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelJob" caption="Работы проектов" panelName="PrjJobs"/>
|
||||
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelGraph" caption="Графики проектов" panelName="PrjGraph"/>
|
||||
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelInfo" caption="Информация о проектах" panelName="PrjInfo"/>
|
||||
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelHelp" caption="Инструкции ПУП" panelName="PrjHelp"/>
|
||||
</App>
|
||||
<App name="EquipSrv">
|
||||
@ -59,6 +60,16 @@
|
||||
icon="insights"
|
||||
showInPanelsList="true"
|
||||
preview="./img/prj_graph.jpg"/>
|
||||
<Panel
|
||||
name="PrjInfo"
|
||||
group="Планирование и учёт в проектах"
|
||||
caption="Информация о проектах"
|
||||
desc="Информация о проектах"
|
||||
url="prj_info"
|
||||
path="prj_info"
|
||||
icon="featured_play_list"
|
||||
showInPanelsList="true"
|
||||
preview="./img/prj_info.jpg"/>
|
||||
<Panel
|
||||
name="PrjHelp"
|
||||
group="Планирование и учёт в проектах"
|
||||
|
Loading…
x
Reference in New Issue
Block a user