From dea0f3643de4ec7fda328b8f011f5f9dc26cb9f8 Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Sat, 18 May 2024 00:52:43 +0300 Subject: [PATCH] =?UTF-8?q?WEB=20APP:=20=D0=A6=D0=98=D0=A2=D0=9A-841=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D1=8C=20"=D0=9C=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=81=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B8=20=D0=B8=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=B9?= =?UTF-8?q?"=20-=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=20SVG-=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D0=B4=D1=91=D0=BD=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D1=8B=D0=B9=20=D1=8D=D1=82=D0=B0=D0=BF=20=D1=80=D0=B5?= =?UTF-8?q?=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/panels/mech_rec_assembly_mon/backend.js | 233 +++---------- .../mech_rec_assembly_mon/blocks/cardBlock.js | 102 ------ .../blocks/cardDetail.js | 289 ---------------- .../components/plan_detail.js | 324 ++++++++++++++++++ .../components/plans_list.js | 83 +++++ .../components/plans_list_item.js | 108 ++++++ .../components/progress_box.js | 75 ++++ .../elements/progressBox.js | 76 ---- .../mech_rec_assembly_mon.js | 100 +++--- .../mech_rec_assembly_mon/styles/themes.js | 1 + 10 files changed, 689 insertions(+), 702 deletions(-) delete mode 100644 app/panels/mech_rec_assembly_mon/blocks/cardBlock.js delete mode 100644 app/panels/mech_rec_assembly_mon/blocks/cardDetail.js create mode 100644 app/panels/mech_rec_assembly_mon/components/plan_detail.js create mode 100644 app/panels/mech_rec_assembly_mon/components/plans_list.js create mode 100644 app/panels/mech_rec_assembly_mon/components/plans_list_item.js create mode 100644 app/panels/mech_rec_assembly_mon/components/progress_box.js delete mode 100644 app/panels/mech_rec_assembly_mon/elements/progressBox.js diff --git a/app/panels/mech_rec_assembly_mon/backend.js b/app/panels/mech_rec_assembly_mon/backend.js index 59f1cda..7264b6b 100644 --- a/app/panels/mech_rec_assembly_mon/backend.js +++ b/app/panels/mech_rec_assembly_mon/backend.js @@ -11,7 +11,7 @@ import { object2Base64XML } from "../../core/utils"; //Вспомогатель //--------- //Размер страницы данных -const DATA_GRID_PAGE_SIZE = 10; +const DATA_GRID_PAGE_SIZE = 0; //----------- //Тело модуля @@ -25,10 +25,10 @@ const useMechRecAssemblyMon = () => { showPlanList: false, planCtlgs: [], planCtlgsLoaded: false, - selectedPlanCtlg: { NRN: null, SNAME: null, NMIN_YEAR: null, NMAX_YEAR: null }, + selectedPlanCtlg: {}, plans: [], plansLoaded: false, - selectedPlan: { NRN: null, SNUMB: null, NPROGRESS: null, SDETAIL: null, NYEAR: null } + selectedPlan: {} }); //Подключение к контексту взаимодействия с сервером @@ -66,19 +66,21 @@ const useMechRecAssemblyMon = () => { ); //Выбор каталога планов - const selectPlan = project => { + const selectPlanCtlg = planCtlg => { setState(pv => ({ ...pv, - selectedPlanCtlg: project, + selectedPlanCtlg: { ...planCtlg }, + selectedPlan: {}, showPlanList: false })); }; //Сброс выбора каталога планов - const unselectPlan = () => + const unselectPlanCtlg = () => setState(pv => ({ ...pv, - selectedPlanCtlg: { NRN: null, SNAME: null, NMIN_YEAR: null, NMAX_YEAR: null }, + selectedPlanCtlg: {}, + selectedPlan: {}, showPlanList: false })); @@ -99,7 +101,7 @@ const useMechRecAssemblyMon = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [state.selectedPlanCtlg]); - return [state, setState, selectPlan, unselectPlan]; + return [state, setState, selectPlanCtlg, unselectPlanCtlg]; }; //Хук для информации по производственным составам @@ -109,6 +111,8 @@ const useCostProductComposition = nProdPlan => { init: false, showPlanList: false, products: [], + productsLoaded: false, + model: null, selectedProduct: null }); @@ -124,7 +128,13 @@ const useCostProductComposition = nProdPlan => { respArg: "COUT", isArray: name => name === "XFCPRODCMP" }); - setCostProductComposition(pv => ({ ...pv, init: true, products: [...(data?.XFCPRODCMP || [])], productsLoaded: true })); + setCostProductComposition(pv => ({ + ...pv, + init: true, + products: [...(data?.XFCPRODCMP || [])], + productsLoaded: true, + model: data?.BMODEL + })); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [costProductComposition.init, executeStored]); @@ -138,204 +148,53 @@ const useCostProductComposition = nProdPlan => { return [costProductComposition, setCostProductComposition]; }; -//Хук для таблицы маршрутных листов -const useCostRouteLists = (plan, product) => { +//Хук для таблицы детализации изделия +const useProductDetailsTable = (plan, product, orders, pageNumber, stored) => { + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(true); + //Собственное состояние - таблица данных - const [costRouteLists, setCostRouteLists] = useState({ - dataLoaded: false, + const [data, setData] = useState({ columnsDef: [], - orders: null, rows: [], - reload: true, - pageNumber: 1, - morePages: true, - selectedProduct: null + morePages: true }); //Подключение к контексту взаимодействия с сервером const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); - //Загрузка данных таблицы с сервера - const loadData = useCallback( - async () => { - if (costRouteLists.reload) { + //Загрузка данных при изменении зависимостей + useEffect(() => { + const loadData = async () => { + try { + setLoading(true); const data = await executeStored({ - stored: "PKG_P8PANELS_MECHREC.FCROUTLST_MON_DG_GET", + stored, args: { NPRODCMPSP: product, NFCPRODPLAN: plan, - CORDERS: { VALUE: object2Base64XML(costRouteLists.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - NPAGE_NUMBER: costRouteLists.pageNumber, + CORDERS: { VALUE: object2Base64XML(orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + NPAGE_NUMBER: pageNumber, NPAGE_SIZE: DATA_GRID_PAGE_SIZE, - NINCLUDE_DEF: costRouteLists.dataLoaded ? 0 : 1 + NINCLUDE_DEF: 1 }, respArg: "COUT" }); - setCostRouteLists(pv => ({ + setData(pv => ({ ...pv, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef, - rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])], - dataLoaded: true, - reload: false, - morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE + rows: pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])], + morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE })); + } finally { + setLoading(false); } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [costRouteLists.reload, costRouteLists.orders, costRouteLists.dataLoaded, costRouteLists.pageNumber, executeStored, SERV_DATA_TYPE_CLOB] - ); + }; + if (plan && product) loadData(); + }, [plan, product, orders, pageNumber, stored, executeStored, SERV_DATA_TYPE_CLOB]); - //При изменении продукта - useEffect(() => { - //Если продукт указан - if (product) { - //Принудительно обновляем состояние - setCostRouteLists(pv => ({ - ...pv, - dataLoaded: false, - columnsDef: [], - orders: null, - rows: [], - reload: true, - pageNumber: 1, - morePages: true, - selectedProduct: null - })); - //Загружаем данные с учетом выбранного продукта - loadData(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [product]); - - //При необходимости обновить данные таблицы - useEffect(() => { - //Если продукт указан и необходимо стандартное обновление - if (product) { - loadData(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [costRouteLists.reload, loadData]); - - //При изменении плана - useEffect(() => { - setCostRouteLists(pv => ({ - ...pv, - dataLoaded: false, - columnsDef: [], - orders: null, - rows: [], - reload: true, - pageNumber: 1, - morePages: true, - selectedProduct: null - })); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [plan]); - - return [costRouteLists, setCostRouteLists]; + //Вернём данные + return { data, isLoading }; }; -//Хук для таблицы комплектовочных ведомостей -const useCostDeliverySheets = (plan, product) => { - //Собственное состояние - таблица данных - const [costDeliverySheets, setCostDeliverySheets] = useState({ - dataLoaded: false, - columnsDef: [], - orders: null, - rows: [], - reload: true, - pageNumber: 1, - morePages: true, - selectedProduct: null - }); - - //Подключение к контексту взаимодействия с сервером - const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); - - //Загрузка данных таблицы с сервера - const loadData = useCallback( - async () => { - if (costDeliverySheets.reload) { - const data = await executeStored({ - stored: "PKG_P8PANELS_MECHREC.FCDELIVSH_DG_GET", - args: { - NPRODCMPSP: product, - NFCPRODPLAN: plan, - CORDERS: { VALUE: object2Base64XML(costDeliverySheets.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, - NPAGE_NUMBER: costDeliverySheets.pageNumber, - NPAGE_SIZE: DATA_GRID_PAGE_SIZE, - NINCLUDE_DEF: costDeliverySheets.dataLoaded ? 0 : 1 - }, - respArg: "COUT" - }); - setCostDeliverySheets(pv => ({ - ...pv, - columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef, - rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])], - dataLoaded: true, - reload: false, - morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE - })); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - costDeliverySheets.reload, - costDeliverySheets.orders, - costDeliverySheets.dataLoaded, - costDeliverySheets.pageNumber, - executeStored, - SERV_DATA_TYPE_CLOB - ] - ); - - //При изменении продукта - useEffect(() => { - //Если продукт указан - if (product) { - //Принудительно обновляем состояние - setCostDeliverySheets(pv => ({ - ...pv, - dataLoaded: false, - columnsDef: [], - orders: null, - rows: [], - reload: true, - pageNumber: 1, - morePages: true - })); - //Загружаем данные с учетом выбранного продукта - loadData(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [product]); - - //При необходимости обновить данные таблицы - useEffect(() => { - //Если продукт указан и необходимо стандартное обновление - if (product) { - loadData(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [costDeliverySheets.reload, loadData]); - - //При изменении плана - useEffect(() => { - setCostDeliverySheets(pv => ({ - ...pv, - dataLoaded: false, - columnsDef: [], - orders: null, - rows: [], - reload: true, - pageNumber: 1, - morePages: true, - selectedProduct: null - })); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [plan]); - - return [costDeliverySheets, setCostDeliverySheets]; -}; - -export { useMechRecAssemblyMon, useCostProductComposition, useCostRouteLists, useCostDeliverySheets }; +export { useMechRecAssemblyMon, useCostProductComposition, useProductDetailsTable }; diff --git a/app/panels/mech_rec_assembly_mon/blocks/cardBlock.js b/app/panels/mech_rec_assembly_mon/blocks/cardBlock.js deleted file mode 100644 index fc1d05d..0000000 --- a/app/panels/mech_rec_assembly_mon/blocks/cardBlock.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - Парус 8 - Панели мониторинга - ПУП - Мониторинг сборки изделий - Панель мониторинга: Информация об объекте -*/ - -//--------------------- -//Подключение библиотек -//--------------------- - -import React from "react"; //Классы React -import PropTypes from "prop-types"; //Контроль свойств компонента -import { Typography, Box, ImageList, ImageListItem } from "@mui/material"; //Интерфейсные элементы -import { ProgressBox } from "../elements/progressBox"; //Блок информации по прогрессу объекта - -//--------- -//Константы -//--------- - -//Стили -const STYLES = { - PLAN_INFO: { - display: "flex", - justifyContent: "center", - alignItems: "center", - flexDirection: "column", - gap: "24px", - border: "1px solid", - borderRadius: "25px" - } -}; - -//------------------------------------ -//Вспомогательные функции и компоненты -//------------------------------------ - -//Картинка объекта -const CardImage = ({ card }) => { - return ( - - - - {"Image - {/* {"Image */} - - - - ); -}; - -//Контроль свойств - Заголовок первого уровня -CardImage.propTypes = { - card: PropTypes.object -}; - -//----------- -//Тело модуля -//----------- - -//Информация об объекте -const CardBlock = ({ card, handleCardClick }) => { - return ( - handleCardClick(card)}> - - - - Номер борта - - {card.SNUMB} - - - - - Год выпуска: - - - {card.NYEAR} - - - - ); -}; - -//Контроль свойств - Заголовок первого уровня -CardBlock.propTypes = { - card: PropTypes.object, - handleCardClick: PropTypes.func -}; - -//---------------- -//Интерфейс модуля -//---------------- - -export { CardBlock }; diff --git a/app/panels/mech_rec_assembly_mon/blocks/cardDetail.js b/app/panels/mech_rec_assembly_mon/blocks/cardDetail.js deleted file mode 100644 index 3304045..0000000 --- a/app/panels/mech_rec_assembly_mon/blocks/cardDetail.js +++ /dev/null @@ -1,289 +0,0 @@ -/* - Парус 8 - Панели мониторинга - ПУП - Мониторинг сборки изделий - Панель мониторинга: Детализация по объекту -*/ - -//--------------------- -//Подключение библиотек -//--------------------- - -import React from "react"; //Классы React -import PropTypes from "prop-types"; //Контроль свойств компонента -import { Box, Grid, Container, Button, Typography } from "@mui/material"; //Интерфейсные элементы -import { ProgressBox } from "../elements/progressBox"; //Блок информации по прогрессу объекта -import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../../components/p8p_data_grid"; //Таблица данных -import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../config_wrapper"; //Подключение компонентов к настройкам приложения -import { useCostProductComposition, useCostRouteLists, useCostDeliverySheets } from "../backend"; //Компоненты панели - -//--------- -//Константы -//--------- - -//Стили -const STYLES = { - TABLE_INFO_MAIN: { - display: "flex", - justifyContent: "center", - alignItems: "center", - flexDirection: "column", - border: "1px solid", - borderRadius: "25px", - height: "35vh" - }, - TABLE_INFO_SUB: { - margin: "21.6px 0px", - maxHeight: "100%", - overflow: "auto", - textAlign: "center", - width: "100%" - }, - DETAIL_INFO: { - display: "flex", - justifyContent: "space-around", - alignItems: "center", - border: "1px solid", - borderRadius: "25px", - height: "17vh" - }, - PRODUCT_SELECTOR: { - display: "flex", - justifyContent: "center", - alignItems: "center", - flexDirection: "column", - border: "1px solid", - borderRadius: "25px", - height: "53vh", - marginTop: "16px" - }, - PLAN_INFO_MAIN: { - display: "flex", - flexDirection: "column", - gap: "16px" - }, - PLAN_INFO_SUB: { - display: "flex", - justifyContent: "space-between", - width: "280px", - borderBottom: "1px solid" - } -}; - -//------------------------------------ -//Вспомогательные функции и компоненты -//------------------------------------ - -//Информация об объекте -const CardDetailInfo = ({ cardInfo }) => { - return ( - <> - - - - Номер борта: - - {cardInfo.SNUMB} - - - - Год выпуска: - - {cardInfo.NYEAR} - - - - - ); -}; - -//Контроль свойств - Информация об объекте -CardDetailInfo.propTypes = { - cardInfo: PropTypes.object -}; - -//Детали объекта -const CardSelector = ({ products, setCostProductComposition }) => { - //При выборе детали в SVG - const handleProductClick = product => { - setCostProductComposition(pv => ({ ...pv, selectedProduct: product })); - }; - - return ( - <> - - {products.map(el => ( - - ))} - - - ); -}; - -//Контроль свойств - Детали объекта -CardSelector.propTypes = { - products: PropTypes.array, - setCostProductComposition: PropTypes.func -}; - -//Генерация представления ячейки заголовка -const headCellRender = ({ columnDef }) => { - //Описываем общий стиль - let cellStyle = { padding: "2px 5px", fontSize: "12px", textAlign: "center", lineHeight: "1rem" }; - let stackProps = { justifyContent: "center" }; - //Дополнительные свойства - switch (columnDef.name) { - case "NREMN_LABOUR": - //Добавляем максимальную ширину - cellStyle = { ...cellStyle, maxWidth: "90px" }; - break; - case "NDEFICIT": - //Добавляем максимальную ширину - cellStyle = { ...cellStyle, maxWidth: "55px" }; - break; - case "NAPPLICABILITY": - //Добавляем максимальную ширину - cellStyle = { ...cellStyle, maxWidth: "90px" }; - break; - default: - break; - } - return { - stackProps, - cellStyle - }; -}; - -//Генерация заливки строки исходя от значений -const dataCellRender = ({ row, columnDef }) => { - //Описываем общий стиль - let cellStyle = { padding: "2px 5px", fontSize: "12px" }; - //Для всех кроме содержания и номенклатуры добавляем выравнивание - switch (columnDef.name) { - case "SOPERATION": - break; - case "SNOMEN": - break; - default: - //Добавляем выравнивание - cellStyle = { ...cellStyle, textAlign: "center" }; - break; - } - return { - cellStyle, - data: row[columnDef] - }; -}; - -//----------- -//Тело модуля -//----------- - -//Детализация по объекту -const CardDetail = ({ card, handleBackClick }) => { - //Собственное состояние - данные производственных составов SVG - const [costProductComposition, setCostProductComposition] = useCostProductComposition(card.NRN); - //Собственное состояние - таблица данных маршрутных листов - const [costRouteLists, setCostRouteLists] = useCostRouteLists(card.NRN, costProductComposition.selectedProduct); - //Собственное состояние - таблица данных комплектовочных ведомостей - const [сostDeliverySheets, setСostDeliverySheets] = useCostDeliverySheets(card.NRN, costProductComposition.selectedProduct); - - //При изменении состояния сортировки маршрутных листов - const costRouteListsOrderChanged = ({ orders }) => setCostRouteLists(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true })); - - //При изменении количества отображаемых страниц маршрутных листов - const costRouteListsPagesCountChanged = () => setCostRouteLists(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); - - //При изменении состояния сортировки комплектовочных ведомостей - const СostDeliverySheetsOrderChanged = ({ orders }) => setСostDeliverySheets(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true })); - - //При изменении количества отображаемых страниц комплектовочных ведомостей - const СostDeliverySheetsPagesCountChanged = () => setСostDeliverySheets(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); - - return ( - - - - - - - {!costRouteLists.dataLoaded ? ( - Выберите агрегат самолёта, чтобы увидеть информацию - ) : costRouteLists.rows.length === 0 ? ( - Нет данных по МК - ) : ( - <> - Маршрутная карта - - - )} - - - - - {!сostDeliverySheets.dataLoaded ? ( - Выберите агрегат самолёта, чтобы увидеть информацию - ) : сostDeliverySheets.rows.length === 0 ? ( - Нет данных по КВ - ) : ( - <> - Дефицит по КВ - - - )} - - - - - - - - - - - - - - ); -}; - -//Контроль свойств - Детализация по объекту -CardDetail.propTypes = { - card: PropTypes.object, - handleBackClick: PropTypes.func -}; - -//---------------- -//Интерфейс модуля -//---------------- - -export { CardDetail }; diff --git a/app/panels/mech_rec_assembly_mon/components/plan_detail.js b/app/panels/mech_rec_assembly_mon/components/plan_detail.js new file mode 100644 index 0000000..7068b2a --- /dev/null +++ b/app/panels/mech_rec_assembly_mon/components/plan_detail.js @@ -0,0 +1,324 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Мониторинг сборки изделий + Панель мониторинга: Детализация по объекту +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useEffect, useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, Grid, Container, Button, Typography, Icon, Stack, IconButton } from "@mui/material"; //Интерфейсные элементы +import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../../components/p8p_data_grid"; //Таблица данных +import { P8PSVG } from "../../../components/p8p_svg"; //Интерактивные изображения +import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../config_wrapper"; //Подключение компонентов к настройкам приложения +import { useCostProductComposition, useProductDetailsTable } from "../backend"; //Взаимодействие с сервером +import { ProgressBox } from "./progress_box"; //Информация по прогрессу объекта + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + BOX_INFO_MAIN: { + border: "1px solid", + borderRadius: "25px", + height: "35vh" + }, + BOX_INFO_SUB: isMessage => ({ + overflow: "hidden", + textAlign: "center", + width: "100%", + height: "100%", + display: "flex", + flexDirection: "column", + justifyContent: isMessage ? "center" : "flex-start", + paddingLeft: "5px", + paddingRight: "5px", + ...(isMessage ? { padding: "5px" } : { paddingTop: "10px" }) + }), + DETAIL_INFO: { + display: "flex", + justifyContent: "space-around", + alignItems: "center", + border: "1px solid", + borderRadius: "25px", + height: "17vh" + }, + PRODUCT_SELECTOR_CONTAINER: { + display: "flex", + justifyContent: "center", + alignItems: "center", + flexDirection: "column", + border: "1px solid", + borderRadius: "25px", + height: "53vh", + marginTop: "16px" + }, + PRODUCT_SELECTOR_MODEL: { width: "70%" }, + PLAN_INFO_MAIN: { + display: "flex", + flexDirection: "column", + gap: "16px" + }, + PLAN_INFO_SUB: { + display: "flex", + justifyContent: "space-between", + width: "280px", + borderBottom: "1px solid" + }, + TABLE_DETAILS: { height: "260px" }, + TABLE_DETAILS_HEADER_CELL: maxWidth => ({ + padding: "2px 2px", + fontSize: "11px", + textAlign: "center", + lineHeight: "1rem", + ...(maxWidth ? { maxWidth } : {}) + }), + TABLE_DETAILS_DATA_CELL: textAlign => ({ padding: "2px 2px", fontSize: "11px", ...(textAlign ? { textAlign } : {}) }), + CARD_DETAILS_CONTAINER: { minWidth: "1200px", maxWidth: "1400px" }, + CARD_DETAILS_NAVIGATION_STACK: { width: "100%" } +}; + +//------------------------------------ +//Вспомогательные функции и компоненты +//------------------------------------ + +//Информация о плане +const PlanInfo = ({ plan }) => { + return ( + <> + + + + Номер борта: + + {plan.SNUMB} + + + + Год выпуска: + + {plan.NYEAR} + + + + + ); +}; + +//Контроль свойств - Информация о плане +PlanInfo.propTypes = { + plan: PropTypes.object +}; + +//Модель выпуска плана +const PlanProductCompositionModel = ({ model, products, setCostProductComposition }) => { + //При выборе детали на модели + const handleProductClick = ({ item }) => { + const product = products.find(p => p.SMODEL_ID == item.id); + if (product) setCostProductComposition(pv => ({ ...pv, selectedProduct: { ...product } })); + }; + + //Генерация содержимого + return ( + <> + + {model ? ( + ({ id: p.SMODEL_ID, backgroundColor: p.SMODEL_BG_COLOR || "red", desc: p.SNAME, title: p.SNAME }))} + fillOpacity={"0.3"} + onItemClick={handleProductClick} + /> + ) : ( + Модель изделия не загружена + )} + + + ); +}; + +//Контроль свойств - Модель выпуска плана +PlanProductCompositionModel.propTypes = { + model: PropTypes.any, + products: PropTypes.array, + setCostProductComposition: PropTypes.func +}; + +//Генерация представления ячейки заголовка +const headCellRender = ({ columnDef }) => ({ + stackProps: { justifyContent: "center" }, + cellStyle: STYLES.TABLE_DETAILS_HEADER_CELL( + ["NREMN_LABOUR", "NAPPLICABILITY"].includes(columnDef.name) ? "90px" : ["NDEFICIT"].includes(columnDef.name) ? "55px" : null + ) +}); + +//Генерация заливки строки исходя от значений +const dataCellRender = ({ row, columnDef }) => ({ + cellStyle: STYLES.TABLE_DETAILS_DATA_CELL(["SOPERATION", "SNOMEN"].includes(columnDef.name) ? null : "center"), + data: row[columnDef] +}); + +//Таблица детализации изделия +const ProductDetailsTable = ({ plan, product, stored, noProductMessage, noDataFoundMessage, title }) => { + //Собственное состояние + const [state, setState] = useState({ plan: null, product: null, orders: null, pageNumber: 1 }); + + //Собственное состояние - данные таблицы + const { data, isLoading } = useProductDetailsTable(state.plan, state.product, state.orders, state.pageNumber, stored); + + //При изменении состояния сортировки + const handleOrderChanged = ({ orders }) => setState(pv => ({ ...pv, orders: [...orders], pageNumber: 1 })); + + //При изменении количества отображаемых страниц + const handlePagesCountChanged = () => setState(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 })); + + //При изменении изделия + useEffect(() => { + setState(pv => ({ ...pv, plan, product, orders: null, pageNumber: 1 })); + }, [product, plan]); + + //Генерация содержимого + return ( + + {!product ? ( + {noProductMessage} + ) : isLoading ? null : data.rows.length === 0 ? ( + {noDataFoundMessage} + ) : ( + <> + + {title} + + + + )} + + ); +}; + +//Контроль свойств - Таблица детализации изделия +ProductDetailsTable.propTypes = { + plan: PropTypes.number.isRequired, + product: PropTypes.number, + stored: PropTypes.string.isRequired, + noProductMessage: PropTypes.string.isRequired, + noDataFoundMessage: PropTypes.string.isRequired, + title: PropTypes.string.isRequired +}; + +//----------- +//Тело модуля +//----------- + +//Детализация по объекту +const PlanDetail = ({ plan, disableNavigatePrev = false, disableNavigateNext = false, onNavigate, onBack }) => { + //Собственное состояние - данные производственных составов SVG + const [costProductComposition, setCostProductComposition] = useCostProductComposition(plan.NRN); + + //Формируем представление + return ( + + + + + (onNavigate ? onNavigate(-1) : null)}> + navigate_before + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (onNavigate ? onNavigate(1) : null)}> + navigate_next + + + + + + ); +}; + +//Контроль свойств - Детализация по объекту +PlanDetail.propTypes = { + plan: PropTypes.object, + disableNavigatePrev: PropTypes.bool, + disableNavigateNext: PropTypes.bool, + onNavigate: PropTypes.func, + onBack: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { PlanDetail }; diff --git a/app/panels/mech_rec_assembly_mon/components/plans_list.js b/app/panels/mech_rec_assembly_mon/components/plans_list.js new file mode 100644 index 0000000..7debec8 --- /dev/null +++ b/app/panels/mech_rec_assembly_mon/components/plans_list.js @@ -0,0 +1,83 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Мониторинг сборки изделий + Компонент: Список планов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import { Container, Grid, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы +import PropTypes from "prop-types"; //Контроль свойств компонента +import { PlansListItem } from "./plans_list_item"; //Элемент списка планов + +//--------- +//Константы +//--------- + +//Количество одновременно отображаемых элементов списка по умолчанию +const DEFAULT_PAGE_SIZE = 5; + +//Стили +const STYLES = { + PLAN_DOCUMENTS_LIST: { minWidth: "1024px" } +}; + +//----------- +//Тело модуля +//----------- + +//Список планов +const PlansList = ({ plans, pageSize = DEFAULT_PAGE_SIZE, onItemClick }) => { + //Состояние прокрутки списка отображаемых планов + const [scroll, setScroll] = useState(0); + + //Отработка нажатия на прокрутку списка планов влево + const handleScrollLeft = () => setScroll(pv => (pv <= 1 ? 0 : pv - 1)); + + //Отработка нажатия на прокрутку списка планов вправо + const handleScrollRight = () => setScroll(pv => (pv + pageSize >= plans.length ? pv : pv + 1)); + + //Сборка представления + return ( + + + + + navigate_before + + + {plans.map((el, i) => + i >= scroll && i < scroll + pageSize ? ( + + (onItemClick ? onItemClick(card, cardIndex) : null)} + /> + + ) : null + )} + + = plans.length}> + navigate_next + + + + + ); +}; + +//Контроль свойств - Список планов +PlansList.propTypes = { + plans: PropTypes.arrayOf(PropTypes.object), + pageSize: PropTypes.number, + onItemClick: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { PlansList }; diff --git a/app/panels/mech_rec_assembly_mon/components/plans_list_item.js b/app/panels/mech_rec_assembly_mon/components/plans_list_item.js new file mode 100644 index 0000000..d01e0be --- /dev/null +++ b/app/panels/mech_rec_assembly_mon/components/plans_list_item.js @@ -0,0 +1,108 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Мониторинг сборки изделий + Компонент: Элемент списка планов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Typography, Box, ImageList, ImageListItem, Icon } from "@mui/material"; //Интерфейсные элементы +import { ProgressBox } from "./progress_box"; //Информация по прогрессу объекта + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + CONTAINER: { + display: "flex", + justifyContent: "center", + alignItems: "center", + flexDirection: "column", + gap: "24px", + border: "1px solid", + borderRadius: "25px", + cursor: "pointer" + }, + IMAGE_BOX: { width: "180px", height: "180px", alignItems: "center", justifyContent: "center", display: "flex" }, + IMAGE_LIST_ITEM: { textAlign: "center" }, + IMAGE_IMG: { width: "160px" } +}; + +//------------------------------------ +//Вспомогательные функции и компоненты +//------------------------------------ + +//Изображение для элемента +const PlansListItemImage = ({ card }) => { + return ( + + + + {card["BIMAGE"] ? ( + + ) : ( + construction + )} + + + + ); +}; + +//Контроль свойств - Изображение для элемента +PlansListItemImage.propTypes = { + card: PropTypes.object +}; + +//----------- +//Тело модуля +//----------- + +//Элемент списка планов +const PlansListItem = ({ card, cardIndex, onClick }) => { + return ( + (onClick ? onClick(card, cardIndex) : null)}> + + + + Номер борта + + {card.SNUMB} + + + + + Год выпуска: + + + {card.NYEAR} + + + + ); +}; + +//Контроль свойств - Элемент списка планов +PlansListItem.propTypes = { + card: PropTypes.object, + cardIndex: PropTypes.number, + onClick: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { PlansListItem }; diff --git a/app/panels/mech_rec_assembly_mon/components/progress_box.js b/app/panels/mech_rec_assembly_mon/components/progress_box.js new file mode 100644 index 0000000..169ff90 --- /dev/null +++ b/app/panels/mech_rec_assembly_mon/components/progress_box.js @@ -0,0 +1,75 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Мониторинг сборки изделий + Компонент: Информация по прогрессу объекта +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Typography, Box } from "@mui/material"; //Интерфейсные элементы + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + PROGRESS_BOX: (width, height) => ({ + display: "flex", + justifyContent: "center", + alignItems: "center", + flexDirection: "column", + margin: "0px 32px", + borderRadius: "50%", + ...(width ? { width } : {}), + ...(height ? { height } : {}) + }) +}; + +//----------- +//Тело модуля +//----------- + +//Информация по прогрессу объекта +const ProgressBox = ({ progress, detail, width, height, progressVariant, detailVariant }) => { + //Определяем цвет тени + let boxShadow = "0 0 30px #d3d3d3"; + switch (true) { + case progress >= 70: + boxShadow = "0 0 30px #21d21e66"; + break; + case progress >= 40: + boxShadow = "0 0 30px #fddd3566"; + break; + case progress >= 10: + boxShadow = "0 0 30px #ea5c4966"; + break; + } + + //Возвращаем содержимое + return ( + + {`${progress}%`} + {detail} + + ); +}; + +//Контроль свойств - Информация по прогрессу объекта +ProgressBox.propTypes = { + progress: PropTypes.number, + detail: PropTypes.string, + width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + progressVariant: PropTypes.string, + detailVariant: PropTypes.string +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { ProgressBox }; diff --git a/app/panels/mech_rec_assembly_mon/elements/progressBox.js b/app/panels/mech_rec_assembly_mon/elements/progressBox.js deleted file mode 100644 index ea8b511..0000000 --- a/app/panels/mech_rec_assembly_mon/elements/progressBox.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - Парус 8 - Панели мониторинга - ПУП - Мониторинг сборки изделий - Панель мониторинга: Блок информации по прогрессу объекта -*/ - -//--------------------- -//Подключение библиотек -//--------------------- - -import React from "react"; //Классы React -import PropTypes from "prop-types"; //Контроль свойств компонента -import { Typography, Box } from "@mui/material"; //Интерфейсные элементы - -//--------- -//Константы -//--------- - -//Стили -const STYLES = { - PROGRESS_INFO: { - display: "flex", - justifyContent: "center", - alignItems: "center", - flexDirection: "column", - margin: "0px 32px", - borderRadius: "50%" - } -}; - -//------------------------------------ -//Вспомогательные функции и компоненты -//------------------------------------ - -//----------- -//Тело модуля -//----------- - -//Детализация по объекту - -//Блок информации по прогрессу объекта -const ProgressBox = ({ prms }) => { - //Инициализируем цвет тени - let boxShadow = null; - //Определяем цвет тени - switch (true) { - case prms.NPROGRESS >= 70: - boxShadow = "0 0 30px #21d21e66"; - break; - case prms.NPROGRESS >= 40: - boxShadow = "0 0 30px #fddd3566"; - break; - case prms.NPROGRESS >= 10: - boxShadow = "0 0 30px #ea5c4966"; - break; - default: - boxShadow = "0 0 30px #d3d3d3"; - } - //Возвращаем блок - return ( - - {`${prms.NPROGRESS}%`} - {prms.SDETAIL} - - ); -}; - -//Контроль свойств - Блок информации по прогрессу объекта -ProgressBox.propTypes = { - prms: PropTypes.object -}; - -//---------------- -//Интерфейс модуля -//---------------- - -export { ProgressBox }; diff --git a/app/panels/mech_rec_assembly_mon/mech_rec_assembly_mon.js b/app/panels/mech_rec_assembly_mon/mech_rec_assembly_mon.js index 2d51675..18e560e 100644 --- a/app/panels/mech_rec_assembly_mon/mech_rec_assembly_mon.js +++ b/app/panels/mech_rec_assembly_mon/mech_rec_assembly_mon.js @@ -9,26 +9,12 @@ import React, { useState, useContext } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента -import { - Drawer, - Fab, - Box, - List, - ListItemButton, - ListItemText, - Typography, - Grid, - TextField, - FormGroup, - FormControlLabel, - Checkbox, - Container -} from "@mui/material"; //Интерфейсные элементы +import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField, FormGroup, FormControlLabel, Checkbox } from "@mui/material"; //Интерфейсные элементы import { ThemeProvider } from "@mui/material/styles"; //Подключение темы import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений -import { CardBlock } from "./blocks/cardBlock"; //Информация об объекте -import { CardDetail } from "./blocks/cardDetail"; //Детализация по объекту -import { theme } from "./styles/themes.js"; //Стиль темы +import { PlansList } from "./components/plans_list"; //Список планов +import { PlanDetail } from "./components/plan_detail"; //Детали плана +import { theme } from "./styles/themes"; //Стиль темы import { useFilteredPlanCtlgs } from "./hooks"; //Вспомогательные хуки import { useMechRecAssemblyMon } from "./backend"; //Хук корневой панели мониторинга сборки изделий @@ -49,7 +35,8 @@ const STYLES = { display: "inline-block", flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box" } - } + }, + PLANS_LIST_BOX: { paddingTop: "20px" } }; //------------------------------------ @@ -131,11 +118,18 @@ PlanCtlgsList.propTypes = { //Корневая панель мониторинга сборки изделий const MechRecAssemblyMon = () => { //Собственное состояние - const [state, setState, selectPlan, unselectPlan] = useMechRecAssemblyMon(); + const [state, setState, selectPlanCtlg, unselectPlanCtlg] = useMechRecAssemblyMon(); - //Состояние для фильтра каталогов + //Состояние фильтра каталогов const [filter, setFilter] = useState({ ctlgName: "", haveDocs: false }); + //Состояние навигации по карточкам детализации + const [planDetailNavigation, setPlanDetailNavigation] = useState({ + disableNavigatePrev: false, + disableNavigateNext: false, + currentPlanIndex: 0 + }); + //Массив отфильтрованных каталогов const filteredPlanCtgls = useFilteredPlanCtlgs(state.planCtlgs, filter); @@ -143,24 +137,38 @@ const MechRecAssemblyMon = () => { const { InlineMsgInfo } = useContext(MessagingСtx); //Обработка нажатия на элемент в списке каталогов планов - const handleProjectClick = project => { - if (state.selectedPlanCtlg.NRN != project.NRN) selectPlan(project); - else unselectPlan(); + const handlePlanCtlgClick = planCtlg => { + if (state.selectedPlanCtlg.NRN != planCtlg.NRN) selectPlanCtlg(planCtlg); + else unselectPlanCtlg(); }; - //Обработка нажатия на карточку объекта - const handleCardClick = plan => { + //Перемещение к нужному плану + const navigateToPlan = planIndex => { + if (planIndex < 0) planIndex = 0; + if (planIndex > state.plans.length - 1) planIndex = state.plans.length - 1; setState(pv => ({ ...pv, - selectedPlan: { NRN: plan.NRN, SNUMB: plan.SNUMB, NPROGRESS: plan.NPROGRESS, SDETAIL: plan.SDETAIL, NYEAR: plan.NYEAR } + selectedPlan: { ...state.plans[planIndex] } + })); + setPlanDetailNavigation(pv => ({ + ...pv, + disableNavigatePrev: planIndex == 0 ? true : false, + disableNavigateNext: planIndex == state.plans.length - 1 ? true : false, + currentPlanIndex: planIndex })); }; + //Обработка нажатия на документ плана + const handlePlanClick = (plan, planIndex) => navigateToPlan(planIndex); + //Обработка нажатия на кнопку "Назад" - const handleBackClick = () => { - setState(pv => ({ ...pv, selectedPlan: { NRN: null, SNUMB: null, NPROGRESS: null, SDETAIL: null, NYEAR: null } })); + const handlePlanDetailBackClick = () => { + setState(pv => ({ ...pv, selectedPlan: {} })); }; + //Обработка навигации из карточки с деталями плана + const handlePlanDetailNavigateClick = direction => navigateToPlan(planDetailNavigation.currentPlanIndex + direction); + //Генерация содержимого return ( @@ -179,34 +187,30 @@ const MechRecAssemblyMon = () => { selectedPlanCtlg={state.selectedPlanCtlg.NRN} filter={filter} setFilter={setFilter} - onClick={handleProjectClick} + onClick={handlePlanCtlgClick} /> {state.init == true ? ( state.selectedPlanCtlg.NRN ? ( <> - - {`${state.selectedPlanCtlg.SNAME} на ${state.selectedPlanCtlg.NMIN_YEAR}г. - ${state.selectedPlanCtlg.NMAX_YEAR}г.`} + + {`${state.selectedPlanCtlg.SNAME} ${ + state.selectedPlanCtlg.NMIN_YEAR ? `с ${state.selectedPlanCtlg.NMIN_YEAR} г` : "" + } ${state.selectedPlanCtlg.NMAX_YEAR ? `по ${state.selectedPlanCtlg.NMAX_YEAR}` : ""}`} {state.plansLoaded == true ? ( state.selectedPlan.NRN ? ( - + ) : ( - - - {state.plans.map(el => ( - = 5 ? 2.4 : 12 / state.plans.length} - key={el.NRN} - display="flex" - justifyContent="center" - > - - - ))} - - + + + ) ) : null} diff --git a/app/panels/mech_rec_assembly_mon/styles/themes.js b/app/panels/mech_rec_assembly_mon/styles/themes.js index 37818ee..277aeae 100644 --- a/app/panels/mech_rec_assembly_mon/styles/themes.js +++ b/app/panels/mech_rec_assembly_mon/styles/themes.js @@ -4,6 +4,7 @@ import { createTheme } from "@mui/material/styles"; //Интерфейсные const theme = createTheme({ palette: { text: { + title: { fontColor: "rgba(0, 0, 0, 0.65)" }, secondary: { fontColor: "rgba(0, 0, 0, 0.298)" } } },