WEB APP: Панель "Выполнение работ" - рефакторинг React-компонентов

This commit is contained in:
Mikhail Chechnev 2024-06-27 19:58:50 +03:00
parent a96738013c
commit 30bbce8804
5 changed files with 399 additions and 307 deletions

View File

@ -8,7 +8,7 @@
//--------------------- //---------------------
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import { Grid, Paper, Box, Link } from "@mui/material"; //Интерфейсные компоненты import { Grid, Paper, Box } from "@mui/material"; //Интерфейсные компоненты
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
@ -16,6 +16,7 @@ import { ApplicationСtx } from "../../context/application"; //Контекст
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { headCellRender, dataCellRender, groupCellRender, DIGITS_REG_EXP, MONTH_NAME_REG_EXP, DAY_NAME_REG_EXP } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов import { headCellRender, dataCellRender, groupCellRender, DIGITS_REG_EXP, MONTH_NAME_REG_EXP, DAY_NAME_REG_EXP } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { Filter } from "./filter"; //Компонент фильтра
import { FilterDialog } from "./filter_dialog"; //Компонент диалогового окна фильтра отбора import { FilterDialog } from "./filter_dialog"; //Компонент диалогового окна фильтра отбора
//----------- //-----------
@ -35,25 +36,21 @@ const EqsPrfrm = () => {
//Состояние фильтра //Состояние фильтра
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
belong: "", isOpen: false,
prodObj: "", isDefault: false,
techServ: "", isSetByUser: false,
respDep: "", values: {
fromMonth: 1, belong: "",
fromYear: 1990, prodObj: "",
toMonth: 1, techServ: "",
toYear: 1990 respDep: "",
fromMonth: 1,
fromYear: 1990,
toMonth: 1,
toYear: 1990
}
}); });
//Состояние хранения копии фильтра
const [filterCopy, setFilterCopy] = useState({ ...filter });
//Состояние открытия фильтра
const [filterOpen, setFilterOpen] = useState(true);
//Состояние данных по умолчанию для фильтра
const [defaultLoaded, setDefaultLoaded] = useState(false);
//Состояние ячейки заголовка даты (по раскрытию/скрытию) //Состояние ячейки заголовка даты (по раскрытию/скрытию)
const [activeRef, setActiveRef] = useState(); const [activeRef, setActiveRef] = useState();
@ -75,14 +72,14 @@ const EqsPrfrm = () => {
const data = await executeStored({ const data = await executeStored({
stored: "PKG_P8PANELS_EQUIPSRV.EQUIPSRV_GRID", stored: "PKG_P8PANELS_EQUIPSRV.EQUIPSRV_GRID",
args: { args: {
SBELONG: filter.belong, SBELONG: filter.values.belong,
SPRODOBJ: filter.prodObj, SPRODOBJ: filter.values.prodObj,
STECHSERV: filter.techServ, STECHSERV: filter.values.techServ,
SRESPDEP: filter.respDep, SRESPDEP: filter.values.respDep,
NFROMMONTH: filter.fromMonth, NFROMMONTH: filter.values.fromMonth,
NFROMYEAR: filter.fromYear, NFROMYEAR: filter.values.fromYear,
NTOMONTH: filter.toMonth, NTOMONTH: filter.values.toMonth,
NTOYEAR: filter.toYear NTOYEAR: filter.values.toYear
}, },
respArg: "COUT", respArg: "COUT",
attributeValueProcessor: (name, val) => (["caption", "name", "parent"].includes(name) ? undefined : val) attributeValueProcessor: (name, val) => (["caption", "name", "parent"].includes(name) ? undefined : val)
@ -144,8 +141,11 @@ const EqsPrfrm = () => {
stored: "PKG_P8PANELS_EQUIPSRV.GET_DEFAULT_FP", stored: "PKG_P8PANELS_EQUIPSRV.GET_DEFAULT_FP",
respArg: "COUT" respArg: "COUT"
}); });
setFilter(pv => ({ ...pv, belong: data.JURPERS, fromMonth: 1, fromYear: data.YEAR, toMonth: 12, toYear: data.YEAR })); setFilter(pv => ({
setDefaultLoaded(true); ...pv,
values: { ...pv.values, belong: data.JURPERS, fromMonth: 1, fromYear: data.YEAR, toMonth: 12, toYear: data.YEAR },
isDefault: true
}));
}, [executeStored]); }, [executeStored]);
//Отбор документа (ТОиР или Ремонтных ведомостей) по ячейке даты //Отбор документа (ТОиР или Ремонтных ведомостей) по ячейке даты
@ -158,10 +158,10 @@ const EqsPrfrm = () => {
const data = await executeStored({ const data = await executeStored({
stored: "PKG_P8PANELS_EQUIPSRV.SELECT_EQUIPSRV", stored: "PKG_P8PANELS_EQUIPSRV.SELECT_EQUIPSRV",
args: { args: {
SBELONG: filter.belong, SBELONG: filter.values.belong,
SPRODOBJ: filter.prodObj, SPRODOBJ: filter.values.prodObj,
STECHSERV: filter.techServ ? filter.techServ : null, STECHSERV: filter.values.techServ ? filter.values.techServ : null,
SRESPDEP: filter.respDep ? filter.respDep : null, SRESPDEP: filter.values.respDep ? filter.values.respDep : null,
STECHNAME: techName, STECHNAME: techName,
SSRVKIND: servKind, SSRVKIND: servKind,
NYEAR: Number(year), NYEAR: Number(year),
@ -176,10 +176,11 @@ const EqsPrfrm = () => {
} else showMsgErr(TEXTS.NO_DATA_FOUND); } else showMsgErr(TEXTS.NO_DATA_FOUND);
}; };
//Открыть фильтр //Показать/скрыть фильтр
const openFilter = () => { const setFilterOpen = isOpen => setFilter(pv => ({ ...pv, isOpen }));
setFilterOpen(true);
}; //Установить значение фильтра
const setFilterValues = values => setFilter(pv => ({ ...pv, isSetByUser: true, values: { ...values } }));
//Отработка события скрытия/раскрытия ячейки даты //Отработка события скрытия/раскрытия ячейки даты
const handleClick = (e, ref) => { const handleClick = (e, ref) => {
@ -195,16 +196,20 @@ const EqsPrfrm = () => {
loadData(); loadData();
}, [loadData, dataGrid.reload]); }, [loadData, dataGrid.reload]);
//При открытом фильтре //При изменении фильтра
useEffect(() => { useEffect(() => {
if (filterOpen) { if (filter.isSetByUser) setDataGrid({ reload: true });
{ }, [
setFilterCopy({ ...filter }); filter.isSetByUser,
if (!defaultLoaded) loadDefaultFilter(); filter.values.belong,
} filter.values.prodObj,
} filter.values.techServ,
//eslint-disable-next-line react-hooks/exhaustive-deps filter.values.respDep,
}, [filterOpen]); filter.values.fromMonth,
filter.values.fromYear,
filter.values.toMonth,
filter.values.toYear
]);
//При нажатии скрытии/раскрытии ячейки даты, фокус на неё //При нажатии скрытии/раскрытии ячейки даты, фокус на неё
useEffect(() => { useEffect(() => {
@ -218,28 +223,34 @@ const EqsPrfrm = () => {
//eslint-disable-next-line react-hooks/exhaustive-deps //eslint-disable-next-line react-hooks/exhaustive-deps
}, [refIsDeprecated]); }, [refIsDeprecated]);
//При загрузке фильтра по умолчанию
useEffect(() => {
if (filter.isDefault) setFilterOpen(true);
}, [filter.isDefault]);
//При подключении к страницк
useEffect(() => {
loadDefaultFilter();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//При открытии диалога фильтра
const handleFilterClick = () => setFilterOpen(true);
//При изменении фильтра в диалоге
const handleFilterOk = filter => {
setFilterValues(filter);
setFilterOpen(false);
};
//При закрытии диалога фильтра
const handleFilterCancel = () => setFilterOpen(false);
//Генерация содержимого //Генерация содержимого
return ( return (
<div> <div>
<FilterDialog {filter.isOpen ? <FilterDialog initial={filter.values} onOk={handleFilterOk} onCancel={handleFilterCancel} /> : null}
filter={filter} <Filter filter={filter.values} onClick={handleFilterClick} />
filterCopy={filterCopy}
filterOpen={filterOpen}
setFilter={setFilter}
setFilterOpen={setFilterOpen}
setDataGrid={setDataGrid}
/>
<Link component="button" variant="body2" textAlign={"left"} onClick={openFilter}>
Фильтр отбора: {filter.belong ? `Принадлежность: ${filter.belong}` : ""}{" "}
{filter.prodObj ? `Производственный объект: ${filter.prodObj}` : ""} {filter.techServ ? `Техническая служба: ${filter.techServ}` : ""}{" "}
{filter.respDep ? `Ответственное подразделение: ${filter.respDep}` : ""}{" "}
{filter.fromMonth && filter.fromYear
? `Начало периода: ${filter.fromMonth < 10 ? "0" + filter.fromMonth : filter.fromMonth}.${filter.fromYear}`
: ""}{" "}
{filter.toMonth && filter.toYear
? `Конец периода: ${filter.toMonth < 10 ? "0" + filter.toMonth : filter.toMonth}.${filter.toYear}`
: ""}
</Link>
{dataGrid.dataLoaded ? ( {dataGrid.dataLoaded ? (
<Paper variant="outlined"> <Paper variant="outlined">
<Grid container spacing={1}> <Grid container spacing={1}>

View File

@ -0,0 +1,91 @@
/*
Парус 8 - Панели мониторинга - ТОиР - Выполнение работ
Компонент: Фильтр отбора
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Chip, Stack, Icon, IconButton } from "@mui/material"; //Интерфейсные компоненты
//--------------------------
//Вспомогательные компоненты
//--------------------------
//Элемент фильтра
const FilterItem = ({ caption, value, onClick }) => {
//При нажатии на элемент
const handleClick = () => (onClick ? onClick() : null);
//Генерация содержимого
return (
<Chip
label={
<Stack direction={"row"} alignItems={"center"}>
<strong>{caption}</strong>:&nbsp;{value}
</Stack>
}
variant="outlined"
onClick={handleClick}
/>
);
};
//Контроль свойств компонента - Элемент фильтра
FilterItem.propTypes = {
caption: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
onClick: PropTypes.func
};
//---------------
//Тело компонента
//---------------
//Фильтр отбора
const Filter = ({ filter, onClick }) => {
//При нажатии на фильтр
const handleClick = () => (onClick ? onClick() : null);
//Генерация содержимого
return (
<Stack direction="row" spacing={1} p={1} alignItems={"center"}>
<IconButton onClick={handleClick}>
<Icon>filter_alt</Icon>
</IconButton>
{filter.belong ? <FilterItem caption={"Принадлежность"} value={filter.belong} onClick={handleClick} /> : null}
{filter.prodObj ? <FilterItem caption={"Производственный объект"} value={filter.prodObj} onClick={handleClick} /> : null}
{filter.techServ ? <FilterItem caption={"Техническая служба"} value={filter.techServ} onClick={handleClick} /> : null}
{filter.respDep ? <FilterItem caption={"Ответственное подразделение"} value={filter.respDep} onClick={handleClick} /> : null}
{filter.fromMonth && filter.fromYear ? (
<FilterItem
caption={"Начало периода"}
value={`${filter.fromMonth < 10 ? "0" + filter.fromMonth : filter.fromMonth}.${filter.fromYear}`}
onClick={handleClick}
/>
) : null}
{filter.toMonth && filter.toYear ? (
<FilterItem
caption={"Конец периода"}
value={`${filter.toMonth < 10 ? "0" + filter.toMonth : filter.toMonth}.${filter.toYear}`}
onClick={handleClick}
/>
) : null}
</Stack>
);
};
//Контроль свойств компонента - Фильтр отбора
Filter.propTypes = {
filter: PropTypes.object.isRequired,
onClick: PropTypes.func
};
//--------------------
//Интерфейс компонента
//--------------------
export { Filter };

View File

@ -1,52 +1,89 @@
/* /*
Парус 8 - Панели мониторинга - ТОиР - Выполнение работ Парус 8 - Панели мониторинга - ТОиР - Выполнение работ
Панель мониторинга: Диалоговое окно фильтра отбора Компонент: Диалоговое окно фильтра отбора
*/ */
//--------------------- //---------------------
//Подключение библиотек //Подключение библиотек
//--------------------- //---------------------
import React, { useState, useContext, useEffect, useCallback } from "react"; //Классы React import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Paper, Box, Grid } from "@mui/material"; //Интерфейсные компоненты import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, Grid } from "@mui/material"; //Интерфейсные компоненты
import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода
import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { STYLES } from "./layouts"; //Стили
//--------- //---------
//Константы //Константы
//--------- //---------
//Массив месяцев //Массив месяцев
export const MONTH_ARRAY = ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"]; const MONTH_ARRAY = ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"];
//Стили
const STYLES = {
DIALOG_ACTIONS: { justifyContent: "center" },
CLOSE_BUTTON: {
position: "absolute",
right: 8,
top: 8,
color: theme => theme.palette.grey[500]
}
};
//-----------------------
//Вспомогательные функции
//-----------------------
//Формирование списка лет
const buildYears = () => {
const res = [1990];
const today = new Date();
for (let i = res[0] + 1; i <= today.getFullYear(); i++) res.push(i);
return res;
};
//Выбор принадлежности
const selectJuridicalPersons = (showDictionary, callBack) => {
showDictionary({
unitCode: "JuridicalPersons",
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
});
};
//Выбор производственного объекта
const selectEquipConfiguration = (showDictionary, callBack) => {
showDictionary({
unitCode: "EquipConfiguration",
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
});
};
//Выбор подразделения
const selectInsDepartment = (showDictionary, callBack) => {
showDictionary({
unitCode: "INS_DEPARTMENT",
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
});
};
//--------------- //---------------
//Тело компонента //Тело компонента
//--------------- //---------------
//Диалоговое окно фильтра отбора //Диалоговое окно фильтра отбора
const FilterDialog = props => { const FilterDialog = ({ initial, onCancel, onOk }) => {
//Свойства //Собственное состояние
const { filter, filterCopy, filterOpen, setFilter, setFilterOpen, setDataGrid } = props; const [filter, setFilter] = useState({ ...initial });
//Состояние ограничения редактирования фильтра
const [filterLock, setFilterLock] = useState(false);
//Состояние массива лет
const [years, setYears] = useState({ array: [1990], filled: false });
//Подключение к контексту приложения //Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx); const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Закрыть фильтр //При закрытии диалога без изменения фильтра
const closeFilter = e => { const handleCancel = () => (onCancel ? onCancel() : null);
if (filterLock && e != undefined) setFilter(filterCopy);
setFilterOpen(false);
};
//Очистить фильтр //При очистке фильтра
const clearFilter = () => { const handleClear = () => {
setFilter({ setFilter({
belong: "", belong: "",
prodObj: "", prodObj: "",
@ -59,179 +96,128 @@ const FilterDialog = props => {
}); });
}; };
//Заполнение состояния массива лет //При закрытии диалога с изменением фильтра
const getYearArray = useCallback(async () => { const handleOK = () => (onOk ? onOk(filter) : null);
const today = new Date();
for (let i = years.array[0] + 1; i <= today.getFullYear(); i++) {
setYears(pv => ({ ...pv, array: [...pv.array, i] }));
}
setYears(pv => ({ ...pv, filled: true }));
//eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Только при первичном рендере //При изменении значения элемента
useEffect(() => { const handleFilterItemChange = (item, value) => setFilter(pv => ({ ...pv, [item]: value }));
if (filterOpen && !years.filled) getYearArray();
}, [filterOpen, getYearArray, years.filled]);
//Генерация содержимого //Генерация содержимого
return ( return (
<div> <div>
<Dialog open={filterOpen} onClose={closeFilter}> <Dialog open onClose={handleCancel} fullWidth maxWidth="sm">
<DialogTitle>Фильтр отбора</DialogTitle> <DialogTitle>Фильтр отбора</DialogTitle>
<IconButton <IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
aria-label="close"
onClick={closeFilter}
sx={{
position: "absolute",
right: 8,
top: 8,
color: theme => theme.palette.grey[500]
}}
>
<Icon>close</Icon> <Icon>close</Icon>
</IconButton> </IconButton>
<DialogContent> <DialogContent>
<Paper> <Box component="section" p={1}>
<Box component="section" sx={{ p: 1 }}> <FilterInputField
<FilterInputField elementCode="belong"
elementCode="belong" elementValue={filter.belong}
elementValue={filter.belong} labelText="Принадлежность"
labelText="Принадлежность" dictionary={callBack => selectJuridicalPersons(pOnlineShowDictionary, callBack)}
changeFunc={() => { required={true}
pOnlineShowDictionary({ onChange={handleFilterItemChange}
unitCode: "JuridicalPersons", />
callBack: res => </Box>
res.success === true ? setFilter(pv => ({ ...pv, belong: res.outParameters.out_CODE })) : null <Box component="section" p={1}>
}); <FilterInputField
}} elementCode="prodObj"
required={true} elementValue={filter.prodObj}
/> labelText="Производственный объект"
</Box> dictionary={callBack => selectEquipConfiguration(pOnlineShowDictionary, callBack)}
<Box component="section" sx={{ p: 1 }}> required={true}
<FilterInputField onChange={handleFilterItemChange}
elementCode="prodObj" />
elementValue={filter.prodObj} </Box>
labelText="Производственный объект" <Box component="section" p={1}>
changeFunc={() => { <FilterInputField
pOnlineShowDictionary({ elementCode="techServ"
unitCode: "EquipConfiguration", elementValue={filter.techServ}
callBack: res => labelText="Техническая служба"
res.success === true ? setFilter(pv => ({ ...pv, prodObj: res.outParameters.out_CODE })) : null dictionary={callBack => selectInsDepartment(pOnlineShowDictionary, callBack)}
}); onChange={handleFilterItemChange}
}} />
required={true} </Box>
/> <Box component="section" p={1}>
</Box> <FilterInputField
<Box component="section" sx={{ p: 1 }}> elementCode="respDep"
<FilterInputField elementValue={filter.respDep}
elementCode="techServ" labelText="Ответственное подразделение"
elementValue={filter.techServ} dictionary={callBack => selectInsDepartment(pOnlineShowDictionary, callBack)}
labelText="Техническая служба" onChange={handleFilterItemChange}
changeFunc={() => { />
pOnlineShowDictionary({ </Box>
unitCode: "INS_DEPARTMENT", <Box component="section" p={1}>
callBack: res => <Grid container spacing={2} alignItems={"center"}>
res.success === true ? setFilter(pv => ({ ...pv, techServ: res.outParameters.out_CODE })) : null <Grid textAlign={"left"} item xs={4}>
}); Начало периода:
}}
/>
</Box>
<Box component="section" sx={{ p: 1 }}>
<FilterInputField
elementCode="respDep"
elementValue={filter.respDep}
labelText="Ответственное подразделение"
changeFunc={() => {
pOnlineShowDictionary({
unitCode: "INS_DEPARTMENT",
callBack: res =>
res.success === true ? setFilter(pv => ({ ...pv, respDep: res.outParameters.out_CODE })) : null
});
}}
/>
</Box>
<Box component="section" sx={{ p: 1 }}>
<Grid container spacing={2}>
<Grid textAlign={"center"} item xs={4}>
Начало периода:
</Grid>
<Grid item xs={4}>
<FilterInputField
elementCode="from-month"
elementValue={filter.fromMonth}
labelText="Месяц"
changeFunc={e => setFilter(pv => ({ ...pv, fromMonth: e.target.value }))}
required={true}
isDateField={true}
/>
</Grid>
<Grid item xs={4}>
<FilterInputField
elementCode="from-year"
elementValue={filter.fromYear}
labelText="Год"
changeFunc={e => setFilter(pv => ({ ...pv, fromYear: e.target.value }))}
required={true}
isDateField={true}
yearArray={years.array}
/>
</Grid>
</Grid> </Grid>
</Box> <Grid item xs={4}>
<Box component="section" sx={{ p: 1 }}> <FilterInputField
<Grid container spacing={2}> elementCode="fromMonth"
<Grid textAlign={"center"} item xs={4}> elementValue={filter.fromMonth}
Конец периода: labelText="Месяц"
</Grid> required={true}
<Grid item xs={4}> items={MONTH_ARRAY.map((item, i) => ({ value: i + 1, caption: item }))}
<FilterInputField onChange={handleFilterItemChange}
elementCode="to-month" />
elementValue={filter.toMonth}
labelText="Месяц"
changeFunc={e => setFilter(pv => ({ ...pv, toMonth: e.target.value }))}
required={true}
isDateField={true}
/>
</Grid>
<Grid item xs={4}>
<FilterInputField
elementCode="to-year"
elementValue={filter.toYear}
labelText="Год"
changeFunc={e => setFilter(pv => ({ ...pv, toYear: e.target.value }))}
required={true}
isDateField={true}
yearArray={years.array}
/>
</Grid>
</Grid> </Grid>
</Box> <Grid item xs={4}>
</Paper> <FilterInputField
elementCode="fromYear"
elementValue={filter.fromYear}
labelText="Год"
required={true}
items={buildYears().map(item => ({ value: item, caption: item }))}
onChange={handleFilterItemChange}
/>
</Grid>
</Grid>
</Box>
<Box component="section" p={1}>
<Grid container spacing={2} alignItems={"center"}>
<Grid textAlign={"left"} item xs={4}>
Конец периода:
</Grid>
<Grid item xs={4}>
<FilterInputField
elementCode="toMonth"
elementValue={filter.toMonth}
labelText="Месяц"
required={true}
items={MONTH_ARRAY.map((item, i) => ({ value: i + 1, caption: item }))}
onChange={handleFilterItemChange}
/>
</Grid>
<Grid item xs={4}>
<FilterInputField
elementCode="toYear"
elementValue={filter.toYear}
labelText="Год"
required={true}
items={buildYears().map(item => ({ value: item, caption: item }))}
onChange={handleFilterItemChange}
/>
</Grid>
</Grid>
</Box>
</DialogContent> </DialogContent>
<DialogActions sx={{ ...STYLES.FILTER_DIALOG_ACTIONS }}> <DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button <Button
variant="text" variant="text"
disabled={ disabled={
filter.belong && filter.prodObj && filter.fromMonth && filter.fromYear && filter.toMonth && filter.toYear ? false : true filter.belong && filter.prodObj && filter.fromMonth && filter.fromYear && filter.toMonth && filter.toYear ? false : true
} }
onClick={() => { onClick={handleOK}
setFilterLock(true);
setDataGrid({ reload: true });
closeFilter();
}}
> >
Сформировать Сформировать
</Button> </Button>
<Button variant="text" onClick={clearFilter}> <Button variant="text" onClick={handleClear}>
Очистить Очистить
</Button> </Button>
<Button <Button variant="text" onClick={handleCancel}>
variant="text"
onClick={() => {
setFilter(filterCopy);
}}
>
Отмена Отмена
</Button> </Button>
</DialogActions> </DialogActions>
@ -242,12 +228,9 @@ const FilterDialog = props => {
//Контроль свойств компонента - Диалоговое окно фильтра отбора //Контроль свойств компонента - Диалоговое окно фильтра отбора
FilterDialog.propTypes = { FilterDialog.propTypes = {
filter: PropTypes.object.isRequired, initial: PropTypes.object.isRequired,
filterCopy: PropTypes.object.isRequired, onOk: PropTypes.func,
filterOpen: PropTypes.bool.isRequired, onCancel: PropTypes.func
setFilter: PropTypes.func.isRequired,
setFilterOpen: PropTypes.func.isRequired,
setDataGrid: PropTypes.func.isRequired
}; };
//-------------------- //--------------------

View File

@ -1,81 +1,89 @@
/* /*
Парус 8 - Панели мониторинга - ТОиР - Выполнение работ Парус 8 - Панели мониторинга - ТОиР - Выполнение работ
Панель мониторинга: Компонент поля ввода Компонент: Поле ввода диалога фильтра
*/ */
//--------------------- //---------------------
//Подключение библиотек //Подключение библиотек
//--------------------- //---------------------
import React, { useEffect, useState, useCallback } from "react"; //Классы React import React, { useEffect, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты
import { MONTH_ARRAY } from "./filter_dialog"; //Название месяцев
//---------
//Константы
//---------
//Стили
const STYLES = {
HELPER_TEXT: { color: "red" }
};
//--------------- //---------------
//Тело компонента //Тело компонента
//--------------- //---------------
//Поле ввода //Поле ввода
const FilterInputField = props => { const FilterInputField = ({ elementCode, elementValue, labelText, onChange, required = false, items = null, dictionary }) => {
//Свойства //Значение элемента
const { elementCode, elementValue, labelText, changeFunc, required, isDateField, yearArray } = props; const [value, setValue] = useState(elementValue);
//Состояние идентификатора элемента //При получении нового значения из вне
const [elementId, setElementId] = useState("");
//Формирование идентификатора элемента
const generateId = useCallback(async () => {
setElementId(!isDateField ? `${elementCode}-input` : `${elementCode}-select`);
}, [elementCode, isDateField]);
//При рендере поля ввода
useEffect(() => { useEffect(() => {
generateId(); setValue(elementValue);
}, [generateId]); }, [elementValue]);
//Выбор значения из словаря
const handleDictionaryClick = () =>
dictionary ? dictionary(res => (res ? handleChange({ target: { name: elementCode, value: res } }) : null)) : null;
//Изменение значения элемента
const handleChange = e => {
setValue(e.target.value);
if (onChange) onChange(e.target.name, e.target.value);
};
//Генерация поля с выбором из словаря Парус //Генерация поля с выбором из словаря Парус
const renderInput = () => { const renderInput = validationError => {
return ( return (
<Input <Input
error={!elementValue && required ? true : false} error={validationError}
id={elementId} id={elementCode}
value={elementValue} name={elementCode}
value={value}
endAdornment={ endAdornment={
<InputAdornment position="end"> dictionary ? (
<IconButton aria-label={`${elementCode} select`} onClick={changeFunc} edge="end"> <InputAdornment position="end">
<Icon>list</Icon> <IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
</IconButton> <Icon>list</Icon>
</InputAdornment> </IconButton>
</InputAdornment>
) : null
} }
aria-describedby={`${elementId}-helper-text`} aria-describedby={`${elementCode}-helper-text`}
label={labelText} label={labelText}
onChange={handleChange}
/> />
); );
}; };
//Генерация поля с выпадающим списком //Генерация поля с выпадающим списком
const renderSelect = () => { const renderSelect = (items, validationError) => {
return ( return (
<Select <Select
error={elementValue ? false : true} error={validationError}
id={elementId} id={elementCode}
value={elementValue} name={elementCode}
aria-describedby={`${elementId}-helper-text`} value={value}
aria-describedby={`${elementCode}-helper-text`}
label={labelText} label={labelText}
onChange={changeFunc} onChange={handleChange}
> >
{labelText === "Месяц" {items
? MONTH_ARRAY.map((item, i) => ( ? items.map((item, i) => (
<MenuItem key={i + 1} value={i + 1}> <MenuItem key={i} value={item.value}>
{item} {item.caption}
</MenuItem>
))
: null}
{labelText === "Год"
? yearArray.map(item => (
<MenuItem key={item} value={item}>
{item}
</MenuItem> </MenuItem>
)) ))
: null} : null}
@ -83,13 +91,16 @@ const FilterInputField = props => {
); );
}; };
//Признак ошибки валидации
const validationError = !value && required ? true : false;
//Генерация содержимого //Генерация содержимого
return ( return (
<FormControl readOnly={isDateField ? false : true} fullWidth variant="standard"> <FormControl fullWidth variant="standard">
<InputLabel htmlFor={elementId}>{labelText}</InputLabel> <InputLabel htmlFor={elementCode}>{labelText}</InputLabel>
{isDateField ? renderSelect() : renderInput()} {items ? renderSelect(items, validationError) : renderInput(validationError)}
{required && !elementValue ? ( {validationError ? (
<FormHelperText id={`${elementId}-helper-text`} sx={{ color: "red" }}> <FormHelperText id={`${elementCode}-helper-text`} sx={STYLES.HELPER_TEXT}>
*Обязательное поле *Обязательное поле
</FormHelperText> </FormHelperText>
) : null} ) : null}
@ -102,16 +113,10 @@ FilterInputField.propTypes = {
elementCode: PropTypes.string.isRequired, elementCode: PropTypes.string.isRequired,
elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
labelText: PropTypes.string.isRequired, labelText: PropTypes.string.isRequired,
changeFunc: PropTypes.func.isRequired,
required: PropTypes.bool, required: PropTypes.bool,
isDateField: PropTypes.bool, items: PropTypes.arrayOf(PropTypes.object),
yearArray: PropTypes.arrayOf(PropTypes.number) dictionary: PropTypes.func,
}; onChange: PropTypes.func
//Значения по умолчанию - Поле ввода
FilterInputField.defaultProps = {
required: false,
isDateField: false
}; };
//-------------------- //--------------------

View File

@ -29,7 +29,9 @@ export const STYLES = {
DCR_PLAN_CELL_STYLE: { cursor: "pointer", backgroundColor: "lightblue", border: "1px solid rgba(0, 0, 0) !important" }, DCR_PLAN_CELL_STYLE: { cursor: "pointer", backgroundColor: "lightblue", border: "1px solid rgba(0, 0, 0) !important" },
DCR_FACT_RELATED_CELL_STYLE: { cursor: "pointer", backgroundColor: "green", border: "1px solid rgba(0, 0, 0) !important" }, DCR_FACT_RELATED_CELL_STYLE: { cursor: "pointer", backgroundColor: "green", border: "1px solid rgba(0, 0, 0) !important" },
DCR_FACT_NOT_RELATED_CELL_STYLE: { cursor: "pointer", backgroundColor: "crimson", border: "1px solid rgba(0, 0, 0) !important" }, DCR_FACT_NOT_RELATED_CELL_STYLE: { cursor: "pointer", backgroundColor: "crimson", border: "1px solid rgba(0, 0, 0) !important" },
FILTER_DIALOG_ACTIONS: { justifyContent: "center" } DCR_DOUBLE_CELL: { padding: "unset" },
DCR_DOUBLE_CELL_GRID_ITEM: backgroundColor => ({ cursor: "pointer", backgroundColor }),
HIDDEN_PARAGRAPH: { display: "none" }
}; };
//----------- //-----------
@ -155,26 +157,26 @@ export const dataCellRender = ({ row, columnDef }, showEquipSrv) => {
//Случай двойного закрашивания месяца //Случай двойного закрашивания месяца
case "green red": case "green red":
case "red green": case "red green":
cellStyle = { ...cellStyle, padding: "unset" }; cellStyle = { ...cellStyle, ...STYLES.DCR_DOUBLE_CELL };
cellProps = { title: formatDate(columnDef.name) }; cellProps = { title: formatDate(columnDef.name) };
data = ( data = (
<Stack sx={{ justifyContent: "center" }} direction="row"> <Stack justifyContent={"center"} direction="row">
<Grid container maxHeight="100%"> <Grid container maxHeight="100%">
<Grid <Grid
item item
xs={6} xs={6}
sx={{ cursor: "pointer", backgroundColor: "green" }} sx={STYLES.DCR_DOUBLE_CELL_GRID_ITEM("green")}
onClick={() => showEquipSrv({ date: columnDef.name, workType: row["SWRKTYPE"], info: row["groupName"] })} onClick={() => showEquipSrv({ date: columnDef.name, workType: row["SWRKTYPE"], info: row["groupName"] })}
> >
<p style={{ display: "none" }}>g</p> <p style={STYLES.HIDDEN_PARAGRAPH}>g</p>
</Grid> </Grid>
<Grid <Grid
item item
xs={6} xs={6}
sx={{ cursor: "pointer", backgroundColor: "crimson" }} sx={STYLES.DCR_DOUBLE_CELL_GRID_ITEM("crimson")}
onClick={() => showEquipSrv({ date: columnDef.name, workType: row["SWRKTYPE"], info: row["groupName"] })} onClick={() => showEquipSrv({ date: columnDef.name, workType: row["SWRKTYPE"], info: row["groupName"] })}
> >
<p style={{ display: "none" }}>r</p> <p style={STYLES.HIDDEN_PARAGRAPH}>r</p>
</Grid> </Grid>
</Grid> </Grid>
</Stack> </Stack>