Merge pull request 'main' (#3) from CITKParus/P8-Panels:main into main

Reviewed-on: Dollerok/P8-Panels#3
This commit is contained in:
Dollerok 2024-10-21 15:25:36 +03:00
commit 86b1dcd717
396 changed files with 4100 additions and 712 deletions

View File

@ -65,6 +65,17 @@
1. Установите сервер приложений "ПАРУС 8 Онлайн" согласно документации (см. "Парус-Онлайн 2. Часть 1. Установка ГГГГ.ММ.docx"), требуется релиз от октября 2023 года и позднее.
2. Разместите на диске сервера приложений библиотеку расширения "P8-Panels-ParusOnlineExt", для этого скопируйте содержимое папки "bin" из [репозитория расширения "P8-Panels-ParusOnlineExt"](https://git.citpb.ru/CITKParus/P8-Panels-ParusOnlineExt), например, в каталог "C:\p8web20\Ext\P8-Panels-ParusOnlineExt".
> **Внимание:**
>
> - **Для релиза "ПАРУС 8 Онлайн" от 30.08.2024**
>
> Требуется [патч до промежуточной сборки 02.09.2024 или старше](https://cloud.mail.ru/public/nEZb/y4oQa1N6D). Установка расширения на данный релиз не рекомендуется, по возможности - пропустите его.
>
> - **Для релизов "ПАРУС 8 Онлайн" до 30.08.2024**
>
> Содержимое папки "bin" следует брать из специальной сборки расширения - [Для сборок Парус-Онлайн до 30.08.2024](https://git.citpb.ru/CITKParus/P8-Panels-ParusOnlineExt/releases/tag/FOR_PARUS_ONLINE_BEFORE_20240830)
3. Подключите библиотеку расширения к серверу приложений "ПАРУС 8 Онлайн". Для этого добавьте ссылку на библиотеку в файл "Config\extensions.config" сервера приложений:
```
@ -314,7 +325,71 @@ const MyPanel = () => {
| SPANEL | TSTRING | Значение | MechRecCostJobsManage |
| SCAPTION | TSTRING | Значение | Выдача сменного задания |
7. Выдайте права но новое действие в "Администраторе", при необходимости - начните новый сеанс в "ПАРУС 8 Онлайн" с очисткой системного кэша.
7. Откройте редактор формы представления данных класса "CostJobs" ("Сменные задания").
Для этого отметьте в списке классов запись с кодом "CostJobs", перейдите на закладку "Методы вызова", укажите метод вызова "main", в его контекстном меню укажите "Формы", в появившемся списке форм выполните действие "Редактор" для формы с наименованием "Форма просмотра".
В открывшемся редакторе формы перейдите в режим редактирования всплывающего меню заголовка (закладка "Таблицы", таблица "CostJobs", затем кнопка "Редактор источника", установить фокус на форме представления данных щелчком мыши, затем пункт "Всплывающее меню" в "Инспекторе объектов"). Найдите в меню пункт, созданный Системой для действия, зарегистрированного на шаге 5 (как правило имеет метку, совпадающую с наименованием действия). Расположите (перетаскиванием) этот пункт меню сразу после пункта "Отработать исполнение по штрих-кодам". Укажите для этого пункта следующие параметры в "Инспекторе объектов":
- `Заголовок` - Выдать сменное задание…
Закройте окна редакторов с сохранением изменений.
8. Выдайте права но новое действие в "Администраторе", при необходимости - начните новый сеанс в "ПАРУС 8 Онлайн" с очисткой системного кэша.
#### Настройка КОР-действия для вызова панели "Производственная программа" из раздела "Планы и отчеты производства изделий"
Входящая в состав поставки фреймворка панель "Производственная программа" доступна для вызова из спецификации "Выпуск" раздела "Планы и отчеты производства изделий" (приложение "Планирование и учёт в дискретном производстве", главное меню > "Документы" > "Планы и отчеты производства изделий").
Для настройки этой возможности:
1. Откройте раздел "Классы" приложения "Конструктор отраслевых расширений" (главное меню > "Учёт" > "Классы")
2. В дереве классов выберите "Планы и отчеты производства изделий (спецификация)", а в списке классов - класс с кодом "CostProductPlansSpecs"
3. В спецификации "Методы", выбранного класса, зарегистрируйте новый метод со следующими атрибутами:
- `Мнемокод` - P8PANELS_OPEN
- `Наименование` - P8PANELS_OPEN
- `Тип метода` - Встроенный
- `Доступность` - Клиентский
4. Для добавленного метода `P8PANELS_OPEN` в спецификации "Параметры" зарегистрируйте следующий набор параметров:
| Имя | Наименование | Тип | Домен | Обязательный | Тип привязки | Контекст | Параметр действия |
| -------- | ------------------- | ------- | ------- | ------------ | ----------------- | -------------------- | ----------------- |
| NSPRN | Рег. номер записи | Входной | TRN | Нет | Контекст | Идентификатор записи | |
| SPANEL | Наименование панели | Входной | TSTRING | Да | Параметр действия | | SPANEL |
| SCAPTION | Заголовок вкладки | Входной | TSTRING | Нет | Параметр действия | | SCAPTION |
5. В спецификации "Действия", выбранного класса, зарегистрируйте новое действие со следующими атрибутами:
- `Тип` - Нестандартное
- `Код` - FCPRODPLANSP_OPEN_COST_PROD_PLANS
- `Наименование` - Открытие панели "Производственная программа"
- `Технология производства` - Конструктор
- `Реализующий метод` - P8PANELS_OPEN
- `Обработка записей` - Для одной текущей записи
- `Завершение транзакции` - После каждого вызова действия
- `Обновление выборки` - Не обновлять
6. Для добавленного действия `FCPRODPLANSP_OPEN_COST_PROD_PLANS` в спецификации "Параметры" зарегистрируйте следующий набор параметров:
| Имя | Домен | Тип привязки | Значение |
| -------- | ------- | ------------ | -------------------------- |
| SPANEL | TSTRING | Значение | MechRecCostProdPlans |
| SCAPTION | TSTRING | Значение | Производственная программа |
7. Откройте редактор формы представления данных класса "CostProductPlans" ("Планы и отчеты производства изделий") - родительский для того, в который добавили действие.
Для этого отметьте в списке классов запись с кодом "CostProductPlans", перейдите на закладку "Методы вызова", укажите метод вызова "main", в его контекстном меню укажите "Формы", в появившемся списке форм выполните действие "Редактор" для формы с наименованием "Форма просмотра".
В открывшемся редакторе формы перейдите в режим редактирования всплывающего меню спецификации (закладка "Таблицы", таблица "CostProductPlansSpecs", затем кнопка "Редактор источника", установить фокус на форме представления данных щелчком мыши, затем пункт "Всплывающее меню" в "Инспекторе объектов"). Найдите в меню пункт, созданный Системой для действия, зарегистрированного на шаге 5 (как правило имеет метку, совпадающую с наименованием действия). Расположите (перетаскиванием) этот пункт меню сразу после пункта "Формирование". Укажите для этого пункта следующие параметры в "Инспекторе объектов":
- `Заголовок` - Открыть диаграмму…
- `Правило доступности` - @nCATEGORY = 1 and @nSTATUS = 2
Закройте окна редакторов с сохранением изменений.
8. Выдайте права но новое действие в "Администраторе", при необходимости - начните новый сеанс в "ПАРУС 8 Онлайн" с очисткой системного кэша.
#### Настройка КОР-действия для вызова панели "Редактор настройки регламентированного отчёта" из раздела "Настройки форм регламентированных отчетов"
@ -1859,7 +1934,7 @@ const MyPanel = () => {
**Свойства**
`height` - обязательный, число, высота области диаграммы на панели (может быть задана в разных единицах измерения - `height="100px"`, `height="50vh"`, `height="500pt"`)\
`containerStyle` - необязательный, объект, стили, которые будут применены к компонету `div`, являющемуся контейнером диаграммы\
`title` - необязательный, строка, заголовок диаграммы (если не указан - не отображается)\
`titleStyle` - необязательный, объект, стили, которые будут применены к компонету `Typography` заголовка диаграммы\
`onTitleClick` - необязательный, функция, будет вызвана при нажатии пользователем на заголовок (если указана - заголовок формируется в виде гиперссылки), сигнатура функции `f()`, результат функции не интерпретируется\

29
app.styles.js Normal file
View File

@ -0,0 +1,29 @@
/*
Парус 8 - Панели мониторинга
Типовые стили
*/
//----------------
//Интерфейс модуля
//----------------
//Стили
export const APP_STYLES = {
SCROLL: {
"&::-webkit-scrollbar": {
height: "8px",
width: "8px"
},
"&::-webkit-scrollbar-track": {
borderRadius: "8px",
backgroundColor: "#EBEBEB"
},
"&::-webkit-scrollbar-thumb": {
borderRadius: "8px",
backgroundColor: "#b4b4b4"
},
"&::-webkit-scrollbar-thumb:hover": {
backgroundColor: "#808080"
}
}
};

View File

@ -29,6 +29,9 @@ import { P8PPanelsMenuDrawer, P8P_PANELS_MENU_PANEL_SHAPE } from "./p8p_panels_m
//Константы
//---------
//Высота главного меню
const APP_BAR_HEIGHT = "64px";
//Стили
const STYLES = {
ROOT_BOX: { display: "flex" },
@ -125,4 +128,4 @@ P8PAppWorkspace.propTypes = {
//Интерфейс модуля
//----------------
export { P8PAppWorkspace };
export { APP_BAR_HEIGHT, P8PAppWorkspace };

View File

@ -9,7 +9,7 @@
import React, { useState, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { P8PTable, P8P_TABLE_SIZE, P8P_TABLE_DATA_TYPE, P8P_TABLE_FILTER_SHAPE } from "./p8p_table"; //Таблица
import { P8PTable, P8P_TABLE_SIZE, P8P_TABLE_DATA_TYPE, P8P_TABLE_FILTER_SHAPE, P8P_TABLE_MORE_HEIGHT, P8P_TABLE_FILTERS_HEIGHT } from "./p8p_table"; //Таблица
//---------
//Константы
@ -24,6 +24,12 @@ const P8P_DATA_GRID_DATA_TYPE = P8P_TABLE_DATA_TYPE;
//Формат фильтра
const P8P_DATA_GRID_FILTER_SHAPE = P8P_TABLE_FILTER_SHAPE;
//Высота кнопки догрузки данных
const P8P_DATA_GRID_MORE_HEIGHT = P8P_TABLE_MORE_HEIGHT;
//Высота фильтров таблицы
const P8P_DATA_GRID_FILTERS_HEIGHT = P8P_TABLE_FILTERS_HEIGHT;
//-----------
//Тело модуля
//-----------
@ -187,4 +193,11 @@ P8PDataGrid.propTypes = {
//Интерфейс модуля
//----------------
export { P8P_DATA_GRID_DATA_TYPE, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE, P8PDataGrid };
export {
P8P_DATA_GRID_DATA_TYPE,
P8P_DATA_GRID_SIZE,
P8P_DATA_GRID_FILTER_SHAPE,
P8P_DATA_GRID_MORE_HEIGHT,
P8P_DATA_GRID_FILTERS_HEIGHT,
P8PDataGrid
};

View File

@ -7,7 +7,7 @@
//Подключение библиотек
//---------------------
import React, { useEffect, useState, useCallback } from "react"; //Классы React
import React, { useEffect, useState, useCallback, useRef } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Box,
@ -78,10 +78,22 @@ const P8P_GANTT_TASK_COLOR_SHAPE = PropTypes.shape({
desc: PropTypes.string.isRequired
});
//Высота заголовка
const TITLE_HEIGHT = "44px";
//Высота панели масштабирования
const ZOOM_HEIGHT = "56px";
//Стили
const STYLES = {
TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" }
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" },
GANTT_TITLE: { height: TITLE_HEIGHT },
GANTT_ZOOM: { height: ZOOM_HEIGHT },
GANTT: (noData, title, zoomBar) => ({
height: `calc(100% - ${zoomBar ? ZOOM_HEIGHT : "0px"} - ${title ? TITLE_HEIGHT : "0px"})`,
display: noData ? "none" : ""
})
};
//--------------------------------
@ -318,7 +330,7 @@ P8PGanttTaskEditor.propTypes = {
//Диаграмма Ганта
const P8PGantt = ({
height,
containerStyle,
title,
titleStyle,
onTitleClick,
@ -352,6 +364,9 @@ const P8PGantt = ({
editTask: null
});
//Ссылки на DOM
const svgContainerRef = useRef(null);
//Отображение диаграммы
const showGantt = useCallback(() => {
if (!state.gantt) {
@ -406,12 +421,23 @@ const P8PGantt = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tasks]);
//При подключении компонента к старице
useEffect(() => {
svgContainerRef.current.children[0].classList.add("scroll");
}, []);
//Генерация содержимого
return (
<div>
<div style={{ ...(containerStyle ? containerStyle : {}) }}>
{state.gantt && state.noData ? <P8PAppInlineError text={noDataFoundText} /> : null}
{state.gantt && !state.noData && title ? (
<Typography p={1} sx={{ ...(titleStyle ? titleStyle : {}) }} align="center" color="textSecondary" variant="subtitle1">
<Typography
p={1}
sx={{ ...STYLES.GANTT_TITLE, ...(titleStyle ? titleStyle : {}) }}
align="center"
color="textSecondary"
variant="subtitle1"
>
{onTitleClick ? (
<Link component="button" variant="body2" underline="hover" onClick={() => onTitleClick()}>
{title}
@ -422,7 +448,7 @@ const P8PGantt = ({
</Typography>
) : null}
{state.gantt && !state.noData && zoomBar ? (
<Box p={1}>
<Box p={1} sx={STYLES.GANTT_ZOOM}>
<IconButton onClick={() => handleZoomChange(-1)} disabled={state.zoom == 0}>
<Icon>zoom_in</Icon>
</IconButton>
@ -450,7 +476,7 @@ const P8PGantt = ({
cancelBtnCaption={cancelTaskEditorBtnCaption}
/>
) : null}
<div style={{ height, display: state.noData ? "none" : "" }}>
<div style={STYLES.GANTT(state.noData, title, zoomBar)} ref={svgContainerRef}>
<svg id="__gantt__" width="100%"></svg>
</div>
</div>
@ -459,7 +485,7 @@ const P8PGantt = ({
//Контроль свойств - Диаграмма Ганта
P8PGantt.propTypes = {
height: PropTypes.string.isRequired,
containerStyle: PropTypes.object,
title: PropTypes.string,
titleStyle: PropTypes.object,
onTitleClick: PropTypes.func,

View File

@ -81,6 +81,12 @@ const P8P_TABLE_FILTER_SHAPE = PropTypes.shape({
to: PropTypes.any
});
//Высота кнопки догрузки данных
const P8P_TABLE_MORE_HEIGHT = "49px";
//Высота фильтров таблицы
const P8P_TABLE_FILTERS_HEIGHT = "48px";
//Стили
const STYLES = {
TABLE: {
@ -956,4 +962,4 @@ P8PTable.propTypes = {
//Интерфейс модуля
//----------------
export { P8P_TABLE_DATA_TYPE, P8P_TABLE_SIZE, P8P_TABLE_FILTER_SHAPE, P8PTable };
export { P8P_TABLE_DATA_TYPE, P8P_TABLE_SIZE, P8P_TABLE_FILTER_SHAPE, P8P_TABLE_MORE_HEIGHT, P8P_TABLE_FILTERS_HEIGHT, P8PTable };

View File

@ -8,7 +8,7 @@
//---------------------
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import { Grid, Paper, Box } from "@mui/material"; //Интерфейсные компоненты
import { Box } from "@mui/material"; //Интерфейсные компоненты
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
@ -18,6 +18,33 @@ import { headCellRender, dataCellRender, groupCellRender, DIGITS_REG_EXP, MONTH_
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { Filter } from "./filter"; //Компонент фильтра
import { FilterDialog } from "./filter_dialog"; //Компонент диалогового окна фильтра отбора
import { useWindowResize } from "./hooks"; //Пользовательские хуки
//---------
//Константы
//---------
//Высота меню Парус (пиксели)
const pxOuterMenuH = 53;
//Высота заголовка панели (пиксели)
const pxPanelHeaderH = 64;
//Минимальная ширина таблицы (пиксели)
const minGridW = 800;
//Минимальная высота таблицы (пиксели)
const minGridH = 200;
//Стили
const STYLES = {
BOX_ROW: { display: "flex", justifyContent: "center", alignItems: "center" },
GRID_PADDING: { paddingTop: 1, paddingBottom: 1 },
GRID_SIZES: (width, height) => ({
padding: "0px",
minWidth: minGridW,
maxWidth: width * 0.975 > minGridW ? width * 0.975 : minGridW,
minHeight: minGridH,
maxHeight: (height - pxOuterMenuH - pxPanelHeaderH) * 0.975 > minGridH ? (height - pxOuterMenuH - pxPanelHeaderH) * 0.975 : minGridH
})
};
//-----------
//Тело модуля
@ -31,6 +58,8 @@ const EqsPrfrm = () => {
columnsDef: [],
groups: [],
rows: [],
fixedHeader: false,
fixedColumns: 0,
reload: false
});
@ -39,6 +68,7 @@ const EqsPrfrm = () => {
isOpen: false,
isDefault: false,
isSetByUser: false,
needSave: false,
values: {
belong: "",
prodObj: "",
@ -128,6 +158,8 @@ const EqsPrfrm = () => {
...pv,
columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
rows: [...(data.XROWS || [])],
fixedHeader: data.XDATA_GRID.fixedHeader,
fixedColumns: data.XDATA_GRID.fixedColumns,
groups: [...(data.XGROUPS || [])],
dataLoaded: true,
reload: false
@ -135,7 +167,7 @@ const EqsPrfrm = () => {
}
}, [dataGrid.reload, filter, executeStored]);
//Загрузка значений фильра по умолчанию
//Загрузка значений фильтра по умолчанию
const loadDefaultFilter = useCallback(async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_EQUIPSRV.GET_DEFAULT_FP",
@ -148,6 +180,17 @@ const EqsPrfrm = () => {
}));
}, [executeStored]);
//Загрузка значений фильтра из локального хранилища браузера
const loadLocalFilter = useCallback(async () => {
let vs = filter.values;
Object.keys(vs).map(function (k) {
vs[k] =
k == "fromMonth" || k == "fromYear" || k == "toMonth" || k == "toYear" ? Number(localStorage.getItem(k)) : localStorage.getItem(k);
});
setFilter(pv => ({ ...pv, isDefault: true, values: { ...vs } }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Отбор документа (ТОиР или Ремонтных ведомостей) по ячейке даты
const showEquipSrv = async ({ date, workType, info }) => {
const [techName, servKind] = info.split("_");
@ -180,7 +223,7 @@ const EqsPrfrm = () => {
const setFilterOpen = isOpen => setFilter(pv => ({ ...pv, isOpen }));
//Установить значение фильтра
const setFilterValues = values => setFilter(pv => ({ ...pv, isSetByUser: true, values: { ...values } }));
const setFilterValues = values => setFilter(pv => ({ ...pv, isSetByUser: true, needSave: true, values: { ...values } }));
//Отработка события скрытия/раскрытия ячейки даты
const handleClick = (e, ref) => {
@ -223,16 +266,26 @@ const EqsPrfrm = () => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [refIsDeprecated]);
//При закрытии панели
useEffect(() => {
filter.needSave
? window.addEventListener("beforeunload", function () {
Object.keys(filter.values).map(function (k) {
localStorage.setItem(k, filter.values[k]);
});
})
: null;
}, [filter.needSave, filter.values]);
//При загрузке фильтра по умолчанию
useEffect(() => {
if (filter.isDefault) setFilterOpen(true);
}, [filter.isDefault]);
//При подключении к страницк
//При подключении к странице
useEffect(() => {
loadDefaultFilter();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
localStorage.getItem("belong") ? loadLocalFilter() : loadDefaultFilter();
}, [loadDefaultFilter, loadLocalFilter]);
//При открытии диалога фильтра
const handleFilterClick = () => setFilterOpen(true);
@ -246,32 +299,37 @@ const EqsPrfrm = () => {
//При закрытии диалога фильтра
const handleFilterCancel = () => setFilterOpen(false);
//Состояние ширины и высоты рабочей области окна
const [width, height] = useWindowResize();
//Генерация содержимого
return (
<div>
{filter.isOpen ? <FilterDialog initial={filter.values} onOk={handleFilterOk} onCancel={handleFilterCancel} /> : null}
<Filter filter={filter.values} onClick={handleFilterClick} />
{dataGrid.dataLoaded ? (
<Paper variant="outlined">
<Grid container spacing={1}>
<Grid item xs={12}>
<Box p={1}>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
columnsDef={dataGrid.columnsDef}
groups={dataGrid.groups}
rows={dataGrid.rows}
size={P8P_DATA_GRID_SIZE.LARGE}
reloading={dataGrid.reload}
headCellRender={prms => headCellRender({ ...prms }, handleClick)}
dataCellRender={prms => dataCellRender({ ...prms }, showEquipSrv)}
groupCellRender={prms => groupCellRender({ ...prms })}
showCellRightBorder={true}
/>
</Box>
</Grid>
</Grid>
</Paper>
<Box sx={{ ...STYLES.GRID_PADDING, ...STYLES.BOX_ROW }}>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{
elevation: 6,
style: {
...STYLES.GRID_SIZES(width, height)
}
}}
columnsDef={dataGrid.columnsDef}
groups={dataGrid.groups}
rows={dataGrid.rows}
fixedHeader={dataGrid.fixedHeader}
fixedColumns={dataGrid.fixedColumns}
size={P8P_DATA_GRID_SIZE.LARGE}
reloading={dataGrid.reload}
headCellRender={prms => headCellRender({ ...prms }, handleClick)}
dataCellRender={prms => dataCellRender({ ...prms }, width * 0.2, showEquipSrv)}
groupCellRender={prms => groupCellRender({ ...prms })}
showCellRightBorder={true}
/>
</Box>
) : null}
</div>
);

View File

@ -0,0 +1,36 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Выполнение работ
Пользовательские хуки
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useLayoutEffect } from "react"; //Классы React
//-----------
//Тело модуля
//-----------
//Хук для отработки изменений ширины и высоты рабочей области окна
const useWindowResize = () => {
//Состояние размера рабочей области
const [size, setSize] = useState([0, 0]);
//При изменении размера
useLayoutEffect(() => {
function updateSize() {
setSize([document.documentElement.clientWidth, document.documentElement.clientHeight]);
}
window.addEventListener("resize", updateSize);
updateSize();
return () => window.removeEventListener("resize", updateSize);
}, []);
return size;
};
//--------------
//Интерфейс хука
//--------------
export { useWindowResize };

View File

@ -20,7 +20,7 @@ export const MONTH_NAME_REG_EXP = /_\d{4}_\d{1,2}/;
export const DAY_NAME_REG_EXP = /_\d{4}_\d{1,2}_\d{1,2}/;
//Стили
export const STYLES = {
const STYLES = {
HIDE_CELL_STYLE: { display: "none" },
HCR_MAIN_STYLE: { border: "1px solid rgba(0, 0, 0)", textAlign: "center" },
HCR_DATE_STYLE: { padding: "5px", minWidth: "25px", maxWidth: "25px" },
@ -31,7 +31,12 @@ export const STYLES = {
DCR_FACT_NOT_RELATED_CELL_STYLE: { cursor: "pointer", backgroundColor: "crimson", border: "1px solid rgba(0, 0, 0) !important" },
DCR_DOUBLE_CELL: { padding: "unset" },
DCR_DOUBLE_CELL_GRID_ITEM: backgroundColor => ({ cursor: "pointer", backgroundColor }),
HIDDEN_PARAGRAPH: { display: "none" }
HIDDEN_PARAGRAPH: { display: "none" },
STICKY_WIDTH_UNSET: { minWidth: "unset", maxWidth: "unset" },
FIRST_STICKY_CELL: { left: "0px" },
OBJINFO_WIDTH: width => ({ minWidth: width, maxWidth: width }),
OBJINFO_WRKNAME_WIDTH: width => ({ minWidth: width * 0.6, maxWidth: width * 0.6 }),
WRKTYPE_WIDTH: width => ({ left: width * 0.6, minWidth: width - width * 0.4, maxWidth: width - width * 0.4 })
};
//-----------
@ -71,7 +76,11 @@ export const headCellRender = ({ columnDef }, hClick) => {
//Объединение нужных колонок и строк
if (columnDef.name == "SINFO" || columnDef.name == "SWRKTYPE") {
cellProps = { colSpan: 2 };
if (columnDef.name == "SINFO") cellProps = { ...cellProps, rowSpan: 2 };
cellStyle = { ...cellStyle, ...STYLES.STICKY_WIDTH_UNSET };
if (columnDef.name == "SINFO") {
cellProps = { ...cellProps, rowSpan: 2 };
cellStyle = { ...cellStyle, ...STYLES.FIRST_STICKY_CELL };
}
}
//Изменения в заголовках с датами
if (columnDef.visible && DAY_NAME_REG_EXP.test(columnDef.name)) {
@ -82,7 +91,7 @@ export const headCellRender = ({ columnDef }, hClick) => {
};
//Генерация представления ячейки
export const dataCellRender = ({ row, columnDef }, showEquipSrv) => {
export const dataCellRender = ({ row, columnDef }, width, showEquipSrv) => {
let curParent = "";
let cellDate;
let cellStyle = STYLES.DCR_MAIN_STYLE;
@ -93,10 +102,10 @@ export const dataCellRender = ({ row, columnDef }, showEquipSrv) => {
//Ячейка "Информация по объекту ремонта"
if (columnDef.name == "SOBJINFO") {
cellProps = { colSpan: 2 };
cellStyle = { ...cellStyle, ...STYLES.DCR_OBJECT_INFO_STYLE };
cellStyle = { ...cellStyle, ...STYLES.DCR_OBJECT_INFO_STYLE, ...STYLES.OBJINFO_WIDTH(width) };
}
//Ячейка "Тип работ"
if (columnDef.name == "SWRKTYPE") cellStyle = STYLES.HIDE_CELL_STYLE;
if (columnDef.name == "SWRKTYPE") cellStyle = { ...STYLES.HIDE_CELL_STYLE };
//Ячейки колонок месяцев
if (columnDef.parent == "" && columnDef.expandable == true && columnDef.expanded == false) {
curParent = columnDef.name;
@ -118,13 +127,17 @@ export const dataCellRender = ({ row, columnDef }, showEquipSrv) => {
}
//Строка плана по объекту ремонта
if (columnDef.name == "SOBJINFO" && row["SWRKTYPE"] == "План") {
cellStyle = { ...cellStyle };
cellStyle = { ...cellStyle, ...STYLES.FIRST_STICKY_CELL, ...STYLES.OBJINFO_WRKNAME_WIDTH(width) };
cellProps = { rowSpan: 2 };
}
//Строка факта по объекту ремонта
if (columnDef.name == "SOBJINFO" && row["SWRKTYPE"] == "Факт") {
cellStyle = { display: "none" };
}
//Ячейка план/факт
if (columnDef.name == "SWRKTYPE") {
cellStyle = { ...cellStyle, ...STYLES.WRKTYPE_WIDTH(width) };
}
//Закрашивание ячеек
switch (row[columnDef.name]) {
case "blue":

View File

@ -9,26 +9,63 @@
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Typography, Box, Checkbox, Grid, Icon, Button, Dialog, DialogContent, TextField, DialogActions, Tooltip } from "@mui/material"; //Интерфейсные элементы
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import {
Typography,
Box,
Checkbox,
Grid,
Icon,
Button,
Dialog,
DialogContent,
TextField,
DialogActions,
Tooltip,
Stack,
DialogTitle
} from "@mui/material"; //Интерфейсные элементы
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_MORE_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useCostJobsSpecs, useEquipConfiguration } from "./hooks"; //Собственные хуки таблиц
import { MAIN_HEADER_HEIGHT, SUB_HEADER_HEIGHT } from "./mech_rec_cost_jobs_manage"; //Заглавный компонент панели
//---------
//Константы
//---------
const sUnitCostJobsSpecs = "CostJobsSpecs"; //Мнемокод раздела операций
const sUnitCostEquipment = "CostEquipment"; //Мнемокод раздела рабочих центров
//Мнемокод раздела операций
const UNIT_COST_JOBS_SPECS = "CostJobsSpecs";
//Мнемокод раздела рабочих центров
const UNIT_COST_EQUIPMENT = "CostEquipment";
//Высота заголовка таблицы
const TABLE_HEADER_HEIGHT = "35px";
//Высота панели кнопок таблицы
const TABLE_BUTTONS_HEIGHT = "35px";
//Отступ таблицы
const TABLE_PADDING_TOP = "15px";
//Стили
const STYLES = {
CONTAINER: { textAlign: "center" },
DATA_GRID_CONTAINER: { minHeight: "65vh", maxHeight: "65vh" },
TABLE: { paddingTop: "15px" },
TABLE_BUTTONS: { display: "flex", justifyContent: "flex-end" },
DATA_GRID_CONTAINER: morePages => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${MAIN_HEADER_HEIGHT} - ${SUB_HEADER_HEIGHT} - ${TABLE_HEADER_HEIGHT} - ${TABLE_BUTTONS_HEIGHT} - ${TABLE_PADDING_TOP} - 32px - ${
morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"
})`,
...APP_STYLES.SCROLL
}),
TABLE: { paddingTop: TABLE_PADDING_TOP },
TABLE_HEADER: { height: TABLE_HEADER_HEIGHT, overflow: "hidden" },
TABLE_BUTTONS: { display: "flex", justifyContent: "flex-end", height: TABLE_BUTTONS_HEIGHT, overflow: "hidden" },
CHECK_BOX: { textAlign: "center" },
JOBS_INFO: { minWidth: "60%", maxWidth: "60%", textAlign: "center" },
EQUIPMENT_INFO: { minWidth: "40%", maxWidth: "40%", textAlign: "center" }
JOBS_INFO: { textAlign: "center" },
EQUIPMENT_INFO: { textAlign: "center" }
};
//Цвета
@ -47,7 +84,7 @@ const dataCellRender = ({ row, columnDef, handleSelectChange, sUnit, selectedRow
//Стиль
let cellStyle = {};
//Если это рабочие центры
if (sUnit === sUnitCostEquipment) {
if (sUnit === UNIT_COST_EQUIPMENT) {
//Признак недоступности
let disabled = true;
//Если в выбранной строке смены указано рабочее место
@ -98,7 +135,7 @@ const dataCellRender = ({ row, columnDef, handleSelectChange, sUnit, selectedRow
};
}
//Если это сменное задание
if (sUnit === sUnitCostJobsSpecs) {
if (sUnit === UNIT_COST_JOBS_SPECS) {
//Если указан станок
if (row["SEQCONFIG"]) {
//Подсвечиваем сменное задание зеленым
@ -193,6 +230,7 @@ const CostJobsSpecsInclude = ({ includeEquipment, setIncludeEquipment, setCostJo
return (
<Dialog open onClose={() => handlePriorEditClose()}>
<DialogTitle>Включить в задание</DialogTitle>
<DialogContent>
<Box>
<TextField
@ -219,15 +257,12 @@ const CostJobsSpecsInclude = ({ includeEquipment, setIncludeEquipment, setCostJo
setState(value);
}}
/>
<Box>
<Button onClick={costJobsSpecIncludeCostEquipment} variant="contained" sx={STYLES.DIALOG_BUTTONS}>
Включить в задание
</Button>
</Box>
<Box></Box>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => handlePriorEditClose(null)}>Закрыть</Button>
<Button onClick={costJobsSpecIncludeCostEquipment}>{BUTTONS.OK}</Button>
<Button onClick={() => handlePriorEditClose(null)}>{BUTTONS.CANCEL}</Button>
</DialogActions>
</Dialog>
);
@ -314,7 +349,7 @@ const CostJobsSpecsDataGrid = ({ task, haveNote, fromAction }) => {
//Исходим от раздела
switch (prms.SUNIT) {
//Сменное задание
case sUnitCostJobsSpecs:
case UNIT_COST_JOBS_SPECS:
//Определяем это новое отмеченное сменное задание или сброс старого
selectedRow = costJobsSpecs.selectedRow.NRN ? (costJobsSpecs.selectedRow.NRN === prms.NRN ? null : prms.NRN) : prms.NRN;
//Актуализируем строки
@ -327,7 +362,7 @@ const CostJobsSpecsDataGrid = ({ task, haveNote, fromAction }) => {
//Выходим
break;
//Рабочие центры
case sUnitCostEquipment:
case UNIT_COST_EQUIPMENT:
//Определяем это новое отмеченное сменное задание или сброс старого
selectedRow = equipConfiguration.selectedRow.NRN ? (equipConfiguration.selectedRow.NRN === prms.NRN ? null : prms.NRN) : prms.NRN;
//Актуализируем строки
@ -356,23 +391,23 @@ const CostJobsSpecsDataGrid = ({ task, haveNote, fromAction }) => {
return (
<div style={STYLES.CONTAINER}>
<Grid container spacing={2}>
<Grid item sx={STYLES.JOBS_INFO}>
<Typography variant={"h6"}>Сменное задание</Typography>
<Grid item sx={STYLES.JOBS_INFO} xs={6}>
<Typography sx={STYLES.TABLE_HEADER} variant={"h6"} color={"text.secondary"}>
Сменное задание
</Typography>
{costJobsSpecs.dataLoaded ? (
<>
<Box sx={STYLES.TABLE_BUTTONS}>
<Tooltip title={haveNote ? "Сменное задание имеет строку с примечанием" : null}>
<Box>
<Button variant="contained" size="small" disabled={haveNote} onClick={costJobsSpecIssue}>
Выдать задания
</Button>
</Box>
<Button variant="contained" size="small" disabled={haveNote} onClick={costJobsSpecIssue}>
Выдать задания
</Button>
</Tooltip>
</Box>
<Box sx={STYLES.TABLE}>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 1 }}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER(costJobsSpecs.morePages), elevation: 1 }}
columnsDef={costJobsSpecs.columnsDef}
rows={costJobsSpecs.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
@ -384,7 +419,7 @@ const CostJobsSpecsDataGrid = ({ task, haveNote, fromAction }) => {
dataCellRender({
...prms,
handleSelectChange,
sUnit: sUnitCostJobsSpecs,
sUnit: UNIT_COST_JOBS_SPECS,
selectedRow: costJobsSpecs.selectedRow.NRN,
selectedJobSpec: costJobsSpecs.selectedRow
})
@ -396,24 +431,26 @@ const CostJobsSpecsDataGrid = ({ task, haveNote, fromAction }) => {
</>
) : null}
</Grid>
<Grid item sx={STYLES.EQUIPMENT_INFO}>
<Typography variant={"h6"}>Рабочие центры</Typography>
<Grid item sx={STYLES.EQUIPMENT_INFO} xs={6}>
<Typography sx={STYLES.TABLE_HEADER} variant={"h6"} color={"text.secondary"}>
Рабочие центры
</Typography>
{equipConfiguration.dataLoaded ? (
<>
<Box sx={STYLES.TABLE_BUTTONS}>
<Button
variant="contained"
size="small"
disabled={
!equipConfiguration.selectedRow.NRN ||
!costJobsSpecs.selectedRow.NRN ||
(equipConfiguration.selectedRow.NRN && equipConfiguration.selectedRow.BFULL_LOADED)
}
onClick={handleIncludeEquipmentOpen}
>
Включить в задание
</Button>
<Box ml={1}>
<Stack direction={"row"} spacing={1}>
<Button
variant="contained"
size="small"
disabled={
!equipConfiguration.selectedRow.NRN ||
!costJobsSpecs.selectedRow.NRN ||
(equipConfiguration.selectedRow.NRN && equipConfiguration.selectedRow.BFULL_LOADED)
}
onClick={handleIncludeEquipmentOpen}
>
Включить в задание
</Button>
<Button
variant="contained"
size="small"
@ -422,12 +459,12 @@ const CostJobsSpecsDataGrid = ({ task, haveNote, fromAction }) => {
>
Исключить из задания
</Button>
</Box>
</Stack>
</Box>
<Box sx={STYLES.TABLE}>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 1 }}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER(equipConfiguration.morePages), elevation: 1 }}
columnsDef={equipConfiguration.columnsDef}
rows={equipConfiguration.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
@ -439,7 +476,7 @@ const CostJobsSpecsDataGrid = ({ task, haveNote, fromAction }) => {
dataCellRender({
...prms,
handleSelectChange,
sUnit: sUnitCostEquipment,
sUnit: UNIT_COST_EQUIPMENT,
selectedRow: equipConfiguration.selectedRow.NRN,
selectedJobSpec: costJobsSpecs.selectedRow
})

View File

@ -55,10 +55,10 @@ const useCostJobs = () => {
//При подключении компонента к странице
useEffect(() => {
const initPlans = async NRN => {
const initPlans = async fcJob => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBS_INIT",
args: { NFCJOBS: NRN ? parseInt(NRN) : null },
args: { NFCJOBS: fcJob ? parseInt(fcJob) : null },
respArg: "COUT",
isArray: name => name === "XFCJOBS",
attributeValueProcessor: (name, val) => (["NHAVE_NOTE"].includes(name) ? val == 1 : val)
@ -69,7 +69,7 @@ const useCostJobs = () => {
jobList: [...(data.XFCJOBS || [])],
selectedJob: data.XFCJOBS_SELECTED ? data.XFCJOBS_SELECTED : {},
jobListLoaded: true,
fromAction: NRN ? true : false
fromAction: fcJob ? true : false
}));
};
if (!state.init) {

View File

@ -18,8 +18,16 @@ import { useCostJobs, useFilteredFcjobs } from "./hooks"; //Вспомогате
//Константы
//---------
//Высота основного заголовка
const MAIN_HEADER_HEIGHT = "35px";
//Высота подзаголовка
const SUB_HEADER_HEIGHT = "35px";
//Стили
const STYLES = {
MAIN_HEADER: { height: MAIN_HEADER_HEIGHT, overflow: "hidden" },
SUB_HEADER: { height: SUB_HEADER_HEIGHT, overflow: "hidden" },
JOBS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
JOBS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
JOBS_BUTTON: { position: "absolute" },
@ -128,8 +136,11 @@ const MechRecCostJobs = () => {
<div style={STYLES.CONTAINER}>
{state.selectedJob.NRN ? (
<>
<Typography variant={"h6"}>{`Сменное задание №${state.selectedJob.SDOC_NUMB} на ${state.selectedJob.SPERIOD}`}</Typography>
<Typography variant={"h6"}>{`${state.selectedJob.SSUBDIV}`}</Typography>
<Typography
sx={STYLES.MAIN_HEADER}
variant={"h6"}
>{`Сменное задание №${state.selectedJob.SDOC_NUMB} на ${state.selectedJob.SPERIOD}`}</Typography>
<Typography sx={STYLES.SUB_HEADER} variant={"h6"}>{`${state.selectedJob.SSUBDIV}`}</Typography>
<CostJobsSpecsDataGrid task={state.selectedJob.NRN} haveNote={state.selectedJob.NHAVE_NOTE} fromAction={state.fromAction} />
</>
) : !state.selectedJob.NRN ? (
@ -144,4 +155,4 @@ const MechRecCostJobs = () => {
//Интерфейс модуля
//----------------
export { MechRecCostJobs };
export { MAIN_HEADER_HEIGHT, SUB_HEADER_HEIGHT, MechRecCostJobs };

View File

@ -40,7 +40,9 @@ import {
} from "@mui/material"; //Интерфейсные элементы
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8PGantt, taskLegendDesc } from "../../components/p8p_gantt"; //Диаграмма Ганта
import { xml2JSON, formatDateJSONDateOnly, formatDateRF, hasValue } from "../../core/utils"; //Вспомогательные функции
import { useFilteredPlanCtlgs } from "./hooks"; //Вспомогательные хуки
@ -58,12 +60,6 @@ const DECLINATIONS = ["план", "плана", "планов"];
const SORT_REP_DATE = "DREP_DATE";
const SORT_REP_DATE_TO = "DREP_DATE_TO";
//Высота диаграммы Ганта
const GANTT_HEIGHT = "75vh";
//Ширина диаграммы Ганта
const GANTT_WIDTH = "98vw";
//Стили
const STYLES = {
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
@ -71,20 +67,23 @@ const STYLES = {
PLANS_LIST_ITEM_ZERODOCS: { backgroundColor: "#ebecec" },
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
PLANS_LIST_ITEM_SECONDARY: { wordWrap: "break-word", fontSize: "0.6rem", textTransform: "uppercase" },
PLANS_BUTTON: { position: "absolute" },
PLANS_BUTTON: { position: "absolute", top: `calc(${APP_BAR_HEIGHT} + 16px)`, left: "16px" },
PLANS_DRAWER: {
width: "350px",
display: "inline-block",
flexShrink: 0,
[`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box" }
},
GANTT_CONTAINER: { height: GANTT_HEIGHT, width: GANTT_WIDTH },
GANTT_TITLE: { paddingLeft: "100px", paddingRight: "120px" },
GANTT_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})`, width: "100vw", paddingTop: "24px" },
GANTT_TITLE: { paddingLeft: "250px", paddingRight: "250px" },
SECOND_TABLE: { paddingTop: "30px" },
TASK_DIALOG_CARD_CONTAINER: { padding: "0px" },
TASK_DIALOG_LIST_ITEM_ICON: { justifyContent: "center" },
TASK_DIALOG_ICON: { fontSize: "2rem" },
TASK_DIALOG_ACTION_CONTAINER: { border: 1, borderColor: "text.primary", borderRadius: "5px", width: "100%" }
TASK_DIALOG_ACTION_CONTAINER: { border: 1, borderColor: "text.primary", borderRadius: "5px", width: "100%" },
FILTERS: { display: "table", float: "right" },
FILTERS_DATE: { display: "table-cell", verticalAlign: "middle" },
FILTERS_LEVEL: { display: "table-cell", verticalAlign: "middle", paddingLeft: "15px" }
};
//------------------------------------
@ -148,7 +147,7 @@ const PlanCtlgsList = ({ planCtlgs = [], selectedPlanCtlg, filter, setFilter, on
>
<ListItemText
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{p.SNAME}</Typography>}
secondary={<Typography sx={{ ...STYLES.PLANS_LIST_ITEM_SECONDARY }}>{formatCountDocs(p.NCOUNT_DOCS)}</Typography>}
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(p.NCOUNT_DOCS)}</Typography>}
/>
</ListItemButton>
))}
@ -210,7 +209,9 @@ const taskDialogRenderer = ({ task, taskColors, close, handleTaskDetailOpen }) =
{task["detail_list"]}
</Button>
) : (
<Typography color="textSecondary">{`Анализ отклонений недоступен: ${task["detail_list"]}`}</Typography>
<Typography color="textSecondary">{`Анализ отклонений недоступен${
task["detail_list"] ? `: ${task["detail_list"]}` : ""
}`}</Typography>
)}
</Box>
</CardActions>
@ -239,7 +240,8 @@ const MechRecCostProdPlans = () => {
selectedPlanCtlgGanttDef: {},
selectedPlanCtlgSpecs: [],
selectedTaskDetail: null,
selectedTaskDetailType: null
selectedTaskDetailType: null,
planSpec: null
});
//Состояние для фильтра каталогов
const [filter, setFilter] = useState({ ctlgName: "", haveDocs: false });
@ -253,7 +255,10 @@ const MechRecCostProdPlans = () => {
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
// Инициализация каталогов планов
//Подключение к контексту навигации
const { getNavigationSearch } = useContext(NavigationCtx);
//Инициализация каталогов планов
const initPlanCtlgs = useCallback(async () => {
if (!state.init) {
const data = await executeStored({
@ -307,7 +312,7 @@ const MechRecCostProdPlans = () => {
async (level = null, sort = null) => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_GET",
args: { NCRN: state.selectedPlanCtlg, NLEVEL: level, SSORT_FIELD: sort }
args: { NCRN: state.selectedPlanCtlg, NLEVEL: level, SSORT_FIELD: sort, NFCPRODPLANSP: state.planSpec }
});
let doc = await parseProdPlanSpXML(data.COUT);
setState(pv => ({
@ -324,7 +329,7 @@ const MechRecCostProdPlans = () => {
}));
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[executeStored, state.ident, state.selectedPlanCtlg]
[executeStored, state.ident, state.selectedPlanCtlg, state.planSpec]
);
//Обработка нажатия на элемент в списке каталогов планов
@ -335,14 +340,16 @@ const MechRecCostProdPlans = () => {
//При подключении компонента к странице
useEffect(() => {
initPlanCtlgs();
const actionPrms = getNavigationSearch();
if (actionPrms.NSPRN) setState(pv => ({ ...pv, planSpec: parseInt(actionPrms.NSPRN), init: true }));
else initPlanCtlgs();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//При смене выбранного каталога плана
//При смене выбранного каталога плана или при явном указании позиции спецификации плана
useEffect(() => {
if (state.selectedPlanCtlg) loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
}, [state.selectedPlanCtlg, loadPlanCtglSpecs]);
if (state.selectedPlanCtlg || state.planSpec) loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
}, [state.selectedPlanCtlg, state.planSpec, loadPlanCtglSpecs]);
//Выбор уровня
const handleChangeSelectLevel = selectedLevel => {
@ -368,35 +375,48 @@ const MechRecCostProdPlans = () => {
//Генерация содержимого
return (
<Box p={2}>
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
Каталоги планов
</Fab>
<Drawer
anchor={"left"}
open={state.showPlanList}
onClose={() => setState(pv => ({ ...pv, showPlanList: false }))}
sx={STYLES.PLANS_DRAWER}
>
<PlanCtlgsList
planCtlgs={filteredPlanCtgls}
selectedPlanCtlg={state.selectedPlanCtlg}
filter={filter}
setFilter={setFilter}
onClick={handleProjectClick}
/>
</Drawer>
<Box>
{!state.planSpec ? (
<>
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
Каталоги планов
</Fab>
<Drawer
anchor={"left"}
open={state.showPlanList}
onClose={() => setState(pv => ({ ...pv, showPlanList: false }))}
sx={STYLES.PLANS_DRAWER}
>
<PlanCtlgsList
planCtlgs={filteredPlanCtgls}
selectedPlanCtlg={state.selectedPlanCtlg}
filter={filter}
setFilter={setFilter}
onClick={handleProjectClick}
/>
</Drawer>
</>
) : null}
{state.init == true ? (
<Grid container spacing={1}>
<Grid container>
<Grid item xs={12}>
{state.selectedPlanCtlgSpecsLoaded ? (
state.selectedPlanCtlgSpecs.length === 0 ? (
<InlineMsgInfo okBtn={false} text={"В каталоге планов отсутствуют записи спецификации"} />
<Box pt={3}>
<InlineMsgInfo
okBtn={false}
text={
state.planSpec
? "Не найдено данных для выбранной позиции плана"
: "В каталоге планов отсутствуют записи спецификации"
}
/>
</Box>
) : (
<Box sx={STYLES.GANTT_CONTAINER} p={1}>
<Box>
{state.selectedPlanCtlgMaxLevel ? (
<Box sx={{ display: "table", float: "right" }}>
<Box sx={{ display: "table-cell", verticalAlign: "middle" }}>
<Box sx={STYLES.FILTERS} p={1}>
<Box sx={STYLES.FILTERS_DATE}>
<InputLabel id="select-label-sort">Сортировка</InputLabel>
<Select
labelId="select-label-sort"
@ -416,7 +436,7 @@ const MechRecCostProdPlans = () => {
</MenuItem>
</Select>
</Box>
<Box sx={{ display: "table-cell", verticalAlign: "middle", paddingLeft: "15px" }}>
<Box sx={STYLES.FILTERS_LEVEL}>
<InputLabel id="select-label-level">До уровня</InputLabel>
<Select
labelId="select-label-level"
@ -440,7 +460,7 @@ const MechRecCostProdPlans = () => {
<P8PGantt
{...P8P_GANTT_CONFIG_PROPS}
{...state.selectedPlanCtlgGanttDef}
height={GANTT_HEIGHT}
containerStyle={STYLES.GANTT_CONTAINER}
titleStyle={STYLES.GANTT_TITLE}
tasks={state.selectedPlanCtlgSpecs}
taskDialogRenderer={prms => taskDialogRenderer({ ...prms, handleTaskDetailOpen })}
@ -448,7 +468,16 @@ const MechRecCostProdPlans = () => {
</Box>
)
) : !state.selectedPlanCtlg ? (
<InlineMsgInfo okBtn={false} text={"Укажите каталог планов для отображения их спецификаций"} />
<Box pt={3}>
<InlineMsgInfo
okBtn={false}
text={
state.planSpec
? "Загружаю график для выбранной позиции плана..."
: "Укажите каталог планов для отображения их спецификаций"
}
/>
</Box>
) : null}
</Grid>
</Grid>

View File

@ -34,9 +34,19 @@ const currentDate = new Date();
const currentMonth = currentDate.getUTCMonth() + 1;
const currentYear = currentDate.getUTCFullYear();
//Высота фильтра
const FILTER_HEIGHT = "68px";
//Стили
const STYLES = {
FILTER_CONTAINER: { display: "flex", flexDirection: "row", justifyContent: "flex-start", alignItems: "flex-end" },
FILTER_CONTAINER: {
height: FILTER_HEIGHT,
overflow: "hidden",
display: "flex",
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "flex-end"
},
FILTER_BLOCK: { maxWidth: "200px", display: "flex" }
};
@ -135,4 +145,4 @@ FilterComponent.propTypes = {
//Интерфейс модуля
//----------------
export { FilterComponent };
export { FILTER_HEIGHT, FilterComponent };

View File

@ -10,9 +10,11 @@
import React from "react"; //Классы React
import { Typography, Box } from "@mui/material"; //Интерфейсные элементы
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useMechRecDeptCostJobs, useFilter } from "./hooks"; //Кастомные состояния
import { FilterComponent } from "./components/filter"; //Компонент фильтра
import { FILTER_HEIGHT, FilterComponent } from "./components/filter"; //Компонент фильтра
//---------
//Константы
@ -24,21 +26,26 @@ const currentMonth = currentDate.getUTCMonth() + 1;
const currentYear = currentDate.getUTCFullYear();
//Кастомные цвета
const colors = {
lightred: "#ef8989",
lightyellow: "#f5f5b0",
blue: "#0097ff"
const COLORS = {
LIGHTRED: "#ef8989",
LIGHTYELLOW: "#f5f5b0",
BLUE: "#0097ff"
};
//Высота заголовка
const TITLE_HEIGHT = "35px";
//Нижний отступ заголовка
const TITLE_PADDING_BOTTOM = "15px";
//Стили
const STYLES = {
CONTAINER: { textAlign: "center", paddingTop: "10px" },
TITLE: { paddingBottom: "15px" },
TITLE: { height: TITLE_HEIGHT, overflow: "hidden", paddingBottom: TITLE_PADDING_BOTTOM },
DATA_GRID_CONTAINER: {
minWidth: "700px",
maxWidth: "100vw",
minHeight: "calc(100vh - 250px)",
maxHeight: "calc(100vh - 250px)"
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 10px)`,
width: "99vw",
...APP_STYLES.SCROLL
},
DATA_GRID_CELL: (row, columnDef) => {
//Определяем тип дня
@ -53,12 +60,12 @@ const STYLES = {
...(dayType
? {
backgroundColor: [1, 3].includes(dayType) ? "lightgrey" : dayType === 4 ? "lightgreen" : null,
color: [2, 3].includes(dayType) ? colors.blue : null
color: [2, 3].includes(dayType) ? COLORS.BLUE : null
}
: procentLoad || procentLoad === 0
? {
backgroundColor:
procentLoad >= 85 ? "lightgreen" : procentLoad >= 50 ? colors.lightyellow : procentLoad > 0 ? colors.lightred : "lightgrey"
procentLoad >= 85 ? "lightgreen" : procentLoad >= 50 ? COLORS.LIGHTYELLOW : procentLoad > 0 ? COLORS.LIGHTRED : "lightgrey"
}
: {})
};
@ -141,7 +148,7 @@ const MechRecDeptCostJobs = () => {
{costJobs.dataLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ elevation: 6, style: STYLES.DATA_GRID_CONTAINER }}
containerComponentProps={{ elevation: 6, sx: STYLES.DATA_GRID_CONTAINER }}
fixedHeader={costJobs.fixedHeader}
fixedColumns={costJobs.fixedColumns}
columnsDef={costJobs.columnsDef}

View File

@ -10,6 +10,8 @@
import React, { useContext, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField, Link, Grid } from "@mui/material"; //Интерфейсные элементы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { useDeptCostProdPlans, useFilteredPlans } from "./hooks"; //Вспомогательные хуки
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
@ -21,6 +23,15 @@ import { CostRouteListsDataGridDialog } from "./fcroutlst"; //Диалог ма
//Константы
//---------
//Высота заголовка
const TITLE_HEIGHT = "35px";
//Верхний отступ заголовка
const TITLE_PADDING_TOP = "10px";
//Нижний отступ заголовка
const TITLE_PADDING_BOTTOM = "20px";
//Стили
const STYLES = {
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
@ -33,7 +44,12 @@ const STYLES = {
[`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box" }
},
CONTAINER: { textAlign: "center" },
DATA_GRID_CONTAINER: { minWidth: "95vw", maxWidth: "95vw", minHeight: "80vh", maxHeight: "80vh" },
TITLE: { height: TITLE_HEIGHT, overflow: "hidden", paddingTop: TITLE_PADDING_TOP, paddingBottom: TITLE_PADDING_BOTTOM, display: "inline-table" },
DATA_GRID_CONTAINER: {
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_HEIGHT} - ${TITLE_PADDING_TOP} - ${TITLE_PADDING_BOTTOM} - 29px)`,
width: "98vw",
...APP_STYLES.SCROLL
},
DATA_GRID_GROUP_CELL: { padding: "2px" },
DATA_GRID_CELL: { padding: "8px", maxWidth: "300px", textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "pre" },
DATA_GRID_CELL_STATUS: (currentStyle, row) => ({ backgroundColor: getRowBackgroudColor(row), ...currentStyle }),
@ -263,7 +279,7 @@ const MechRecDeptCostProdPlans = () => {
onClick={handlePlanClick}
/>
</Drawer>
<Grid container spacing={1}>
<Grid container>
<Grid item xs={12}>
<Box display="flex" justifyContent="center" alignItems="center">
{state.dataLoaded ? (
@ -271,13 +287,13 @@ const MechRecDeptCostProdPlans = () => {
<InlineMsgInfo okBtn={false} text={"В плане отсутствуют записи спецификации"} />
) : (
<Box sx={STYLES.CONTAINER}>
<Typography pt={1} variant={"h6"}>
<Typography sx={STYLES.TITLE} variant={"h6"}>
{`Производственный план цеха №${state.selectedPlan.SSUBDIV} на ${state.selectedPlan.SPERIOD}`}
</Typography>
<Box pt={2.5}>
<Box>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ elevation: 6, style: STYLES.DATA_GRID_CONTAINER }}
containerComponentProps={{ elevation: 6, sx: STYLES.DATA_GRID_CONTAINER }}
fixedHeader={state.fixedHeader}
fixedColumns={state.fixedColumns}
columnsDef={state.columnsDef}

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Some files were not shown because too many files have changed in this diff Show More