This commit is contained in:
Dollerino 2026-04-02 13:44:02 +03:00
commit 261f2cf490
20 changed files with 15017 additions and 0 deletions

113
hauler_anl/chart_filter.js Normal file
View File

@ -0,0 +1,113 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Компонент: Фильтр отбора
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Chip, Stack, Box } from "@mui/material"; //Интерфейсные компоненты
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { hasValue } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Стили
const STYLES = {
FILTERS_STACK: {
paddingBottom: "5px",
...APP_STYLES.SCROLL,
overflowY: "auto",
alignItems: "flex-end"
},
STACK_FILTER: { maxWidth: "99vw", alignItems: "flex-end" }
};
//Графики
const CHART_NAMES = {
info: "INFO",
calcs: "CALCS"
};
//--------------------------
//Вспомогательные компоненты
//--------------------------
//Считывание наименования состояния
const getStateName = state => {
return state === 0 ? "На линии" : state === 1 ? "Ремонт" : "Не определено";
};
//Элемент фильтра
const FilterItem = ({ field, caption, value, onDelete }) => {
//При удалении фильтра
const handleDelete = () => (onDelete ? onDelete(field) : null);
//Генерация содержимого
return (
<Chip
label={
<Stack direction={"row"} alignItems={"center"}>
<strong>{caption}</strong>
{value ? `:\u00A0${value}` : null}
</Stack>
}
variant="outlined"
onDelete={handleDelete}
/>
);
};
//Контроль свойств компонента - Элемент фильтра
FilterItem.propTypes = {
field: PropTypes.string.isRequired,
caption: PropTypes.string.isRequired,
value: PropTypes.any,
onDelete: PropTypes.func
};
//---------------
//Тело компонента
//---------------
//Фильтр отбора
const ChartFilter = ({ chartName, filter, onFilterChange }) => {
//При удалении фильтра
const handleDelete = field => onFilterChange && onFilterChange({ chartName, filter: { ...filter, [field]: null } });
//Генерация содержимого
return (
<div>
<Box>
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.STACK_FILTER}>
<Stack direction="row" spacing={1} alignItems={"center"} sx={STYLES.FILTERS_STACK}>
{chartName === CHART_NAMES.info ? (
hasValue(filter.nState) ? (
<FilterItem field={"nState"} caption={"Готовность"} value={getStateName(filter.nState)} onDelete={handleDelete} />
) : null
) : chartName === CHART_NAMES.calcs ? (
<FilterItem field={"sDepartment"} caption={"Подразделение"} value={filter.sDepartment} onDelete={handleDelete} />
) : null}
</Stack>
</Stack>
</Box>
</div>
);
};
//Контроль свойств компонента - Фильтр отбора
ChartFilter.propTypes = {
chartName: PropTypes.string,
filter: PropTypes.object.isRequired,
onFilterChange: PropTypes.func.isRequired
};
//--------------------
//Интерфейс компонента
//--------------------
export { ChartFilter };

101
hauler_anl/filter.js Normal file
View File

@ -0,0 +1,101 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Компонент: Фильтр отбора
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Chip, Stack, Icon, IconButton, Box } from "@mui/material"; //Интерфейсные компоненты
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { formatDateRF } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Стили
const STYLES = {
FILTERS_STACK: {
paddingBottom: "5px",
...APP_STYLES.SCROLL,
overflowY: "auto",
alignItems: "flex-end"
},
STACK_FILTER: { maxWidth: "99vw", alignItems: "flex-end" }
};
//--------------------------
//Вспомогательные компоненты
//--------------------------
//Элемент фильтра
const FilterItem = ({ caption, value, onClick }) => {
//При нажатии на элемент
const handleClick = () => (onClick ? onClick() : null);
//Генерация содержимого
return (
<Chip
label={
<Stack direction={"row"} alignItems={"center"}>
<strong>{caption}</strong>
{value ? `:\u00A0${value}` : null}
</Stack>
}
variant="outlined"
onClick={handleClick}
/>
);
};
//Контроль свойств компонента - Элемент фильтра
FilterItem.propTypes = {
caption: PropTypes.string.isRequired,
value: PropTypes.any,
onClick: PropTypes.func
};
//---------------
//Тело компонента
//---------------
//Фильтр отбора
const Filter = ({ filter, onFilterOpen }) => {
//При нажатии на элемент фильтра
const handleClick = () => onFilterOpen && onFilterOpen();
//Генерация содержимого
return (
<div>
<Box>
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.STACK_FILTER}>
<IconButton onClick={handleClick}>
<Icon>filter_alt</Icon>
</IconButton>
<Stack direction="row" spacing={1} alignItems={"center"} sx={STYLES.FILTERS_STACK}>
{filter.dDate ? <FilterItem caption={"На дату"} value={formatDateRF(filter.dDate)} onClick={handleClick} /> : null}
{filter.sCustomerDept ? (
<FilterItem caption={"Транспортный участок"} value={filter.sCustomerDept} onClick={handleClick} />
) : null}
</Stack>
</Stack>
</Box>
</div>
);
};
//Контроль свойств компонента - Фильтр отбора
Filter.propTypes = {
filter: PropTypes.object.isRequired,
onFilterOpen: PropTypes.func.isRequired
};
//--------------------
//Интерфейс компонента
//--------------------
export { Filter };

187
hauler_anl/filter_dialog.js Normal file
View File

@ -0,0 +1,187 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Компонент: Диалоговое окно фильтра отбора
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, TextField, InputAdornment } from "@mui/material"; //Интерфейсные компоненты
import { useDictionary } from "./hooks/dict_hooks"; //Хуки открытий разделов
import { hasValue, formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Стили
const STYLES = {
DIALOG_ACTIONS: { justifyContent: "flex-end" },
CLOSE_BUTTON: {
position: "absolute",
right: 8,
top: 8,
color: theme => theme.palette.grey[500]
},
BOX_WITH_LEGEND: { border: "1px solid #939393", marginBottom: "1px" },
LEGEND: { textAlign: "left" }
};
//--------------------------
//Вспомогательные компоненты
//--------------------------
//Проверка возможности выполнения действия
const isActionAllow = (filter, isFiltersInit = false) => {
//Если указана "На дату", а также это не инициализация
return hasValue(filter.dDate) && !isFiltersInit;
};
//---------------
//Тело компонента
//---------------
//Диалоговое окно фильтра отбора
const FilterDialog = ({ initial, isFiltersInit, onCancel, onOk }) => {
//Собственное состояние
const [filter, setFilter] = useState({ ...initial });
//Вспомогательные функции открытия раздела
const { handleInsDepartmentOpen } = useDictionary();
//Отработка воода значения в фильтр
const handleValueChanged = e => setFilter(pv => ({ ...pv, [e.target.name]: e.target.value }));
//При изменении каталога фильтра
const handleCustomerDeptSelect = () =>
handleInsDepartmentOpen({
sCode: filter.sCustomerDept,
callBack: res => {
setFilter(pv => ({ ...pv, sCustomerDept: res.outParameters.out_CODE }));
}
});
//При загрузке текущей даты
const handleLoadCurrentDate = () => {
//Определяем текущую дату
const currentDate = formatDateJSONDateOnly(new Date());
//Устанавливаем
setFilter(pv => ({ ...pv, dDate: currentDate }));
};
//При очистке фильтра
const handleClear = () => {
setFilter({ dDate: "", sCustomerDept: "" });
};
//При закрытии диалога без изменения фильтра
const handleCancel = () => (isActionAllow(initial, isFiltersInit) ? onCancel && onCancel() : null);
//При закрытии диалога с изменением фильтра
const handleOK = () => (isActionAllow(filter) && onOk ? onOk(filter) : null);
//Генерация содержимого
return (
<div>
<Dialog open onClose={handleCancel} fullWidth maxWidth="sm">
<DialogTitle>Фильтр отбора</DialogTitle>
<IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
<Icon>close</Icon>
</IconButton>
<DialogContent>
<Box sx={{ display: "flex", flexDirection: "column", gap: "10px" }}>
<TextField
name="dDate"
type="date"
InputLabelProps={{ shrink: true }}
value={filter.dDate}
onChange={handleValueChanged}
label="На дату"
variant="standard"
fullWidth
required
error={!filter.dDate}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton aria-label={`dDate currentDate`} onClick={handleLoadCurrentDate} edge="end">
<Icon>refresh</Icon>
</IconButton>
</InputAdornment>
)
}}
/>
<TextField
name="sCustomerDept"
value={filter.sCustomerDept}
onChange={handleValueChanged}
label="Транспортный участок"
variant="standard"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton aria-label={`sCustomerDept select`} onClick={handleCustomerDeptSelect} edge="end">
<Icon>list</Icon>
</IconButton>
</InputAdornment>
)
}}
fullWidth
/>
<Box sx={STYLES.BOX_WITH_LEGEND} component="fieldset">
<legend style={STYLES.LEGEND}>Расчет КОА</legend>
<TextField
name="dDateKOAFrom"
type="date"
InputLabelProps={{ shrink: true }}
value={filter.dDateKOAFrom}
onChange={handleValueChanged}
label="Дата с"
variant="standard"
fullWidth
/>
<TextField
name="dDateKOATo"
type="date"
InputLabelProps={{ shrink: true }}
value={filter.dDateKOATo}
onChange={handleValueChanged}
label="Дата по"
variant="standard"
fullWidth
/>
</Box>
</Box>
</DialogContent>
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button variant="text" disabled={!isActionAllow(filter)} onClick={handleOK}>
Применить
</Button>
<Button variant="text" onClick={handleClear}>
Очистить
</Button>
<Button variant="text" disabled={!isActionAllow(initial, isFiltersInit)} onClick={handleCancel}>
Отмена
</Button>
</DialogActions>
</Dialog>
</div>
);
};
//Контроль свойств компонента - Диалоговое окно фильтра отбора
FilterDialog.propTypes = {
initial: PropTypes.object.isRequired,
isFiltersInit: PropTypes.bool,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//--------------------
//Интерфейс компонента
//--------------------
export { FilterDialog };

233
hauler_anl/hauler_anl.js Normal file
View File

@ -0,0 +1,233 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Панель мониторинга: Корневая панель гаражки
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useEffect, useMemo } from "react"; //Классы React
import { Box, Grid, Paper, Fab, Icon } from "@mui/material"; //Интерфейсные компоненты
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8PChart } from "../../components/p8p_chart"; //График
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_MORE_HEIGHT, P8P_DATA_GRID_FILTERS_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { FilterDialog } from "./filter_dialog"; //Диалог фильтра
import { Filter } from "./filter"; //Фильтры
import { useFilters } from "./hooks/filter_hooks"; //Хуки фильтров
import { useChartInfo, useChartCalcs, useTableATC } from "./hooks/hooks";
import { valueFormatter } from "./layouts"; //Вспомогательные функции верстки
import { ChartFilter } from "./chart_filter"; //Фильтр графика
import { hasValue } from "../../core/utils"; //Вспомогательные функции
import { useDictionary } from "./hooks/dict_hooks"; //Хуки для открытия раздела
//---------
//Константы
//---------
//Высота графиков
const CHART_HEIGHT = "300px";
//Стили
const STYLES = {
TABLE_PROJECTS: (showCharts, morePages, filters, isChartsFiltered) => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${showCharts ? CHART_HEIGHT : "0px"} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - ${
filters ? P8P_DATA_GRID_FILTERS_HEIGHT : "0px"
} - ${showCharts && isChartsFiltered ? "53px" : "0px"} - 90px)`,
maxWidth: `calc(100vw - 16px)`,
...APP_STYLES.SCROLL
}),
CHART: { maxHeight: CHART_HEIGHT, display: "flex", justifyContent: "center" },
CHART_PAPER: { height: "100%", paddingBottom: "5px" },
CHART_FAB: { position: "absolute", top: 80, left: 16 }
};
//Графики страницы
const CHART_NAMES = {
info: "INFO",
calcs: "CALCS"
};
//-----------
//Тело модуля
//-----------
//Корневая панель гаражки
const HaulerAnl = () => {
//Вспомогательные функции открытия раздела
const { handleEquipRepairSheetsOpen } = useDictionary();
//Собственное состояние - признак отображения фильтров
const [isFilterOpen, setIsFilterOpen] = useState(true);
//Состояния графиков
const [showCharts, setShowCharts] = useState(true);
//Собственное состояние - общие фильтры
const [filterValues, isFiltersLoaded, filtersInit, handleFilterChange] = useFilters();
//Собственное состояние - фильтры информации
const [filterInfo, setFilterInfo] = useState({ nState: null });
//Собственное состояние - фильтры расчетов
const [filterCalcs, setFilterCalcs] = useState({ sDepartment: null });
//Общие фильтры панели
const allFilters = useMemo(() => {
return { ...filterValues, nState: filterInfo.nState, sDepartment: filterCalcs.sDepartment };
}, [filterValues, filterInfo, filterCalcs]);
//Состояние графика информации АТС
const { chartInfo, handleReload: handleChartInfoReload } = useChartInfo({ storedArgs: allFilters });
//Состояние графика расчетов
const { chartCalcs, handleReload: handleChartCalcsReload } = useChartCalcs({ storedArgs: allFilters });
//Состояние таблицы ремонтных ведомостей
const {
dataGrid,
handleReload: handleTableATCReload,
handleFilterChanged,
handleOrderChanged,
handlePagesCountChanged
} = useTableATC({
storedArgs: allFilters
});
//При изменении фильтра в диалоге
const handleFilterOk = async filter => {
//Обновляем фильтры
await handleFilterChange({ filter });
//Если указан общий и внутренний фильтр по подразделению
if (filter.sCustomerDept && filterCalcs.sDepartment) {
//Очищаем внутренний
setFilterCalcs({ sDepartment: null, ignoreReload: true });
}
//Закрываем диалог фильтра
setIsFilterOpen(false);
};
//При закрытии диалога фильтра
const handleFilterCancel = () => setIsFilterOpen(false);
//При открытии диалога фильтра
const handleFilterDialogOpen = () => setIsFilterOpen(true);
//При нажатии на элемент графика "Информация АТС"
const handleChartInfoClick = ({ item }) => {
setFilterInfo({ nState: item.NSTATE });
};
//При нажатии на элемент графика расчетов
const handleChartCalcsClick = ({ item }) => {
//Если подразделение не соответствует текущему
if (item.SCODE !== filterCalcs.sDepartment && !filterValues.sCustomerDept) setFilterCalcs({ sDepartment: item.SCODE });
};
//При изменении фильтра графика
const handleChartFilterChange = ({ chartName, filter }) => {
//При изменении фильтров графика "Информация АТС"
if (chartName === CHART_NAMES.info) {
setFilterInfo({ ...filter });
}
//При изменении фильтров графика расчетов
if (chartName === CHART_NAMES.calcs) setFilterCalcs({ ...filter });
};
//При изменении фильтра
useEffect(() => {
//Если фильтр установлен
if (!filtersInit) {
handleChartInfoReload();
handleChartCalcsReload();
handleTableATCReload();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allFilters, filtersInit]);
//Генерация содержимого
return (
<Box p={1}>
{!filtersInit ? <Filter filter={filterValues} onFilterOpen={handleFilterDialogOpen} /> : null}
<Grid container spacing={1}>
{showCharts ? (
<>
<Grid item xs={6}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{hasValue(filterInfo.nState) ? (
<ChartFilter chartName={CHART_NAMES.info} filter={filterInfo} onFilterChange={handleChartFilterChange} />
) : null}
{chartInfo.loaded ? (
<P8PChart {...chartInfo} style={STYLES.CHART} onClick={handleChartInfoClick} legendPosition={"top"} />
) : null}
</Paper>
</Grid>
<Grid item xs={6}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{hasValue(filterCalcs.sDepartment) ? (
<ChartFilter chartName={CHART_NAMES.calcs} filter={filterCalcs} onFilterChange={handleChartFilterChange} />
) : null}
{chartCalcs.loaded ? (
<P8PChart
{...chartCalcs}
style={STYLES.CHART}
options={{
scales: {
x: {
stacked: true
},
y: {
stacked: true
}
}
}}
legendPosition={"top"}
onClick={handleChartCalcsClick}
/>
) : null}
</Paper>
</Grid>
</>
) : null}
<Grid item xs={12}>
{dataGrid.dataLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...dataGrid}
size={P8P_DATA_GRID_SIZE.LARGE}
containerComponentProps={{
sx: STYLES.TABLE_PROJECTS(
showCharts,
dataGrid.morePages,
(dataGrid.filters || []).length > 0,
hasValue(filterInfo.nState) || hasValue(filterCalcs.sDepartment)
)
}}
filtersInitial={dataGrid.filters}
onOrderChanged={handleOrderChanged}
onFilterChanged={handleFilterChanged}
onPagesCountChanged={handlePagesCountChanged}
valueFormatter={prms => valueFormatter({ ...prms, onRepairSheetOpen: handleEquipRepairSheetsOpen })}
/>
) : null}
</Grid>
</Grid>
{chartInfo.loaded && chartCalcs.loaded ? (
<Fab size="small" color="secondary" sx={STYLES.CHART_FAB} onClick={() => setShowCharts(!showCharts)}>
<Icon>{showCharts ? "expand_less" : "expand_more"}</Icon>
</Fab>
) : null}
{isFilterOpen && isFiltersLoaded ? (
<FilterDialog initial={filterValues} isFiltersInit={filtersInit} onOk={handleFilterOk} onCancel={handleFilterCancel} />
) : null}
</Box>
);
};
//----------------
//Интерфейс модуля
//----------------
export { HaulerAnl };

View File

@ -0,0 +1,91 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Пользовательские хуки: Хуки открытия разделов
*/
//---------------------
//Подключение библиотек
//---------------------
import { useContext, useCallback } from "react"; //Классы React
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
//-----------
//Тело модуля
//-----------
//Состояние открытия разделов
const useDictionary = () => {
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Отображение раздела "Штатные подразделения"
const handleInsDepartmentOpen = useCallback(
async prms => {
pOnlineShowDictionary({
unitCode: "INS_DEPARTMENT",
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
callBack: res => {
res.success ? prms.callBack(res) : null;
}
});
},
[pOnlineShowDictionary]
);
//Отображение раздела "Типовые работы по техническому обслуживанию и ремонту"
const handleEquipTypeWorksOpen = useCallback(
async prms => {
pOnlineShowDictionary({
unitCode: "EquipTypeWorks",
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
callBack: res => {
res.success ? prms.callBack(res) : null;
}
});
},
[pOnlineShowDictionary]
);
//Отображение раздела "Виды работ по техническому обслуживанию и ремонту"
const handleEquipWorkKindsOpen = useCallback(
async prms => {
pOnlineShowDictionary({
unitCode: "EquipWorkKinds",
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
callBack: res => {
res.success ? prms.callBack(res) : null;
}
});
},
[pOnlineShowDictionary]
);
//Отображение раздела "Ремонтные ведомости"
const handleEquipRepairSheetsOpen = useCallback(
async prms => {
pOnlineShowDictionary({
unitCode: "EquipRepairSheets",
inputParameters: [{ name: "in_RN", value: prms.nRn }],
callBack: res => {
res.success ? prms.callBack(res) : null;
}
});
},
[pOnlineShowDictionary]
);
//Возвращаем функции открытия разделов
return {
handleInsDepartmentOpen,
handleEquipTypeWorksOpen,
handleEquipWorkKindsOpen,
handleEquipRepairSheetsOpen
};
};
//----------------
//Интерфейс модуля
//----------------
export { useDictionary };

View File

@ -0,0 +1,92 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Пользовательские хуки: Хуки фильтров
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML } from "../../../core/utils"; //Вспомогательные функции
//-----------
//Тело модуля
//-----------
//Состояние открытия разделов
const useFilters = () => {
//Собственное состояние - общие значения фильтров
const [filterValues, setFilterValues] = useState({
loaded: false,
dDate: "",
sCustomerDept: "",
dDateKOAFrom: "",
dDateKOATo: ""
});
//Собственное состояние - признак загрузки настроек
const [isFiltersLoaded, setIsFiltersLoaded] = useState(false);
//Собственное состояние - признак инициализации
const [filtersInit, setFiltersInit] = useState(true);
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При изменении фильтров
const handleChange = useCallback(
async ({ filter }) => {
//Формируем новые фильтры
const newFilters = { ...filterValues, ...filter };
//Если фильтры изменились - обновим их
if (JSON.stringify(filterValues) != JSON.stringify(newFilters)) {
await executeStored({
stored: "UDO_PKG_P8PANELS_USETTINGS.SETTINGS_SET",
args: {
SPANEL: "hauler_anl",
CSETTINGS: {
VALUE: object2Base64XML(newFilters, { arrayNodeName: "filters" }),
SDATA_TYPE: SERV_DATA_TYPE_CLOB
}
}
});
setFilterValues({ ...newFilters });
}
//Это не инициализация
filtersInit ? setFiltersInit(false) : false;
},
[SERV_DATA_TYPE_CLOB, executeStored, filterValues, filtersInit]
);
//При загрузке панели
useEffect(() => {
//Загрузка настроек с сервера
const loadSettings = async () => {
try {
const data = await executeStored({
stored: "UDO_PKG_P8PANELS_USETTINGS.SETTINGS_GET",
args: { SPANEL: "hauler_anl" },
respArg: "COUT"
});
//Если есть настройки - указываем
if (data) setFilterValues({ ...data });
} finally {
setIsFiltersLoaded(true);
}
};
if (!isFiltersLoaded) loadSettings();
}, [executeStored, isFiltersLoaded]);
//Возвращаем функции открытия разделов
return [filterValues, isFiltersLoaded, filtersInit, handleChange];
};
//----------------
//Интерфейс модуля
//----------------
export { useFilters };

184
hauler_anl/hooks/hooks.js Normal file
View File

@ -0,0 +1,184 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Пользовательские хуки: Хуки основных компонентов
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML, formatDateRF } from "../../../core/utils";
//--------------------------
//Вспомогательные компоненты
//--------------------------
//-----------
//Тело модуля
//-----------
//Хук графика АТС
const useChartInfo = ({ storedArgs = {} }) => {
//Собственное состояние - график
const [chartInfo, setChartInfo] = useState({ loaded: false, reload: false });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости перезагрузки графика
const handleReload = useCallback(() => setChartInfo(pv => ({ ...pv, reload: true })), []);
//При подключении к странице
useEffect(() => {
//Загрузка данных графика с сервера
const loadChart = async () => {
try {
const chart = await executeStored({
stored: "UDO_PKG_P8PANELS_HAULER_ANL.CHART_ATC_INFO",
args: {
DDATE: storedArgs.dDate ? new Date(storedArgs.dDate) : null,
SDEPARTMENT: storedArgs.sCustomerDept || storedArgs.sDepartment,
NSTATE: storedArgs.nState
},
respArg: "COUT"
});
setChartInfo(pv => ({ ...pv, loaded: true, reload: false, ...chart.XCHART }));
} catch (e) {
setChartInfo(pv => ({ ...pv, loaded: false, reload: false }));
}
};
//При необходимости перезагрузить
if (chartInfo.reload) loadChart();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chartInfo.reload, executeStored]);
//Возвращаем график
return { chartInfo, handleReload };
};
//Хук графика КГТ/КОА
const useChartCalcs = ({ storedArgs = {} }) => {
//Собственное состояние - график
const [chartCalcs, setChartCalcs] = useState({ loaded: false, reload: false });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости перезагрузки графика
const handleReload = useCallback(() => setChartCalcs(pv => ({ ...pv, reload: true })), []);
//При подключении к странице
useEffect(() => {
//Загрузка данных графика с сервера
const loadChart = async () => {
try {
const chart = await executeStored({
stored: "UDO_PKG_P8PANELS_HAULER_ANL.CHART_ATC_KGT_KOA",
args: {
SDEPARTMENT: storedArgs.sCustomerDept || storedArgs.sDepartment,
DDATE_KOA_FROM: storedArgs.dDateKOAFrom ? new Date(storedArgs.dDateKOAFrom) : null,
DDATE_KOA_TO: storedArgs.dDateKOATo ? new Date(storedArgs.dDateKOATo) : null,
DDATE: storedArgs.dDate ? new Date(storedArgs.dDate) : null,
NSTATE: storedArgs.nState
},
respArg: "COUT"
});
setChartCalcs(pv => ({
...pv,
loaded: true,
reload: false,
labels: chart.XCHART?.labels ? [...chart.XCHART.labels] : [],
...chart.XCHART
}));
} catch (e) {
setChartCalcs(pv => ({ ...pv, loaded: false, reload: false }));
}
};
//При необходимости перезагрузить
if (chartCalcs.reload) loadChart();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chartCalcs.reload, executeStored]);
//Возвращаем график
return { chartCalcs, handleReload };
};
//Хук таблицы АТС
const useTableATC = ({ storedArgs = {} }) => {
//Собственное состояние - таблица
const [dataGrid, setDataGrid] = useState({
dataLoaded: false,
filters: [],
orders: null,
pageNumber: 1,
morePages: true,
reloading: false
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При изменении состояния фильтра
const handleFilterChanged = ({ filters }) => setDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1, reloading: true }));
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reloading: true }));
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reloading: true }));
//При необходимости перезагрузки таблицы
const handleReload = () => setDataGrid(pv => ({ ...pv, pageNumber: 1, reloading: true }));
//При подключении к странице
useEffect(() => {
//Загрузка данных графика с сервера
const loadDataGrid = async () => {
try {
const data = await executeStored({
stored: "UDO_PKG_P8PANELS_HAULER_ANL.DATA_GRID_ATC",
args: {
CFILTERS: { VALUE: object2Base64XML(dataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
CORDERS: { VALUE: object2Base64XML(dataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: dataGrid.pageNumber,
NPAGE_SIZE: 10,
NINCLUDE_DEF: dataGrid.dataLoaded ? 0 : 1,
DDATE: storedArgs.dDate ? new Date(storedArgs.dDate) : null,
DDATE_KOA_FROM: storedArgs.dDateKOAFrom ? new Date(storedArgs.dDateKOAFrom) : null,
DDATE_KOA_TO: storedArgs.dDateKOATo ? new Date(storedArgs.dDateKOATo) : null,
SDEPARTMENT: storedArgs.sCustomerDept || storedArgs.sDepartment,
NSTATE: storedArgs.nState
},
attributeValueProcessor: (name, val) => (["DDOCDATE", "DDATEFACT_BEG", "DDATEFACT_END"].includes(name) ? formatDateRF(val) : val),
respArg: "COUT"
});
setDataGrid(pv => ({
...pv,
...data.XDATA_GRID,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])],
dataLoaded: true,
reloading: false,
morePages: (data.XDATA_GRID.rows || []).length >= 10
}));
} catch (e) {
console.log(e);
setDataGrid(pv => ({ ...pv, loaded: false, reload: false }));
}
};
//При необходимости перезагрузить
if (dataGrid.reloading) loadDataGrid();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataGrid.reloading, executeStored]);
//Возвращаем график
return { dataGrid, handleReload, handleFilterChanged, handleOrderChanged, handlePagesCountChanged };
};
//----------------
//Интерфейс модуля
//----------------
export { useChartInfo, useChartCalcs, useTableATC };

16
hauler_anl/index.js Normal file
View File

@ -0,0 +1,16 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Панель мониторинга: Точка входа
*/
//---------------------
//Подключение библиотек
//---------------------
import { HaulerAnl } from "./hauler_anl"; //Корневая панель гаражки
//----------------
//Интерфейс модуля
//----------------
export const RootClass = HaulerAnl;

111
hauler_anl/layouts.js Normal file
View File

@ -0,0 +1,111 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Гаражка
Дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Icon, Stack, Link, Typography } from "@mui/material"; //Интерфейсные компоненты
import { hasValue, formatDateRF } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Маска разделителя
const SMASK_SEPARATE = "<|>";
//-----------
//Тело модуля
//-----------
//Формирование значения для колонки "Состояние"
const formatStateValue = (value, addText = false) => {
const [text, icon] = value == 0 ? ["На линии", "done"] : value == 1 ? ["Ремонт", "build"] : ["Не определен", "question_mark"];
return (
<Stack direction="row" gap={0.5} alignItems="center" justifyContent="center">
<Icon title={text}>{icon}</Icon>
{addText == true ? text : null}
</Stack>
);
};
//Формирование списка значений
const formatListValue = value => {
//Считываем массив значений
const listValues = hasValue(value) ? value.split(SMASK_SEPARATE) : [];
//Формируем список значений
return (
<>
{listValues.length !== 0 ? (
<Stack direction="column" gap={0.5} alignItems="flex-start" justifyContent="center">
{listValues.map((el, index) => {
return <Typography variant="body2" key={index}>{`${el}`}</Typography>;
})}
</Stack>
) : null}
</>
);
};
//Формирование значения для колонки "Документ ремонта"
const formatRepairDocValue = (value, onRepairSheetOpen) => {
//Считываем массив документов
const listDocs = hasValue(value) ? value.split(SMASK_SEPARATE) : [];
//Формируем список документов с ссылкой
return (
<>
{listDocs.length !== 0 ? (
<Stack direction="column" gap={0.5} alignItems="flex-start" justifyContent="center">
{listDocs.map((el, index) => {
//Считываем рег. номер
const rn = el.match(/<(.*?)>/)[1];
//Считываем информацию о документе
const docInfo = el.replace(`<${rn}>`, "");
//Формируем ссылку на документ
return (
<Link
component="button"
align="left"
variant="body2"
underline="hover"
onClick={() => onRepairSheetOpen({ nRn: rn })}
key={index}
>
{docInfo}
</Link>
);
})}
</Stack>
) : null}
</>
);
};
//Форматирование значений колонок
export const valueFormatter = ({ value, columnDef, onRepairSheetOpen }) => {
switch (columnDef.name) {
case "NSTATE":
return formatStateValue(value, false);
case "SDAMTYPE_NAME":
return formatListValue(value);
case "SEQDAMCTRL_NOTE":
return formatListValue(value);
case "SDRIVER":
return formatListValue(value);
case "SREPAIR_DOC":
return formatRepairDocValue(value, onRepairSheetOpen);
}
return value;
};
//Форматирование фильтров панелей для фильтров таблицы
export const formatFilterValues = filter => {
return [
{ name: "DDATE", from: formatDateRF(filter.dDate), to: formatDateRF(filter.dDate) },
{ name: "SCUSTOMERDEPT_FILTER", from: filter.sCustomerDept, to: "" }
];
};

12544
p8-panels.js Normal file

File diff suppressed because one or more lines are too long

222
p8panels.config Normal file
View File

@ -0,0 +1,222 @@
<?xml version="1.0"?>
<CITK.P8Panels>
<MenuItems>
<App name="ProjectPlanning">
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" separator="true"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelsRoot" caption="Панели мониторинга" url="Modules/P8-Panels/"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" separator="true"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelFin" caption="Экономика проектов" panelName="PrjFin"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelJob" caption="Работы проектов" panelName="PrjJobs"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelGraph" caption="Графики проектов" panelName="PrjGraph"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelInfo" caption="Информация о проектах" panelName="PrjInfo"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelHelp" caption="Инструкции ПУП" panelName="PrjHelp"/>
</App>
<App name="EquipSrv">
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" separator="true"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowEqsPanelsRoot" caption="Панели мониторинга" url="Modules/P8-Panels/"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" separator="true"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowEqsPrfrm" caption="Выполнение работ по ТОиР" panelName="EqsPrfrm"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowRepairAnlATC" caption="Аналитика по ремонтам АТС" panelName="RepairAnlATC"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowHaulerAnl" caption="Гаражка" panelName="HaulerAnl"/>
</App>
<App name="MechanicalRecords">
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" separator="true"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowMechRecPanelsRoot" caption="Панели мониторинга" url="Modules/P8-Panels/"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" separator="true"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowMechRecPanelCostProdPlans" caption="Производственная программа" panelName="MechRecCostProdPlans"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowMechRecPanelDeptCostProdPlans" caption="Производственный план цеха" panelName="MechRecDeptCostProdPlans"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowMechRecPanelDeptCostJobsManage" caption="Выдача сменного задания" panelName="MechRecCostJobsManage"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowMechRecPanelDeptCostJobs" caption="Загрузка цеха" panelName="MechRecDeptCostJobs"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowMechRecPanelAssemblyMon" caption="Мониторинг сборки изделий" panelName="MechRecAssemblyMon"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowMechRecPanelHelp" caption="Руководство &quot;Оперативное управление производством&quot;" panelName="MechRecHelp"/>
</App>
<App name="Client">
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" separator="true"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowClntPanelsRoot" caption="Панели мониторинга" url="Modules/P8-Panels/"/>
<MenuItem parent="{CA9A9853-AC90-412A-85B1-E39927147846}" separator="true"/>
<MenuItem parent="{CA9A9853-AC90-412A-85B1-E39927147846}" name="ShowClntTaskBoard" caption="Доски задач" panelName="ClntTaskBoard"/>
</App>
</MenuItems>
<Panels urlBase="Modules/P8-Panels/#/">
<Panel
name="PrjFin"
group="Планирование и учёт в проектах"
caption="Экономика проектов"
desc="Мониторинг калькуляции проекта, графиков финансирования, договоров с поставщиками материалов и ПКИ"
url="prj_fin"
path="prj_fin"
icon="bar_chart"
showInPanelsList="true"
preview="./img/prj_fin.png"/>
<Panel
name="PrjJobs"
group="Планирование и учёт в проектах"
caption="Работы проектов"
desc="Управление и контроль сроков выполнения собственных работ по проектам"
url="prj_jobs"
path="prj_jobs"
icon="engineering"
showInPanelsList="true"
preview="./img/prj_jobs.jpg"/>
<Panel
name="PrjGraph"
group="Планирование и учёт в проектах"
caption="Графики проектов"
desc="Сводный график выполнения активных проектов компании"
url="prj_graph"
path="prj_graph"
icon="insights"
showInPanelsList="true"
preview="./img/prj_graph.jpg"/>
<Panel
name="PrjInfo"
group="Планирование и учёт в проектах"
caption="Информация о проектах"
desc="Информация о проектах"
url="prj_info"
path="prj_info"
icon="featured_play_list"
showInPanelsList="true"
preview="./img/prj_info.jpg"/>
<Panel
name="PrjHelp"
group="Планирование и учёт в проектах"
caption="Инструкции ПУП"
desc="Краткое описание работы с макетом &quot;Управление экономикой проектов&quot;"
url="prj_help"
path="prj_help"
icon="help_outline"
showInPanelsList="true"
preview="./img/help.jpg"/>
<Panel
name="EqsPrfrm"
group="Управление техническим обслуживанием и ремонтами"
caption="Выполнение работ по ТОиР"
desc="Визуализация запланированных и выполненных работ по ТОиР предприятия (подразделения) по месяцам и дням года"
url="eqs_prfrm"
path="eqs_prfrm"
icon="build"
showInPanelsList="true"
preview="./img/eqs_prfrm.jpg"/>
<Panel
name="MechRecCostProdPlans"
group="Планирование и учёт в дискретном производстве"
caption="Производственная программа"
desc="Контроль исполнения производственной программы по срокам и количеству запуска и выпуска ДСЕ"
url="mech_rec_cost_prod_plans"
path="mech_rec_cost_prod_plans"
icon="calendar_month"
showInPanelsList="true"
preview="./img/mech_rec_cost_prod_plans.jpg"/>
<Panel
name="MechRecDeptCostProdPlans"
group="Планирование и учёт в дискретном производстве"
caption="Производственный план цеха"
desc="Мониторинг и управление (установка приоритетов партий, заказов) производственным планом цеха"
url="mech_rec_dept_cost_prod_plans"
path="mech_rec_dept_cost_prod_plans"
icon="free_cancellation"
showInPanelsList="true"
preview="./img/mech_rec_dept_cost_prod_plans.jpg"/>
<Panel
name="MechRecCostJobsManage"
group="Планирование и учёт в дискретном производстве"
caption="Выдача сменного задания"
desc="Управление составом сменных заданий цеха/участка"
url="mech_rec_cost_jobs_manage"
path="mech_rec_cost_jobs_manage"
icon="psychology"
showInPanelsList="true"
preview="./img/mech_rec_cost_jobs_manage.jpg"/>
<Panel
name="MechRecCostJobsManageMP"
group="Планирование и учёт в дискретном производстве"
caption="Выдача сменного задания на участок"
desc="Управление составом сменных заданий трудового ресурса"
url="mech_rec_cost_jobs_manage_mp"
path="mech_rec_cost_jobs_manage_mp"
icon=""
showInPanelsList="false"
preview=""/>
<Panel
name="MechRecDeptCostJobs"
group="Планирование и учёт в дискретном производстве"
caption="Загрузка цеха"
desc="Просмотр сведений о производственной загрузке цеха/участка"
url="mech_rec_dept_cost_jobs"
path="mech_rec_dept_cost_jobs"
icon="factory"
showInPanelsList="true"
preview="./img/mech_rec_dept_cost_jobs.jpg"/>
<Panel
name="MechRecAssemblyMon"
group="Планирование и учёт в дискретном производстве"
caption="Мониторинг сборки изделий"
desc="Отображение текущего состояния комплектации и сборки изделий производственными подразделениями"
url="mech_rec_assembly_mon"
path="mech_rec_assembly_mon"
icon="monitor_heart"
showInPanelsList="true"
preview="./img/mech_rec_assembly_mon.jpg"/>
<Panel
name="MechRecHelp"
group="Планирование и учёт в дискретном производстве"
caption="Руководство &quot;Оперативное управление производством&quot;"
desc="Краткое описание работы с макетом &quot;Оперативное управление производством&quot;"
url="mech_rec_help"
path="mech_rec_help"
icon="help_outline"
showInPanelsList="true"
preview="./img/help.jpg"/>
<Panel
name="Samples"
group=""
caption="Примеры для разработчиков"
desc="Примеры из README.md"
url="samples"
path="samples"
icon="javascript"
showInPanelsList="true"
preview="./img/default_preview.png"/>
<Panel
name="RrpConfEditor"
group=""
caption="Редактор настройки регламентированного отчёта"
desc=""
url="rrp_conf_editor"
path="rrp_conf_editor"
icon=""
showInPanelsList="false"
preview=""/>
<Panel
name="ClntTaskBoard"
group="Управление деловыми процессами"
caption="Доски задач"
desc="Мониторинг и управление назначенными задачами на Канбан-доске"
url="clnt_task_board"
path="clnt_task_board"
icon="dashboard_customize"
showInPanelsList="true"
preview="./img/clnt_task_board.jpg"/>
<Panel
name="RepairAnlATC"
group="Управление техническим обслуживанием и ремонтами"
caption="Аналитика по ремонтам АТС"
desc="Аналитика по ремонтам АТС"
url="repair_anl_atc"
path="repair_anl_atc"
icon="home_repair_service"
showInPanelsList="true"
preview="./img/default_preview.png"/>
<Panel
name="HaulerAnl"
group="Управление техническим обслуживанием и ремонтами"
caption="Гаражка"
desc="Гаражка"
url="hauler_anl"
path="hauler_anl"
icon="warehouse"
showInPanelsList="true"
preview="./img/default_preview.png"/>
</Panels>
</CITK.P8Panels>

View File

@ -0,0 +1,118 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Компонент: Фильтр отбора
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Chip, Stack, Box } from "@mui/material"; //Интерфейсные компоненты
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { hasValue } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Стили
const STYLES = {
FILTERS_STACK: {
paddingBottom: "5px",
...APP_STYLES.SCROLL,
overflowY: "auto",
alignItems: "flex-end"
},
STACK_FILTER: { maxWidth: "99vw", alignItems: "flex-end" }
};
//Графики
const CHART_NAMES = {
repairs: "REPAIRS",
spendings: "SPENDINGS"
};
//--------------------------
//Вспомогательные компоненты
//--------------------------
//Считывание наименования состояния
const getStateName = state => {
return state === 0 ? "Не завершенные" : "Завершенные";
};
//Считывание типа затрат
const getTypeSpendings = type => {
return type === 0 ? "Внутренние" : "Внешние";
};
//Элемент фильтра
const FilterItem = ({ field, caption, value, onDelete }) => {
//При удалении фильтра
const handleDelete = () => (onDelete ? onDelete(field) : null);
//Генерация содержимого
return (
<Chip
label={
<Stack direction={"row"} alignItems={"center"}>
<strong>{caption}</strong>
{value ? `:\u00A0${value}` : null}
</Stack>
}
variant="outlined"
onDelete={handleDelete}
/>
);
};
//Контроль свойств компонента - Элемент фильтра
FilterItem.propTypes = {
field: PropTypes.string.isRequired,
caption: PropTypes.string.isRequired,
value: PropTypes.any,
onDelete: PropTypes.func
};
//---------------
//Тело компонента
//---------------
//Фильтр отбора
const ChartFilter = ({ chartName, filter, onFilterChange }) => {
//При удалении фильтра
const handleDelete = field => onFilterChange && onFilterChange({ chartName, filter: { ...filter, [field]: null } });
//Генерация содержимого
return (
<div>
<Box>
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.STACK_FILTER}>
<Stack direction="row" spacing={1} alignItems={"center"} sx={STYLES.FILTERS_STACK}>
{chartName === CHART_NAMES.repairs ? (
hasValue(filter.nState) ? (
<FilterItem field={"nState"} caption={"Состояние"} value={getStateName(filter.nState)} onDelete={handleDelete} />
) : null
) : chartName === CHART_NAMES.spendings ? (
<FilterItem field={"nType"} caption={"Тип затрат"} value={getTypeSpendings(filter.nType)} onDelete={handleDelete} />
) : null}
</Stack>
</Stack>
</Box>
</div>
);
};
//Контроль свойств компонента - Фильтр отбора
ChartFilter.propTypes = {
chartName: PropTypes.string,
filter: PropTypes.object.isRequired,
onFilterChange: PropTypes.func.isRequired
};
//--------------------
//Интерфейс компонента
//--------------------
export { ChartFilter };

104
repair_anl_atc/filter.js Normal file
View File

@ -0,0 +1,104 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Компонент: Фильтр отбора
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Chip, Stack, Icon, IconButton, Box } from "@mui/material"; //Интерфейсные компоненты
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { formatDateRF } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Стили
const STYLES = {
FILTERS_STACK: {
paddingBottom: "5px",
...APP_STYLES.SCROLL,
overflowY: "auto",
alignItems: "flex-end"
},
STACK_FILTER: { maxWidth: "99vw", alignItems: "flex-end" }
};
//--------------------------
//Вспомогательные компоненты
//--------------------------
//Элемент фильтра
const FilterItem = ({ caption, value, onClick }) => {
//При нажатии на элемент
const handleClick = () => (onClick ? onClick() : null);
//Генерация содержимого
return (
<Chip
label={
<Stack direction={"row"} alignItems={"center"}>
<strong>{caption}</strong>
{value ? `:\u00A0${value}` : null}
</Stack>
}
variant="outlined"
onClick={handleClick}
/>
);
};
//Контроль свойств компонента - Элемент фильтра
FilterItem.propTypes = {
caption: PropTypes.string.isRequired,
value: PropTypes.any,
onClick: PropTypes.func
};
//---------------
//Тело компонента
//---------------
//Фильтр отбора
const Filter = ({ filter, onFilterOpen }) => {
//При нажатии на элемент фильтра
const handleClick = () => onFilterOpen && onFilterOpen();
//Генерация содержимого
return (
<div>
<Box>
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.STACK_FILTER}>
<IconButton onClick={handleClick}>
<Icon>filter_alt</Icon>
</IconButton>
<Stack direction="row" spacing={1} alignItems={"center"} sx={STYLES.FILTERS_STACK}>
{filter.dDateBegin ? <FilterItem caption={"Дата с"} value={formatDateRF(filter.dDateBegin)} onClick={handleClick} /> : null}
{filter.dDateEnd ? <FilterItem caption={"Дата по"} value={formatDateRF(filter.dDateEnd)} onClick={handleClick} /> : null}
{filter.sCustomerDept ? (
<FilterItem caption={"Транспортный участок"} value={filter.sCustomerDept} onClick={handleClick} />
) : null}
{filter.sWorkType ? <FilterItem caption={"Типовая работа"} value={filter.sWorkType} onClick={handleClick} /> : null}
{filter.sWorkKind ? <FilterItem caption={"Вид типовых работ"} value={filter.sWorkKind} onClick={handleClick} /> : null}
</Stack>
</Stack>
</Box>
</div>
);
};
//Контроль свойств компонента - Фильтр отбора
Filter.propTypes = {
filter: PropTypes.object.isRequired,
onFilterOpen: PropTypes.func.isRequired
};
//--------------------
//Интерфейс компонента
//--------------------
export { Filter };

View File

@ -0,0 +1,209 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Компонент: Диалоговое окно фильтра отбора
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, TextField, InputAdornment } from "@mui/material"; //Интерфейсные компоненты
import { useDictionary } from "./hooks/dict_hooks"; //Хуки открытий разделов
import { hasValue } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Стили
const STYLES = {
DIALOG_ACTIONS: { justifyContent: "flex-end" },
CLOSE_BUTTON: {
position: "absolute",
right: 8,
top: 8,
color: theme => theme.palette.grey[500]
}
};
//--------------------------
//Вспомогательные компоненты
//--------------------------
//Проверка возможности выполнения действия
const isActionAllow = (filter, isFiltersInit = false) => {
//Если указана "Дата с" и "Дата по", а также это не инициализация
return hasValue(filter.dDateBegin) && hasValue(filter.dDateEnd) && !isFiltersInit;
};
//---------------
//Тело компонента
//---------------
//Диалоговое окно фильтра отбора
const FilterDialog = ({ initial, isFiltersInit, onCancel, onOk }) => {
//Собственное состояние
const [filter, setFilter] = useState({ ...initial });
//Вспомогательные функции открытия раздела
const { handleInsDepartmentOpen, handleEquipTypeWorksOpen, handleEquipWorkKindsOpen } = useDictionary();
//Отработка воода значения в фильтр
const handleValueChanged = e => setFilter(pv => ({ ...pv, [e.target.name]: e.target.value }));
//При изменении каталога фильтра
const handleCustomerDeptSelect = () =>
handleInsDepartmentOpen({
sCode: filter.sCustomerDept,
callBack: res => {
setFilter(pv => ({ ...pv, sCustomerDept: res.outParameters.out_CODE }));
}
});
//При изменении типовой работы
const handleTypeWorkSelect = () =>
handleEquipTypeWorksOpen({
sCode: filter.sWorkType,
callBack: res => {
setFilter(pv => ({ ...pv, sWorkType: res.outParameters.out_CODE }));
}
});
//При изменении вида типовых работ
const handleWorkKindSelect = () =>
handleEquipWorkKindsOpen({
sCode: filter.sWorkKind,
callBack: res => {
setFilter(pv => ({ ...pv, sWorkKind: res.outParameters.out_CODE }));
}
});
//При очистке фильтра
const handleClear = () => {
setFilter({ dDateBegin: "", dDateEnd: "", sCustomerDept: "", sWorkType: "", sWorkKind: "" });
};
//При закрытии диалога без изменения фильтра
const handleCancel = () => (isActionAllow(initial, isFiltersInit) ? onCancel && onCancel() : null);
//При закрытии диалога с изменением фильтра
const handleOK = () => (isActionAllow(filter) && onOk ? onOk(filter) : null);
//Генерация содержимого
return (
<div>
<Dialog open onClose={handleCancel} fullWidth maxWidth="sm">
<DialogTitle>Фильтр отбора</DialogTitle>
<IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
<Icon>close</Icon>
</IconButton>
<DialogContent>
<Box sx={{ display: "flex", flexDirection: "column", gap: "10px" }}>
<TextField
name="dDateBegin"
type="date"
InputLabelProps={{ shrink: true }}
value={filter.dDateBegin}
onChange={handleValueChanged}
label="Дата с"
variant="standard"
fullWidth
required
error={!filter.dDateBegin}
/>
<TextField
name="dDateEnd"
type="date"
InputLabelProps={{ shrink: true }}
value={filter.dDateEnd}
onChange={handleValueChanged}
label="Дата по"
variant="standard"
fullWidth
required
error={!filter.dDateEnd}
/>
<TextField
name="sCustomerDept"
value={filter.sCustomerDept}
onChange={handleValueChanged}
label="Транспортный участок"
variant="standard"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton aria-label={`sCustomerDept select`} onClick={handleCustomerDeptSelect} edge="end">
<Icon>list</Icon>
</IconButton>
</InputAdornment>
)
}}
fullWidth
/>
<TextField
name="sWorkType"
value={filter.sWorkType}
onChange={handleValueChanged}
label="Типовая работа"
variant="standard"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton aria-label={`sWorkType select`} onClick={handleTypeWorkSelect} edge="end">
<Icon>list</Icon>
</IconButton>
</InputAdornment>
)
}}
fullWidth
/>
<TextField
name="sWorkKind"
value={filter.sWorkKind}
onChange={handleValueChanged}
label="Вид типовых работ"
variant="standard"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton aria-label={`sWorkKind select`} onClick={handleWorkKindSelect} edge="end">
<Icon>list</Icon>
</IconButton>
</InputAdornment>
)
}}
fullWidth
/>
</Box>
</DialogContent>
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button variant="text" disabled={!isActionAllow(filter)} onClick={handleOK}>
Применить
</Button>
<Button variant="text" onClick={handleClear}>
Очистить
</Button>
<Button variant="text" disabled={!isActionAllow(initial, isFiltersInit)} onClick={handleCancel}>
Отмена
</Button>
</DialogActions>
</Dialog>
</div>
);
};
//Контроль свойств компонента - Диалоговое окно фильтра отбора
FilterDialog.propTypes = {
initial: PropTypes.object.isRequired,
isFiltersInit: PropTypes.bool,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//--------------------
//Интерфейс компонента
//--------------------
export { FilterDialog };

View File

@ -0,0 +1,91 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Пользовательские хуки: Хуки открытия разделов
*/
//---------------------
//Подключение библиотек
//---------------------
import { useContext, useCallback } from "react"; //Классы React
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
//-----------
//Тело модуля
//-----------
//Состояние открытия разделов
const useDictionary = () => {
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Отображение раздела "Штатные подразделения"
const handleInsDepartmentOpen = useCallback(
async prms => {
pOnlineShowDictionary({
unitCode: "INS_DEPARTMENT",
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
callBack: res => {
res.success ? prms.callBack(res) : null;
}
});
},
[pOnlineShowDictionary]
);
//Отображение раздела "Типовые работы по техническому обслуживанию и ремонту"
const handleEquipTypeWorksOpen = useCallback(
async prms => {
pOnlineShowDictionary({
unitCode: "EquipTypeWorks",
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
callBack: res => {
res.success ? prms.callBack(res) : null;
}
});
},
[pOnlineShowDictionary]
);
//Отображение раздела "Виды работ по техническому обслуживанию и ремонту"
const handleEquipWorkKindsOpen = useCallback(
async prms => {
pOnlineShowDictionary({
unitCode: "EquipWorkKinds",
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
callBack: res => {
res.success ? prms.callBack(res) : null;
}
});
},
[pOnlineShowDictionary]
);
//Отображение раздела "Ремонтные ведомости"
const handleEquipRepairSheetsOpen = useCallback(
async prms => {
pOnlineShowDictionary({
unitCode: "EquipRepairSheets",
inputParameters: [{ name: "in_RN", value: prms.nRn }],
callBack: res => {
res.success ? prms.callBack(res) : null;
}
});
},
[pOnlineShowDictionary]
);
//Возвращаем функции открытия разделов
return {
handleInsDepartmentOpen,
handleEquipTypeWorksOpen,
handleEquipWorkKindsOpen,
handleEquipRepairSheetsOpen
};
};
//----------------
//Интерфейс модуля
//----------------
export { useDictionary };

View File

@ -0,0 +1,93 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Пользовательские хуки: Хуки фильтров
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML } from "../../../core/utils"; //Вспомогательные функции
//-----------
//Тело модуля
//-----------
//Состояние открытия разделов
const useFilters = () => {
//Собственное состояние - общие значения фильтров
const [filterValues, setFilterValues] = useState({
loaded: false,
dDateBegin: "",
dDateEnd: "",
sCustomerDept: "",
sWorkType: "",
sWorkKind: ""
});
//Собственное состояние - признак загрузки настроек
const [isFiltersLoaded, setIsFiltersLoaded] = useState(false);
//Собственное состояние - признак инициализации
const [filtersInit, setFiltersInit] = useState(true);
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При изменении фильтров
const handleChange = useCallback(
async ({ filter }) => {
//Формируем новые фильтры
const newFilters = { ...filterValues, ...filter };
//Если фильтры изменились - обновим их
if (JSON.stringify(filterValues) != JSON.stringify(newFilters)) {
await executeStored({
stored: "UDO_PKG_P8PANELS_USETTINGS.SETTINGS_SET",
args: {
SPANEL: "repair_anl_atc",
CSETTINGS: {
VALUE: object2Base64XML(newFilters, { arrayNodeName: "filters" }),
SDATA_TYPE: SERV_DATA_TYPE_CLOB
}
}
});
setFilterValues({ ...newFilters });
}
//Это не инициализация
filtersInit ? setFiltersInit(false) : false;
},
[SERV_DATA_TYPE_CLOB, executeStored, filterValues, filtersInit]
);
//При загрузке панели
useEffect(() => {
//Загрузка настроек с сервера
const loadSettings = async () => {
try {
const data = await executeStored({
stored: "UDO_PKG_P8PANELS_USETTINGS.SETTINGS_GET",
args: { SPANEL: "repair_anl_atc" },
respArg: "COUT"
});
//Если есть настройки - указываем
if (data) setFilterValues({ ...data });
} finally {
setIsFiltersLoaded(true);
}
};
if (!isFiltersLoaded) loadSettings();
}, [executeStored, isFiltersLoaded]);
//Возвращаем функции открытия разделов
return [filterValues, isFiltersLoaded, filtersInit, handleChange];
};
//----------------
//Интерфейс модуля
//----------------
export { useFilters };

View File

@ -0,0 +1,182 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Пользовательские хуки: Хуки основных компонентов
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML, formatDateRF } from "../../../core/utils";
//--------------------------
//Вспомогательные компоненты
//--------------------------
//-----------
//Тело модуля
//-----------
//Хук графика статусов
const useChartStatuses = ({ storedArgs = {} }) => {
//Собственное состояние - график
const [chartStatuses, setChartStatuses] = useState({ loaded: false, reload: false });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости перезагрузки графика
const handleReload = useCallback(() => setChartStatuses(pv => ({ ...pv, reload: true })), []);
//При подключении к странице
useEffect(() => {
//Загрузка данных графика с сервера
const loadChart = async () => {
try {
const chart = await executeStored({
stored: "UDO_PKG_P8PANELS_RPR_ANL.CHART_STATUSES",
args: {
DDATE_FROM: storedArgs.dDateBegin ? new Date(storedArgs.dDateBegin) : null,
DDATE_TO: storedArgs.dDateEnd ? new Date(storedArgs.dDateEnd) : null,
SINS_DEPARTMENT: storedArgs.sCustomerDept,
SEQTYPEWRK: storedArgs.sWorkType,
SEQWRKKIND: storedArgs.sWorkKind,
NSTATE: storedArgs.nState,
NTYPESPEND: storedArgs.nType
},
respArg: "COUT"
});
setChartStatuses(pv => ({ ...pv, loaded: true, reload: false, ...chart.XCHART }));
} catch (e) {
setChartStatuses(pv => ({ ...pv, loaded: false, reload: false }));
}
};
//При необходимости перезагрузить
if (chartStatuses.reload) loadChart();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chartStatuses.reload, executeStored]);
//Возвращаем график
return { chartStatuses, handleReload };
};
//Хук графика трудовых затрат
const useChartSpendings = ({ storedArgs = {} }) => {
//Собственное состояние - график
const [chartSpendings, setChartSpendings] = useState({ loaded: false, reload: false });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости перезагрузки графика
const handleReload = useCallback(() => setChartSpendings(pv => ({ ...pv, reload: true })), []);
//При подключении к странице
useEffect(() => {
//Загрузка данных графика с сервера
const loadChart = async () => {
try {
const chart = await executeStored({
stored: "UDO_PKG_P8PANELS_RPR_ANL.CHART_SPENDINGS",
args: {
DDATE_FROM: storedArgs.dDateBegin ? new Date(storedArgs.dDateBegin) : null,
DDATE_TO: storedArgs.dDateEnd ? new Date(storedArgs.dDateEnd) : null,
SINS_DEPARTMENT: storedArgs.sCustomerDept,
SEQTYPEWRK: storedArgs.sWorkType,
SEQWRKKIND: storedArgs.sWorkKind,
NSTATE: storedArgs.nState,
NTYPESPEND: storedArgs.nType
},
respArg: "COUT"
});
setChartSpendings(pv => ({ ...pv, loaded: true, reload: false, ...chart.XCHART }));
} catch (e) {
setChartSpendings(pv => ({ ...pv, loaded: false, reload: false }));
}
};
//При необходимости перезагрузить
if (chartSpendings.reload) loadChart();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chartSpendings.reload, executeStored]);
//Возвращаем график
return { chartSpendings, handleReload };
};
//Хук таблицы ремотных ведомостей
const useTableRepairs = ({ storedArgs = [] }) => {
//Собственное состояние - таблица
const [dataGrid, setDataGrid] = useState({
dataLoaded: false,
filters: [],
orders: null,
pageNumber: 1,
morePages: true,
reloading: false
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При изменении состояния фильтра
const handleFilterChanged = ({ filters }) => setDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1, reloading: true }));
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reloading: true }));
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reloading: true }));
//При необходимости перезагрузки таблицы
const handleReload = () => setDataGrid(pv => ({ ...pv, pageNumber: 1, reloading: true }));
//При подключении к странице
useEffect(() => {
//Загрузка данных графика с сервера
const loadDataGrid = async () => {
try {
const data = await executeStored({
stored: "UDO_PKG_P8PANELS_RPR_ANL.DATA_GRID",
args: {
CFILTERS: {
VALUE: object2Base64XML([...dataGrid.filters, ...storedArgs], { arrayNodeName: "filters" }),
SDATA_TYPE: SERV_DATA_TYPE_CLOB
},
CORDERS: { VALUE: object2Base64XML(dataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: dataGrid.pageNumber,
NPAGE_SIZE: 10,
NINCLUDE_DEF: dataGrid.dataLoaded ? 0 : 1
},
attributeValueProcessor: (name, val) => (["DDOCDATE", "DDATEFACT_BEG", "DDATEFACT_END"].includes(name) ? formatDateRF(val) : val),
respArg: "COUT"
});
setDataGrid(pv => ({
...pv,
...data.XDATA_GRID,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])],
dataLoaded: true,
reloading: false,
morePages: (data.XDATA_GRID.rows || []).length >= 10
}));
} catch (e) {
console.log(e);
setDataGrid(pv => ({ ...pv, loaded: false, reload: false }));
}
};
//При необходимости перезагрузить
if (dataGrid.reloading) loadDataGrid();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataGrid.reloading, executeStored]);
//Возвращаем график
return { dataGrid, handleReload, handleFilterChanged, handleOrderChanged, handlePagesCountChanged };
};
//----------------
//Интерфейс модуля
//----------------
export { useChartStatuses, useChartSpendings, useTableRepairs };

16
repair_anl_atc/index.js Normal file
View File

@ -0,0 +1,16 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Панель мониторинга: Точка входа
*/
//---------------------
//Подключение библиотек
//---------------------
import { RepairAnlATC } from "./repair_anl_atc"; //Корневая панель аналитики по ремонтам АТС
//----------------
//Интерфейс модуля
//----------------
export const RootClass = RepairAnlATC;

77
repair_anl_atc/layouts.js Normal file
View File

@ -0,0 +1,77 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Icon, Stack, Link } from "@mui/material"; //Интерфейсные компоненты
import { formatDateRF } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//-----------
//Тело модуля
//-----------
//Формирование значения для колонки "Состояние"
const formatStateValue = (value, addText = false) => {
const [text, icon] =
value == 0
? ["Не утвержден", "clear"]
: value == 2
? ["Утвержден", "done"]
: value == 3
? ["Закрыт", "lock_outlined"]
: ["Не определен", "question_mark"];
return (
<Stack direction="row" gap={0.5} alignItems="center" justifyContent="center">
<Icon title={text}>{icon}</Icon>
{addText == true ? text : null}
</Stack>
);
};
//Формирование значения для колонки "Номер документа"
const formatNumbValue = (row, columnDef, showEquipRepairSheets) => {
console.log(columnDef.name);
return (
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => showEquipRepairSheets({ nRn: row["NRN"] })}>
{row[columnDef.name]}
</Link>
);
};
//Генерация представления ячейки c данными
export const dataCellRender = ({ row, columnDef, showEquipRepairSheets }) => {
switch (columnDef.name) {
case "SNUMB":
return { data: formatNumbValue(row, columnDef, showEquipRepairSheets) };
}
};
//Форматирование значений колонок
export const valueFormatter = ({ value, columnDef }) => {
switch (columnDef.name) {
case "NSTATE":
return formatStateValue(value, false);
}
return value;
};
//Форматирование фильтров панелей для фильтров таблицы
export const formatFilterValues = filter => {
return [
{ name: "DDATEBEGIN_FILTER", from: formatDateRF(filter.dDateBegin), to: formatDateRF(filter.dDateEnd) },
{ name: "SCUSTOMERDEPT_FILTER", from: filter.sCustomerDept, to: "" },
{ name: "SWORKTYPE_FILTER", from: filter.sWorkType, to: "" },
{ name: "SWORKKIND_FILTER", from: filter.sWorkKind, to: "" },
{ name: "NSTATE_FILTER", from: filter.nState, to: "" },
{ name: "NTYPESPEND_FILTER", from: filter.nType, to: "" }
];
};

View File

@ -0,0 +1,233 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Аналитика по ремонтам АТС
Панель мониторинга: Корневая панель аналитики по ремонтам АТС
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useEffect, useMemo } from "react"; //Классы React
import { Box, Grid, Paper, Fab, Icon } from "@mui/material"; //Интерфейсные компоненты
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8PChart } from "../../components/p8p_chart"; //График
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_MORE_HEIGHT, P8P_DATA_GRID_FILTERS_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { FilterDialog } from "./filter_dialog"; //Диалог фильтра
import { Filter } from "./filter"; //Фильтры
import { useFilters } from "./hooks/filter_hooks"; //Хуки фильтров
import { useChartStatuses, useChartSpendings, useTableRepairs } from "./hooks/hooks";
import { useDictionary } from "./hooks/dict_hooks";
import { dataCellRender, valueFormatter, formatFilterValues } from "./layouts";
import { ChartFilter } from "./chart_filter";
import { hasValue } from "../../core/utils";
//---------
//Константы
//---------
//Высота графиков
const CHART_HEIGHT = "300px";
//Стили
const STYLES = {
TABLE_PROJECTS: (showCharts, morePages, filters, isChartsFiltered) => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${showCharts ? CHART_HEIGHT : "0px"} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - ${
filters ? P8P_DATA_GRID_FILTERS_HEIGHT : "0px"
} - ${showCharts && isChartsFiltered ? "53px" : "0px"} - 90px)`,
maxWidth: `calc(100vw - 16px)`,
...APP_STYLES.SCROLL
}),
CHART: { maxHeight: CHART_HEIGHT, display: "flex", justifyContent: "center" },
CHART_PAPER: { height: "100%", paddingBottom: "5px" },
CHART_FAB: { position: "absolute", top: 80, left: 16 }
};
//Графики страницы
const CHART_NAMES = {
repairs: "REPAIRS",
spendings: "SPENDINGS"
};
//-----------
//Тело модуля
//-----------
//Корневая панель аналитики по ремонтам АТС
const RepairAnlATC = () => {
//Собственное состояние - признак отображения фильтров
const [isFilterOpen, setIsFilterOpen] = useState(true);
//Состояния графиков
const [showCharts, setShowCharts] = useState(true);
//Собственное состояние - общие фильтры
const [filterValues, isFiltersLoaded, filtersInit, handleFilterChange] = useFilters();
//Собственное состояние - фильтры ремонтов
const [filterRepairs, setFilterRepairs] = useState({ nState: null });
//Собственное состояние - фильтры затрат
const [filterSpendings, setFilterSpendings] = useState({ nType: null });
//Общие фильтры панели
const allFilters = useMemo(() => {
return { ...filterValues, nState: filterRepairs.nState, nType: filterSpendings.nType };
}, [filterRepairs.nState, filterSpendings.nType, filterValues]);
//Состояние графика статусов
const { chartStatuses, handleReload: handleChartStatusesReload } = useChartStatuses({ storedArgs: allFilters });
//Состояние графика трудовых затрат
const { chartSpendings, handleReload: handleChartSpendingsReload } = useChartSpendings({ storedArgs: allFilters });
//Состояние таблицы ремонтных ведомостей
const {
dataGrid,
handleReload: handleTableRepairsReload,
handleFilterChanged,
handleOrderChanged,
handlePagesCountChanged
} = useTableRepairs({
storedArgs: formatFilterValues(allFilters)
});
//Вспомогательные функции открытия раздела
const { handleEquipRepairSheetsOpen } = useDictionary();
//При изменении фильтра в диалоге
const handleFilterOk = filter => {
//Обновляем фильтры
handleFilterChange({ filter });
//Закрываем диалог фильтра
setIsFilterOpen(false);
};
//При закрытии диалога фильтра
const handleFilterCancel = () => setIsFilterOpen(false);
//При открытии диалога фильтра
const handleFilterDialogOpen = () => setIsFilterOpen(true);
//При нажатии на элемент графика "Ремонты"
const handleChartStatusesClick = ({ item }) => {
setFilterRepairs({ nState: item.NSTATE });
};
//При нажатии на элемент графика "Трудовые затраты"
const handleChartSpendingsClick = ({ item }) => {
//Если тип не соответствует текущему
if (item.NTYPE !== filterSpendings.nType) setFilterSpendings({ nType: item.NTYPE });
};
//При изменении фильтра графика
const handleChartFilterChange = ({ chartName, filter }) => {
//При изменении фильтров графика "Ремонты"
if (chartName === CHART_NAMES.repairs) {
setFilterRepairs({ ...filter });
}
//При изменении фильтров графика "Трудовые затраты"
if (chartName === CHART_NAMES.spendings) setFilterSpendings({ ...filter });
};
//При изменении фильтра
useEffect(() => {
//Если фильтр установлен
if (!filtersInit) {
handleChartStatusesReload();
handleChartSpendingsReload();
handleTableRepairsReload();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allFilters, filtersInit]);
//Генерация содержимого
return (
<Box p={1}>
{!filtersInit ? <Filter filter={filterValues} onFilterOpen={handleFilterDialogOpen} /> : null}
<Grid container spacing={1}>
{showCharts ? (
<>
<Grid item xs={6}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{hasValue(filterRepairs.nState) ? (
<ChartFilter chartName={CHART_NAMES.repairs} filter={filterRepairs} onFilterChange={handleChartFilterChange} />
) : null}
{chartStatuses.loaded ? (
<P8PChart {...chartStatuses} style={STYLES.CHART} onClick={handleChartStatusesClick} legendPosition={"top"} />
) : null}
</Paper>
</Grid>
<Grid item xs={6}>
<Paper elevation={3} sx={STYLES.CHART_PAPER}>
{hasValue(filterSpendings.nType) ? (
<ChartFilter
chartName={CHART_NAMES.spendings}
filter={filterSpendings}
onFilterChange={handleChartFilterChange}
/>
) : null}
{chartSpendings.loaded ? (
<P8PChart
{...chartSpendings}
style={STYLES.CHART}
options={{
scales: {
x: {
stacked: true
},
y: {
stacked: true
}
}
}}
legendPosition={"top"}
onClick={handleChartSpendingsClick}
/>
) : null}
</Paper>
</Grid>
</>
) : null}
<Grid item xs={12}>
{dataGrid.dataLoaded ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...dataGrid}
size={P8P_DATA_GRID_SIZE.LARGE}
containerComponentProps={{
sx: STYLES.TABLE_PROJECTS(
showCharts,
dataGrid.morePages,
(dataGrid.filters || []).length > 0,
hasValue(filterRepairs.nState) || hasValue(filterSpendings.nType)
)
}}
filtersInitial={dataGrid.filters}
onOrderChanged={handleOrderChanged}
onFilterChanged={handleFilterChanged}
onPagesCountChanged={handlePagesCountChanged}
dataCellRender={prms => dataCellRender({ ...prms, showEquipRepairSheets: handleEquipRepairSheetsOpen })}
valueFormatter={valueFormatter}
/>
) : null}
</Grid>
</Grid>
{chartStatuses.loaded && chartSpendings.loaded ? (
<Fab size="small" color="secondary" sx={STYLES.CHART_FAB} onClick={() => setShowCharts(!showCharts)}>
<Icon>{showCharts ? "expand_less" : "expand_more"}</Icon>
</Fab>
) : null}
{isFilterOpen && isFiltersLoaded ? (
<FilterDialog initial={filterValues} isFiltersInit={filtersInit} onOk={handleFilterOk} onCancel={handleFilterCancel} />
) : null}
</Box>
);
};
//----------------
//Интерфейс модуля
//----------------
export { RepairAnlATC };