ЦИТК-932;ЦИТК-933;ЦИТК-935

This commit is contained in:
Vladislav 2025-02-20 16:11:08 +03:00
parent c64c9cd3a1
commit 4b2d589e63
10 changed files with 1019 additions and 272 deletions

View File

@ -15,9 +15,10 @@ import { TaskFormDialog } from "./components/task_form"; //Компонент ф
import { Filter } from "./filter.js"; //Компонент фильтров import { Filter } from "./filter.js"; //Компонент фильтров
import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора
import { TaskCardSettings } from "./components/task_card_settings.js"; //Компонент настроек карточки события import { TaskCardSettings } from "./components/task_card_settings.js"; //Компонент настроек карточки события
import { useTasks, COLORS } from "./hooks.js"; //Вспомогательные хуки import { useTasks, useSettings, COLORS } from "./hooks.js"; //Вспомогательные хуки
import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания
import { SettingsDialog } from "./components/settings_dialog.js"; //Диалог дополнительных настроек
//--------- //---------
//Константы //Константы
@ -35,11 +36,10 @@ const FILTER_HEIGHT = "56px";
//Стили //Стили
const STYLES = { const STYLES = {
CONTAINER: { width: "100%", padding: 0 }, CONTAINER: { width: "100%", padding: 0 },
FILTER: { position: "fixed" },
STATUSES_STACK: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...APP_STYLES.SCROLL }, STATUSES_STACK: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...APP_STYLES.SCROLL },
STATUS_BLOCK: statusColor => { STATUS_BLOCK: statusColor => {
return { return {
width: "315px", width: "350px",
height: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`, height: `calc(100vh - ${TITLE_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`,
backgroundColor: statusColor, backgroundColor: statusColor,
padding: "8px" padding: "8px"
@ -61,9 +61,14 @@ const STYLES = {
textAlign: "left", textAlign: "left",
textOverflow: "ellipsis", textOverflow: "ellipsis",
overflow: "hidden", overflow: "hidden",
whiteSpace: "pre", display: "-webkit-box",
maxWidth: "calc(250px)", hyphens: "auto",
width: "-webkit-fill-available" WebkitBoxOrient: "vertical",
WebkitLineClamp: 1,
maxWidth: "calc(300px)",
width: "-webkit-fill-available",
fontSize: "1.2rem",
cursor: "default"
}, },
PADDING_0: { padding: 0 } PADDING_0: { padding: 0 }
}; };
@ -77,11 +82,6 @@ const ClntTaskBoard = () => {
//Собственное состояние //Собственное состояние
const [ const [
tasks, tasks,
eventRoutes,
eventPoints,
noteTypes,
docLinks,
accounts,
taskFormOpen, taskFormOpen,
setTaskFormOpen, setTaskFormOpen,
cardSettings, cardSettings,
@ -97,6 +97,9 @@ const ClntTaskBoard = () => {
getDocLinks getDocLinks
] = useTasks(); ] = useTasks();
//Состояние дополнительных настроек
const [settings, settingsOpen, settingsClose, handleSettingsOk] = useSettings(tasks.statuses);
//Состояние диалога примечания //Состояние диалога примечания
const [noteDialog, setNoteDialog] = useState({ visible: false, callback: null }); const [noteDialog, setNoteDialog] = useState({ visible: false, callback: null });
@ -109,6 +112,11 @@ const ClntTaskBoard = () => {
//Состояние доступных маршрутов события //Состояние доступных маршрутов события
const [availableRoutes, setAvailableRoutes] = useState({ sorce: "", routes: [] }); const [availableRoutes, setAvailableRoutes] = useState({ sorce: "", routes: [] });
//Очистка состояния доступных маршрутов события
const clearAvailableRoutesState = () => {
setAvailableRoutes({ sorce: "", routes: [] });
};
//Состояние перетаскиваемого события //Состояние перетаскиваемого события
const [dragItem, setDragItem] = useState({ type: "", status: "" }); const [dragItem, setDragItem] = useState({ type: "", status: "" });
@ -117,11 +125,6 @@ const ClntTaskBoard = () => {
setDragItem({ type: "", status: "" }); setDragItem({ type: "", status: "" });
}; };
//Очистка состояния
const clearARState = () => {
setAvailableRoutes({ sorce: "", routes: [] });
};
//Проверка доступности карточки события //Проверка доступности карточки события
const isCardAvailable = code => { const isCardAvailable = code => {
return availableRoutes.sorce === code || availableRoutes.routes.find(r => r.dest === code) || !availableRoutes.sorce ? true : false; return availableRoutes.sorce === code || availableRoutes.routes.find(r => r.dest === code) || !availableRoutes.sorce ? true : false;
@ -133,31 +136,41 @@ const ClntTaskBoard = () => {
{tasks.filters.isOpen ? ( {tasks.filters.isOpen ? (
<FilterDialog <FilterDialog
initial={tasks.filters.values} initial={tasks.filters.values}
docs={docLinks} docs={tasks.extraData.docLinks}
onOk={handleFilterOk} onOk={handleFilterOk}
onCancel={handleFilterCancel} onCancel={handleFilterCancel}
getDocLinks={getDocLinks} getDocLinks={getDocLinks}
/> />
) : null} ) : null}
<Filter {settings.open ? <SettingsDialog initial={settings} onOk={handleSettingsOk} onCancel={settingsClose} /> : null}
filter={tasks.filters.values} <Box sx={{ display: "flex", alignItems: "center" }}>
selectedDoc={tasks.filters.values.docLink ? docLinks.find(d => d.id === tasks.filters.values.docLink) : null} <Stack direction="row">
handleFilterClick={handleFilterClick} <Filter
handleReload={handleReload} filter={tasks.filters.values}
orders={tasks.orders} selectedDoc={tasks.filters.values.docLink ? tasks.extraData.docLinks.find(d => d.id === tasks.filters.values.docLink) : null}
handleOrderChanged={handleOrderChanged} handleFilterClick={handleFilterClick}
sx={STYLES.FILTER} handleReload={handleReload}
/> orders={tasks.orders}
{noteDialog.visible ? <NoteDialog noteTypes={noteTypes} onOk={n => noteDialog.callback(n)} onCancel={handleNoteDialogClose} /> : null} handleOrderChanged={handleOrderChanged}
//sx={STYLES.FILTER}
/>
</Stack>
<IconButton title="Настройки" onClick={settingsOpen} sx={{ marginLeft: "auto" }}>
<Icon>settings</Icon>
</IconButton>
</Box>
{noteDialog.visible ? (
<NoteDialog noteTypes={tasks.extraData.noteTypes} onOk={n => noteDialog.callback(n)} onCancel={handleNoteDialogClose} />
) : null}
{tasks.filters.values.type ? ( {tasks.filters.values.type ? (
<DragDropContext <DragDropContext
onDragStart={e => { onDragStart={e => {
let srcCode = tasks.statuses.find(s => s.id == e.source.droppableId).code; let srcCode = tasks.statuses.find(s => s.id == e.source.droppableId).code;
setAvailableRoutes({ sorce: srcCode, routes: [...eventRoutes.filter(r => r.src === srcCode)] }); setAvailableRoutes({ sorce: srcCode, routes: [...tasks.extraData.evRoutes.filter(r => r.src === srcCode)] });
}} }}
onDragEnd={e => { onDragEnd={e => {
onDragEnd(e, eventPoints, handleNoteDialogOpen); onDragEnd(e, tasks.extraData.evPoints, handleNoteDialogOpen);
clearARState(); clearAvailableRoutesState();
}} }}
> >
<div style={STYLES.STATUSES_DIV}> <div style={STYLES.STATUSES_DIV}>
@ -165,74 +178,85 @@ const ClntTaskBoard = () => {
{provided => ( {provided => (
<div ref={provided.innerRef}> <div ref={provided.innerRef}>
<Stack direction="row" spacing={2} sx={STYLES.STATUSES_STACK}> <Stack direction="row" spacing={2} sx={STYLES.STATUSES_STACK}>
{tasks.statuses.map((status, index) => ( {settings.statusesSort.sorted
<div key={index}> ? settings.statusesSort.statuses.map((status, index) => (
<Droppable isDropDisabled={!isCardAvailable(status.code)} droppableId={status.id.toString()}> <div key={index}>
{provided => ( <Droppable isDropDisabled={!isCardAvailable(status.code)} droppableId={status.id.toString()}>
<div ref={provided.innerRef}> {provided => (
<Card <div ref={provided.innerRef}>
className="category-card" <Card
sx={{ className="category-card"
...STYLES.STATUS_BLOCK(status.color), sx={{
...STYLES.BLOCK_OPACITY(isCardAvailable(status.code)) ...STYLES.STATUS_BLOCK(status.color),
}} ...STYLES.BLOCK_OPACITY(isCardAvailable(status.code))
> }}
<CardHeader >
action={ <CardHeader
<IconButton action={
aria-label="settings" <IconButton
onClick={() => handleCardSettingsClick(status)} aria-label="settings"
> onClick={() => handleCardSettingsClick(status)}
<Icon>more_vert</Icon> >
</IconButton> <Icon>more_vert</Icon>
} </IconButton>
title={ }
<Typography sx={STYLES.MARK_INFO} title={status.caption} variant="h5"> title={
{status.caption} <Typography
</Typography> sx={STYLES.MARK_INFO}
} title={status[settings.statusesSort.attr] || status.name}
subheader={ variant="h5"
<Button >
onClick={() => { {status[settings.statusesSort.attr] || status.name}
setDragItem({ </Typography>
type: tasks.filters.values.type, }
status: status.code subheader={
}); <Button
setTaskFormOpen(true); onClick={() => {
}} setDragItem({
> type: tasks.filters.values.type,
+ Добавить status: status.code
</Button> });
} setTaskFormOpen(true);
sx={STYLES.PADDING_0} }}
/> >
<CardContent sx={STYLES.CARD_CONTENT}> + Добавить
<Stack spacing={1}> </Button>
{tasks.rows }
.filter(item => item.category === status.id) sx={STYLES.PADDING_0}
.map((item, index) => ( />
<TaskCard <CardContent sx={STYLES.CARD_CONTENT}>
task={item} <Stack spacing={1}>
account={accounts.find(a => {tasks.rows
a.evRnList.find(rn => rn == item.nrn) .filter(item => item.category === status.id)
)} .map((item, index) => (
index={index} <TaskCard
handleReload={handleReload} task={item}
key={item.id} avatar={
eventPoints={eventPoints} tasks.extraData.accounts.find(
pointSettings={eventPoints.find(p => p.point === status.code)} a => a.agnAbbr === item.sSender
openNoteDialog={handleNoteDialogOpen} ).image
/> }
))} index={index}
{provided.placeholder} handleReload={handleReload}
</Stack> key={item.id}
</CardContent> eventPoints={tasks.extraData.evPoints}
</Card> colorRule={settings.colorRule}
</div> pointSettings={tasks.extraData.evPoints.find(
)} p => p.point === status.code
</Droppable> )}
</div> openNoteDialog={handleNoteDialogOpen}
))} />
))}
{provided.placeholder}
</Stack>
</CardContent>
</Card>
</div>
)}
</Droppable>
</div>
))
: null}
</Stack> </Stack>
{provided.placeholder} {provided.placeholder}
</div> </div>
@ -245,6 +269,7 @@ const ClntTaskBoard = () => {
<TaskFormDialog <TaskFormDialog
taskType={dragItem.type} taskType={dragItem.type}
taskStatus={dragItem.status} taskStatus={dragItem.status}
handleReload={handleReload}
onClose={() => { onClose={() => {
setTaskFormOpen(false); setTaskFormOpen(false);
clearDragItem(); clearDragItem();

View File

@ -34,6 +34,7 @@ import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
//Константы //Константы
//--------- //---------
//Перечисление "Состояние события"
export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" }); export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" });
//Стили //Стили
@ -101,7 +102,6 @@ const selectSendDivision = (value, showDictionary, callBack) => {
const selectSendUsrGrp = (value, showDictionary, callBack) => { const selectSendUsrGrp = (value, showDictionary, callBack) => {
showDictionary({ showDictionary({
unitCode: "CostStaffGroups", unitCode: "CostStaffGroups",
//showMethod: "dictionary",
inputParameters: [{ name: "in_CODE", value: value }], inputParameters: [{ name: "in_CODE", value: value }],
callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null)) callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
}); });
@ -120,15 +120,12 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
const [curType, setCurType] = useState(initial.type); const [curType, setCurType] = useState(initial.type);
//Состояние учётных документов //Состояние учётных документов
const [curDocLinks, setCurDocLinks] = useState([...docs]); const [curDocLinks, setCurDocLinks] = useState(docs);
//Состояние изменения типа события
const [typeDif, setTypeDif] = useState(false);
//Подключение к контексту взаимодействия с сервером //Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx); const { executeStored } = useContext(BackEndСtx);
//Получение субкаталогов //Получение подкаталогов
const getSubCatalogs = useCallback(async () => { const getSubCatalogs = useCallback(async () => {
const data = await executeStored({ const data = await executeStored({
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET", stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET",
@ -174,7 +171,9 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
}; };
//При изменении значения элемента //При изменении значения элемента
const handleFilterItemChange = (item, value) => setFilter(pv => ({ ...pv, [item]: value })); const handleFilterItemChange = (item, value) => {
item === "type" && filter.docLink ? setFilter(pv => ({ ...pv, [item]: value, docLink: "" })) : setFilter(pv => ({ ...pv, [item]: value }));
};
//Очистка учётного документа //Очистка учётного документа
const clearDocLink = () => setFilter(pv => ({ ...pv, docLink: "" })); const clearDocLink = () => setFilter(pv => ({ ...pv, docLink: "" }));
@ -182,13 +181,12 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
//При изменении типа события //При изменении типа события
useEffect(() => { useEffect(() => {
if (curType) { if (curType) {
if (curType === filter.type) setTypeDif(false); if (curType !== filter.type) {
else {
setTypeDif(true);
clearDocLink(); clearDocLink();
} setCurDocLinks([]);
} else if (curType === filter.type && curType === initial.type && !curDocLinks.length) setCurDocLinks(docs);
} }
}, [curType, filter.type]); }, [curDocLinks, curType, docs, filter.type, initial.type]);
//Обработка изменений с каталогами //Обработка изменений с каталогами
useEffect(() => { useEffect(() => {
@ -287,10 +285,10 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
<Stack direction="row" sx={STYLES.DOCLINK_STACK}> <Stack direction="row" sx={STYLES.DOCLINK_STACK}>
<FilterInputField <FilterInputField
elementCode="docLink" elementCode="docLink"
elementValue={!typeDif ? filter.docLink : ""} elementValue={filter.docLink}
labelText="Учётный документ" labelText="Учётный документ"
items={!typeDif ? curDocLinks : []} items={curDocLinks}
disabled={typeDif || curDocLinks.length === 0 ? true : false} disabled={!curDocLinks.length ? true : false}
onChange={handleFilterItemChange} onChange={handleFilterItemChange}
sx={STYLES.SELECT} sx={STYLES.SELECT}
/> />
@ -299,12 +297,13 @@ const FilterDialog = ({ initial, docs, onCancel, onOk, getDocLinks }) => {
</IconButton> </IconButton>
<IconButton <IconButton
title="Обновить" title="Обновить"
disabled={!((!curType || typeDif) && filter.type)} disabled={!((!curType || curType !== filter.type) && filter.type)}
onClick={() => { onClick={() => {
setCurType(filter.type); setCurType(filter.type);
clearDocLink(); clearDocLink();
getDocLinks(filter.type).then(dl => { getDocLinks(filter.type).then(dl => {
setCurDocLinks([...dl]); setCurDocLinks(dl);
console.log(dl);
}); });
}} }}
> >

View File

@ -19,7 +19,9 @@ import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
//Стили //Стили
const STYLES = { const STYLES = {
HELPER_TEXT: { color: "red" }, HELPER_TEXT: { color: "red" },
SELECT_MENU: { overflowY: "auto", ...APP_STYLES.SCROLL } SELECT_MENU: w => {
return { overflowY: "auto", ...APP_STYLES.SCROLL, width: w ? w : null };
}
}; };
//--------------- //---------------
@ -82,7 +84,7 @@ const FilterInputField = ({ elementCode, elementValue, labelText, onChange, requ
value={value} value={value}
aria-describedby={`${elementCode}-helper-text`} aria-describedby={`${elementCode}-helper-text`}
label={labelText} label={labelText}
MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU } } }} MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU(document.getElementById(elementCode)?.parentElement.clientWidth) } } }}
onChange={handleChange} onChange={handleChange}
{...other} {...other}
> >

View File

@ -1,6 +1,6 @@
/* /*
Парус 8 - Панели мониторинга - УДП - Доски задач Парус 8 - Панели мониторинга - УДП - Доски задач
Компонент: Диалоговое окно примечания Компонент: Диалог примечания
*/ */
//--------------------- //---------------------
@ -46,6 +46,7 @@ const STYLES = {
//Тело компонента //Тело компонента
//--------------- //---------------
//Диалог примечания
const NoteDialog = ({ noteTypes, onOk, onCancel }) => { const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
//Собственное состояние //Собственное состояние
const [note, setNote] = useState({ headerV: 0, text: "" }); const [note, setNote] = useState({ headerV: 0, text: "" });
@ -125,7 +126,7 @@ const NoteDialog = ({ noteTypes, onOk, onCancel }) => {
); );
}; };
//Контроль свойств - Диалоговое окно примечания //Контроль свойств - Диалог примечания
NoteDialog.propTypes = { NoteDialog.propTypes = {
noteTypes: PropTypes.array, noteTypes: PropTypes.array,
onOk: PropTypes.func.isRequired, onOk: PropTypes.func.isRequired,

View File

@ -0,0 +1,93 @@
/*
Парус 8 - Панели мониторинга - УДП - Доски задач
Компонент: Выпадающий список выбора заливки событий
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { FormControl, InputLabel, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты
import { useColorRules } from "../hooks.js"; //Вспомогательные хуки
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
//---------
//Константы
//---------
//Стили
const STYLES = {
SELECT_MENU: w => {
return { overflowY: "auto", ...APP_STYLES.SCROLL, width: w ? w : null };
}
};
//---------------
//Тело компонента
//---------------
//Выпадающий список выбора заливки событий
const RulesSelect = ({ initRule, handleChange, ...other }) => {
//Состояние пользовательских настроек заливки событий
const [colorRules] = useColorRules();
//Собственное состояние
const [curRule, setCurRule] = useState(initRule > -1 ? "" : initRule);
//При получении нового значения заливки из вне
useEffect(() => {
if (
(colorRules.loaded && initRule > -1 && curRule === "") ||
(Number.isInteger(initRule) && Number.isInteger(curRule) && initRule !== curRule)
)
setCurRule(initRule);
}, [colorRules, curRule, initRule]);
//При изменении заливки событий
const handleRuleChange = e => {
let id = e.target.value;
setCurRule(id);
handleChange(id > -1 ? colorRules.rules[id] : {});
};
//Генерация содержимого
return colorRules ? (
<FormControl size="small" variant="standard" {...other}>
<InputLabel htmlFor="clrRules">Заливка событий</InputLabel>
<Select
id="clrRules"
name="clrRules"
value={curRule}
aria-describedby="clrRules-helper-text"
label="Заливка событий"
MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU(document.getElementById("clrRules")?.parentElement.clientWidth) } } }}
onChange={handleRuleChange}
>
<MenuItem key={-1} value={-1}>
{"-"}
</MenuItem>
{colorRules.rules
? colorRules.rules.map((item, i) => (
<MenuItem key={i} value={item.id}>
{item.propName}
</MenuItem>
))
: null}
</Select>
</FormControl>
) : null;
};
//Контроль свойств - Выпадающий список выбора заливки событий
RulesSelect.propTypes = {
initRule: PropTypes.number.isRequired,
handleChange: PropTypes.func.isRequired
};
//--------------------
//Интерфейс компонента
//--------------------
export { RulesSelect };

View File

@ -0,0 +1,140 @@
/*
Парус 8 - Панели мониторинга - УДП - Доски задач
Компонент: Диалог дополнительных настроек
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Icon, Button, Box, Stack } from "@mui/material"; //Интерфейсные компоненты
import { RulesSelect } from "./rules_select.js";
import { FilterInputField } from "./filter_input_field.js";
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
//---------
//Константы
//---------
//Стили
const STYLES = {
FILTERS_SCROLL: { overflowY: "auto", ...APP_STYLES.SCROLL },
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" },
CLOSE_BUTTON: {
position: "absolute",
right: 8,
top: 8,
color: theme => theme.palette.grey[500]
},
DOCLINK_STACK: { alignItems: "baseline" },
SELECT: { width: "100%" },
BOX_WITH_LEGEND: { border: "1px solid #939393" },
LEGEND: { textAlign: "left" },
SELECT_MENU: w => {
return { overflowY: "auto", ...APP_STYLES.SCROLL, width: w ? w : null };
}
};
//---------------
//Тело компонента
//---------------
//Диалог дополнительных настроек
const SettingsDialog = ({ initial, onOk, onCancel, ...other }) => {
//Состояние дополнительных настроек
const [settings, setSettings] = useState(
initial.statusesSort.attr ? { ...initial } : { ...initial, statusesSort: { sorted: true, attr: "name", dest: "asc" } }
);
//Допустимые значение поля сортировки
const sortAttrs = [
{ id: "code", descr: "Мнемокод" },
{ id: "name", descr: "Наименование" },
{ id: "pointDescr", descr: "Описание точки маршрута" }
];
//Допустимые значения направления сортировки
const sortDest = [];
sortDest[-1] = "desc";
sortDest[1] = "asc";
//Изменение заливки событий
const handleColorRuleChange = cr => setSettings(pv => ({ ...pv, colorRule: cr }));
//Изменение поля сортировки
const handleSortAttrChange = (item, value) => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, [item]: value } }));
//Изменение направления сортировки
const handleSortDestChange = d => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, dest: d } }));
//Генерация содержимого
return (
<div {...other}>
<Dialog open onClose={onCancel} fullWidth maxWidth="sm">
<DialogTitle>Настройки</DialogTitle>
<IconButton aria-label="close" onClick={onCancel} sx={STYLES.CLOSE_BUTTON}>
<Icon>close</Icon>
</IconButton>
<DialogContent sx={STYLES.FILTERS_SCROLL}>
<Box component="section" p={1}>
<RulesSelect
initRule={settings.colorRule.id !== undefined ? settings.colorRule.id : -1}
handleChange={handleColorRuleChange}
sx={STYLES.SELECT}
/>
</Box>
<Box component="section" p={1}>
<Stack direction="row" sx={STYLES.DOCLINK_STACK}>
<FilterInputField
elementCode="attr"
elementValue={settings.statusesSort.attr}
labelText="Поле сортировки"
items={sortAttrs}
onChange={handleSortAttrChange}
MenuProps={{
slotProps: { paper: { sx: STYLES.SELECT_MENU(document.getElementById("attr")?.parentElement.clientWidth) } }
}}
sx={STYLES.SELECT}
/>
<IconButton
title={settings.statusesSort.dest === "asc" ? "По возрастанию" : "По убыванию"}
onClick={() => handleSortDestChange(sortDest[sortDest.indexOf(settings.statusesSort.dest) * -1])}
>
<Icon>{settings.statusesSort.dest === "asc" ? "arrow_upward" : "arrow_downward"}</Icon>
</IconButton>
</Stack>
</Box>
</DialogContent>
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button variant="text" onClick={() => onOk(settings)}>
ОК
</Button>
<Button
variant="text"
onClick={() => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, attr: "name", dest: "asc" }, colorRule: {} }))}
>
Очистить
</Button>
<Button variant="text" onClick={onCancel}>
Отмена
</Button>
</DialogActions>
</Dialog>
</div>
);
};
//Контроль свойств компонента - Диалог дополнительных настроек
SettingsDialog.propTypes = {
initial: PropTypes.object.isRequired,
onOk: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired
};
//--------------------
//Интерфейс компонента
//--------------------
export { SettingsDialog };

View File

@ -1,30 +1,39 @@
/* /*
Парус 8 - Панели мониторинга - УДП - Доски задач Парус 8 - Панели мониторинга - УДП - Доски задач
Компонент панели: Карточка задачи Компонент панели: Карточка события
*/ */
//--------------------- //---------------------
//Подключение библиотек //Подключение библиотек
//--------------------- //---------------------
import React from "react"; //Классы React import React, { useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты
import { useTaskCard } from "../hooks"; //Вспомогательные хуки import { useTaskCard } from "../hooks"; //Вспомогательные хуки
import { TaskFormDialog } from "./task_form"; //Форма события import { TaskFormDialog } from "./task_form"; //Форма события
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
//--------- //---------
//Константы //Константы
//--------- //---------
//Перечисление "Цвет индикации"
const EVENT_INDICATORS = Object.freeze({ EXPIRED: "#ff0000", EXPIRES_SOON: "#ffdf00", LINKED: "#1e90ff" });
//Стили //Стили
const STYLES = { const STYLES = {
CONTAINER: { margin: "5px 0px", textAlign: "center" }, CONTAINER: { margin: "5px 0px", textAlign: "center" },
MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" }, MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" },
CARD: (indicatorClr, bgClr) => {
const i = indicatorClr ? { borderLeft: `solid ${indicatorClr}` } : null;
const bc = bgClr ? { backgroundColor: bgClr } : null;
return { ...i, ...bc };
},
CARD_HEADER_TITLE: { CARD_HEADER_TITLE: {
padding: "4px", padding: "4px",
width: "252px", width: "292px",
display: "-webkit-box", display: "-webkit-box",
hyphens: "auto", hyphens: "auto",
WebkitBoxOrient: "vertical", WebkitBoxOrient: "vertical",
@ -39,7 +48,9 @@ const STYLES = {
color: "text.secondary", color: "text.secondary",
fontSize: 14 fontSize: 14
}, },
ICON_COLOR: { color: theme => theme.palette.grey[500] } ICON_COLOR: linked => {
return { color: theme => (linked ? EVENT_INDICATORS.LINKED : theme.palette.grey[500]) };
}
}; };
//------------------------------------ //------------------------------------
@ -111,16 +122,60 @@ DataCellCardActions.propTypes = {
//----------- //-----------
//Карточка события //Карточка события
const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettings, openNoteDialog }) => { const TaskCard = ({ task, avatar, index, handleReload, eventPoints, colorRule, pointSettings, openNoteDialog }) => {
//Собственное состояние //Собственное состояние
const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard(); const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard();
//Подключение к контексту приложения
const { pOnlineShowDocument } = useContext(ApplicationСtx);
//Конвертация формата HEX в формат RGB
const hexToRGB = hex => {
let r = parseInt(hex.slice(1, 3), 16);
let g = parseInt(hex.slice(3, 5), 16);
let b = parseInt(hex.slice(5, 7), 16);
let a = 0.5;
r = Math.round((a * (r / 255) + a * (255 / 255)) * 255);
g = Math.round((a * (g / 255) + a * (255 / 255)) * 255);
b = Math.round((a * (b / 255) + a * (255 / 255)) * 255);
return "rgb(" + r + ", " + g + ", " + b + ")";
};
//Проверка выполнения условия заливки события
const bgColorRule = () => {
let ruleCode;
let bgColor = null;
if (colorRule.vType === "string") ruleCode = `S${colorRule.fieldCode}`;
else if (colorRule.vType === "number") ruleCode = `N${colorRule.fieldCode}`;
else if (colorRule.vType === "date") ruleCode = `D${colorRule.fieldCode}`;
ruleCode ? (task.docProps[ruleCode] == colorRule.from ? (bgColor = hexToRGB(colorRule.color)) : null) : null;
return bgColor;
};
//Индикация истечения срока отработки события
const indicatorColorRule = task => {
let sysDate = new Date();
let expireDate = task.dexpire_date ? new Date(task.dexpire_date) : null;
let daysDiff = null;
if (expireDate) {
daysDiff = ((expireDate.getTime() - sysDate.getTime()) / (1000 * 60 * 60 * 24)).toFixed(2);
if (daysDiff < 0) return EVENT_INDICATORS.EXPIRED;
else if (daysDiff < 4) return EVENT_INDICATORS.EXPIRES_SOON;
}
return null;
};
//Генерация содержимого //Генерация содержимого
return ( return (
<Box> <Box>
<Draggable draggableId={task.id.toString()} key={task.id} index={index}> <Draggable draggableId={task.id.toString()} key={task.id} index={index}>
{provided => ( {provided => (
<Card ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> <Card
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
sx={STYLES.CARD(indicatorColorRule(task), colorRule.color ? bgColorRule() : null)}
>
<CardHeader <CardHeader
title={ title={
<Typography <Typography
@ -151,12 +206,21 @@ const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettin
/> />
<CardContent sx={STYLES.CARD_CONTENT}> <CardContent sx={STYLES.CARD_CONTENT}>
<Box sx={STYLES.CARD_CONTENT_BOX}> <Box sx={STYLES.CARD_CONTENT_BOX}>
<Icon sx={STYLES.ICON_COLOR}>assignment</Icon> <IconButton
title={task.nlinked_rn ? "Событие получено по статусной модели" : null}
onClick={
task.nlinked_rn ? () => pOnlineShowDocument({ unitCode: task.slinked_unit, document: task.nlinked_rn }) : null
}
sx={STYLES.ICON_COLOR(task.nlinked_rn)}
disabled={!task.nlinked_rn}
>
<Icon>assignment</Icon>
</IconButton>
<Typography sx={STYLES.SECONDARY_TEXT}>{task.name}</Typography> <Typography sx={STYLES.SECONDARY_TEXT}>{task.name}</Typography>
{account ? ( {task.sSender ? (
<Stack direction="row" spacing={0.5} sx={STYLES.ACCOUNT_STACK}> <Stack direction="row" spacing={0.5} sx={STYLES.ACCOUNT_STACK}>
<Typography sx={STYLES.SECONDARY_TEXT}>{account.authId ? account.authId : account.agnAbbr}</Typography> <Typography sx={STYLES.SECONDARY_TEXT}>{task.sSender}</Typography>
<Avatar src={account.image ? `data:image/png;base64,${account.image}` : null} /> <Avatar src={avatar ? `data:image/png;base64,${avatar}` : null} />
</Stack> </Stack>
) : null} ) : null}
</Box> </Box>
@ -167,7 +231,9 @@ const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettin
{taskCard.openEdit ? ( {taskCard.openEdit ? (
<TaskFormDialog <TaskFormDialog
taskRn={task.nrn} taskRn={task.nrn}
taskType={task.stype}
editable={pointSettings.banUpdate ? false : true} editable={pointSettings.banUpdate ? false : true}
handleReload={handleReload}
onClose={() => { onClose={() => {
setTaskCard(pv => ({ ...pv, openEdit: false })); setTaskCard(pv => ({ ...pv, openEdit: false }));
}} }}
@ -180,10 +246,11 @@ const TaskCard = ({ task, account, index, handleReload, eventPoints, pointSettin
//Контроль свойств - Карточка события //Контроль свойств - Карточка события
TaskCard.propTypes = { TaskCard.propTypes = {
task: PropTypes.object.isRequired, task: PropTypes.object.isRequired,
account: PropTypes.object, avatar: PropTypes.string,
index: PropTypes.number.isRequired, index: PropTypes.number.isRequired,
handleReload: PropTypes.func, handleReload: PropTypes.func,
eventPoints: PropTypes.array, eventPoints: PropTypes.array,
colorRule: PropTypes.object,
pointSettings: PropTypes.object, pointSettings: PropTypes.object,
openNoteDialog: PropTypes.func openNoteDialog: PropTypes.func
}; };

View File

@ -1,6 +1,6 @@
/* /*
Парус 8 - Панели мониторинга - УДП - Доски задач Парус 8 - Панели мониторинга - УДП - Доски задач
Компонент: Диалоговое окно настройки карточки событий Компонент: Диалог настройки карточки событий
*/ */
//--------------------- //---------------------
@ -40,15 +40,11 @@ const STYLES = {
BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor }) BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
}; };
//-----------------------
//Вспомогательные функции
//-----------------------
//--------------- //---------------
//Тело компонента //Тело компонента
//--------------- //---------------
//Диалоговое окно фильтра отбора //Диалог настройки карточки событий
const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => { const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
//Собственное состояние //Собственное состояние
const [settings, setSettings] = useState({ ...initial }); const [settings, setSettings] = useState({ ...initial });
@ -112,7 +108,7 @@ const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
); );
}; };
//Контроль свойств компонента - Диалоговое окно настройки карточки событий //Контроль свойств компонента - Диалог настройки карточки событий
TaskCardSettings.propTypes = { TaskCardSettings.propTypes = {
initial: PropTypes.object.isRequired, initial: PropTypes.object.isRequired,
availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired, availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,

View File

@ -7,16 +7,28 @@
//Подключение библиотек //Подключение библиотек
//--------------------- //---------------------
import React, { useState } from "react"; //Классы React import React, { useState, useContext, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
import { useClientEvent } from "../hooks"; //Вспомогательные хуки import { useClientEvent, useDocsProps } from "../hooks"; //Вспомогательные хуки
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
import dayjs from "dayjs"; //Работа с датами
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
//--------- //---------
//Константы //Константы
//--------- //---------
//Перечисление "Значение по умолчанию"
const DP_DEFAULT_VALUE = Object.freeze({ 0: "defaultStr", 1: "defaultNum", 2: "defaultDate", 3: "defaultNum" });
//Перечисление "Префикс формата данных"
const DP_TYPE_PREFIX = Object.freeze({ 0: "S", 1: "N", 2: "D", 3: "N" });
//Перечисление "Входящее значение дополнительного словаря"
const DP_IN_VALUE = Object.freeze({ 0: "pos_str_value", 1: "pos_num_value", 2: "pos_date_value", 3: "pos_num_value" });
//Перечисление "Исходящее значение дополнительного словаря"
const DP_RETURN_VALUE = Object.freeze({ 0: "str_value", 1: "num_value", 2: "date_value", 3: "num_value" });
//Стили //Стили
const STYLES = { const STYLES = {
CONTAINER: { margin: "5px 0px", textAlign: "center" }, CONTAINER: { margin: "5px 0px", textAlign: "center" },
@ -52,6 +64,9 @@ const STYLES = {
//Вспомогательные функции и компоненты //Вспомогательные функции и компоненты
//------------------------------------ //------------------------------------
//Подключение настройки пользовательского формата даты
dayjs.extend(customParseFormat);
//Свойства вкладки //Свойства вкладки
function a11yProps(index) { function a11yProps(index) {
return { return {
@ -205,7 +220,7 @@ const MainEventInfoTab = ({
//Контроль свойств - Вкладка основной информации //Контроль свойств - Вкладка основной информации
MainEventInfoTab.propTypes = { MainEventInfoTab.propTypes = {
task: PropTypes.object.isRequired, task: PropTypes.object.isRequired,
editable: PropTypes.bool, editable: PropTypes.bool.isRequired,
handleFieldEdit: PropTypes.func.isRequired, handleFieldEdit: PropTypes.func.isRequired,
handleClientClientsOpen: PropTypes.func.isRequired, handleClientClientsOpen: PropTypes.func.isRequired,
handleClientPersonOpen: PropTypes.func.isRequired, handleClientPersonOpen: PropTypes.func.isRequired,
@ -220,12 +235,12 @@ const ExecutorEventInfoTab = ({ task, handleFieldEdit, handleClientPersonOpen })
<Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset"> <Box sx={{ ...STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset">
<legend style={STYLES.LEGEND}>Планирование</legend> <legend style={STYLES.LEGEND}>Планирование</legend>
<TextField <TextField
id="dstart_date" id="dplan_date"
label="Начало работ" label="Начало работ"
InputLabelProps={{ shrink: true }} InputLabelProps={{ shrink: true }}
type="datetime-local" type="datetime-local"
variant="standard" variant="standard"
value={task.dstart_date} value={task.dplan_date ? dayjs(task.dplan_date, "DD.MM.YYYY HH:mm").format("YYYY-MM-DD HH:mm") : ""}
onChange={handleFieldEdit} onChange={handleFieldEdit}
disabled={task.isUpdate} disabled={task.isUpdate}
></TextField> ></TextField>
@ -336,15 +351,192 @@ ExecutorEventInfoTab.propTypes = {
handleClientPersonOpen: PropTypes.func.isRequired handleClientPersonOpen: PropTypes.func.isRequired
}; };
//Вкладка информации со свойствами
const PropsEventInfoTab = ({ task, docProps, handlePropEdit }) => {
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Формат дополнительного свойства типа число (длина, точность)
const DPNumFormat = (l, p) => new RegExp("^(\\d{1," + (l - p) + "}" + (p > 0 ? "((\\.|,)\\d{1," + p + "})?" : "") + ")?$");
//Формат дополнительного свойства типа строка (длина)
const DPStrFormat = l => new RegExp("^.{0," + l + "}$");
//Проверка валидности числа
const isValidDPNum = (length, prec, value) => {
return DPNumFormat(length, prec).test(value);
};
//Проверка валидности строки
const isValidDPStr = (length, value) => {
return DPStrFormat(length).test(value);
};
//Признак ошибки валидации
const validationError = (value = "", format, numW, numPrec, strW) => {
if (format === 0) return isValidDPStr(strW, value);
else if (format === 1) {
return isValidDPNum(numW, numPrec, value);
} else return true;
};
//Конвертация времени в привычный формат
const timeFromSqlFormat = ts => {
if (ts.indexOf(".") !== -1) {
let s = 24 * 60 * 60 * ts;
const h = Math.trunc(s / (60 * 60));
s = s % (60 * 60);
const m = Math.trunc(s / 60);
s = Math.round(s % 60);
const formattedTime = ("0" + h).slice(-2) + ":" + ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2);
return formattedTime;
}
return ts;
};
//Выбор из словаря или дополнительного словаря
const handleDict = async (dp, curValue = null) => {
dp.entryType === 1
? pOnlineShowDictionary({
unitCode: dp.unitcode,
showMethod: dp.showMethodCode,
inputParameters: dp.paramRn ? [{ name: dp.paramIn, value: curValue }] : null,
callBack: res => {
res.success
? handlePropEdit({
target: {
id: `${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`,
value: res.outParameters[dp.paramOut]
}
})
: null;
}
})
: pOnlineShowDictionary({
unitCode: "ExtraDictionaries",
showMethod: "values",
inputParameters: [
{ name: "pos_rn", value: dp.extraDictRn },
{ name: DP_IN_VALUE[dp.format], value: curValue }
],
callBack: res => {
res.success
? handlePropEdit({
target: {
id: `${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`,
value: res.outParameters[DP_RETURN_VALUE[dp.format]]
}
})
: null;
}
});
};
//Инициализация дополнительного свойства
const initProp = prop => {
//Значение свойства
const value = task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`];
if (
(task.nrn || task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`]) &&
task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`] !== undefined
) {
//Строка или число
if (prop.format < 2) return prop.numPrecision ? String(value).replace(".", ",") : value;
//Дата
else if (prop.format === 2) {
//Дата без времени
if (prop.dataSubtype === 0) return dayjs(value).format("YYYY-MM-DD");
//Дата + время без секунд
else if (prop.dataSubtype === 1) return dayjs(value).format("YYYY-MM-DD HH:mm");
//Дата + время с секундами
else return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
}
//Время
else {
return timeFromSqlFormat(value);
}
} else if (task.nrn) {
return "";
} else return prop[DP_DEFAULT_VALUE[prop.format]];
};
//Генерация содержимого
return (
<Box>
<Box sx={STYLES.BOX_WITH_LEGEND} component="fieldset">
{docProps.props.map(dp => {
return dp.showInGrid ? (
<TextField
error={
!validationError(
task.docProps[`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`],
dp.format,
dp.numWidth,
dp.numPrecision,
dp.strWidth
)
}
key={dp.id}
sx={STYLES.TEXT_FIELD()}
id={`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`}
type={dp.format < 2 ? "string" : dp.format === 2 ? (dp.dataSubtype === 0 ? "date" : "datetime-local") : "time"}
label={dp.name}
fullWidth
value={initProp(dp)}
variant="standard"
onChange={handlePropEdit}
inputProps={(dp.format === 2 && dp.dataSubtype === 2) || (dp.format === 3 && dp.dataSubtype === 1) ? { step: 1 } : {}}
InputProps={
dp.entryType > 0
? getInputProps(() => handleDict(dp, task.docProps[`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`]))
: null
}
InputLabelProps={
dp.format < 2
? {}
: {
shrink: true
}
}
required={dp.require}
disabled={dp.readonly}
/>
) : null;
})}
</Box>
</Box>
);
};
//Контроль свойств - Вкладка информации со свойствами
PropsEventInfoTab.propTypes = {
task: PropTypes.object.isRequired,
docProps: PropTypes.object.isRequired,
handlePropEdit: PropTypes.func.isRequired
};
//----------- //-----------
//Тело модуля //Тело модуля
//----------- //-----------
//Форма события //Форма события
const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, handleEventNextNumbGet }) => { const TaskForm = ({
task,
taskType,
setTask,
editable,
handleClientClientsOpen,
handleClientPersonOpen,
handleCrnOpen,
handleEventNextNumbGet,
handleDPReady
}) => {
//Состояние вкладки //Состояние вкладки
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
//Состояние допустимых дополнительных свойств
const [docProps] = useDocsProps(taskType);
//При изменении вкладки //При изменении вкладки
const handleChange = (event, newValue) => { const handleChange = (event, newValue) => {
setValue(newValue); setValue(newValue);
@ -361,6 +553,21 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
})); }));
}; };
//При изменении свойства
const handlePropEdit = e => {
setTask(pv => ({
...pv,
docProps: { ...pv.docProps, [e.target.id]: e.target.value }
}));
};
//При заполнении всех обязательных свойств
useEffect(() => {
let i = 0;
docProps.props.filter(dp => dp.require === true).map(prop => (!task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`] ? i++ : null));
docProps.loaded && i === 0 ? handleDPReady(true) : handleDPReady(false);
}, [docProps, handleDPReady, task.docProps]);
//Генерация содержимого //Генерация содержимого
return ( return (
<Box sx={STYLES.CONTAINER}> <Box sx={STYLES.CONTAINER}>
@ -370,11 +577,12 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
<Tabs value={value} onChange={handleChange} aria-label="tabs of values"> <Tabs value={value} onChange={handleChange} aria-label="tabs of values">
<Tab label="Событие" {...a11yProps(0)} /> <Tab label="Событие" {...a11yProps(0)} />
<Tab label="Исполнитель" {...a11yProps(1)} /> <Tab label="Исполнитель" {...a11yProps(1)} />
{docProps.props.length > 0 ? <Tab label="Свойства" {...a11yProps(2)} /> : null}
</Tabs> </Tabs>
<CustomTabPanel value={value} index={0}> <CustomTabPanel value={value} index={0}>
<MainEventInfoTab <MainEventInfoTab
task={task} task={task}
editable={task.nrn ? editable : null} editable={editable}
handleFieldEdit={handleFieldEdit} handleFieldEdit={handleFieldEdit}
handleClientClientsOpen={handleClientClientsOpen} handleClientClientsOpen={handleClientClientsOpen}
handleClientPersonOpen={handleClientPersonOpen} handleClientPersonOpen={handleClientPersonOpen}
@ -385,6 +593,11 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
<CustomTabPanel value={value} index={1}> <CustomTabPanel value={value} index={1}>
<ExecutorEventInfoTab task={task} handleFieldEdit={handleFieldEdit} handleClientPersonOpen={handleClientPersonOpen} /> <ExecutorEventInfoTab task={task} handleFieldEdit={handleFieldEdit} handleClientPersonOpen={handleClientPersonOpen} />
</CustomTabPanel> </CustomTabPanel>
{docProps.props.length > 0 ? (
<CustomTabPanel value={value} index={2}>
<PropsEventInfoTab task={task} taskType={taskType} docProps={docProps} handlePropEdit={handlePropEdit} />
</CustomTabPanel>
) : null}
</Box> </Box>
); );
}; };
@ -392,41 +605,57 @@ const TaskForm = ({ task, setTask, editable, handleClientClientsOpen, handleClie
//Контроль свойств - Форма события //Контроль свойств - Форма события
TaskForm.propTypes = { TaskForm.propTypes = {
task: PropTypes.object.isRequired, task: PropTypes.object.isRequired,
taskType: PropTypes.string.isRequired,
setTask: PropTypes.func.isRequired, setTask: PropTypes.func.isRequired,
editable: PropTypes.bool, editable: PropTypes.bool.isRequired,
handleClientClientsOpen: PropTypes.func.isRequired, handleClientClientsOpen: PropTypes.func.isRequired,
handleClientPersonOpen: PropTypes.func.isRequired, handleClientPersonOpen: PropTypes.func.isRequired,
handleCrnOpen: PropTypes.func.isRequired, handleCrnOpen: PropTypes.func.isRequired,
handleEventNextNumbGet: PropTypes.func.isRequired handleEventNextNumbGet: PropTypes.func.isRequired,
handleDPReady: PropTypes.func.isRequired
}; };
//Диалог с формой события //Диалог с формой события
const TaskFormDialog = ({ taskRn, taskType, taskStatus, editable, onClose }) => { const TaskFormDialog = ({ taskRn, taskType, taskStatus, editable, handleReload, onClose }) => {
//Собственное состояние //Собственное состояние
const [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, handleEventNextNumbGet] = const [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, handleEventNextNumbGet] =
useClientEvent(taskRn, taskType, taskStatus); useClientEvent(taskRn, taskType, taskStatus);
//Состояние заполненности всех обязательных свойств
const [dpReady, setDPReady] = useState(false);
//Изменение состояния заполненности всех обязательных свойств
const handleDPReady = v => setDPReady(v);
//Генерация содержимого
return ( return (
<Dialog open onClose={onClose ? onClose : null} fullWidth> <Dialog open onClose={onClose ? onClose : null} fullWidth>
<DialogContent sx={STYLES.DIALOG_CONTENT}> <DialogContent sx={STYLES.DIALOG_CONTENT}>
<TaskForm <TaskForm
task={task} task={task}
taskType={taskType}
setTask={setTask} setTask={setTask}
editable={taskRn ? editable : null} editable={!taskRn || editable ? true : false}
handleClientClientsOpen={handleClientClientsOpen} handleClientClientsOpen={handleClientClientsOpen}
handleClientPersonOpen={handleClientPersonOpen} handleClientPersonOpen={handleClientPersonOpen}
handleCrnOpen={handleCrnOpen} handleCrnOpen={handleCrnOpen}
handleEventNextNumbGet={handleEventNextNumbGet} handleEventNextNumbGet={handleEventNextNumbGet}
handleDPReady={handleDPReady}
/> />
</DialogContent> </DialogContent>
{onClose ? ( {onClose ? (
<DialogActions sx={STYLES.DIALOG_ACTIONS}> <DialogActions sx={STYLES.DIALOG_ACTIONS}>
{taskRn ? ( {taskRn ? (
<Button onClick={() => updateEvent(onClose)} disabled={task.updateDisabled || !editable}> <Button onClick={() => updateEvent(onClose).then(handleReload)} disabled={task.updateDisabled || !editable || !dpReady}>
Исправить Исправить
</Button> </Button>
) : ( ) : (
<Button onClick={() => insertEvent(onClose)} disabled={task.insertDisabled}> <Button
onClick={() => {
insertEvent(onClose).then(handleReload);
}}
disabled={task.insertDisabled || !dpReady}
>
Добавить Добавить
</Button> </Button>
)} )}
@ -440,9 +669,10 @@ const TaskFormDialog = ({ taskRn, taskType, taskStatus, editable, onClose }) =>
//Контроль свойств - Диалог с формой события //Контроль свойств - Диалог с формой события
TaskFormDialog.propTypes = { TaskFormDialog.propTypes = {
taskRn: PropTypes.number, taskRn: PropTypes.number,
taskType: PropTypes.string, taskType: PropTypes.string.isRequired,
taskStatus: PropTypes.string, taskStatus: PropTypes.string,
editable: PropTypes.bool, editable: PropTypes.bool,
handleReload: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired onClose: PropTypes.func.isRequired
}; };

View File

@ -12,7 +12,6 @@ import { ApplicationСtx } from "../../context/application"; //Контекст
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { object2Base64XML, deepCopyObject } from "../../core/utils"; //Вспомогательные функции import { object2Base64XML, deepCopyObject } from "../../core/utils"; //Вспомогательные функции
import dayjs from "dayjs"; //Работа с датами
import { EVENT_STATES } from "./components/filter_dialog"; //Перечисление состояний события import { EVENT_STATES } from "./components/filter_dialog"; //Перечисление состояний события
//--------- //---------
@ -72,21 +71,6 @@ const useTasks = () => {
//Состояние изменения настройки статуса //Состояние изменения настройки статуса
const [cardSettings, setCardSettings] = useState({ isOpen: false, settings: {} }); const [cardSettings, setCardSettings] = useState({ isOpen: false, settings: {} });
//Состояние маршрута события
const [eventRoutes, setEventRoutes] = useState([]);
//Состояние точек маршрута события
const [eventPoints, setEventPoints] = useState([]);
//Состояние типов заголовков событий
const [noteTypes, setNoteTypes] = useState([]);
//Состояние учётных документов
const [docLinks, setDocLinks] = useState([]);
//Состояние аккаунтов
const [accounts, setAccounts] = useState([]);
//Состояние событий //Состояние событий
const [tasks, setTasks] = useState({ const [tasks, setTasks] = useState({
groupsLoaded: false, groupsLoaded: false,
@ -117,6 +101,7 @@ const useTasks = () => {
{ name: "NLINKED_RN", from: "", to: "" } { name: "NLINKED_RN", from: "", to: "" }
] ]
}, },
extraData: { typeLoaded: "", evRoutes: [], evPoints: [], noteTypes: [], docLinks: [], accounts: [] },
rows: [], rows: [],
statuses: [], statuses: [],
openCardForm: false, openCardForm: false,
@ -131,6 +116,11 @@ const useTasks = () => {
//Инициализация параметров события //Инициализация параметров события
const initTask = (id, gp, task) => { const initTask = (id, gp, task) => {
//Добавление дополнительных свойств
let newDocProps = {};
Object.keys(task)
.filter(k => k.includes("DP_"))
.map(dp => (newDocProps = { ...newDocProps, [dp]: task[dp] }));
return { return {
id: id, id: id,
name: task.SPREF_NUMB, name: task.SPREF_NUMB,
@ -146,6 +136,7 @@ const useTasks = () => {
sclnt_clnperson: "", sclnt_clnperson: "",
dchange_date: task.DCHANGE_DATE, dchange_date: task.DCHANGE_DATE,
dstart_date: task.DREG_DATE, dstart_date: task.DREG_DATE,
dexpire_date: task.DEXPIRE_DATE,
dplan_date: task.DPLAN_DATE, dplan_date: task.DPLAN_DATE,
sinit_clnperson: task.SINIT_PERSON, sinit_clnperson: task.SINIT_PERSON,
sinit_user: "", sinit_user: "",
@ -166,7 +157,11 @@ const useTasks = () => {
sto_user: "", sto_user: "",
//SEND_USER_GROUP //SEND_USER_GROUP
sto_usergrp: task.SSEND_USRGRP, sto_usergrp: task.SSEND_USRGRP,
scurrent_user: "" sSender: task.SSENDER,
scurrent_user: "",
slinked_unit: task.SLINKED_UNIT,
nlinked_rn: task.NLINKED_RN,
docProps: newDocProps
}; };
}; };
@ -238,7 +233,7 @@ const useTasks = () => {
tasks.filters.needSave tasks.filters.needSave
? window.addEventListener("beforeunload", function () { ? window.addEventListener("beforeunload", function () {
Object.keys(tasks.filters.values).map(function (k) { Object.keys(tasks.filters.values).map(function (k) {
k !== "docLink" ? localStorage.setItem(k, tasks.filters.values[k]) : null; k !== "docLink" ? localStorage.setItem(k, tasks.filters.values[k] ? tasks.filters.values[k] : "") : null;
}); });
}) })
: null; : null;
@ -435,7 +430,7 @@ const useTasks = () => {
const data = await executeStored({ const data = await executeStored({
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DOCLINKS", stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DOCLINKS",
args: { args: {
SCODE: type SEVNTYPE_CODE: type
}, },
respArg: "COUT" respArg: "COUT"
}); });
@ -447,79 +442,16 @@ const useTasks = () => {
newDocLinks.push({ id: d.NRN, descr: d.SDESCR }); newDocLinks.push({ id: d.NRN, descr: d.SDESCR });
}); });
} }
//Указываем сформированные учётные документы //Возвращаем сформированные учётные документы
setDocLinks([...newDocLinks]);
return newDocLinks; return newDocLinks;
}, },
[executeStored, tasks.filters.values.type] [executeStored, tasks.filters.values.type]
); );
useEffect(() => {
//Считывание вспомогательных данных
let getEventData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INFO_BY_CODE",
args: {
SCODE: tasks.filters.values.type
},
respArg: "COUT"
});
//Инициализируем маршруты событий
let newRoutes = [];
//Если найдены маршруты
if (data.XEVROUTES) {
arrayFormer(data.XEVROUTES).map(r => {
newRoutes.push({ src: r.SSOURCE, dest: r.SDESTINATION });
});
}
//Инициализируем точки событий
let newPoints = [];
if (data.XEVPOINTS) {
arrayFormer(data.XEVPOINTS).map(p => {
newPoints.push({ point: p.SEVPOINT, addNoteOnChst: p.ADDNOTE_ONCHST, addNoteOnSend: p.ADDNOTE_ONSEND, banUpdate: p.BAN_UPDATE });
});
}
//Инициализируем типы заголовков примечаний
let newNoteTypes = [];
if (data.XNOTETYPES) {
arrayFormer(data.XNOTETYPES).map(nt => {
newNoteTypes.push(nt.SNAME);
});
}
//Инициализируем пользователей
let newAccounts = [];
//Если найдены пользователи
if (data.XACCOUNTS) {
arrayFormer(data.XACCOUNTS).map(a => {
newAccounts.push({
agnAbbr: a.SAGNABBR,
image: a.BIMAGE,
evRnList: a.SEVRN_LIST.toString().includes(";") ? a.SEVRN_LIST.toString().split(";") : [a.SEVRN_LIST.toString()]
});
});
}
//Указываем сформированные маршруты
setEventRoutes([...newRoutes]);
//Указываем сформированные точки маршрута
setEventPoints([...newPoints]);
//Указываем типы заголовков примечаний
setNoteTypes([...newNoteTypes]);
//Указываем сформированные аккаунты
setAccounts([...newAccounts]);
};
//Если указан тип событий
if (tasks.filters.values.type) {
//Загружаем данные
getEventData();
//Загружаем учётные документы
getDocLinks();
}
}, [tasks.filters.values.type, executeStored, getDocLinks]);
useEffect(() => { useEffect(() => {
//Считывание данных с учетом фильтрации //Считывание данных с учетом фильтрации
let getTasks = async () => { let getTasks = async () => {
const data = await executeStored({ const ds = await executeStored({
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DATASET", stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DATASET",
args: { args: {
CFILTERS: { VALUE: object2Base64XML(tasks.filters.fArray, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, CFILTERS: { VALUE: object2Base64XML(tasks.filters.fArray, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
@ -532,53 +464,124 @@ const useTasks = () => {
let newGroups = []; let newGroups = [];
let newRows = []; let newRows = [];
//Если статусы есть //Если статусы есть
if (data.XGROUPS) { if (ds.XDATA_GRID.groups) {
//Формируем структуру статусов //Формируем структуру статусов
arrayFormer(data.XGROUPS).map((group, i) => { arrayFormer(ds.XDATA_GRID.groups).map((group, i) => {
newGroups.push({ id: i, code: group.name, caption: group.caption, color: randomColor(i + 1) }); newGroups.push({ id: i, code: group.name, name: group.caption, color: randomColor(i + 1) });
}); });
//Если есть события //Если есть события
if (data.XROWS) { if (ds.XDATA_GRID.rows) {
//Формируем структуру событий //Формируем структуру событий
arrayFormer(data.XROWS).map((task, i) => { arrayFormer(ds.XDATA_GRID.rows).map((task, i) => {
newRows.push(initTask(i, newGroups.find(x => x.caption === task.groupName).id, task)); newRows.push(initTask(i, newGroups.find(x => x.name === task.groupName).id, task));
}); });
} }
} }
//Указываем сформированные данные //Возвращаем информацию
return { statuses: [...newGroups], rows: [...newRows] };
};
//Считывание вспомогательных данных
let getEventData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INFO_BY_CODE",
args: {
SEVNTYPE_CODE: tasks.filters.values.type
},
respArg: "COUT"
});
//Инициализируем маршруты событий
let newRoutes = data.XEVROUTES
? arrayFormer(data.XEVROUTES).reduce((prev, cur) => {
prev.push({ src: cur.SSOURCE, dest: cur.SDESTINATION });
return prev;
}, [])
: [];
//Инициализируем точки событий
let newPoints = data.XEVPOINTS
? arrayFormer(data.XEVPOINTS).reduce((prev, cur) => {
prev.push({
point: cur.SEVPOINT,
pointDescr: cur.SEVPOINT_DESCR,
addNoteOnChst: cur.ADDNOTE_ONCHST,
addNoteOnSend: cur.ADDNOTE_ONSEND,
banUpdate: cur.BAN_UPDATE
});
return prev;
}, [])
: [];
//Инициализируем типы заголовков примечаний
let newNoteTypes = data.XNOTETYPES
? arrayFormer(data.XNOTETYPES).reduce((prev, cur) => {
prev.push(cur.SNAME);
return prev;
}, [])
: [];
//Инициализируем пользователей
let newAccounts = data.XACCOUNTS
? arrayFormer(data.XACCOUNTS).reduce((prev, cur) => {
prev.push({
agnAbbr: cur.SAGNABBR,
image: cur.BIMAGE
});
return prev;
}, [])
: [];
//Загружаем учётные документы
let docLinks = await getDocLinks(tasks.filters.values.type);
//Возвращаем результат
return {
typeLoaded: tasks.filters.values.type,
evRoutes: [...newRoutes],
evPoints: [...newPoints],
noteTypes: [...newNoteTypes],
docLinks: [...docLinks],
accounts: [...newAccounts]
};
};
//Считывание данных
let getData = async () => {
//Инициализируем информацию о типе событии
let eventData = { ...tasks.extraData };
//Если необходимо обновить информацию о типе события
if (!tasks.extraData.typeLoaded || tasks.filters.values.type !== tasks.extraData.typeLoaded) {
//Загружаем информацию о типе события
eventData = await getEventData();
}
//Считываем информацию о задачах
let eventTasks = await getTasks();
//Добавление описания точки маршрута
eventTasks.statuses.map(s => (s["pointDescr"] = eventData.evPoints.find(ep => ep.point === s.code).pointDescr));
//Загружаем данные
setTasks(pv => ({ setTasks(pv => ({
...pv, ...pv,
groupsLoaded: true, groupsLoaded: true,
tasksLoaded: true, tasksLoaded: true,
statuses: [...newGroups], statuses: eventTasks.statuses,
rows: [...newRows], rows: eventTasks.rows,
extraData: eventData,
reload: false reload: false
})); }));
}; };
//Если необходимо загрузить данные и указан тип событий //Если необходимо загрузить данные и указан тип событий
if (tasks.reload && tasks.filters.values.type) { if (tasks.reload && tasks.filters.values.type) {
//Загружаем данные //Загружаем данные
getTasks(); getData();
} }
}, [ }, [
tasks.reload, tasks.reload,
tasks.filters.values.type, tasks.filters.values.type,
tasks.filters.fArray,
tasks.orders, tasks.orders,
tasks.tasksLoaded,
tasks.statuses.length,
tasks.rows.length,
executeStored, executeStored,
SERV_DATA_TYPE_CLOB SERV_DATA_TYPE_CLOB,
tasks.filters.fArray,
tasks.tasksLoaded,
tasks.extraData,
getDocLinks
]); ]);
return [ return [
tasks, tasks,
eventRoutes,
eventPoints,
noteTypes,
docLinks,
accounts,
taskFormOpen, taskFormOpen,
setTaskFormOpen, setTaskFormOpen,
cardSettings, cardSettings,
@ -595,6 +598,112 @@ const useTasks = () => {
]; ];
}; };
//Хук для получения пользовательских настроек разметки
const useColorRules = () => {
//Собственное состояние
const [clrRules, setClrRules] = useState({ loaded: false, rules: [] });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
useEffect(() => {
let getClrRules = async () => {
//Получаем массив пользовательских настроек
const data = await executeStored({
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DP_RULES_GET",
respArg: "COUT"
});
//Инициализируем
let newClrRules = [];
if (data) {
//Формируем структуру настройки
arrayFormer(data.XRULES).map((cr, i) => {
let fromV;
let toV;
if (cr.STYPE === "number") {
fromV = cr.NFROM;
toV = cr.NTO;
} else if (cr.STYPE === "string") {
fromV = cr.SFROM;
toV = cr.STO;
} else {
fromV = cr.DFROM;
toV = cr.DTO;
}
newClrRules.push({ id: i, fieldCode: cr.SFIELD, propName: cr.SDP_NAME, color: cr.SCOLOR, vType: cr.STYPE, from: fromV, to: toV });
});
setClrRules({ loaded: true, rules: [...newClrRules] });
}
};
if (!clrRules.loaded) getClrRules();
}, [clrRules.loaded, executeStored]);
return [clrRules];
};
//Хук для получения свойств раздела "События"
const useDocsProps = taskType => {
//Собственное состояние
const [docProps, setDocsProps] = useState({ loaded: false, props: [] });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
useEffect(() => {
let getDocsProps = async () => {
//Получаем массив пользовательских настроек
const data = await executeStored({
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_PROPS_GET",
args: { SEVNTYPE_CODE: taskType },
respArg: "COUT"
});
//Инициализируем
let newDocProps = [];
if (data) {
//Формируем структуру настройки
arrayFormer(data.XPROPS).map((dp, i) => {
newDocProps.push({
id: i,
rn: dp.NRN,
name: dp.SNAME,
readonly: dp.READONLY,
checkValue: dp.CHECK_VALUE,
checkUnique: dp.CHECK_UNIQUE,
require: dp.REQUIRE,
duplicateValue: dp.DUPLICATE_VALUE,
accessMode: dp.NACCESS_MODE,
showInGrid: dp.SHOW_IN_GRID,
defaultStr: dp.SDEFAULT_STR,
defaultNum: dp.NDEFAULT_NUM,
defaultDate: dp.DDEFAULT_DATE,
entryType: dp.NENTRY_TYPE,
format: dp.NFORMAT,
dataSubtype: dp.NDATA_SUBTYPE,
numWidth: dp.NNUM_WIDTH,
numPrecision: dp.NNUM_PRECISION,
strWidth: dp.NSTR_WIDTH,
unitcode: dp.SUNITCODE,
paramRn: dp.NPARAM_RN,
paramIn: dp.SPARAM_IN_CODE,
paramOut: dp.SPARAM_OUT_CODE,
showMethodRn: dp.NSHOW_METHOD_RN,
showMethodCode: dp.SMETHOD_CODE,
extraDictRn: dp.NEXTRA_DICT_RN,
initRn: dp.NINIT_RN
});
});
setDocsProps({ loaded: true, props: [...newDocProps] });
}
};
if (!docProps.loaded) getDocsProps();
}, [docProps.loaded, executeStored, taskType]);
return [docProps];
};
//Хук для события //Хук для события
const useClientEvent = (taskRn, taskType = "", taskStatus = "") => { const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
//Собственное состояние //Собственное состояние
@ -624,11 +733,12 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
scurrent_user: "", scurrent_user: "",
isUpdate: false, isUpdate: false,
insertDisabled: true, insertDisabled: true,
updateDisabled: true updateDisabled: true,
docProps: {}
}); });
//Подключение к контексту взаимодействия с сервером //Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx); const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//Подключение к контексту приложения //Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx); const { pOnlineShowDictionary } = useContext(ApplicationСtx);
@ -746,29 +856,46 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
SNUMB: task.snumber, SNUMB: task.snumber,
STYPE: task.stype, STYPE: task.stype,
SSTATUS: task.sstatus, SSTATUS: task.sstatus,
SPLAN_DATE: task.dstart_date ? dayjs(task.dstart_date).format("DD.MM.YYYY HH:mm") : null, SPLAN_DATE: task.dplan_date, // ? dayjs(task.dplan_date).format("DD.MM.YYYY HH:mm") : null,
SINIT_PERSON: task.sinit_clnperson, SINIT_PERSON: task.sinit_clnperson,
SCLIENT_CLIENT: task.sclnt_clnclients, SCLIENT_CLIENT: task.sclnt_clnclients,
SCLIENT_PERSON: task.sclnt_clnperson, SCLIENT_PERSON: task.sclnt_clnperson,
SDESCRIPTION: task.sdescription, SDESCRIPTION: task.sdescription,
SREASON: task.sinit_reason SREASON: task.sinit_reason,
CPROPS: {
VALUE: object2Base64XML(
[
Object.fromEntries(
Object.entries(task.docProps)
// eslint-disable-next-line no-unused-vars
.filter(([_, v]) => v != (null || ""))
)
],
{
arrayNodeName: "props"
}
),
SDATA_TYPE: SERV_DATA_TYPE_CLOB
}
} }
}); });
callBack(); callBack();
}, },
[ [
executeStored, executeStored,
task.dstart_date, task.scrn,
task.sprefix,
task.snumber,
task.stype,
task.sstatus,
task.dplan_date,
task.sinit_clnperson,
task.sclnt_clnclients, task.sclnt_clnclients,
task.sclnt_clnperson, task.sclnt_clnperson,
task.scrn,
task.sdescription, task.sdescription,
task.sinit_clnperson,
task.sinit_reason, task.sinit_reason,
task.snumber, task.docProps,
task.sprefix, SERV_DATA_TYPE_CLOB
task.sstatus,
task.stype
] ]
); );
@ -781,12 +908,19 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
NCLNEVENTS: task.nrn, NCLNEVENTS: task.nrn,
SCLIENT_CLIENT: task.sclnt_clnclients, SCLIENT_CLIENT: task.sclnt_clnclients,
SCLIENT_PERSON: task.sclnt_clnperson, SCLIENT_PERSON: task.sclnt_clnperson,
SDESCRIPTION: task.sdescription SDESCRIPTION: task.sdescription,
CPROPS: {
// eslint-disable-next-line no-unused-vars
VALUE: object2Base64XML([Object.fromEntries(Object.entries(task.docProps).filter(([_, v]) => v != (null || "")))], {
arrayNodeName: "props"
}),
SDATA_TYPE: SERV_DATA_TYPE_CLOB
}
} }
}); });
callBack(); callBack();
}, },
[executeStored, task.nrn, task.sclnt_clnclients, task.sclnt_clnperson, task.sdescription] [SERV_DATA_TYPE_CLOB, executeStored, task.docProps, task.nrn, task.sclnt_clnclients, task.sclnt_clnperson, task.sdescription]
); );
useEffect(() => { useEffect(() => {
@ -801,6 +935,10 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
}, },
respArg: "COUT" respArg: "COUT"
}); });
let newDocProps = {};
Object.keys(data.XEVENT)
.filter(k => k.includes("DP_"))
.map(dp => (newDocProps = { ...newDocProps, [dp]: data.XEVENT[dp] }));
setTask(pv => ({ setTask(pv => ({
...pv, ...pv,
scrn: data.XEVENT.SCRN, scrn: data.XEVENT.SCRN,
@ -811,7 +949,7 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
sdescription: data.XEVENT.SDESCRIPTION, sdescription: data.XEVENT.SDESCRIPTION,
sclnt_clnclients: data.XEVENT.SCLIENT_CLIENT, sclnt_clnclients: data.XEVENT.SCLIENT_CLIENT,
sclnt_clnperson: data.XEVENT.SCLIENT_PERSON, sclnt_clnperson: data.XEVENT.SCLIENT_PERSON,
dstart_date: data.XEVENT.SPLAN_DATE ? dayjs(data.XEVENT.SPLAN_DATE).format("YYYY-MM-DD HH:mm") : "", dplan_date: data.XEVENT.SPLAN_DATE,
sinit_clnperson: data.XEVENT.SINIT_PERSON, sinit_clnperson: data.XEVENT.SINIT_PERSON,
sinit_user: data.XEVENT.SINIT_AUTHID, sinit_user: data.XEVENT.SINIT_AUTHID,
sinit_reason: data.XEVENT.SREASON, sinit_reason: data.XEVENT.SREASON,
@ -825,7 +963,8 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
sto_usergrp: data.XEVENT.SSEND_USER_GROUP, sto_usergrp: data.XEVENT.SSEND_USER_GROUP,
scurrent_user: data.XEVENT.SINIT_AUTHID, scurrent_user: data.XEVENT.SINIT_AUTHID,
isUpdate: true, isUpdate: true,
init: false init: false,
docProps: newDocProps
})); }));
}; };
//Инициализация параметров события //Инициализация параметров события
@ -878,7 +1017,62 @@ const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
return [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, getEventNextNumb]; return [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, getEventNextNumb];
}; };
//Карточка события //Хук дополнительныч настроек
const useSettings = statuses => {
//Собственное состояние
const [settings, setSettings] = useState({
open: false,
statusesSort: {
sorted: false,
attr: localStorage.getItem("settingsSortAttr") ? localStorage.getItem("settingsSortAttr") : "name",
dest: localStorage.getItem("settingsSortDest") ? localStorage.getItem("settingsSortDest") : "asc",
statuses: []
},
colorRule: localStorage.getItem("settingsColorRule") ? JSON.parse(localStorage.getItem("settingsColorRule")) : {}
});
//При открытии диалога дополнительных настроек
const settingsOpen = () => setSettings(pv => ({ ...pv, open: true }));
//При закрытии диалога дополнительных настроек
const settingsClose = () => setSettings(pv => ({ ...pv, open: false }));
//Изменение состояния после сортировки
const afterSort = statuses => setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, sorted: true, statuses: statuses } }));
//При закрытии диалога дополнительных настроек по кнопке ОК
const handleSettingsOk = s => {
setSettings({ ...s, open: false, statusesSort: { ...s.statusesSort, sorted: false } });
};
//При получении новых настроек сортировки
useEffect(() => {
//Подгрузкка новых статусов
if (statuses.length > 0 && statuses.toString() !== settings.statusesSort.statuses.toString() && settings.statusesSort.sorted)
setSettings(pv => ({ ...pv, statusesSort: { ...pv.statusesSort, sorted: false } }));
//Сортировка
if (statuses.length > 0 && !settings.statusesSort.sorted) {
const attr = settings.statusesSort.attr;
const d = settings.statusesSort.dest;
let s = statuses;
s.sort((a, b) => (d === "asc" ? a[attr].localeCompare(b[attr]) : b[attr].localeCompare(a[attr])));
afterSort(s);
}
}, [settings.statusesSort.attr, settings.statusesSort.dest, settings.statusesSort.sorted, settings.statusesSort.statuses, statuses]);
//Сохранение при закрытии панели
useEffect(() => {
window.addEventListener("beforeunload", function () {
localStorage.setItem("settingsSortAttr", settings.statusesSort.attr);
localStorage.setItem("settingsSortDest", settings.statusesSort.dest);
localStorage.setItem("settingsColorRule", JSON.stringify(settings.colorRule));
});
}, [settings.colorRule, settings.statusesSort.attr, settings.statusesSort.dest]);
return [settings, settingsOpen, settingsClose, handleSettingsOk];
};
//Хук карточки события
const useTaskCard = () => { const useTaskCard = () => {
//Собственное состояние //Собственное состояние
const [taskCard, setTaskCard] = useState({ openEdit: false }); const [taskCard, setTaskCard] = useState({ openEdit: false });
@ -1257,4 +1451,4 @@ const useOrders = () => {
return [menuOrders, handleOrdersMenuButtonClick, handleOrdersMenuClose]; return [menuOrders, handleOrdersMenuButtonClick, handleOrdersMenuClose];
}; };
export { useTasks, useClientEvent, useTaskCard, useOrders }; export { useTasks, useSettings, useColorRules, useDocsProps, useClientEvent, useTaskCard, useOrders };