diff --git a/README.md b/README.md index 8912d47..c6a6461 100644 --- a/README.md +++ b/README.md @@ -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()`, результат функции не интерпретируется\ diff --git a/app.styles.js b/app.styles.js new file mode 100644 index 0000000..9627d6b --- /dev/null +++ b/app.styles.js @@ -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" + } + } +}; diff --git a/app/components/p8p_app_workspace.js b/app/components/p8p_app_workspace.js index cba6e27..88a37fd 100644 --- a/app/components/p8p_app_workspace.js +++ b/app/components/p8p_app_workspace.js @@ -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 }; diff --git a/app/components/p8p_data_grid.js b/app/components/p8p_data_grid.js index 46c960e..441aac0 100644 --- a/app/components/p8p_data_grid.js +++ b/app/components/p8p_data_grid.js @@ -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 +}; diff --git a/app/components/p8p_gantt.js b/app/components/p8p_gantt.js index 6c562b2..0cb42f8 100644 --- a/app/components/p8p_gantt.js +++ b/app/components/p8p_gantt.js @@ -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 ( -
+
{state.gantt && state.noData ? : null} {state.gantt && !state.noData && title ? ( - + {onTitleClick ? ( onTitleClick()}> {title} @@ -422,7 +448,7 @@ const P8PGantt = ({ ) : null} {state.gantt && !state.noData && zoomBar ? ( - + handleZoomChange(-1)} disabled={state.zoom == 0}> zoom_in @@ -450,7 +476,7 @@ const P8PGantt = ({ cancelBtnCaption={cancelTaskEditorBtnCaption} /> ) : null} -
+
@@ -459,7 +485,7 @@ const P8PGantt = ({ //Контроль свойств - Диаграмма Ганта P8PGantt.propTypes = { - height: PropTypes.string.isRequired, + containerStyle: PropTypes.object, title: PropTypes.string, titleStyle: PropTypes.object, onTitleClick: PropTypes.func, diff --git a/app/components/p8p_table.js b/app/components/p8p_table.js index ca0045d..f5d3c31 100644 --- a/app/components/p8p_table.js +++ b/app/components/p8p_table.js @@ -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 }; diff --git a/app/panels/eqs_prfrm/eqs_prfrm.js b/app/panels/eqs_prfrm/eqs_prfrm.js index f49142b..01e4014 100644 --- a/app/panels/eqs_prfrm/eqs_prfrm.js +++ b/app/panels/eqs_prfrm/eqs_prfrm.js @@ -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 (
{filter.isOpen ? : null} {dataGrid.dataLoaded ? ( - - - - - headCellRender({ ...prms }, handleClick)} - dataCellRender={prms => dataCellRender({ ...prms }, showEquipSrv)} - groupCellRender={prms => groupCellRender({ ...prms })} - showCellRightBorder={true} - /> - - - - + + headCellRender({ ...prms }, handleClick)} + dataCellRender={prms => dataCellRender({ ...prms }, width * 0.2, showEquipSrv)} + groupCellRender={prms => groupCellRender({ ...prms })} + showCellRightBorder={true} + /> + ) : null}
); diff --git a/app/panels/eqs_prfrm/hooks.js b/app/panels/eqs_prfrm/hooks.js new file mode 100644 index 0000000..4637885 --- /dev/null +++ b/app/panels/eqs_prfrm/hooks.js @@ -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 }; diff --git a/app/panels/eqs_prfrm/layouts.js b/app/panels/eqs_prfrm/layouts.js index 82b08cb..c854873 100644 --- a/app/panels/eqs_prfrm/layouts.js +++ b/app/panels/eqs_prfrm/layouts.js @@ -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": diff --git a/app/panels/mech_rec_cost_jobs_manage/fcjobssp.js b/app/panels/mech_rec_cost_jobs_manage/fcjobssp.js index fae1f43..6678a69 100644 --- a/app/panels/mech_rec_cost_jobs_manage/fcjobssp.js +++ b/app/panels/mech_rec_cost_jobs_manage/fcjobssp.js @@ -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 ( handlePriorEditClose()}> + Включить в задание - - - + - + + ); @@ -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 (
- - Сменное задание + + + Сменное задание + {costJobsSpecs.dataLoaded ? ( <> - - - + { 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} - - Рабочие центры + + + Рабочие центры + {equipConfiguration.dataLoaded ? ( <> - - + + - + { dataCellRender({ ...prms, handleSelectChange, - sUnit: sUnitCostEquipment, + sUnit: UNIT_COST_EQUIPMENT, selectedRow: equipConfiguration.selectedRow.NRN, selectedJobSpec: costJobsSpecs.selectedRow }) diff --git a/app/panels/mech_rec_cost_jobs_manage/hooks.js b/app/panels/mech_rec_cost_jobs_manage/hooks.js index ef5b645..01eab21 100644 --- a/app/panels/mech_rec_cost_jobs_manage/hooks.js +++ b/app/panels/mech_rec_cost_jobs_manage/hooks.js @@ -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) { diff --git a/app/panels/mech_rec_cost_jobs_manage/mech_rec_cost_jobs_manage.js b/app/panels/mech_rec_cost_jobs_manage/mech_rec_cost_jobs_manage.js index 6ad6111..5d97e7d 100644 --- a/app/panels/mech_rec_cost_jobs_manage/mech_rec_cost_jobs_manage.js +++ b/app/panels/mech_rec_cost_jobs_manage/mech_rec_cost_jobs_manage.js @@ -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 = () => {
{state.selectedJob.NRN ? ( <> - {`Сменное задание №${state.selectedJob.SDOC_NUMB} на ${state.selectedJob.SPERIOD}`} - {`${state.selectedJob.SSUBDIV}`} + {`Сменное задание №${state.selectedJob.SDOC_NUMB} на ${state.selectedJob.SPERIOD}`} + {`${state.selectedJob.SSUBDIV}`} ) : !state.selectedJob.NRN ? ( @@ -144,4 +155,4 @@ const MechRecCostJobs = () => { //Интерфейс модуля //---------------- -export { MechRecCostJobs }; +export { MAIN_HEADER_HEIGHT, SUB_HEADER_HEIGHT, MechRecCostJobs }; diff --git a/app/panels/mech_rec_cost_prod_plans/mech_rec_cost_prod_plans.js b/app/panels/mech_rec_cost_prod_plans/mech_rec_cost_prod_plans.js index a6da453..cea0cad 100644 --- a/app/panels/mech_rec_cost_prod_plans/mech_rec_cost_prod_plans.js +++ b/app/panels/mech_rec_cost_prod_plans/mech_rec_cost_prod_plans.js @@ -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 > {p.SNAME}} - secondary={{formatCountDocs(p.NCOUNT_DOCS)}} + secondary={{formatCountDocs(p.NCOUNT_DOCS)}} /> ))} @@ -210,7 +209,9 @@ const taskDialogRenderer = ({ task, taskColors, close, handleTaskDetailOpen }) = {task["detail_list"]} ) : ( - {`Анализ отклонений недоступен: ${task["detail_list"]}`} + {`Анализ отклонений недоступен${ + task["detail_list"] ? `: ${task["detail_list"]}` : "" + }`} )} @@ -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 ( - - setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}> - Каталоги планов - - setState(pv => ({ ...pv, showPlanList: false }))} - sx={STYLES.PLANS_DRAWER} - > - - + + {!state.planSpec ? ( + <> + setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}> + Каталоги планов + + setState(pv => ({ ...pv, showPlanList: false }))} + sx={STYLES.PLANS_DRAWER} + > + + + + ) : null} {state.init == true ? ( - + {state.selectedPlanCtlgSpecsLoaded ? ( state.selectedPlanCtlgSpecs.length === 0 ? ( - + + + ) : ( - + {state.selectedPlanCtlgMaxLevel ? ( - - + + Сортировка - + До уровня