diff --git a/app/panels/clnt_task_board/clnt_task_board.js b/app/panels/clnt_task_board/clnt_task_board.js
new file mode 100644
index 0000000..0e10304
--- /dev/null
+++ b/app/panels/clnt_task_board/clnt_task_board.js
@@ -0,0 +1,234 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Панель мониторинга: Корневая панель доски задач
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState } from "react"; //Классы React
+import { DragDropContext, Droppable } from "react-beautiful-dnd"; //Работа с drag&drop
+import { Stack, Card, CardHeader, CardContent, Box, Button, IconButton, Icon, Typography } from "@mui/material"; //Интерфейсные компоненты
+import { TaskCard } from "./components/task_card"; //Компонент карточки события
+import { TaskFormDialog } from "./components/task_form"; //Компонент формы события
+import { Filter } from "./filter.js"; //Компонент фильтров
+import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора
+import { TaskCardSettings } from "./components/task_card_settings.js"; //Компонент настроек карточки события
+import { useTasks, useWindowResize, COLORS } from "./hooks.js"; //Вспомогательные хуки
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ CONTAINER: { width: "100%", padding: 1 },
+ STATUS_BLOCK: statusColor => {
+ return {
+ maxWidth: "300px",
+ minWidth: "300px",
+ minHeight: "100px",
+ backgroundColor: statusColor,
+ padding: "8px"
+ };
+ },
+ BLOCK_OPACITY: isAvailable => {
+ return isAvailable ? { opacity: 1 } : { opacity: 0.5 };
+ },
+ MARK_INFO: {
+ textAlign: "left",
+ textOverflow: "ellipsis",
+ overflow: "hidden",
+ whiteSpace: "pre",
+ maxWidth: "calc(250px)",
+ width: "-webkit-fill-available"
+ }
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Корневая панель доски задач
+const ClntTaskBoard = () => {
+ //Собственное состояние
+ const [
+ tasks,
+ eventRoutes,
+ docLinks,
+ taskFormOpen,
+ setTaskFormOpen,
+ cardSettings,
+ handleFilterOk,
+ handleFilterCancel,
+ handleFilterClick,
+ handleCardSettingsClick,
+ handleCardSettingsOk,
+ handleCardSettingsCancel,
+ handleReload,
+ onDragEnd,
+ handleOrderChanged
+ ] = useTasks();
+
+ //Состояние доступных маршрутов события
+ const [availableRoutes, setAvailableRoutes] = useState({ sorce: "", routes: [] });
+
+ //Состояние перетаскиваемого события
+ const [dragItem, setDragItem] = useState({ type: "", status: "" });
+
+ const clearDragItem = () => {
+ setDragItem({ type: "", status: "" });
+ };
+
+ //Очистка состояния
+ const clearARState = () => {
+ setAvailableRoutes({ sorce: "", routes: [] });
+ };
+
+ //Проверка доступности карточки события
+ const isCardAvailable = code => {
+ return availableRoutes.sorce === code || availableRoutes.routes.find(r => r.dest === code) || !availableRoutes.sorce ? true : false;
+ };
+
+ //Состояние ширины и высоты рабочей области окна
+ // eslint-disable-next-line no-unused-vars
+ const [width, height] = useWindowResize();
+
+ // useEffect(() => {
+ // console.log(`w: ${width}, h: ${height}`);
+ // }, [width, height]);
+
+ // useEffect(() => {
+ // console.log(availableRoutes);
+ // }, [availableRoutes]);
+
+ return (
+
+ {tasks.filters.isOpen ? (
+
+ ) : null}
+ d.id === tasks.filters.values.docLink) : null}
+ handleFilterClick={handleFilterClick}
+ handleReload={handleReload}
+ orders={tasks.orders}
+ handleOrderChanged={handleOrderChanged}
+ />
+ {tasks.filters.values.type ? (
+ {
+ let srcCode = tasks.statuses.find(s => s.id == e.source.droppableId).code;
+ setAvailableRoutes({ sorce: srcCode, routes: [...eventRoutes.filter(r => r.src === srcCode)] });
+ }}
+ onDragEnd={e => {
+ onDragEnd(e);
+ clearARState();
+ }}
+ >
+
+
+ {provided => (
+
+
+ {tasks.statuses.map((status, index) => (
+
+
+ {provided => (
+
+
+ handleCardSettingsClick(status)}
+ >
+ more_vert
+
+ }
+ title={
+
+ {status.caption}
+
+ }
+ subheader={
+
+ }
+ sx={{ padding: 0 }}
+ />
+
+
+ {tasks.rows
+ .filter(item => item.category === status.id)
+ .map((item, index) => (
+
+ ))}
+ {provided.placeholder}
+
+
+
+
+ )}
+
+
+ ))}
+
+ {provided.placeholder}
+
+ )}
+
+
+
+ ) : null}
+ {taskFormOpen ? (
+ {
+ setTaskFormOpen(false);
+ clearDragItem();
+ }}
+ />
+ ) : null}
+ {cardSettings.isOpen ? (
+
+ ) : null}
+
+ );
+};
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export { ClntTaskBoard };
diff --git a/app/panels/clnt_task_board/clnt_task_board1.js b/app/panels/clnt_task_board/clnt_task_board1.js
new file mode 100644
index 0000000..8dc0fa4
--- /dev/null
+++ b/app/panels/clnt_task_board/clnt_task_board1.js
@@ -0,0 +1,177 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Панель мониторинга: Корневая панель доски задач
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState, useContext, useCallback, useEffect, useRef } from "react"; //Классы React
+import { DragDropContext, Droppable } from "react-beautiful-dnd";
+import { Stack, Card, CardHeader, CardContent, Box, Button, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
+//import { Draggable } from "react-draggable";
+import { TaskCard } from "./components/task_card";
+import { TaskFormDialog } from "./components/task_form";
+
+//---------
+//Константы
+//---------
+
+const STYLES = {
+ CONTAINER: { width: "100%", padding: 1 },
+ DEF_SIZE: { minWidth: "200px", minHeight: "100px" },
+ SETTINGS_BUTTON: {
+ position: "absolute",
+ right: 8,
+ top: 8,
+ color: theme => theme.palette.grey[500]
+ }
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Корневая панель доски задач
+const ClntTaskBoard = () => {
+ const [insertTask, setInsertTask] = useState(false);
+
+ const [categories, setCategories] = useState([
+ { id: 1, name: "Новое" },
+ { id: 2, name: "Старое" }
+ ]);
+ const [tasks, setTasks] = useState([
+ { nrn: 150688812, id: 1, name: "Задание1", category: 1 },
+ { nrn: 150688812, id: 2, name: "Задание2", category: 1 },
+ { nrn: 150688812, id: 3, name: "Задание3", category: 2 },
+ { nrn: 150688812, id: 4, name: "Задание4", category: 2 }
+ ]);
+
+ const onDragEnd = result => {
+ const { source, destination } = result;
+
+ if (!destination) {
+ return;
+ }
+
+ if (destination.droppableId === "Categories") {
+ setCategories(categories);
+ } else if (destination.droppableId !== source.droppableId) {
+ setTasks(tasks =>
+ tasks.map(task =>
+ task.id === parseInt(result.draggableId)
+ ? {
+ ...task,
+ category: parseInt(result.destination.droppableId)
+ }
+ : task
+ )
+ );
+ } else {
+ setTasks(tasks);
+ }
+ };
+
+ return (
+
+
+
+
+ {provided => (
+
+
+ {categories.map((category, index) => (
+
+
+ {provided => (
+
+
+ {
+ console.log("Опции типа");
+ }}
+ >
+ more_vert
+
+ }
+ title={category.name}
+ subheader={}
+ sx={{ padding: 0 }}
+ />
+
+
+ {tasks
+ .filter(item => item.category === category.id)
+ .map((item, index) => (
+
+ //
+ // {provided => (
+ //
+ // {
+ // console.log("Опции задачи");
+ // }}
+ // >
+ // more_vert
+ //
+ // }
+ // title={
+ //
+ // {item.id} {item.name}
+ //
+ // }
+ // sx={{ padding: 0 }}
+ // />
+ //
+ // )}
+ //
+ ))}
+ {provided.placeholder}
+
+
+
+
+ )}
+
+
+ ))}
+
+ {provided.placeholder}
+
+ )}
+
+
+
+ {insertTask ? (
+ {
+ setInsertTask(false);
+ }}
+ />
+ ) : null}
+
+ );
+};
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export { ClntTaskBoard };
diff --git a/app/panels/clnt_task_board/components/filter_dialog.js b/app/panels/clnt_task_board/components/filter_dialog.js
new file mode 100644
index 0000000..ccd241e
--- /dev/null
+++ b/app/panels/clnt_task_board/components/filter_dialog.js
@@ -0,0 +1,189 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Диалоговое окно фильтра отбора
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState, useContext } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box } from "@mui/material"; //Интерфейсные компоненты
+import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода
+import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
+import { hasValue } from "../../../core/utils"; //Вспомогательные функции
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ DIALOG_ACTIONS: { justifyContent: "center" },
+ CLOSE_BUTTON: {
+ position: "absolute",
+ right: 8,
+ top: 8,
+ color: theme => theme.palette.grey[500]
+ }
+};
+
+//-----------------------
+//Вспомогательные функции
+//-----------------------
+
+//Выбор типа события
+const selectEventType = (value, showDictionary, callBack) => {
+ showDictionary({
+ unitCode: "ClientEventTypes",
+ inputParameters: [{ name: "in_EVNTYPE_CODE", value: value }],
+ callBack: res => (res.success === true ? callBack(res.outParameters.out_EVNTYPE_CODE) : callBack(null))
+ });
+};
+
+//Выбор производственного объекта
+const selectSendPerson = (value, showDictionary, callBack) => {
+ showDictionary({
+ unitCode: "AGNLIST",
+ inputParameters: [{ name: "in_AGNABBR", value: value }],
+ callBack: res => (res.success === true ? callBack(res.outParameters.out_AGNABBR) : callBack(null))
+ });
+};
+
+//Выбор подразделения
+const selectSendDivision = (value, showDictionary, callBack) => {
+ showDictionary({
+ unitCode: "INS_DEPARTMENT",
+ inputParameters: [{ name: "in_CODE", value: value }],
+ callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
+ });
+};
+
+//Выбор группы пользователей
+const selectSendUsrGrp = (value, showDictionary, callBack) => {
+ showDictionary({
+ unitCode: "CostStaffGroups",
+ inputParameters: [{ name: "in_CODE", value: value }],
+ callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
+ });
+};
+
+//---------------
+//Тело компонента
+//---------------
+
+//Диалоговое окно фильтра отбора
+const FilterDialog = ({ initial, docs, onCancel, onOk }) => {
+ //Собственное состояние
+ const [filter, setFilter] = useState({ ...initial });
+
+ //Подключение к контексту приложения
+ const { pOnlineShowDictionary } = useContext(ApplicationСtx);
+
+ //При закрытии диалога без изменения фильтра
+ const handleCancel = () => (onCancel ? onCancel() : null);
+
+ //При очистке фильтра
+ const handleClear = () => {
+ setFilter({
+ type: "",
+ sendPerson: "",
+ sendDivision: "",
+ sendUsrGrp: "",
+ docLink: ""
+ });
+ };
+
+ //При закрытии диалога с изменением фильтра
+ const handleOK = () => (onOk ? onOk(filter) : null);
+
+ //При изменении значения элемента
+ const handleFilterItemChange = (item, value) => setFilter(pv => ({ ...pv, [item]: value }));
+
+ //Генерация содержимого
+ return (
+
+
+
+ );
+};
+
+//Контроль свойств компонента - Диалоговое окно фильтра отбора
+FilterDialog.propTypes = {
+ initial: PropTypes.object.isRequired,
+ docs: PropTypes.arrayOf(PropTypes.object),
+ onOk: PropTypes.func,
+ onCancel: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { FilterDialog };
diff --git a/app/panels/clnt_task_board/components/filter_input_field.js b/app/panels/clnt_task_board/components/filter_input_field.js
new file mode 100644
index 0000000..f582fc5
--- /dev/null
+++ b/app/panels/clnt_task_board/components/filter_input_field.js
@@ -0,0 +1,126 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Поле ввода диалога фильтра
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useEffect, useState } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ HELPER_TEXT: { color: "red" }
+};
+
+//---------------
+//Тело компонента
+//---------------
+
+//Поле ввода
+const FilterInputField = ({ elementCode, elementValue, labelText, onChange, required = false, items = null, dictionary }) => {
+ //Значение элемента
+ const [value, setValue] = useState(elementValue);
+
+ //При получении нового значения из вне
+ useEffect(() => {
+ setValue(elementValue);
+ }, [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 = validationError => {
+ return (
+
+
+ list
+
+
+ ) : null
+ }
+ aria-describedby={`${elementCode}-helper-text`}
+ label={labelText}
+ onChange={handleChange}
+ />
+ );
+ };
+
+ //Генерация поля с выпадающим списком
+ const renderSelect = (items, validationError) => {
+ return (
+
+ );
+ };
+
+ //Признак ошибки валидации
+ const validationError = !value && required ? true : false;
+
+ //Генерация содержимого
+ return (
+
+ {labelText}
+ {items ? renderSelect(items, validationError) : renderInput(validationError)}
+ {validationError ? (
+
+ *Обязательное поле
+
+ ) : null}
+
+ );
+};
+
+//Контроль свойств - Поле ввода
+FilterInputField.propTypes = {
+ elementCode: PropTypes.string.isRequired,
+ elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ labelText: PropTypes.string.isRequired,
+ required: PropTypes.bool,
+ items: PropTypes.arrayOf(PropTypes.object),
+ dictionary: PropTypes.func,
+ onChange: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { FilterInputField };
diff --git a/app/panels/clnt_task_board/components/task_card.js b/app/panels/clnt_task_board/components/task_card.js
new file mode 100644
index 0000000..97999c6
--- /dev/null
+++ b/app/panels/clnt_task_board/components/task_card.js
@@ -0,0 +1,131 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент панели: Карточка задачи
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { Draggable } from "react-beautiful-dnd";
+import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem } from "@mui/material"; //Интерфейсные компоненты
+import { useTaskCard } from "../hooks"; //Вспомогательные хуки
+import { TaskFormDialog } from "./task_form"; //Форма события
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ CONTAINER: { margin: "5px 0px", textAlign: "center" },
+ MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" },
+ CARD_HEADER_TITLE: { padding: "2px" }
+};
+
+//------------------------------------
+//Вспомогательные функции и компоненты
+//------------------------------------
+
+//Действия карточки события
+const DataCellCardActions = ({ taskRn, menuItems, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, handleReload }) => {
+ return (
+
+
+
+
+ );
+};
+
+//Контроль свойств - Действия карточки события
+DataCellCardActions.propTypes = {
+ taskRn: PropTypes.number.isRequired,
+ menuItems: PropTypes.array.isRequired,
+ cardActions: PropTypes.object.isRequired,
+ handleMethodsMenuButtonClick: PropTypes.func.isRequired,
+ handleMethodsMenuClose: PropTypes.func.isRequired,
+ handleReload: PropTypes.func
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Карточка события
+const TaskCard = ({ task, index, handleReload }) => {
+ //Собственное состояние
+ const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard();
+
+ //Генерация содержимого
+ return (
+
+
+ {provided => (
+
+
+ {task.id} {task.name}
+
+ }
+ sx={{ padding: 0 }}
+ action={
+
+ }
+ />
+
+ )}
+
+ {taskCard.openEdit ? (
+ {
+ setTaskCard(pv => ({ ...pv, openEdit: false }));
+ }}
+ />
+ ) : null}
+
+ );
+};
+
+//Контроль свойств - Карточка события
+TaskCard.propTypes = {
+ task: PropTypes.object.isRequired,
+ index: PropTypes.number.isRequired,
+ handleReload: PropTypes.func
+};
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export { TaskCard };
diff --git a/app/panels/clnt_task_board/components/task_card_settings.js b/app/panels/clnt_task_board/components/task_card_settings.js
new file mode 100644
index 0000000..b4167a7
--- /dev/null
+++ b/app/panels/clnt_task_board/components/task_card_settings.js
@@ -0,0 +1,126 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Диалоговое окно настройки карточки событий
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import {
+ Dialog,
+ DialogTitle,
+ IconButton,
+ Icon,
+ DialogContent,
+ DialogActions,
+ Button,
+ Box,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem
+} from "@mui/material"; //Интерфейсные компоненты
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ DIALOG_ACTIONS: { justifyContent: "center" },
+ CLOSE_BUTTON: {
+ position: "absolute",
+ right: 8,
+ top: 8,
+ color: theme => theme.palette.grey[500]
+ },
+ BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
+};
+
+//-----------------------
+//Вспомогательные функции
+//-----------------------
+
+//---------------
+//Тело компонента
+//---------------
+
+//Диалоговое окно фильтра отбора
+const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
+ //Собственное состояние
+ const [settings, setSettings] = useState({ ...initial });
+
+ //При закрытии диалога без изменений
+ const handleCancel = () => (onCancel ? onCancel() : null);
+
+ //При закрытии диалога с изменениями
+ const handleOK = () => (onOk ? onOk(settings) : null);
+
+ //При изменении значения элемента
+ const handleSettingsItemChange = e => {
+ setSettings(pv => ({ ...pv, color: e.target.value }));
+ };
+
+ //Генерация содержимого
+ return (
+
+
+
+ );
+};
+
+//Контроль свойств компонента - Диалоговое окно настройки карточки событий
+TaskCardSettings.propTypes = {
+ initial: PropTypes.object.isRequired,
+ availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,
+ onOk: PropTypes.func,
+ onCancel: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { TaskCardSettings };
diff --git a/app/panels/clnt_task_board/components/task_form.js b/app/panels/clnt_task_board/components/task_form.js
new file mode 100644
index 0000000..d54584e
--- /dev/null
+++ b/app/panels/clnt_task_board/components/task_form.js
@@ -0,0 +1,489 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент панели: Диалог формы события
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
+import { useClientEvent } from "../hooks"; //Вспомогательные хуки
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ CONTAINER: { margin: "5px 0px", textAlign: "center" },
+ DIALOG_CONTENT: {
+ paddingBottom: "0px",
+ maxHeight: "740px",
+ minHeight: "740px",
+ "&::-webkit-scrollbar": {
+ width: "8px"
+ },
+ "&::-webkit-scrollbar-track": {
+ borderRadius: "8px",
+ backgroundColor: "#EBEBEB"
+ },
+ "&::-webkit-scrollbar-thumb": {
+ borderRadius: "8px",
+ backgroundColor: "#b4b4b4"
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: "#808080"
+ }
+ },
+ BOX_WITH_LEGEND: { border: "1px solid #939393" },
+ BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" },
+ BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" },
+ BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" },
+ LEGEND: { textAlign: "left" },
+ TEXT_FIELD: (widthVal, greyDisabled = false) => ({
+ margin: "4px",
+ ...(widthVal ? { width: widthVal } : {}),
+ ...(greyDisabled
+ ? {
+ "& .MuiInputBase-input.Mui-disabled": {
+ WebkitTextFillColor: "rgba(0, 0, 0, 0.87)"
+ },
+ "& .MuiInputLabel-root.Mui-disabled": {
+ WebkitTextFillColor: "rgba(0, 0, 0, 0.6)"
+ }
+ }
+ : {})
+ })
+};
+
+//------------------------------------
+//Вспомогательные функции и компоненты
+//------------------------------------
+
+//Свойства вкладки
+function a11yProps(index) {
+ return {
+ id: `simple-tab-${index}`,
+ "aria-controls": `simple-tabpanel-${index}`
+ };
+}
+
+//Формирование кнопки для открытия раздела
+const getInputProps = (onClick, disabled = false, icon = "list") => {
+ return {
+ endAdornment: (
+
+
+ {icon}
+
+
+ )
+ };
+};
+
+//Вкладка информации
+function CustomTabPanel(props) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+ {value === index && {children}}
+
+ );
+}
+
+//Контроль свойств - Вкладка информации
+CustomTabPanel.propTypes = {
+ children: PropTypes.node,
+ index: PropTypes.number.isRequired,
+ value: PropTypes.number.isRequired
+};
+
+//Вкладка основной информации
+const MainEventInfoTab = ({
+ task,
+ handleFieldEdit,
+ //handleTypeOpen,
+ //handleStatusOpen,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ handleEventNextNumbGet
+}) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ handleClientPersonOpen(0), !task.stype)}
+ >
+
+
+ );
+};
+
+//Контроль свойств - Вкладка основной информации
+MainEventInfoTab.propTypes = {
+ task: PropTypes.object.isRequired,
+ handleFieldEdit: PropTypes.func.isRequired,
+ //handleTypeOpen: PropTypes.func.isRequired,
+ //handleStatusOpen: PropTypes.func.isRequired,
+ handleClientClientsOpen: PropTypes.func.isRequired,
+ handleClientPersonOpen: PropTypes.func.isRequired,
+ handleCrnOpen: PropTypes.func.isRequired,
+ handleEventNextNumbGet: PropTypes.func.isRequired
+};
+
+//Вкладка информации об исполнителе
+const ExecutorEventInfoTab = ({ task, handleFieldEdit, handleClientPersonOpen }) => {
+ return (
+
+
+
+
+
+
+
+ handleClientPersonOpen(1), task.isUpdate)}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+//Контроль свойств - Вкладка информации об исполнителе
+ExecutorEventInfoTab.propTypes = {
+ task: PropTypes.object.isRequired,
+ handleFieldEdit: PropTypes.func.isRequired,
+ handleClientPersonOpen: PropTypes.func.isRequired
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Форма события
+const TaskForm = ({
+ task,
+ setTask,
+ //handleTypeOpen,
+ //handleStatusOpen,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ handleEventNextNumbGet
+}) => {
+ //Состояние вкладки
+ const [value, setValue] = useState(0);
+
+ //При изменении вкладки
+ const handleChange = (event, newValue) => {
+ setValue(newValue);
+ };
+
+ //При изменении поля
+ const handleFieldEdit = e => {
+ setTask(pv => ({
+ ...pv,
+ [e.target.id]: e.target.value,
+ //Связанные значения, если меняется одно, то необходимо обнулить другое
+ ...(e.target.id === "sclnt_clnperson" ? { sclnt_clnclients: "" } : {}),
+ ...(e.target.id === "sclnt_clnclients" ? { sclnt_clnperson: "" } : {})
+ }));
+ };
+
+ //Генерация содержимого
+ return (
+
+
+ {task.nrn ? "Исправление события" : "Добавление события"}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+//Контроль свойств - Форма события
+TaskForm.propTypes = {
+ task: PropTypes.object.isRequired,
+ setTask: PropTypes.func.isRequired,
+ //handleTypeOpen: PropTypes.func.isRequired,
+ //handleStatusOpen: PropTypes.func.isRequired,
+ handleClientClientsOpen: PropTypes.func.isRequired,
+ handleClientPersonOpen: PropTypes.func.isRequired,
+ handleCrnOpen: PropTypes.func.isRequired,
+ handleEventNextNumbGet: PropTypes.func.isRequired
+};
+
+//Диалог с формой события
+const TaskFormDialog = ({ taskRn, taskType, taskStatus, onClose }) => {
+ //Собственное состояние
+ const [
+ task,
+ setTask,
+ insertEvent,
+ updateEvent,
+ //handleTypeOpen,
+ //handleStatusOpen,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ handleEventNextNumbGet
+ ] = useClientEvent(taskRn, taskType, taskStatus);
+
+ return (
+
+ );
+};
+
+//Контроль свойств - Диалог с формой события
+TaskFormDialog.propTypes = {
+ taskRn: PropTypes.number,
+ taskType: PropTypes.string,
+ taskStatus: PropTypes.string,
+ onClose: PropTypes.func.isRequired
+};
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export { TaskFormDialog };
diff --git a/app/panels/clnt_task_board/filter.js b/app/panels/clnt_task_board/filter.js
new file mode 100644
index 0000000..2ae9cad
--- /dev/null
+++ b/app/panels/clnt_task_board/filter.js
@@ -0,0 +1,158 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Фильтр отбора
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { Chip, Stack, Icon, IconButton, Box, Menu, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
+import { useOrders } from "./hooks.js"; //Хук меню сортировки
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ ICON_ORDERS: orders => {
+ return orders.length > 0 ? { color: "#1976d2" } : {};
+ },
+ ORDER_MENU: {
+ width: "250px"
+ },
+ ORDER_MENU_ITEM: {
+ display: "flex",
+ justifyContent: "space-between"
+ }
+};
+
+//--------------------------
+//Вспомогательные компоненты
+//--------------------------
+
+//Элемент меню сортировок
+const SortMenuItem = ({ item, caption, orders, handleOrderChanged }) => {
+ //Кнопка сортировки
+ const order = orders.find(o => o.name == item);
+
+ return (
+
+ );
+};
+
+//Контроль свойств компонента - Элемент меню сортировок
+SortMenuItem.propTypes = {
+ item: PropTypes.string.isRequired,
+ caption: PropTypes.string.isRequired,
+ orders: PropTypes.array,
+ handleOrderChanged: PropTypes.func.isRequired
+};
+
+//Меню сортировок
+const SortMenu = ({ menuOrders, handleOrdersMenuClose, orders, handleOrderChanged }) => {
+ return (
+
+ );
+};
+
+//Контроль свойств компонента - Меню сортировок
+SortMenu.propTypes = {
+ menuOrders: PropTypes.object.isRequired,
+ handleOrdersMenuClose: PropTypes.func.isRequired,
+ orders: PropTypes.array,
+ handleOrderChanged: PropTypes.func.isRequired
+};
+
+//Элемент фильтра
+const FilterItem = ({ caption, value, onClick }) => {
+ //При нажатии на элемент
+ const handleClick = () => (onClick ? onClick() : null);
+
+ //Генерация содержимого
+ return (
+
+ {caption}: {value}
+
+ }
+ variant="outlined"
+ onClick={handleClick}
+ />
+ );
+};
+
+//Контроль свойств компонента - Элемент фильтра
+FilterItem.propTypes = {
+ caption: PropTypes.string.isRequired,
+ value: PropTypes.any.isRequired,
+ onClick: PropTypes.func
+};
+
+//---------------
+//Тело компонента
+//---------------
+
+//Фильтр отбора
+const Filter = ({ filter, selectedDoc, handleFilterClick, handleReload, orders, handleOrderChanged }) => {
+ //Меню сортировки
+ const [menuOrders, handleOrdersMenuButtonClick, handleOrdersMenuClose] = useOrders();
+
+ //При нажатии на фильтр
+ const handleClick = () => (handleFilterClick ? handleFilterClick() : null);
+
+ //Генерация содержимого
+ return (
+
+
+
+ refresh
+
+
+ sort
+
+
+ filter_alt
+
+ {filter.type ? : null}
+ {filter.sendPerson ? : null}
+ {filter.sendDivision ? : null}
+ {filter.sendUsrGrp ? : null}
+ {filter.docLink && selectedDoc ? : null}
+
+
+
+ );
+};
+
+//Контроль свойств компонента - Фильтр отбора
+Filter.propTypes = {
+ filter: PropTypes.object.isRequired,
+ selectedDoc: PropTypes.object,
+ handleFilterClick: PropTypes.func,
+ handleReload: PropTypes.func,
+ orders: PropTypes.array,
+ handleOrderChanged: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { Filter };
diff --git a/app/panels/clnt_task_board/hooks.js b/app/panels/clnt_task_board/hooks.js
new file mode 100644
index 0000000..bfe945d
--- /dev/null
+++ b/app/panels/clnt_task_board/hooks.js
@@ -0,0 +1,1112 @@
+/*
+ Парус 8 - Панели мониторинга - Редактор настройки регламентированного отчёта
+ Пользовательские хуки
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import { useState, useContext, useEffect, useCallback, useLayoutEffect } from "react"; //Классы React
+import { ApplicationСtx } from "../../context/application"; //Контекст приложения
+import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
+import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
+import { object2Base64XML, deepCopyObject } from "../../core/utils"; //Вспомогательные функции
+import dayjs from "dayjs"; //Работа с датами
+
+//---------
+//Константы
+//---------
+
+//Цвета статусов
+export const COLORS = [
+ "mediumSlateBlue",
+ "lightSalmon",
+ "fireBrick",
+ "orange",
+ "gold",
+ "limeGreen",
+ "yellowGreen",
+ "mediumAquaMarine",
+ "paleTurquoise",
+ "steelBlue",
+ "skyBlue",
+ "tan"
+];
+
+//---------------------------------------------
+//Вспомогательные функции форматирования данных
+//---------------------------------------------
+
+//Формирование случайного цвета
+const randomColor = index => {
+ const hue = index * 137.508;
+ return `hsl(${hue},50%,75%)`;
+};
+
+//Хук для отработки изменений ширины и высоты рабочей области окна
+const useWindowResize = () => {
+ //Состояние размера рабочей области
+ const [size, setSize] = useState([0, 0]);
+
+ //При изменении размера
+ useLayoutEffect(() => {
+ const updateSize = () => {
+ setSize([document.documentElement.clientWidth, document.documentElement.clientHeight]);
+ };
+ window.addEventListener("resize", updateSize);
+ updateSize();
+ return () => window.removeEventListener("resize", updateSize);
+ }, []);
+
+ //Вернём размеры
+ return size;
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Хук основных данных
+const useTasks = () => {
+ //Состояние открытия формы события
+ const [taskFormOpen, setTaskFormOpen] = useState(false);
+
+ //Состояние изменения настройки статуса
+ const [cardSettings, setCardSettings] = useState({ isOpen: false, settings: {} });
+
+ //Состояние маршрута события
+ const [eventRoutes, setEventRoutes] = useState([]);
+
+ //Состояние учётных документов
+ const [docLinks, setDocLinks] = useState([]);
+
+ //Состояние событий
+ const [tasks, setTasks] = useState({
+ groupsLoaded: false,
+ tasksLoaded: false,
+ orders: [],
+ filters: {
+ isOpen: true,
+ isSetByUser: false,
+ needSave: false,
+ values: { type: "", sendPerson: "", sendDivision: "", sendUsrGrp: "", docLink: "" },
+ fArray: [
+ { name: "SEVTYPE_CODE", from: "", to: "" },
+ { name: "SSEND_PERSON", from: "", to: "" },
+ { name: "SSEND_DIVISION", from: "", to: "" },
+ { name: "SSEND_USRGRP", from: "", to: "" },
+ { name: "NLINKED_RN", from: "", to: "" }
+ ]
+ },
+ rows: [],
+ statuses: [],
+ openCardForm: false,
+ reload: true
+ });
+
+ //Подключение к контексту взаимодействия с сервером
+ const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
+
+ //Подключение к контексту приложения
+ const { pOnlineShowDictionary } = useContext(ApplicationСtx);
+
+ //Инициализация параметров события
+ const initTask = (id, gp, task) => {
+ return {
+ id: id,
+ name: task.SPREF_NUMB,
+ category: gp,
+ nrn: task.NRN,
+ scrn: "",
+ sprefix: task.SEVPREF,
+ snumber: task.SEVNUMB,
+ stype: task.SEVTYPE_CODE,
+ sstatus: task.SEVSTAT_NAME,
+ sdescription: task.SEVDESCR,
+ sclnt_clnclients: "",
+ sclnt_clnperson: "",
+ dstart_date: task.DREG_DATE,
+ dplan_date: task.DPLAN_DATE,
+ sinit_clnperson: task.SINIT_PERSON,
+ sinit_user: "",
+ sinit_reason: "",
+ sto_company: "",
+ sto_department: "",
+ sto_clnpost: "",
+ sto_clnpsdep: "",
+ sto_clnperson: "",
+ sto_fcstaffgrp: "",
+ sto_user: task.SSEND_PERSON,
+ sto_usergrp: task.SSEND_USRGRP,
+ scurrent_user: ""
+ };
+ };
+
+ //При открытии диалога фильтра
+ const handleFilterClick = () => setFilterOpen(true);
+
+ //При изменении фильтра в диалоге
+ const handleFilterOk = filter => {
+ setFilterValues(filter);
+ setFilterOpen(false);
+ };
+
+ //При закрытии диалога фильтра
+ const handleFilterCancel = () => setFilterOpen(false);
+
+ //Установить значение фильтра
+ const setFilterValues = (values, ns = true) => {
+ //Считываем массив фильтров
+ let filterArr = tasks.filters.fArray.slice();
+ //Тип
+ filterArr.find(f => f.name === "SEVTYPE_CODE").from = values.type ? values.type : null;
+ //Исполнитель
+ filterArr.find(f => f.name === "SSEND_PERSON").from = values.sendPerson ? values.sendPerson : null;
+ //Подразделение
+ filterArr.find(f => f.name === "SSEND_DIVISION").from = values.sendDivision ? values.sendDivision : null;
+ //Группа пользователей
+ filterArr.find(f => f.name === "SSEND_USRGRP").from = values.sendUsrGrp ? values.sendUsrGrp : null;
+ //Учётный документ
+ filterArr.find(f => f.name === "NLINKED_RN").from = values.docLink ? values.docLink : null;
+ //Устанавливаем фильтры
+ setTasks(pv => ({
+ ...pv,
+ filters: { ...pv.filters, isSetByUser: true, needSave: ns, values: { ...values }, fArray: [...filterArr] },
+ reload: true
+ }));
+ };
+
+ //Загрузка значений фильтра из локального хранилища браузера
+ const loadLocalFilter = useCallback(async () => {
+ let vs = { ...tasks.filters.values };
+ Object.keys(vs).map(function (k) {
+ k !== "docLink" ? (vs[k] = localStorage.getItem(k)) : null;
+ });
+ setFilterValues(vs, false);
+ setFilterOpen(false);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ //При закрытии панели
+ useEffect(() => {
+ tasks.filters.needSave
+ ? window.addEventListener("beforeunload", function () {
+ Object.keys(tasks.filters.values).map(function (k) {
+ k !== "docLink" ? localStorage.setItem(k, tasks.filters.values[k]) : null;
+ });
+ })
+ : null;
+ }, [tasks.filters.needSave, tasks.filters.values]);
+
+ //При отсутствии пользовательских настроек фильтра
+ useEffect(() => {
+ if (!tasks.filters.isSetByUser) setFilterOpen(true);
+ }, [tasks.filters.isSetByUser]);
+
+ //При подключении к странице
+ useEffect(() => {
+ localStorage.length > 0 ? loadLocalFilter() : null;
+ }, [loadLocalFilter]);
+
+ //Показать/скрыть фильтр
+ const setFilterOpen = isOpen => {
+ setTasks(pv => ({ ...pv, filters: { ...pv.filters, isOpen } }));
+ };
+
+ //Открытие настройки статуса
+ const handleCardSettingsClick = curSettings => {
+ setCardSettings({ isOpen: true, settings: { ...curSettings } });
+ };
+
+ //Закрытие настройки статуса
+ const handleCardSettingsCancel = () => setCardSettings(pv => ({ ...pv, isOpen: false }));
+
+ //Применение настройки статуса
+ const handleCardSettingsOk = settings => {
+ //Считываем статусы
+ let cloneS = tasks.statuses.slice();
+ //Изменяем статус у выбранного
+ cloneS[tasks.statuses.findIndex(x => x.id === settings.id)] = { ...settings };
+ setTasks(pv => ({ ...pv, statuses: cloneS }));
+ setCardSettings({ isOpen: false, settings: {} });
+ };
+
+ //При изменении сортировки
+ const handleOrderChanged = useCallback(
+ columnName => {
+ let newOrders = deepCopyObject(tasks.orders);
+ const colOrder = newOrders.find(o => o.name == columnName);
+ const newDirection = colOrder?.direction == "ASC" ? "DESC" : colOrder?.direction == "DESC" ? null : "ASC";
+ if (newDirection == null && colOrder) newOrders.splice(newOrders.indexOf(colOrder), 1);
+ if (newDirection != null && !colOrder) newOrders.push({ name: columnName, direction: newDirection });
+ if (newDirection != null && colOrder) colOrder.direction = newDirection;
+ setTasks(pv => ({ ...pv, orders: newOrders, reload: true }));
+ },
+ [tasks.orders]
+ );
+
+ //Изменение статуса события (переносом)
+ const handleStateChange = useCallback(
+ async (nEvent, sNextStat) => {
+ try {
+ //Выполняем инициализацию параметров
+ const firstStep = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
+ args: {
+ NSTEP: 1,
+ NEVENT: nEvent,
+ SNEXT_STAT: sNextStat
+ }
+ });
+ if (firstStep) {
+ //Если требуется выбрать получателя
+ if (firstStep.NSELECT_EXEC === 1) {
+ //Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
+ pOnlineShowDictionary({
+ unitCode: "EventRoutesPointExecuters",
+ showMethod: "executers",
+ inputParameters: [
+ { name: "in_IDENT", value: firstStep.NIDENT },
+ { name: "in_EVENT", value: nEvent },
+ { name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE },
+ { name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT },
+ { name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON },
+ { name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME },
+ { name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT },
+ { name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON }
+ ],
+ callBack: async send => {
+ //Выполняем переход к выбранной точке с исполнителем
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
+ args: {
+ NIDENT: firstStep.NIDENT,
+ NSTEP: 3,
+ SEVENT_STAT: firstStep.SEVENT_STAT,
+ SSEND_CLIENT: send.outParameters.out_CLIENT_CODE,
+ SSEND_DIVISION: send.outParameters.out_DIVISION_CODE,
+ SSEND_POST: send.outParameters.out_POST_CODE,
+ SSEND_PERFORM: send.outParameters.out_POST_IN_DIV_CODE,
+ SSEND_PERSON: send.outParameters.out_PERSON_CODE,
+ SSEND_STAFFGRP: send.outParameters.out_STAFFGRP_CODE,
+ SSEND_USER_GROUP: send.outParameters.out_USER_GROUP_CODE,
+ SSEND_USER_NAME: send.outParameters.out_USER_NAME,
+ NSEND_PREDEFINED_EXEC: send.outParameters.out_PREDEFINED_EXEC,
+ NSEND_PREDEFINED_PROC: send.outParameters.out_PREDEFINED_PROC
+ }
+ });
+ //Необходимо обновить данные
+ setTasks(pv => ({ ...pv, reload: true }));
+ }
+ });
+ } else {
+ //Выполняем переход к выбранной точке с предопределенным исполнителем
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
+ args: {
+ NIDENT: firstStep.NIDENT,
+ NSTEP: 3,
+ SEVENT_STAT: firstStep.SEVENT_STAT
+ }
+ });
+ //Необходимо обновить данные
+ setTasks(pv => ({ ...pv, reload: true }));
+ }
+ }
+ } catch (e) {
+ //Необходимо обновить данные
+ setTasks(pv => ({ ...pv, reload: true }));
+ }
+ },
+ [executeStored, pOnlineShowDictionary]
+ );
+
+ //При необходимости обновить события
+ const handleReload = useCallback(() => {
+ setTasks(pv => ({ ...pv, reload: true }));
+ }, []);
+
+ //Взаимодействие с событием (через перенос)
+ const onDragEnd = useCallback(
+ result => {
+ //Определяем нужные параметры
+ const { source, destination } = result;
+ //Если путь не указан
+ if (!destination) {
+ return;
+ }
+ //Если происходит изменение статуса
+ if (destination.droppableId !== source.droppableId) {
+ //Считываем строку, у которой изменяется статус
+ let row = tasks.rows.find(f => f.id === parseInt(result.draggableId));
+ //Формируем события с учетом изменения
+ let rows = tasks.rows.map(task =>
+ task.id === parseInt(result.draggableId)
+ ? {
+ ...task,
+ category: parseInt(result.destination.droppableId)
+ }
+ : task
+ );
+ //Переинициализируем строки с учетом изменений (для визуального отображения)
+ setTasks(pv => ({ ...pv, rows: [...rows] }));
+ //Изменяем статус события
+ handleStateChange(row.nrn, tasks.statuses.find(s => s.id == destination.droppableId).code);
+ }
+ },
+ [handleStateChange, tasks.rows, tasks.statuses]
+ );
+
+ 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) {
+ data.XEVROUTES.map(r => {
+ newRoutes.push({ src: r.SSOURCE, dest: r.SDESTINATION });
+ });
+ }
+ //Инициализируем учётные документы
+ let newDocLinks = [];
+ //Если найдены учётные документы
+ if (data.XDOCLINKS) {
+ data.XDOCLINKS.map(d => {
+ newDocLinks.push({ id: d.NRN, descr: d.SDESCR });
+ });
+ }
+ //Указываем сформированные маршруты
+ setEventRoutes([...newRoutes]);
+ //Указываем сформированные учётные документы
+ setDocLinks([...newDocLinks]);
+ };
+ //Если указан тип событий
+ if (tasks.filters.values.type) {
+ //Загружаем данные
+ getEventData();
+ }
+ }, [tasks.filters.values.type, executeStored]);
+
+ useEffect(() => {
+ //Считывание данных с учетом фильтрации
+ let getTasks = async () => {
+ const data = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DATASET",
+ args: {
+ CFILTERS: { VALUE: object2Base64XML(tasks.filters.fArray, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
+ CORDERS: { VALUE: object2Base64XML(tasks.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
+ NINCLUDE_DEF: tasks.tasksLoaded ? 0 : 1
+ },
+ respArg: "COUT"
+ });
+ //Инициализируем статусы и события
+ let newGroups = [];
+ let newRows = [];
+ //Если статусы есть
+ if (data.XGROUPS) {
+ //Формируем структуру статусов
+ data.XGROUPS.map((group, i) => {
+ newGroups.push({ id: i, code: group.name, caption: group.caption, color: randomColor(i + 1) });
+ });
+ //Если есть события
+ if (data.XROWS) {
+ //Формируем структуру событий
+ data.XROWS.map((task, i) => {
+ newRows.push(initTask(i, newGroups.find(x => x.caption === task.groupName).id, task));
+ });
+ }
+ }
+ //Указываем сформированные данные
+ setTasks(pv => ({
+ ...pv,
+ groupsLoaded: true,
+ tasksLoaded: true,
+ statuses: [...newGroups],
+ rows: [...newRows],
+ reload: false
+ }));
+ };
+ //Если необходимо загрузить данные и указан тип событий
+ if (tasks.reload && tasks.filters.values.type) {
+ //Загружаем данные
+ getTasks();
+ }
+ }, [
+ tasks.reload,
+ tasks.filters.values.type,
+ tasks.filters.fArray,
+ tasks.orders,
+ tasks.tasksLoaded,
+ tasks.statuses.length,
+ tasks.rows.length,
+ executeStored,
+ SERV_DATA_TYPE_CLOB
+ ]);
+
+ return [
+ tasks,
+ eventRoutes,
+ docLinks,
+ taskFormOpen,
+ setTaskFormOpen,
+ cardSettings,
+ handleFilterOk,
+ handleFilterCancel,
+ handleFilterClick,
+ handleCardSettingsClick,
+ handleCardSettingsOk,
+ handleCardSettingsCancel,
+ handleReload,
+ onDragEnd,
+ handleOrderChanged
+ ];
+};
+
+//Хук для события
+const useClientEvent = (taskRn, taskType = "", taskStatus = "") => {
+ //Собственное состояние
+ const [task, setTask] = useState({
+ init: true,
+ nrn: taskRn,
+ scrn: "",
+ sprefix: "",
+ snumber: "",
+ stype: taskType,
+ sstatus: taskStatus,
+ sdescription: "",
+ sclnt_clnclients: "",
+ sclnt_clnperson: "",
+ dstart_date: "",
+ sinit_clnperson: "",
+ sinit_user: "",
+ sinit_reason: "",
+ sto_company: "",
+ sto_department: "",
+ sto_clnpost: "",
+ sto_clnpsdep: "",
+ sto_clnperson: "",
+ sto_fcstaffgrp: "",
+ sto_user: "",
+ sto_usergrp: "",
+ scurrent_user: "",
+ isUpdate: false,
+ insertDisabled: true,
+ updateDisabled: true
+ });
+
+ //Подключение к контексту взаимодействия с сервером
+ const { executeStored } = useContext(BackEndСtx);
+
+ //Подключение к контексту приложения
+ const { pOnlineShowDictionary } = useContext(ApplicationСtx);
+
+ const initEventType = useCallback(async () => {
+ //Считываем параметры исходя из типа события
+ const data = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVNTYPES_INIT",
+ args: {
+ SEVENT_TYPE: task.stype,
+ SCURRENT_PREF: task.sprefix
+ },
+ tagValueProcessor: () => undefined
+ });
+ if (data) {
+ setTask(pv => ({
+ ...pv,
+ sprefix: data.SPREF,
+ snumber: data.SNUMB
+ }));
+ }
+ }, [task.sprefix, task.stype, executeStored]);
+
+ //Отображение раздела "Типы событий"
+ // const handleTypeOpen = useCallback(async () => {
+ // pOnlineShowDictionary({
+ // unitCode: "ClientEventTypes",
+ // showMethod: "main",
+ // inputParameters: [{ name: "in_EVNTYPE_NAME", value: task.stype }],
+ // callBack: async res => {
+ // if (res.success) {
+ // //Считываем параметры исходя из типа события
+ // const data = await executeStored({
+ // stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVNTYPES_INIT",
+ // args: {
+ // SEVENT_TYPE: res.outParameters.out_EVNTYPE_CODE,
+ // SCURRENT_PREF: task.sprefix
+ // },
+ // tagValueProcessor: () => undefined
+ // });
+ // if (data) {
+ // setTask(pv => ({
+ // ...pv,
+ // stype: res.outParameters.out_EVNTYPE_CODE,
+ // sprefix: data.SPREF,
+ // snumber: data.SNUMB,
+ // sstatus: data.SDEFAULT_STATUS
+ // }));
+ // }
+ // }
+ // }
+ // });
+ // }, [executeStored, pOnlineShowDictionary, task.sprefix, task.stype]);
+
+ //Отображение раздела "Статусы типового события"
+ // const handleStatusOpen = useCallback(async () => {
+ // pOnlineShowDictionary({
+ // unitCode: "ClientEventTypesStates",
+ // showMethod: "main",
+ // inputParameters: [
+ // { name: "in_SEVNTYPE_CODE", value: task.stype },
+ // { name: "in_EVENT_STATUS_EVNSTAT_CODE", value: task.sstatus }
+ // ],
+ // callBack: res => {
+ // res.success
+ // ? setTask(pv => ({
+ // ...pv,
+ // sstatus: res.outParameters.out_EVENT_STATUS_EVNSTAT_CODE
+ // }))
+ // : null;
+ // }
+ // });
+ // }, [pOnlineShowDictionary, task.sstatus, task.stype]);
+
+ //Отображение раздела "Клиенты"
+ const handleClientClientsOpen = useCallback(async () => {
+ pOnlineShowDictionary({
+ unitCode: "ClientClients",
+ showMethod: "main",
+ inputParameters: [{ name: "in_CLIENT_CODE", value: task.sclnt_clnclients }],
+ callBack: res => {
+ res.success
+ ? setTask(pv => ({
+ ...pv,
+ sclnt_clnclients: res.outParameters.out_CLIENT_CODE,
+ sclnt_clnperson: ""
+ }))
+ : null;
+ }
+ });
+ }, [pOnlineShowDictionary, task.sclnt_clnclients]);
+
+ //Отображение раздела "Сотрудники"
+ const handleClientPersonOpen = useCallback(
+ //Тип открытия (0 - для клиента, 1 - для инициатора)
+ async (nType = 0) => {
+ pOnlineShowDictionary({
+ unitCode: "ClientPersons",
+ showMethod: "main",
+ inputParameters: [{ name: "in_CODE", value: nType === 0 ? task.sclnt_clnperson : task.sinit_clnperson }],
+ callBack: res => {
+ if (res.success) {
+ if (nType === 0) {
+ setTask(pv => ({
+ ...pv,
+ sclnt_clnperson: res.outParameters.out_CODE,
+ sclnt_clnclients: ""
+ }));
+ } else {
+ setTask(pv => ({
+ ...pv,
+ sinit_clnperson: res.outParameters.out_CODE
+ }));
+ }
+ }
+ }
+ });
+ },
+ [pOnlineShowDictionary, task.sclnt_clnperson, task.sinit_clnperson]
+ );
+
+ //Отображение раздела "Каталоги" для событий
+ const handleCrnOpen = useCallback(async () => {
+ pOnlineShowDictionary({
+ unitCode: "CatalogTree",
+ showMethod: "main",
+ inputParameters: [
+ { name: "in_DOCNAME", value: "ClientEvents" },
+ { name: "in_NAME", value: task.scrn }
+ ],
+ callBack: res => {
+ res.success
+ ? setTask(pv => ({
+ ...pv,
+ scrn: res.outParameters.out_NAME
+ }))
+ : null;
+ }
+ });
+ }, [pOnlineShowDictionary, task.scrn]);
+
+ //Считывание следующего номера события
+ const getEventNextNumb = useCallback(async () => {
+ const data = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_NEXTNUMB_GET",
+ args: {
+ SPREFIX: task.sprefix
+ }
+ });
+ if (data) {
+ setTask(pv => ({
+ ...pv,
+ snumber: data.SEVENT_NUMB
+ }));
+ }
+ }, [executeStored, task.sprefix]);
+
+ //Добавление события
+ const insertEvent = useCallback(
+ async callBack => {
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INSERT",
+ args: {
+ SCRN: task.scrn,
+ SPREF: task.sprefix,
+ SNUMB: task.snumber,
+ STYPE: task.stype,
+ SSTATUS: task.sstatus,
+ SPLAN_DATE: task.dstart_date ? dayjs(task.dstart_date).format("DD.MM.YYYY HH:mm") : null,
+ SINIT_PERSON: task.sinit_clnperson,
+ SCLIENT_CLIENT: task.sclnt_clnclients,
+ SCLIENT_PERSON: task.sclnt_clnperson,
+ SDESCRIPTION: task.sdescription,
+ SREASON: task.sinit_reason
+ }
+ });
+ callBack();
+ },
+ [
+ executeStored,
+ task.dstart_date,
+ task.sclnt_clnclients,
+ task.sclnt_clnperson,
+ task.scrn,
+ task.sdescription,
+ task.sinit_clnperson,
+ task.sinit_reason,
+ task.snumber,
+ task.sprefix,
+ task.sstatus,
+ task.stype
+ ]
+ );
+
+ //Исправление события
+ const updateEvent = useCallback(
+ async callBack => {
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_UPDATE",
+ args: {
+ NCLNEVENTS: task.nrn,
+ SCLIENT_CLIENT: task.sclnt_clnclients,
+ SCLIENT_PERSON: task.sclnt_clnperson,
+ SDESCRIPTION: task.sdescription
+ }
+ });
+ callBack();
+ },
+ [executeStored, task.nrn, task.sclnt_clnclients, task.sclnt_clnperson, task.sdescription]
+ );
+
+ useEffect(() => {
+ if (task.init) {
+ if (taskRn) {
+ //Считывание параметров события
+ const readEvent = async () => {
+ const data = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_GET",
+ args: {
+ NCLNEVENTS: task.nrn
+ },
+ respArg: "COUT"
+ });
+ setTask(pv => ({
+ ...pv,
+ scrn: data.XEVENT.SCRN,
+ sprefix: data.XEVENT.SPREF,
+ snumber: data.XEVENT.SNUMB,
+ stype: data.XEVENT.STYPE,
+ sstatus: data.XEVENT.SSTATUS,
+ sdescription: data.XEVENT.SDESCRIPTION,
+ sclnt_clnclients: data.XEVENT.SCLIENT_CLIENT,
+ sclnt_clnperson: data.XEVENT.SCLIENT_PERSON,
+ dstart_date: data.XEVENT.SPLAN_DATE ? dayjs(data.XEVENT.SPLAN_DATE).format("YYYY-MM-DD HH:mm") : "",
+ sinit_clnperson: data.XEVENT.SINIT_PERSON,
+ sinit_user: data.XEVENT.SINIT_AUTHID,
+ sinit_reason: data.XEVENT.SREASON,
+ sto_company: data.XEVENT.SSEND_CLIENT,
+ sto_department: data.XEVENT.SSEND_DIVISION,
+ sto_clnpost: data.XEVENT.SSEND_POST,
+ sto_clnpsdep: data.XEVENT.SSEND_PERFORM,
+ sto_clnperson: data.XEVENT.SSEND_PERSON,
+ sto_fcstaffgrp: data.XEVENT.SSEND_STAFFGRP,
+ sto_user: data.XEVENT.SSEND_USER_NAME,
+ sto_usergrp: data.XEVENT.SSEND_USER_GROUP,
+ scurrent_user: data.XEVENT.SINIT_AUTHID,
+ isUpdate: true,
+ init: false
+ }));
+ };
+ //Инициализация параметров события
+ readEvent();
+ } else {
+ //Считывание изначальных параметров события
+ const initEvent = async () => {
+ const data = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INIT",
+ args: {}
+ });
+ if (data) {
+ setTask(pv => ({
+ ...pv,
+ sprefix: data.SPREF,
+ snumber: data.SNUMB,
+ scurrent_user: data.SINIT_AUTHNAME,
+ sinit_clnperson: data.SINIT_PERSON,
+ sinit_user: !data.SINIT_PERSON ? data.SINIT_AUTHNAME : "",
+ init: false
+ }));
+ }
+ };
+ //Инициализация изначальных параметров события
+ initEvent();
+ initEventType();
+ }
+ }
+ if (!task.init) {
+ setTask(pv => ({ ...pv, sinit_user: !task.sinit_clnperson ? task.scurrent_user : "" }));
+ }
+ }, [executeStored, task.init, task.nrn, task.stype, task.scurrent_user, task.sinit_clnperson, taskRn, initEventType]);
+
+ //Проверка доступности действия
+ useEffect(() => {
+ setTask(pv => ({
+ ...pv,
+ insertDisabled:
+ !task.scrn ||
+ !task.sprefix ||
+ !task.snumber ||
+ !task.stype ||
+ !task.sstatus ||
+ !task.sdescription ||
+ (!task.sinit_clnperson && !task.sinit_user),
+ updateDisabled: !task.sdescription
+ }));
+ }, [task.scrn, task.sdescription, task.sinit_clnperson, task.sinit_user, task.snumber, task.sprefix, task.sstatus, task.stype]);
+
+ return [
+ task,
+ setTask,
+ insertEvent,
+ updateEvent,
+ //handleTypeOpen,
+ //handleStatusOpen,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ getEventNextNumb
+ ];
+};
+
+//Карточка события
+const useTaskCard = () => {
+ //Собственное состояние
+ const [taskCard, setTaskCard] = useState({ openEdit: false });
+
+ //Состояние действий
+ const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false });
+
+ //Подключение к контексту взаимодействия с сервером
+ const { executeStored } = useContext(BackEndСtx);
+
+ //Подключение к контексту сообщений
+ const { showMsgWarn } = useContext(MessagingСtx);
+
+ //Подключение к контексту приложения
+ const { pOnlineShowDictionary } = useContext(ApplicationСtx);
+
+ //Удаление контрагента
+ const deleteTask = useCallback(
+ async (nEvent, handleReload) => {
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DELETE",
+ args: { NCLNEVENTS: nEvent }
+ });
+ //Если требуется перезагрузить данные
+ if (handleReload) {
+ handleReload();
+ }
+ },
+ [executeStored]
+ );
+
+ //Возврат в предыдущую точку события
+ const returnTask = useCallback(
+ async (nEvent, handleReload) => {
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_RETURN",
+ args: { NCLNEVENTS: nEvent }
+ });
+ //Если требуется перезагрузить данные
+ if (handleReload) {
+ handleReload();
+ }
+ },
+ [executeStored]
+ );
+
+ //По нажатию на открытие меню действий
+ const handleMethodsMenuButtonClick = event => {
+ setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true }));
+ };
+
+ //При закрытии меню
+ const handleMethodsMenuClose = () => {
+ setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false }));
+ };
+
+ //По нажатия действия "Редактировать"
+ const handleTaskEdit = () => {
+ setTaskCard(pv => ({ ...pv, openEdit: true }));
+ };
+
+ //По нажатию действия "Удалить"
+ const handleTaskDelete = (nEvent, handleReload) => {
+ showMsgWarn("Удалить событие?", () => deleteTask(nEvent, handleReload));
+ };
+
+ //По нажатию действия "Выполнить возврат"
+ const handleTaskReturn = (nEvent, handleReload) => {
+ showMsgWarn("Выполнить возврат события в предыдущую точку?", () => returnTask(nEvent, handleReload));
+ };
+
+ //По нажатию действия "Примечания"
+ const handleEventNotesOpen = useCallback(
+ async nEvent => {
+ pOnlineShowDictionary({
+ unitCode: "ClientEventsNotes",
+ showMethod: "main",
+ inputParameters: [{ name: "in_PRN", value: nEvent }]
+ });
+ },
+ [pOnlineShowDictionary]
+ );
+
+ //По нажатию действия "Присоединенные документы"
+ const handleFileLinksOpen = useCallback(
+ async nEvent => {
+ pOnlineShowDictionary({
+ unitCode: "FileLinks",
+ showMethod: "main_link",
+ inputParameters: [
+ { name: "in_PRN", value: nEvent },
+ { name: "in_UNITCODE", value: "ClientEvents" }
+ ]
+ });
+ },
+ [pOnlineShowDictionary]
+ );
+
+ //По нажатию действия "Перейти"
+ const handleStateChange = useCallback(
+ async (nEvent, handleReload) => {
+ //Выполняем инициализацию параметров
+ const firstStep = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
+ args: {
+ NSTEP: 1,
+ NEVENT: nEvent
+ }
+ });
+ if (firstStep) {
+ //Открываем раздел "Маршруты событий (точки перехода)" для выбора следующей точки
+ pOnlineShowDictionary({
+ unitCode: "EventRoutesPointsPasses",
+ showMethod: "main_passes",
+ inputParameters: [
+ { name: "in_ENVTYPE_CODE", value: firstStep.SEVENT_TYPE },
+ { name: "in_ENVSTAT_CODE", value: firstStep.SEVENT_STAT },
+ { name: "in_POINT", value: firstStep.NPOINT }
+ ],
+ callBack: async point => {
+ //Выполняем проверку необходимости выбора исполнителя
+ const secondStep = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
+ args: {
+ NIDENT: firstStep.NIDENT,
+ NSTEP: 2,
+ NPASS: point.outParameters.out_RN
+ }
+ });
+ if (secondStep) {
+ //Если требуется выбрать получателя
+ if (secondStep.NSELECT_EXEC === 1) {
+ //Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
+ pOnlineShowDictionary({
+ unitCode: "EventRoutesPointExecuters",
+ showMethod: "executers",
+ inputParameters: [
+ { name: "in_IDENT", value: firstStep.NIDENT },
+ { name: "in_EVENT", value: nEvent },
+ { name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE },
+ { name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT },
+ { name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON },
+ { name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME },
+ { name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT },
+ { name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON }
+ ],
+ callBack: async send => {
+ //Выполняем переход к выбранной точке с исполнителем
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
+ args: {
+ NIDENT: firstStep.NIDENT,
+ NSTEP: 3,
+ SEVENT_STAT: point.outParameters.out_NEXT_POINT,
+ SSEND_CLIENT: send.outParameters.out_CLIENT_CODE,
+ SSEND_DIVISION: send.outParameters.out_DIVISION_CODE,
+ SSEND_POST: send.outParameters.out_POST_CODE,
+ SSEND_PERFORM: send.outParameters.out_POST_IN_DIV_CODE,
+ SSEND_PERSON: send.outParameters.out_PERSON_CODE,
+ SSEND_STAFFGRP: send.outParameters.out_STAFFGRP_CODE,
+ SSEND_USER_GROUP: send.outParameters.out_USER_GROUP_CODE,
+ SSEND_USER_NAME: send.outParameters.out_USER_NAME,
+ NSEND_PREDEFINED_EXEC: send.outParameters.out_PREDEFINED_EXEC,
+ NSEND_PREDEFINED_PROC: send.outParameters.out_PREDEFINED_PROC
+ }
+ });
+ //Если требуется перезагрузить данные
+ if (handleReload) {
+ handleReload();
+ }
+ }
+ });
+ } else {
+ //Выполняем переход к выбранной точке с предопределенным исполнителем
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
+ args: {
+ NIDENT: firstStep.NIDENT,
+ NSTEP: 3,
+ SEVENT_STAT: point.outParameters.out_NEXT_POINT
+ }
+ });
+ //Если требуется перезагрузить данные
+ if (handleReload) {
+ handleReload();
+ }
+ }
+ }
+ }
+ });
+ }
+ },
+ [executeStored, pOnlineShowDictionary]
+ );
+
+ //Изменение статуса события
+ const handleSend = useCallback(
+ async (nEvent, handleReload) => {
+ //Выполняем инициализацию параметров
+ const firstStep = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND",
+ args: {
+ NSTEP: 1,
+ NEVENT: nEvent
+ }
+ });
+ if (firstStep) {
+ //Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
+ pOnlineShowDictionary({
+ unitCode: "EventRoutesPointExecuters",
+ showMethod: "executers",
+ inputParameters: [
+ { name: "in_IDENT", value: firstStep.NIDENT },
+ { name: "in_EVENT", value: nEvent },
+ { name: "in_PERSON_CODE", value: firstStep.SSEND_PERSON },
+ { name: "in_USER_NAME", value: firstStep.SSEND_USER_NAME },
+ { name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE },
+ { name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT },
+ { name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON },
+ { name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME },
+ { name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT },
+ { name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON }
+ ],
+ callBack: async send => {
+ //Выполняем проверку необходимости выбора исполнителя
+ await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND",
+ args: {
+ NIDENT: firstStep.NIDENT,
+ NSTEP: 2,
+ SSEND_CLIENT: send.outParameters.out_CLIENT_CODE,
+ SSEND_DIVISION: send.outParameters.out_DIVISION_CODE,
+ SSEND_POST: send.outParameters.out_POST_CODE,
+ SSEND_PERFORM: send.outParameters.out_POST_IN_DIV_CODE,
+ SSEND_PERSON: send.outParameters.out_PERSON_CODE,
+ SSEND_STAFFGRP: send.outParameters.out_STAFFGRP_CODE,
+ SSEND_USER_GROUP: send.outParameters.out_USER_GROUP_CODE,
+ SSEND_USER_NAME: send.outParameters.out_USER_NAME,
+ NSEND_PREDEFINED_EXEC: send.outParameters.out_PREDEFINED_EXEC,
+ NSEND_PREDEFINED_PROC: send.outParameters.out_PREDEFINED_PROC
+ }
+ });
+ //Если требуется перезагрузить данные
+ if (handleReload) {
+ handleReload();
+ }
+ }
+ });
+ }
+ },
+ [executeStored, pOnlineShowDictionary]
+ );
+
+ //Формируем меню показателей
+ const menuItems = [
+ { method: "EDIT", name: "Исправить", icon: "edit", delimiter: false, needReload: false, func: handleTaskEdit },
+ { method: "DELETE", name: "Удалить", icon: "delete", delimiter: true, needReload: true, func: handleTaskDelete },
+ { method: "TASK_STATE_CHANGE", name: "Перейти", icon: "turn_right", delimiter: false, needReload: true, func: handleStateChange },
+ { method: "TASK_RETURN", name: "Выполнить возврат", icon: "turn_left", delimiter: false, needReload: true, func: handleTaskReturn },
+ { method: "TASK_SEND", name: "Направить", icon: "send", delimiter: true, needReload: true, func: handleSend },
+ { method: "NOTES", name: "Примечания", icon: "event_note", delimiter: true, needReload: false, func: handleEventNotesOpen },
+ {
+ method: "FILE_LINKS",
+ name: "Присоединенные документы",
+ icon: "attach_file",
+ delimiter: false,
+ needReload: false,
+ func: handleFileLinksOpen
+ }
+ ];
+
+ return [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems];
+};
+
+//Хук для сортировок
+const useOrders = () => {
+ //Состояние меню сортировки
+ const [menuOrders, setMenuOrders] = useState({ anchorMenuOrders: null, openOrders: false });
+
+ //По нажатию на открытие меню сортировки
+ const handleOrdersMenuButtonClick = event => {
+ setMenuOrders(pv => ({ ...pv, anchorMenuOrders: event.currentTarget, openOrders: true }));
+ };
+
+ //При закрытии меню
+ const handleOrdersMenuClose = () => {
+ setMenuOrders(pv => ({ ...pv, anchorMenuOrders: null, openOrders: false }));
+ };
+
+ return [menuOrders, handleOrdersMenuButtonClick, handleOrdersMenuClose];
+};
+
+export { useTasks, useClientEvent, useTaskCard, useOrders, useWindowResize };
diff --git a/app/panels/clnt_task_board/index.js b/app/panels/clnt_task_board/index.js
new file mode 100644
index 0000000..0324d4a
--- /dev/null
+++ b/app/panels/clnt_task_board/index.js
@@ -0,0 +1,16 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Панель мониторинга: Точка входа
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export const RootClass = ClntTaskBoard;
diff --git a/app/panels/clnt_task_boardOld/clnt_task_board.js b/app/panels/clnt_task_boardOld/clnt_task_board.js
new file mode 100644
index 0000000..827150e
--- /dev/null
+++ b/app/panels/clnt_task_boardOld/clnt_task_board.js
@@ -0,0 +1,167 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Панель мониторинга: Корневая панель доски задач
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState } from "react"; //Классы React
+import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
+import { Stack, Card, CardHeader, CardContent, Typography, Box, Button, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
+//import { Draggable } from "react-draggable";
+
+//---------
+//Константы
+//---------
+
+const STYLES = {
+ CONTAINER: { width: "100%", padding: 1 },
+ DEF_SIZE: { minWidth: "200px", minHeight: "100px" },
+ SETTINGS_BUTTON: {
+ position: "absolute",
+ right: 8,
+ top: 8,
+ color: theme => theme.palette.grey[500]
+ }
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Корневая панель доски задач
+const ClntTaskBoard = () => {
+ const [categories, setCategories] = useState([
+ { id: 1, name: "Новое" },
+ { id: 2, name: "Старое" }
+ ]);
+ const [tasks, setTasks] = useState([
+ { id: 1, name: "Задание1", category: 1 },
+ { id: 2, name: "Задание2", category: 1 },
+ { id: 3, name: "Задание3", category: 2 },
+ { id: 4, name: "Задание4", category: 2 }
+ ]);
+
+ const onDragEnd = result => {
+ const { source, destination } = result;
+
+ if (!destination) {
+ return;
+ }
+
+ if (destination.droppableId === "Categories") {
+ setCategories(categories);
+ } else if (destination.droppableId !== source.droppableId) {
+ setTasks(tasks =>
+ tasks.map(task =>
+ task.id === parseInt(result.draggableId)
+ ? {
+ ...task,
+ category: parseInt(result.destination.droppableId)
+ }
+ : task
+ )
+ );
+ } else {
+ setTasks(tasks);
+ }
+ };
+
+ return (
+
+
+
+
+ {provided => (
+
+
+ {categories.map((category, index) => (
+
+
+ {provided => (
+
+
+ {
+ console.log("Опции типа");
+ }}
+ >
+ more_vert
+
+ }
+ title={category.name}
+ subheader={
+
+ }
+ sx={{ padding: 0 }}
+ />
+
+
+ {tasks
+ .filter(item => item.category === category.id)
+ .map((item, index) => (
+
+ {provided => (
+
+ {
+ console.log("Опции задачи");
+ }}
+ >
+ more_vert
+
+ }
+ title={
+
+ {item.id} {item.name}
+
+ }
+ sx={{ padding: 0 }}
+ />
+
+ )}
+
+ ))}
+ {provided.placeholder}
+
+
+
+
+ )}
+
+
+ ))}
+
+ {provided.placeholder}
+
+ )}
+
+
+
+
+ );
+};
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export { ClntTaskBoard };
diff --git a/app/panels/clnt_task_boardOld/filter.js b/app/panels/clnt_task_boardOld/filter.js
new file mode 100644
index 0000000..4ae86d3
--- /dev/null
+++ b/app/panels/clnt_task_boardOld/filter.js
@@ -0,0 +1,74 @@
+/*
+ Парус 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 (
+
+ {caption}: {value}
+
+ }
+ 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 (
+
+
+ filter_alt
+
+ {/*filter. ? : null*/}
+
+ );
+};
+
+//Контроль свойств компонента - Фильтр отбора
+Filter.propTypes = {
+ filter: PropTypes.object.isRequired,
+ onClick: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { Filter };
diff --git a/app/panels/clnt_task_boardOld/index.js b/app/panels/clnt_task_boardOld/index.js
new file mode 100644
index 0000000..0324d4a
--- /dev/null
+++ b/app/panels/clnt_task_boardOld/index.js
@@ -0,0 +1,16 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Панель мониторинга: Точка входа
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export const RootClass = ClntTaskBoard;
diff --git a/app/panels/clnt_task_boardOld/task_card.js b/app/panels/clnt_task_boardOld/task_card.js
new file mode 100644
index 0000000..ef33362
--- /dev/null
+++ b/app/panels/clnt_task_boardOld/task_card.js
@@ -0,0 +1,31 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Карточка события
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import {} from "@mui/material"; //Интерфейсные компоненты
+
+//---------------
+//Тело компонента
+//---------------
+
+//Карточка события
+const TaskCard = () => {
+ //Генерация содержимого
+ return ;
+};
+
+//Контроль свойств компонента - Контейнер типа события
+TaskCard.propTypes = {};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { TaskCard };
diff --git a/app/panels/clnt_task_boardOld/tasks_category.js b/app/panels/clnt_task_boardOld/tasks_category.js
new file mode 100644
index 0000000..8cce348
--- /dev/null
+++ b/app/panels/clnt_task_boardOld/tasks_category.js
@@ -0,0 +1,31 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Контейнер типа события
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import {} from "@mui/material"; //Интерфейсные компоненты
+
+//---------------
+//Тело компонента
+//---------------
+
+//Контейнер типа события
+const TasksCategory = () => {
+ //Генерация содержимого
+ return ;
+};
+
+//Контроль свойств компонента - Контейнер типа события
+TasksCategory.propTypes = {};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { TasksCategory };
diff --git a/app/panels/clnt_task_boardOld2/clnt_task_board.js b/app/panels/clnt_task_boardOld2/clnt_task_board.js
new file mode 100644
index 0000000..48d1f53
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/clnt_task_board.js
@@ -0,0 +1,346 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Панель мониторинга: Корневая панель доски задач
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState, useContext, useCallback, useEffect, useRef } from "react"; //Классы React
+import { DragDropContext, Droppable } from "react-beautiful-dnd";
+import { Stack, Card, CardHeader, CardContent, Box, Button, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
+import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
+//import { Draggable } from "react-draggable";
+import { TaskCard } from "./components/task_card";
+import { TaskFormDialog } from "./components/task_form";
+import { object2Base64XML } from "../../core/utils";
+import { Filter } from "./filter.js";
+import { FilterDialog } from "./components/filter_dialog"; //Компонент диалогового окна фильтра отбора
+import { TaskCardSettings } from "./components/task_card_settings.js"; //
+
+//---------
+//Константы
+//---------
+
+const STYLES = {
+ CONTAINER: { width: "100%", padding: 1 },
+ DEF_SIZE: { minWidth: "200px", minHeight: "100px" },
+ SETTINGS_BUTTON: {
+ position: "absolute",
+ right: 8,
+ top: 8,
+ color: theme => theme.palette.grey[500]
+ }
+};
+
+const COLORS = [
+ "mediumSlateBlue",
+ "lightSalmon",
+ "fireBrick",
+ "orange",
+ "gold",
+ "limeGreen",
+ "yellowGreen",
+ "mediumAquaMarine",
+ "paleTurquoise",
+ "steelBlue",
+ "skyBlue",
+ "tan"
+];
+
+//-----------
+//Тело модуля
+//-----------
+
+//Корневая панель доски задач
+const ClntTaskBoard = () => {
+ const [insertTask, setInsertTask] = useState(false);
+
+ const [cardSettings, setCardSettings] = useState({ isOpen: false, settings: {} });
+
+ const [config, setConfig] = useState({
+ groupsLoaded: false,
+ tasksLoaded: false,
+ filters: {
+ isOpen: false,
+ isSetByUser: false,
+ values: { type: "", sendPerson: "", sendDivision: "", sendUsrGrp: "" },
+ fArray: [
+ { name: "SEVTYPE_CODE", from: "", to: "" },
+ { name: "SSEND_PERSON", from: "", to: "" },
+ { name: "SSEND_DIVISION", from: "", to: "" },
+ { name: "SSEND_USRGRP", from: "", to: "" }
+ ]
+ },
+ reload: true
+ });
+
+ const [statuses, setStatuses] = useState([]);
+
+ const [tasks, setTasks] = useState([]);
+
+ let usedColors = useRef([]);
+
+ //Подключение к контексту взаимодействия с сервером
+ const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
+
+ // const appendGroup = newStatus => {};
+
+ // const appendTask = task => {
+ // //setTasks([...tasks, task]);
+ // };
+
+ const randomColor = () => {
+ let color = COLORS[Math.floor(Math.random() * COLORS.length)];
+ while (usedColors.current.find(c => c === color) !== undefined) {
+ color = COLORS[Math.floor(Math.random() * COLORS.length)];
+ }
+ usedColors.current = [...usedColors.current, color];
+ return color;
+ };
+
+ const initTask = (id, gp, task) => {
+ return {
+ id: id,
+ name: `${task.SEVPREF}-${task.NEVNUMB}`,
+ category: gp,
+ nrn: task.NRN,
+ scrn: "",
+ sprefix: task.SEVPREF,
+ snumber: task.NEVNUMB,
+ stype: task.SEVTYPE_CODE,
+ sstatus: task.SEVSTAT_NAME,
+ sdescription: task.SEVDESCR,
+ sclnt_clnclients: "",
+ sclnt_clnperson: "",
+ dstart_date: task.DREG_DATE,
+ sinit_clnperson: task.SINIT_PERSON,
+ sinit_user: "",
+ sinit_reason: "",
+ sto_company: "",
+ sto_department: "",
+ sto_clnpost: "",
+ sto_clnpsdep: "",
+ sto_clnperson: "",
+ sto_fcstaffgrp: "",
+ sto_user: task.SSEND_PERSON,
+ sto_usergrp: task.SSEND_USRGRP,
+ scurrent_user: ""
+ };
+ };
+
+ const getTasks = useCallback(async () => {
+ if (config.reload) {
+ const data = await executeStored({
+ stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DATASET",
+ args: {
+ CFILTERS: { VALUE: object2Base64XML(config.filters.fArray, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
+ NINCLUDE_DEF: config.tasksLoaded ? 0 : 1
+ },
+ respArg: "COUT"
+ });
+ console.log(object2Base64XML(config.filters.fArray, { arrayNodeName: "filters" }));
+ let newSt = [];
+ if (data.XGROUPS != null) {
+ const x = statuses.length;
+ data.XGROUPS.map((group, i) => {
+ //setStatuses([...statuses, { id: x + i, name: group.name }]);
+ newSt.push({ id: x + i, name: group.name, color: randomColor() });
+ //console.log(`${x + i} ${group.name}`);
+ });
+ setStatuses([...newSt].sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)));
+ setConfig(pv => ({ ...pv, groupsLoaded: true }));
+ }
+ if (data.XROWS != null) {
+ let newT = [];
+ const x = tasks.length;
+ data.XROWS.map((task, i) => {
+ //setStatuses([...statuses, { id: x + i, name: group.name }]);
+ //console.log(statuses.find(x => x.name === task.groupName).id ? newSt.find(x => x.name === task.groupName) : null);
+ newT.push(initTask(x + i, newSt.find(x => x.name === task.groupName).id, task));
+ });
+ setTasks([...newT]);
+ setConfig(pv => ({ ...pv, tasksLoaded: true, reload: false }));
+ }
+ }
+ }, [SERV_DATA_TYPE_CLOB, config.filters, config.reload, config.tasksLoaded, executeStored, statuses, tasks]);
+
+ useEffect(() => {
+ getTasks();
+ }, [config.reload, getTasks]);
+
+ // useEffect(() => {
+ // config.groupsLoaded ? console.log(statuses) : null;
+ // config.tasksLoaded ? console.log(tasks) : null;
+ // }, [config.groupsLoaded, statuses]);
+
+ const onDragEnd = result => {
+ const { source, destination } = result;
+
+ if (!destination) {
+ return;
+ }
+
+ if (destination.droppableId !== source.droppableId) {
+ setTasks(tasks =>
+ tasks.map(task =>
+ task.id === parseInt(result.draggableId)
+ ? {
+ ...task,
+ category: parseInt(result.destination.droppableId)
+ }
+ : task
+ )
+ );
+ console.log(statuses.find(s => s.id == destination.droppableId).name); // Тут вызов смены статуса
+ } else {
+ setTasks(tasks);
+ }
+ };
+
+ //При открытии диалога фильтра
+ const handleFilterClick = () => setFilterOpen(true);
+
+ //При изменении фильтра в диалоге
+ const handleFilterOk = filter => {
+ setFilterValues(filter);
+ setFilterOpen(false);
+ };
+
+ //При закрытии диалога фильтра
+ const handleFilterCancel = () => setFilterOpen(false);
+
+ //Установить значение фильтра
+ const setFilterValues = values => {
+ let filterArr = config.filters.fArray.slice();
+ values.type ? (filterArr.find(f => f.name === "SEVTYPE_CODE").from = values.type) : null;
+ values.sendPerson ? (filterArr.find(f => f.name === "SSEND_PERSON").from = values.sendPerson) : null;
+ values.sendDivision ? (filterArr.find(f => f.name === "SSEND_DIVISION").from = values.sendDivision) : null;
+ values.sendUsrGrp ? (filterArr.find(f => f.name === "SSEND_USRGRP").from = values.sendUsrGrp) : null;
+ setConfig(pv => ({ ...pv, filters: { ...pv.filters, isSetByUser: true, values: { ...values }, fArray: [...filterArr] }, reload: true }));
+ };
+
+ //Показать/скрыть фильтр
+ const setFilterOpen = isOpen => setConfig(pv => ({ ...pv, filters: { ...pv.filters, isOpen } }));
+
+ const handleCardSettingsClick = curSettings => {
+ setCardSettings({ isOpen: true, settings: { ...curSettings } });
+ };
+
+ const handleCardSettingsCancel = () => setCardSettings(pv => ({ ...pv, isOpen: false }));
+
+ const handleCardSettingsOk = settings => {
+ let cloneS = statuses.slice();
+ cloneS[statuses.findIndex(x => x.id === settings.id)] = { ...settings };
+ setStatuses(cloneS);
+ setCardSettings({ isOpen: false, settings: {} });
+ };
+
+ return (
+
+ {config.filters.isOpen ? : null}
+
+
+
+
+ {provided => (
+
+
+ {statuses.map((status, index) => (
+
+
+ {provided => (
+
+
+ handleCardSettingsClick(status)}>
+ more_vert
+
+ }
+ title={status.name}
+ subheader={}
+ sx={{ padding: 0 }}
+ />
+
+
+ {tasks
+ .filter(item => item.category === status.id)
+ .map((item, index) => (
+
+ //
+ // {provided => (
+ //
+ // {
+ // console.log("Опции задачи");
+ // }}
+ // >
+ // more_vert
+ //
+ // }
+ // title={
+ //
+ // {item.id} {item.name}
+ //
+ // }
+ // sx={{ padding: 0 }}
+ // />
+ //
+ // )}
+ //
+ ))}
+ {provided.placeholder}
+
+
+
+
+ )}
+
+
+ ))}
+
+ {provided.placeholder}
+
+ )}
+
+
+
+ {insertTask ? (
+ {
+ setInsertTask(false);
+ }}
+ />
+ ) : null}
+ {cardSettings.isOpen ? (
+ !usedColors.current.includes(c))}
+ onOk={handleCardSettingsOk}
+ onCancel={handleCardSettingsCancel}
+ />
+ ) : null}
+
+ );
+};
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export { ClntTaskBoard };
diff --git a/app/panels/clnt_task_boardOld2/components/filter_dialog.js b/app/panels/clnt_task_boardOld2/components/filter_dialog.js
new file mode 100644
index 0000000..14f8b22
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/components/filter_dialog.js
@@ -0,0 +1,171 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Диалоговое окно фильтра отбора
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState, useContext } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box } from "@mui/material"; //Интерфейсные компоненты
+import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода
+import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ DIALOG_ACTIONS: { justifyContent: "center" },
+ CLOSE_BUTTON: {
+ position: "absolute",
+ right: 8,
+ top: 8,
+ color: theme => theme.palette.grey[500]
+ }
+};
+
+//-----------------------
+//Вспомогательные функции
+//-----------------------
+
+//Выбор типа события
+const selectEventType = (showDictionary, callBack) => {
+ showDictionary({
+ unitCode: "ClientEventTypes",
+ callBack: res => (res.success === true ? callBack(res.outParameters.out_EVNTYPE_CODE) : callBack(null))
+ });
+};
+
+//Выбор производственного объекта
+const selectSendPerson = (showDictionary, callBack) => {
+ showDictionary({
+ unitCode: "ClientPersons",
+ callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
+ });
+};
+
+//Выбор подразделения
+const selectSendDivision = (showDictionary, callBack) => {
+ showDictionary({
+ unitCode: "INS_DEPARTMENT",
+ callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
+ });
+};
+
+//Выбор группы пользователей
+const selectSendUsrGrp = (showDictionary, callBack) => {
+ showDictionary({
+ unitCode: "CostStaffGroups",
+ callBack: res => (res.success === true ? callBack(res.outParameters.out_CODE) : callBack(null))
+ });
+};
+
+//---------------
+//Тело компонента
+//---------------
+
+//Диалоговое окно фильтра отбора
+const FilterDialog = ({ initial, onCancel, onOk }) => {
+ //Собственное состояние
+ const [filter, setFilter] = useState({ ...initial });
+
+ //Подключение к контексту приложения
+ const { pOnlineShowDictionary } = useContext(ApplicationСtx);
+
+ //При закрытии диалога без изменения фильтра
+ const handleCancel = () => (onCancel ? onCancel() : null);
+
+ //При очистке фильтра
+ const handleClear = () => {
+ setFilter({
+ type: "",
+ sendPerson: "",
+ sendDivision: "",
+ sendUsrGrp: ""
+ });
+ };
+
+ //При закрытии диалога с изменением фильтра
+ const handleOK = () => (onOk ? onOk(filter) : null);
+
+ //При изменении значения элемента
+ const handleFilterItemChange = (item, value) => setFilter(pv => ({ ...pv, [item]: value }));
+
+ //Генерация содержимого
+ return (
+
+
+
+ );
+};
+
+//Контроль свойств компонента - Диалоговое окно фильтра отбора
+FilterDialog.propTypes = {
+ initial: PropTypes.object.isRequired,
+ onOk: PropTypes.func,
+ onCancel: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { FilterDialog };
diff --git a/app/panels/clnt_task_boardOld2/components/filter_input_field.js b/app/panels/clnt_task_boardOld2/components/filter_input_field.js
new file mode 100644
index 0000000..af3507a
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/components/filter_input_field.js
@@ -0,0 +1,126 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Поле ввода диалога фильтра
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useEffect, useState } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem } from "@mui/material"; //Интерфейсные компоненты
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ HELPER_TEXT: { color: "red" }
+};
+
+//---------------
+//Тело компонента
+//---------------
+
+//Поле ввода
+const FilterInputField = ({ elementCode, elementValue, labelText, onChange, required = false, items = null, dictionary }) => {
+ //Значение элемента
+ const [value, setValue] = useState(elementValue);
+
+ //При получении нового значения из вне
+ useEffect(() => {
+ setValue(elementValue);
+ }, [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 = validationError => {
+ return (
+
+
+ list
+
+
+ ) : null
+ }
+ aria-describedby={`${elementCode}-helper-text`}
+ label={labelText}
+ onChange={handleChange}
+ />
+ );
+ };
+
+ //Генерация поля с выпадающим списком
+ const renderSelect = (items, validationError) => {
+ return (
+
+ );
+ };
+
+ //Признак ошибки валидации
+ const validationError = !value && required ? true : false;
+
+ //Генерация содержимого
+ return (
+
+ {labelText}
+ {items ? renderSelect(items, validationError) : renderInput(validationError)}
+ {validationError ? (
+
+ *Обязательное поле
+
+ ) : null}
+
+ );
+};
+
+//Контроль свойств - Поле ввода
+FilterInputField.propTypes = {
+ elementCode: PropTypes.string.isRequired,
+ elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ labelText: PropTypes.string.isRequired,
+ required: PropTypes.bool,
+ items: PropTypes.arrayOf(PropTypes.object),
+ dictionary: PropTypes.func,
+ onChange: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { FilterInputField };
diff --git a/app/panels/clnt_task_boardOld2/components/task_card.js b/app/panels/clnt_task_boardOld2/components/task_card.js
new file mode 100644
index 0000000..501e6ee
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/components/task_card.js
@@ -0,0 +1,124 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент панели: Карточка задачи
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { Draggable } from "react-beautiful-dnd";
+import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem } from "@mui/material"; //Интерфейсные компоненты
+import { useTaskCard } from "../hooks"; //Вспомогательные хуки
+import { TaskFormDialog } from "./task_form"; //Форма события
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ CONTAINER: { margin: "5px 0px", textAlign: "center" },
+ DATA_GRID_CELL_DEFAULT: { cursor: "pointer", backgroundColor: "#ADD8E6" }
+};
+
+//------------------------------------
+//Вспомогательные функции и компоненты
+//------------------------------------
+
+//Действия карты показателя
+const DataCellCardActions = ({ taskRn, menuItems, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose }) => {
+ return (
+
+
+
+
+ );
+};
+
+//Контроль свойств - Действия карты показателя
+DataCellCardActions.propTypes = {
+ taskRn: PropTypes.number.isRequired,
+ menuItems: PropTypes.array.isRequired,
+ cardActions: PropTypes.object.isRequired,
+ handleMethodsMenuButtonClick: PropTypes.func.isRequired,
+ handleMethodsMenuClose: PropTypes.func.isRequired
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Карточка задачи
+const TaskCard = ({ task, index }) => {
+ //Собственное состояние
+ const [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems] = useTaskCard();
+
+ //Генерация содержимого
+ return (
+
+
+ {provided => (
+
+
+ {task.id} {task.name}
+
+ }
+ sx={{ padding: 0 }}
+ action={
+
+ }
+ />
+
+ )}
+
+ {taskCard.openEdit ? (
+ {
+ setTaskCard(pv => ({ ...pv, openEdit: false }));
+ }}
+ />
+ ) : null}
+
+ );
+};
+
+//Контроль свойств - Карточка задачи
+TaskCard.propTypes = {
+ task: PropTypes.object.isRequired,
+ index: PropTypes.number.isRequired
+};
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export { TaskCard };
diff --git a/app/panels/clnt_task_boardOld2/components/task_card_settings.js b/app/panels/clnt_task_boardOld2/components/task_card_settings.js
new file mode 100644
index 0000000..b4167a7
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/components/task_card_settings.js
@@ -0,0 +1,126 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Диалоговое окно настройки карточки событий
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import {
+ Dialog,
+ DialogTitle,
+ IconButton,
+ Icon,
+ DialogContent,
+ DialogActions,
+ Button,
+ Box,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem
+} from "@mui/material"; //Интерфейсные компоненты
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ DIALOG_ACTIONS: { justifyContent: "center" },
+ CLOSE_BUTTON: {
+ position: "absolute",
+ right: 8,
+ top: 8,
+ color: theme => theme.palette.grey[500]
+ },
+ BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
+};
+
+//-----------------------
+//Вспомогательные функции
+//-----------------------
+
+//---------------
+//Тело компонента
+//---------------
+
+//Диалоговое окно фильтра отбора
+const TaskCardSettings = ({ initial, availableClrs, onCancel, onOk }) => {
+ //Собственное состояние
+ const [settings, setSettings] = useState({ ...initial });
+
+ //При закрытии диалога без изменений
+ const handleCancel = () => (onCancel ? onCancel() : null);
+
+ //При закрытии диалога с изменениями
+ const handleOK = () => (onOk ? onOk(settings) : null);
+
+ //При изменении значения элемента
+ const handleSettingsItemChange = e => {
+ setSettings(pv => ({ ...pv, color: e.target.value }));
+ };
+
+ //Генерация содержимого
+ return (
+
+
+
+ );
+};
+
+//Контроль свойств компонента - Диалоговое окно настройки карточки событий
+TaskCardSettings.propTypes = {
+ initial: PropTypes.object.isRequired,
+ availableClrs: PropTypes.arrayOf(PropTypes.string).isRequired,
+ onOk: PropTypes.func,
+ onCancel: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { TaskCardSettings };
diff --git a/app/panels/clnt_task_boardOld2/components/task_form.js b/app/panels/clnt_task_boardOld2/components/task_form.js
new file mode 100644
index 0000000..114137f
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/components/task_form.js
@@ -0,0 +1,487 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент панели: Диалог формы события
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
+import { useClientEvent } from "../hooks"; //Вспомогательные хуки
+
+//---------
+//Константы
+//---------
+
+//Стили
+const STYLES = {
+ CONTAINER: { margin: "5px 0px", textAlign: "center" },
+ DIALOG_CONTENT: {
+ paddingBottom: "0px",
+ maxHeight: "740px",
+ minHeight: "740px",
+ "&::-webkit-scrollbar": {
+ width: "8px"
+ },
+ "&::-webkit-scrollbar-track": {
+ borderRadius: "8px",
+ backgroundColor: "#EBEBEB"
+ },
+ "&::-webkit-scrollbar-thumb": {
+ borderRadius: "8px",
+ backgroundColor: "#b4b4b4"
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: "#808080"
+ }
+ },
+ BOX_WITH_LEGEND: { border: "1px solid #939393" },
+ BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" },
+ BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" },
+ BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" },
+ LEGEND: { textAlign: "left" },
+ TEXT_FIELD: (widthVal, greyDisabled = false) => ({
+ margin: "4px",
+ ...(widthVal ? { width: widthVal } : {}),
+ ...(greyDisabled
+ ? {
+ "& .MuiInputBase-input.Mui-disabled": {
+ WebkitTextFillColor: "rgba(0, 0, 0, 0.87)"
+ },
+ "& .MuiInputLabel-root.Mui-disabled": {
+ WebkitTextFillColor: "rgba(0, 0, 0, 0.6)"
+ }
+ }
+ : {})
+ })
+};
+
+//------------------------------------
+//Вспомогательные функции и компоненты
+//------------------------------------
+
+//Свойства вкладки
+function a11yProps(index) {
+ return {
+ id: `simple-tab-${index}`,
+ "aria-controls": `simple-tabpanel-${index}`
+ };
+}
+
+//Формирование кнопки для открытия раздела
+const getInputProps = (onClick, disabled = false, icon = "list") => {
+ return {
+ endAdornment: (
+
+
+ {icon}
+
+
+ )
+ };
+};
+
+//Вкладка информации
+function CustomTabPanel(props) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+ {value === index && {children}}
+
+ );
+}
+
+//Контроль свойств - Вкладка информации
+CustomTabPanel.propTypes = {
+ children: PropTypes.node,
+ index: PropTypes.number.isRequired,
+ value: PropTypes.number.isRequired
+};
+
+//Вкладка основной информации
+const MainEventInfoTab = ({
+ task,
+ handleFieldEdit,
+ handleTypeOpen,
+ handleStatusOpen,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ handleEventNextNumbGet
+}) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ handleClientPersonOpen(0), !task.stype)}
+ >
+
+
+ );
+};
+
+//Контроль свойств - Вкладка основной информации
+MainEventInfoTab.propTypes = {
+ task: PropTypes.object.isRequired,
+ handleFieldEdit: PropTypes.func.isRequired,
+ handleTypeOpen: PropTypes.func.isRequired,
+ handleStatusOpen: PropTypes.func.isRequired,
+ handleClientClientsOpen: PropTypes.func.isRequired,
+ handleClientPersonOpen: PropTypes.func.isRequired,
+ handleCrnOpen: PropTypes.func.isRequired,
+ handleEventNextNumbGet: PropTypes.func.isRequired
+};
+
+//Вкладка информации об исполнителе
+const ExecutorEventInfoTab = ({ task, handleFieldEdit, handleClientPersonOpen }) => {
+ return (
+
+
+
+
+
+
+
+ handleClientPersonOpen(1), task.isUpdate)}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+//Контроль свойств - Вкладка информации об исполнителе
+ExecutorEventInfoTab.propTypes = {
+ task: PropTypes.object.isRequired,
+ handleFieldEdit: PropTypes.func.isRequired,
+ handleClientPersonOpen: PropTypes.func.isRequired
+};
+
+//-----------
+//Тело модуля
+//-----------
+
+//Форма события
+const TaskForm = ({
+ task,
+ setTask,
+ handleTypeOpen,
+ handleStatusOpen,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ handleEventNextNumbGet
+}) => {
+ //Состояние вкладки
+ const [value, setValue] = useState(0);
+
+ //При изменении вкладки
+ const handleChange = (event, newValue) => {
+ setValue(newValue);
+ };
+
+ //При изменении поля
+ const handleFieldEdit = e => {
+ setTask(pv => ({
+ ...pv,
+ [e.target.id]: e.target.value,
+ //Связанные значения, если меняется одно, то необходимо обнулить другое
+ ...(e.target.id === "sclnt_clnperson" ? { sclnt_clnclients: "" } : {}),
+ ...(e.target.id === "sclnt_clnclients" ? { sclnt_clnperson: "" } : {})
+ }));
+ };
+
+ //Генерация содержимого
+ return (
+
+
+ {task.nrn ? "Исправление события" : "Добавление события"}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+//Контроль свойств - Форма события
+TaskForm.propTypes = {
+ task: PropTypes.object.isRequired,
+ setTask: PropTypes.func.isRequired,
+ handleTypeOpen: PropTypes.func.isRequired,
+ handleStatusOpen: PropTypes.func.isRequired,
+ handleClientClientsOpen: PropTypes.func.isRequired,
+ handleClientPersonOpen: PropTypes.func.isRequired,
+ handleCrnOpen: PropTypes.func.isRequired,
+ handleEventNextNumbGet: PropTypes.func.isRequired
+};
+
+//Диалог с формой события
+const TaskFormDialog = ({ taskRn, onClose }) => {
+ //Собственное состояние
+ const [
+ task,
+ setTask,
+ insertEvent,
+ updateEvent,
+ handleTypeOpen,
+ handleStatusOpen,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ handleEventNextNumbGet
+ ] = useClientEvent(taskRn);
+
+ return (
+
+ );
+};
+
+//Контроль свойств - Диалог с формой события
+TaskFormDialog.propTypes = {
+ taskRn: PropTypes.number,
+ onClose: PropTypes.func.isRequired
+};
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export { TaskFormDialog };
diff --git a/app/panels/clnt_task_boardOld2/components/task_notes.js b/app/panels/clnt_task_boardOld2/components/task_notes.js
new file mode 100644
index 0000000..6fb7057
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/components/task_notes.js
@@ -0,0 +1,13 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент панели: Диалог примечаний события
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React, { useState } from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
+import { useClientEvent } from "../hooks"; //Вспомогательные хуки
diff --git a/app/panels/clnt_task_boardOld2/components/tasks_group.js b/app/panels/clnt_task_boardOld2/components/tasks_group.js
new file mode 100644
index 0000000..2c5591d
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/components/tasks_group.js
@@ -0,0 +1,31 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Компонент: Контейнер с типом событий
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import React from "react"; //Классы React
+import PropTypes from "prop-types"; //Контроль свойств компонента
+import {} from "@mui/material"; //Интерфейсные компоненты
+
+//---------------
+//Тело компонента
+//---------------
+
+//Контейнер типа события
+const TasksGroup = () => {
+ //Генерация содержимого
+ return ;
+};
+
+//Контроль свойств компонента - Контейнер типа события
+TasksGroup.propTypes = {};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { TasksGroup };
diff --git a/app/panels/clnt_task_boardOld2/filter.js b/app/panels/clnt_task_boardOld2/filter.js
new file mode 100644
index 0000000..a0b53ca
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/filter.js
@@ -0,0 +1,77 @@
+/*
+ Парус 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 (
+
+ {caption}: {value}
+
+ }
+ 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 (
+
+
+ filter_alt
+
+ {filter.type ? : null}
+ {filter.sendPerson ? : null}
+ {filter.sendDivision ? : null}
+ {filter.sendUsrGrp ? : null}
+
+ );
+};
+
+//Контроль свойств компонента - Фильтр отбора
+Filter.propTypes = {
+ filter: PropTypes.object.isRequired,
+ onClick: PropTypes.func
+};
+
+//--------------------
+//Интерфейс компонента
+//--------------------
+
+export { Filter };
diff --git a/app/panels/clnt_task_boardOld2/hooks.js b/app/panels/clnt_task_boardOld2/hooks.js
new file mode 100644
index 0000000..768750d
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/hooks.js
@@ -0,0 +1,404 @@
+/*
+ Парус 8 - Панели мониторинга - Редактор настройки регламентированного отчёта
+ Пользовательские хуки
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
+import { ApplicationСtx } from "../../context/application"; //Контекст приложения
+import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
+import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
+import dayjs from "dayjs"; //Работа с датами
+
+//---------------------------------------------
+//Вспомогательные функции форматирования данных
+//---------------------------------------------
+
+//-----------
+//Тело модуля
+//-----------
+
+//Хук для события
+const useClientEvent = taskRn => {
+ //Собственное состояние
+ const [task, setTask] = useState({
+ init: true,
+ nrn: taskRn,
+ scrn: "",
+ sprefix: "",
+ snumber: "",
+ stype: "",
+ sstatus: "",
+ sdescription: "",
+ sclnt_clnclients: "",
+ sclnt_clnperson: "",
+ dstart_date: "",
+ sinit_clnperson: "",
+ sinit_user: "",
+ sinit_reason: "",
+ sto_company: "",
+ sto_department: "",
+ sto_clnpost: "",
+ sto_clnpsdep: "",
+ sto_clnperson: "",
+ sto_fcstaffgrp: "",
+ sto_user: "",
+ sto_usergrp: "",
+ scurrent_user: "",
+ isUpdate: false,
+ insertDisabled: true,
+ updateDisabled: true
+ });
+
+ //Подключение к контексту взаимодействия с сервером
+ const { executeStored } = useContext(BackEndСtx);
+
+ //Подключение к контексту приложения
+ const { pOnlineShowDictionary } = useContext(ApplicationСtx);
+
+ //Отображение раздела "Типы событий"
+ const handleTypeOpen = useCallback(async () => {
+ pOnlineShowDictionary({
+ unitCode: "ClientEventTypes",
+ showMethod: "main",
+ inputParameters: [{ name: "in_EVNTYPE_NAME", value: task.stype }],
+ callBack: async res => {
+ if (res.success) {
+ //Считываем параметры исходя из типа события
+ const data = await executeStored({
+ stored: "UDO_P8PANELS_TEST.CLNEVNTYPES_INIT",
+ args: {
+ SEVENT_TYPE: res.outParameters.out_EVNTYPE_CODE,
+ SCURRENT_PREF: task.sprefix
+ },
+ tagValueProcessor: () => undefined
+ });
+ if (data) {
+ setTask(pv => ({
+ ...pv,
+ stype: res.outParameters.out_EVNTYPE_CODE,
+ sprefix: data.SPREF,
+ snumber: data.SNUMB,
+ sstatus: data.SDEFAULT_STATUS
+ }));
+ }
+ }
+ }
+ });
+ }, [executeStored, pOnlineShowDictionary, task.sprefix, task.stype]);
+
+ //Отображение раздела "Статусы типового события"
+ const handleStatusOpen = useCallback(async () => {
+ pOnlineShowDictionary({
+ unitCode: "ClientEventTypesStates",
+ showMethod: "main",
+ inputParameters: [
+ { name: "in_SEVNTYPE_CODE", value: task.stype },
+ { name: "in_EVENT_STATUS_EVNSTAT_CODE", value: task.sstatus }
+ ],
+ callBack: res => {
+ res.success
+ ? setTask(pv => ({
+ ...pv,
+ sstatus: res.outParameters.out_EVENT_STATUS_EVNSTAT_CODE
+ }))
+ : null;
+ }
+ });
+ }, [pOnlineShowDictionary, task.sstatus, task.stype]);
+
+ //Отображение раздела "Клиенты"
+ const handleClientClientsOpen = useCallback(async () => {
+ pOnlineShowDictionary({
+ unitCode: "ClientClients",
+ showMethod: "main",
+ inputParameters: [{ name: "in_CLIENT_CODE", value: task.sclnt_clnclients }],
+ callBack: res => {
+ res.success
+ ? setTask(pv => ({
+ ...pv,
+ sclnt_clnclients: res.outParameters.out_CLIENT_CODE,
+ sclnt_clnperson: ""
+ }))
+ : null;
+ }
+ });
+ }, [pOnlineShowDictionary, task.sclnt_clnclients]);
+
+ //Отображение раздела "Сотрудники"
+ const handleClientPersonOpen = useCallback(
+ //Тип открытия (0 - для клиента, 1 - для инициатора)
+ async (nType = 0) => {
+ pOnlineShowDictionary({
+ unitCode: "ClientPersons",
+ showMethod: "main",
+ inputParameters: [{ name: "in_CODE", value: nType === 0 ? task.sclnt_clnperson : task.sinit_clnperson }],
+ callBack: res => {
+ if (res.success) {
+ if (nType === 0) {
+ setTask(pv => ({
+ ...pv,
+ sclnt_clnperson: res.outParameters.out_CODE,
+ sclnt_clnclients: ""
+ }));
+ } else {
+ setTask(pv => ({
+ ...pv,
+ sinit_clnperson: res.outParameters.out_CODE
+ }));
+ }
+ }
+ }
+ });
+ },
+ [pOnlineShowDictionary, task.sclnt_clnperson, task.sinit_clnperson]
+ );
+
+ //Отображение раздела "Каталоги" для событий
+ const handleCrnOpen = useCallback(async () => {
+ pOnlineShowDictionary({
+ unitCode: "CatalogTree",
+ showMethod: "main",
+ inputParameters: [
+ { name: "in_DOCNAME", value: "ClientEvents" },
+ { name: "in_NAME", value: task.scrn }
+ ],
+ callBack: res => {
+ res.success
+ ? setTask(pv => ({
+ ...pv,
+ scrn: res.outParameters.out_NAME
+ }))
+ : null;
+ }
+ });
+ }, [pOnlineShowDictionary, task.scrn]);
+
+ //Считывание следующего номера события
+ const getEventNextNumb = useCallback(async () => {
+ const data = await executeStored({
+ stored: "UDO_P8PANELS_TEST.CLNEVENTS_NEXTNUMB_GET",
+ args: {
+ SPREFIX: task.sprefix
+ }
+ });
+ if (data) {
+ setTask(pv => ({
+ ...pv,
+ snumber: data.SEVENT_NUMB
+ }));
+ }
+ }, [executeStored, task.sprefix]);
+
+ //Добавление события
+ const insertEvent = useCallback(
+ async callBack => {
+ await executeStored({
+ stored: "UDO_P8PANELS_TEST.CLNEVENTS_INSERT",
+ args: {
+ SCRN: task.scrn,
+ SPREF: task.sprefix,
+ SNUMB: task.snumber,
+ STYPE: task.stype,
+ SSTATUS: task.sstatus,
+ SPLAN_DATE: task.dstart_date ? dayjs(task.dstart_date).format("DD.MM.YYYY HH:mm") : null,
+ SINIT_PERSON: task.sinit_clnperson,
+ SCLIENT_CLIENT: task.sclnt_clnclients,
+ SCLIENT_PERSON: task.sclnt_clnperson,
+ SDESCRIPTION: task.sdescription,
+ SREASON: task.sinit_reason
+ }
+ });
+ callBack();
+ },
+ [
+ executeStored,
+ task.dstart_date,
+ task.sclnt_clnclients,
+ task.sclnt_clnperson,
+ task.scrn,
+ task.sdescription,
+ task.sinit_clnperson,
+ task.sinit_reason,
+ task.snumber,
+ task.sprefix,
+ task.sstatus,
+ task.stype
+ ]
+ );
+
+ //Исправление события
+ const updateEvent = useCallback(
+ async callBack => {
+ await executeStored({
+ stored: "UDO_P8PANELS_TEST.CLNEVENTS_UPDATE",
+ args: {
+ NCLNEVENTS: task.nrn,
+ SCLIENT_CLIENT: task.sclnt_clnclients,
+ SCLIENT_PERSON: task.sclnt_clnperson,
+ SDESCRIPTION: task.sdescription
+ }
+ });
+ callBack();
+ },
+ [executeStored, task.nrn, task.sclnt_clnclients, task.sclnt_clnperson, task.sdescription]
+ );
+
+ useEffect(() => {
+ if (task.init) {
+ if (taskRn) {
+ //Считывание параметров события
+ const readEvent = async () => {
+ const data = await executeStored({
+ stored: "UDO_P8PANELS_TEST.CLNEVENTS_GET",
+ args: {
+ NCLNEVENTS: task.nrn
+ },
+ respArg: "COUT"
+ });
+ setTask(pv => ({
+ ...pv,
+ scrn: data.XEVENT.SCRN,
+ sprefix: data.XEVENT.SPREF,
+ snumber: data.XEVENT.SNUMB,
+ stype: data.XEVENT.STYPE,
+ sstatus: data.XEVENT.SSTATUS,
+ sdescription: data.XEVENT.SDESCRIPTION,
+ sclnt_clnclients: data.XEVENT.SCLIENT_CLIENT,
+ sclnt_clnperson: data.XEVENT.SCLIENT_PERSON,
+ dstart_date: data.XEVENT.SPLAN_DATE ? dayjs(data.XEVENT.SPLAN_DATE).format("YYYY-MM-DD HH:mm") : "",
+ sinit_clnperson: data.XEVENT.SINIT_PERSON,
+ sinit_user: data.XEVENT.SINIT_AUTHID,
+ sinit_reason: data.XEVENT.SREASON,
+ sto_company: data.XEVENT.SSEND_CLIENT,
+ sto_department: data.XEVENT.SSEND_DIVISION,
+ sto_clnpost: data.XEVENT.SSEND_POST,
+ sto_clnpsdep: data.XEVENT.SSEND_PERFORM,
+ sto_clnperson: data.XEVENT.SSEND_PERSON,
+ sto_fcstaffgrp: data.XEVENT.SSEND_STAFFGRP,
+ sto_user: data.XEVENT.SSEND_USER_NAME,
+ sto_usergrp: data.XEVENT.SSEND_USER_GROUP,
+ scurrent_user: data.XEVENT.SINIT_AUTHID,
+ isUpdate: true,
+ init: false
+ }));
+ };
+ //Инициализация параметров события
+ readEvent();
+ } else {
+ //Считывание изначальных параметров события
+ const initEvent = async () => {
+ const data = await executeStored({
+ stored: "UDO_P8PANELS_TEST.CLNEVENTS_INIT",
+ args: {}
+ });
+ if (data) {
+ setTask(pv => ({
+ ...pv,
+ sprefix: data.SPREF,
+ snumber: data.SNUMB,
+ scurrent_user: data.SINIT_AUTHNAME,
+ sinit_clnperson: data.SINIT_PERSON,
+ sinit_user: !data.SINIT_PERSON ? data.SINIT_AUTHNAME : "",
+ init: false
+ }));
+ }
+ };
+ //Инициализация изначальных параметров события
+ initEvent();
+ }
+ }
+ if (!task.init) {
+ setTask(pv => ({ ...pv, sinit_user: !task.sinit_clnperson ? task.scurrent_user : "" }));
+ }
+ }, [executeStored, task.init, task.nrn, task.scurrent_user, task.sinit_clnperson, taskRn]);
+
+ //Проверка доступности действия
+ useEffect(() => {
+ setTask(pv => ({
+ ...pv,
+ insertDisabled:
+ !task.scrn ||
+ !task.sprefix ||
+ !task.snumber ||
+ !task.stype ||
+ !task.sstatus ||
+ !task.sdescription ||
+ (!task.sinit_clnperson && !task.sinit_user),
+ updateDisabled: !task.sdescription
+ }));
+ }, [task.scrn, task.sdescription, task.sinit_clnperson, task.sinit_user, task.snumber, task.sprefix, task.sstatus, task.stype]);
+
+ return [
+ task,
+ setTask,
+ insertEvent,
+ updateEvent,
+ handleTypeOpen,
+ handleStatusOpen,
+ handleClientClientsOpen,
+ handleClientPersonOpen,
+ handleCrnOpen,
+ getEventNextNumb
+ ];
+};
+
+//Карточка события
+const useTaskCard = () => {
+ //Собственное состояние
+ const [taskCard, setTaskCard] = useState({
+ openEdit: false
+ });
+ //Состояние действий
+ const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false });
+
+ //Подключение к контексту взаимодействия с сервером
+ const { executeStored } = useContext(BackEndСtx);
+
+ //Подключение к контексту сообщений
+ const { showMsgWarn } = useContext(MessagingСtx);
+
+ //Удаление контрагента
+ const deleteEvent = useCallback(
+ async rn => {
+ await executeStored({
+ stored: "UDO_P8PANELS_TEST.CLNEVENTS_DELETE",
+ args: { NCLNEVENTS: rn }
+ });
+ },
+ [executeStored]
+ );
+
+ //По нажатию на открытие меню действий
+ const handleMethodsMenuButtonClick = event => {
+ setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true }));
+ };
+
+ //При закрытии меню
+ const handleMethodsMenuClose = () => {
+ setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false }));
+ };
+
+ //По нажатия действия "Редактировать"
+ const handleTaskEdit = id => {
+ setTaskCard(pv => ({ ...pv, openEdit: true }));
+ };
+
+ //По нажатию действия "Удалить"
+ const handleTaskDelete = id => {
+ showMsgWarn("Удалить событие?", () => deleteEvent(id));
+ };
+
+ //Формируем меню показателей
+ const menuItems = [
+ { method: "EDIT", name: "Исправить", icon: "edit", func: handleTaskEdit },
+ { method: "DELETE", name: "Удалить", icon: "delete", func: handleTaskDelete }
+ ];
+
+ return [taskCard, setTaskCard, cardActions, handleMethodsMenuButtonClick, handleMethodsMenuClose, menuItems];
+};
+
+export { useClientEvent, useTaskCard };
diff --git a/app/panels/clnt_task_boardOld2/index.js b/app/panels/clnt_task_boardOld2/index.js
new file mode 100644
index 0000000..0324d4a
--- /dev/null
+++ b/app/panels/clnt_task_boardOld2/index.js
@@ -0,0 +1,16 @@
+/*
+ Парус 8 - Панели мониторинга - УДП - Доски задач
+ Панель мониторинга: Точка входа
+*/
+
+//---------------------
+//Подключение библиотек
+//---------------------
+
+import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ
+
+//----------------
+//Интерфейс модуля
+//----------------
+
+export const RootClass = ClntTaskBoard;