342 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | ||
|     Парус 8 - Панели мониторинга - ТОиР - Выполнение работ
 | ||
|     Панель мониторинга: Корневая панель выполнения работ
 | ||
| */
 | ||
| 
 | ||
| //---------------------
 | ||
| //Подключение библиотек
 | ||
| //---------------------
 | ||
| 
 | ||
| import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
 | ||
| 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"; //Контекст взаимодействия с сервером
 | ||
| import { ApplicationСtx } from "../../context/application"; //Контекст приложения
 | ||
| import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
 | ||
| import { headCellRender, dataCellRender, groupCellRender, DIGITS_REG_EXP, MONTH_NAME_REG_EXP, DAY_NAME_REG_EXP } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
 | ||
| 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
 | ||
|     })
 | ||
| };
 | ||
| 
 | ||
| //-----------
 | ||
| //Тело модуля
 | ||
| //-----------
 | ||
| 
 | ||
| //Корневая панель выполнения работ
 | ||
| const EqsPrfrm = () => {
 | ||
|     //Собственное состояние - таблица данных
 | ||
|     const [dataGrid, setDataGrid] = useState({
 | ||
|         dataLoaded: false,
 | ||
|         columnsDef: [],
 | ||
|         groups: [],
 | ||
|         rows: [],
 | ||
|         fixedHeader: false,
 | ||
|         fixedColumns: 0,
 | ||
|         reload: false
 | ||
|     });
 | ||
| 
 | ||
|     //Состояние фильтра
 | ||
|     const [filter, setFilter] = useState({
 | ||
|         isOpen: false,
 | ||
|         isDefault: false,
 | ||
|         isSetByUser: false,
 | ||
|         needSave: false,
 | ||
|         values: {
 | ||
|             belong: "",
 | ||
|             prodObj: "",
 | ||
|             techServ: "",
 | ||
|             respDep: "",
 | ||
|             fromMonth: 1,
 | ||
|             fromYear: 1990,
 | ||
|             toMonth: 1,
 | ||
|             toYear: 1990
 | ||
|         }
 | ||
|     });
 | ||
| 
 | ||
|     //Состояние ячейки заголовка даты (по раскрытию/скрытию)
 | ||
|     const [activeRef, setActiveRef] = useState();
 | ||
| 
 | ||
|     //Состояние актуальности ссылки на ячейку
 | ||
|     const [refIsDeprecated, setRidFlag] = useState(true);
 | ||
| 
 | ||
|     //Подключение к контексту приложения
 | ||
|     const { pOnlineShowUnit } = useContext(ApplicationСtx);
 | ||
| 
 | ||
|     //Подключение к контексту взаимодействия с сервером
 | ||
|     const { executeStored } = useContext(BackEndСtx);
 | ||
| 
 | ||
|     //Подключение к контексту сообщений
 | ||
|     const { showMsgErr } = useContext(MessagingСtx);
 | ||
| 
 | ||
|     //Загрузка данных таблицы с сервера
 | ||
|     const loadData = useCallback(async () => {
 | ||
|         if (dataGrid.reload) {
 | ||
|             const data = await executeStored({
 | ||
|                 stored: "PKG_P8PANELS_EQUIPSRV.EQUIPSRV_GRID",
 | ||
|                 args: {
 | ||
|                     SBELONG: filter.values.belong,
 | ||
|                     SPRODOBJ: filter.values.prodObj,
 | ||
|                     STECHSERV: filter.values.techServ,
 | ||
|                     SRESPDEP: filter.values.respDep,
 | ||
|                     NFROMMONTH: filter.values.fromMonth,
 | ||
|                     NFROMYEAR: filter.values.fromYear,
 | ||
|                     NTOMONTH: filter.values.toMonth,
 | ||
|                     NTOYEAR: filter.values.toYear
 | ||
|                 },
 | ||
|                 respArg: "COUT",
 | ||
|                 attributeValueProcessor: (name, val) => (["caption", "name", "parent"].includes(name) ? undefined : val)
 | ||
|             });
 | ||
|             let cP = 0;
 | ||
|             let sP = 0;
 | ||
|             let cF = 0;
 | ||
|             let sF = 0;
 | ||
|             let properties = [];
 | ||
|             if (data.XDATA_GRID.rows != null) {
 | ||
|                 data.XDATA_GRID.rows.map(row => {
 | ||
|                     properties = [];
 | ||
|                     Object.entries(row).forEach(([key, value]) => properties.push({ name: key, data: value }));
 | ||
|                     let info2 = properties.find(element => {
 | ||
|                         return element.name === "SINFO2";
 | ||
|                     });
 | ||
|                     if (info2 != undefined) {
 | ||
|                         if (info2.data == "План") {
 | ||
|                             properties.map(p => {
 | ||
|                                 if (DAY_NAME_REG_EXP.test(p.name)) cP = cP + 1;
 | ||
|                             });
 | ||
|                         } else if (info2.data == "Факт") {
 | ||
|                             properties.map(p => {
 | ||
|                                 if (DAY_NAME_REG_EXP.test(p.name)) cF = cF + 1;
 | ||
|                             });
 | ||
|                         }
 | ||
|                     } else {
 | ||
|                         properties.map(p => {
 | ||
|                             if (MONTH_NAME_REG_EXP.test(p.name)) {
 | ||
|                                 let str = p.data;
 | ||
|                                 let m = [];
 | ||
|                                 let i = 0;
 | ||
|                                 while ((m = DIGITS_REG_EXP.exec(str)) != null) {
 | ||
|                                     if (i == 0) sP = sP + Number(m[0].replace(",", "."));
 | ||
|                                     else {
 | ||
|                                         sF = sF + Number(m[0].replace(",", "."));
 | ||
|                                     }
 | ||
|                                     i++;
 | ||
|                                 }
 | ||
|                             }
 | ||
|                         });
 | ||
|                     }
 | ||
|                 });
 | ||
|             }
 | ||
|             setDataGrid(pv => ({
 | ||
|                 ...pv,
 | ||
|                 ...data.XDATA_GRID,
 | ||
|                 columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef,
 | ||
|                 rows: [...(data.XDATA_GRID.rows || [])],
 | ||
|                 groups: [...(data.XDATA_GRID.groups || [])],
 | ||
|                 dataLoaded: true,
 | ||
|                 reload: false
 | ||
|             }));
 | ||
|         }
 | ||
|     }, [dataGrid.reload, filter, executeStored]);
 | ||
| 
 | ||
|     //Загрузка значений фильтра по умолчанию
 | ||
|     const loadDefaultFilter = useCallback(async () => {
 | ||
|         const data = await executeStored({
 | ||
|             stored: "PKG_P8PANELS_EQUIPSRV.GET_DEFAULT_FP",
 | ||
|             respArg: "COUT"
 | ||
|         });
 | ||
|         setFilter(pv => ({
 | ||
|             ...pv,
 | ||
|             values: { ...pv.values, belong: data.JURPERS, fromMonth: 1, fromYear: data.YEAR, toMonth: 12, toYear: data.YEAR },
 | ||
|             isDefault: true
 | ||
|         }));
 | ||
|     }, [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("_");
 | ||
|         let type;
 | ||
|         if (workType == "План") type = 0;
 | ||
|         else type = 1;
 | ||
|         let [year, month, day] = date.substring(1).split("_");
 | ||
|         const data = await executeStored({
 | ||
|             stored: "PKG_P8PANELS_EQUIPSRV.SELECT_EQUIPSRV",
 | ||
|             args: {
 | ||
|                 SBELONG: filter.values.belong,
 | ||
|                 SPRODOBJ: filter.values.prodObj,
 | ||
|                 STECHSERV: filter.values.techServ ? filter.values.techServ : null,
 | ||
|                 SRESPDEP: filter.values.respDep ? filter.values.respDep : null,
 | ||
|                 STECHNAME: techName,
 | ||
|                 SSRVKIND: servKind,
 | ||
|                 NYEAR: Number(year),
 | ||
|                 NMONTH: Number(month),
 | ||
|                 NDAY: day ? Number(day) : null,
 | ||
|                 NWORKTYPE: type
 | ||
|             }
 | ||
|         });
 | ||
|         if (data.NIDENT) {
 | ||
|             if (type == 0) pOnlineShowUnit({ unitCode: "EquipTechServices", inputParameters: [{ name: "in_Ident", value: data.NIDENT }] });
 | ||
|             else pOnlineShowUnit({ unitCode: "EquipRepairSheets", inputParameters: [{ name: "in_Ident", value: data.NIDENT }] });
 | ||
|         } else showMsgErr(TEXTS.NO_DATA_FOUND);
 | ||
|     };
 | ||
| 
 | ||
|     //Показать/скрыть фильтр
 | ||
|     const setFilterOpen = isOpen => setFilter(pv => ({ ...pv, isOpen }));
 | ||
| 
 | ||
|     //Установить значение фильтра
 | ||
|     const setFilterValues = values => setFilter(pv => ({ ...pv, isSetByUser: true, needSave: true, values: { ...values } }));
 | ||
| 
 | ||
|     //Отработка события скрытия/раскрытия ячейки даты
 | ||
|     const handleClick = (e, ref) => {
 | ||
|         const curCell = ref.current;
 | ||
|         if (e.target.type == "button" || e.target.offsetParent.type == "button") {
 | ||
|             setActiveRef(curCell);
 | ||
|             setRidFlag(false);
 | ||
|         }
 | ||
|     };
 | ||
| 
 | ||
|     //При необходимости обновить данные таблицы
 | ||
|     useEffect(() => {
 | ||
|         loadData();
 | ||
|     }, [loadData, dataGrid.reload]);
 | ||
| 
 | ||
|     //При изменении фильтра
 | ||
|     useEffect(() => {
 | ||
|         if (filter.isSetByUser) setDataGrid({ reload: true });
 | ||
|     }, [
 | ||
|         filter.isSetByUser,
 | ||
|         filter.values.belong,
 | ||
|         filter.values.prodObj,
 | ||
|         filter.values.techServ,
 | ||
|         filter.values.respDep,
 | ||
|         filter.values.fromMonth,
 | ||
|         filter.values.fromYear,
 | ||
|         filter.values.toMonth,
 | ||
|         filter.values.toYear
 | ||
|     ]);
 | ||
| 
 | ||
|     //При нажатии скрытии/раскрытии ячейки даты, фокус на неё
 | ||
|     useEffect(() => {
 | ||
|         if (!refIsDeprecated) {
 | ||
|             if (activeRef) {
 | ||
|                 var cellRect = activeRef.getBoundingClientRect();
 | ||
|                 window.scrollTo(window.scrollX + cellRect.left + activeRef.clientWidth / 2 - window.innerWidth / 2, 0);
 | ||
|                 setRidFlag(true);
 | ||
|             }
 | ||
|         }
 | ||
|         //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(() => {
 | ||
|         localStorage.getItem("belong") ? loadLocalFilter() : loadDefaultFilter();
 | ||
|     }, [loadDefaultFilter, loadLocalFilter]);
 | ||
| 
 | ||
|     //При открытии диалога фильтра
 | ||
|     const handleFilterClick = () => setFilterOpen(true);
 | ||
| 
 | ||
|     //При изменении фильтра в диалоге
 | ||
|     const handleFilterOk = filter => {
 | ||
|         setFilterValues(filter);
 | ||
|         setFilterOpen(false);
 | ||
|     };
 | ||
| 
 | ||
|     //При закрытии диалога фильтра
 | ||
|     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 ? (
 | ||
|                 <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>
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| //----------------
 | ||
| //Интерфейс модуля
 | ||
| //----------------
 | ||
| 
 | ||
| export { EqsPrfrm };
 |