From 4ad1024741f1ae625a7d885b920213886bb3e2b2 Mon Sep 17 00:00:00 2001 From: davay-popozhe Date: Fri, 20 Sep 2024 15:39:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-878.=20=D0=A1=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=2019.?= =?UTF-8?q?09.24?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/panels/clnt_task_board/clnt_task_board.js | 234 ++++ .../clnt_task_board/clnt_task_board1.js | 177 +++ .../components/filter_dialog.js | 189 +++ .../components/filter_input_field.js | 126 ++ .../clnt_task_board/components/task_card.js | 131 ++ .../components/task_card_settings.js | 126 ++ .../clnt_task_board/components/task_form.js | 489 ++++++++ app/panels/clnt_task_board/filter.js | 158 +++ app/panels/clnt_task_board/hooks.js | 1112 +++++++++++++++++ app/panels/clnt_task_board/index.js | 16 + .../clnt_task_boardOld/clnt_task_board.js | 167 +++ app/panels/clnt_task_boardOld/filter.js | 74 ++ app/panels/clnt_task_boardOld/index.js | 16 + app/panels/clnt_task_boardOld/task_card.js | 31 + .../clnt_task_boardOld/tasks_category.js | 31 + .../clnt_task_boardOld2/clnt_task_board.js | 346 +++++ .../components/filter_dialog.js | 171 +++ .../components/filter_input_field.js | 126 ++ .../components/task_card.js | 124 ++ .../components/task_card_settings.js | 126 ++ .../components/task_form.js | 487 ++++++++ .../components/task_notes.js | 13 + .../components/tasks_group.js | 31 + app/panels/clnt_task_boardOld2/filter.js | 77 ++ app/panels/clnt_task_boardOld2/hooks.js | 404 ++++++ app/panels/clnt_task_boardOld2/index.js | 16 + 26 files changed, 4998 insertions(+) create mode 100644 app/panels/clnt_task_board/clnt_task_board.js create mode 100644 app/panels/clnt_task_board/clnt_task_board1.js create mode 100644 app/panels/clnt_task_board/components/filter_dialog.js create mode 100644 app/panels/clnt_task_board/components/filter_input_field.js create mode 100644 app/panels/clnt_task_board/components/task_card.js create mode 100644 app/panels/clnt_task_board/components/task_card_settings.js create mode 100644 app/panels/clnt_task_board/components/task_form.js create mode 100644 app/panels/clnt_task_board/filter.js create mode 100644 app/panels/clnt_task_board/hooks.js create mode 100644 app/panels/clnt_task_board/index.js create mode 100644 app/panels/clnt_task_boardOld/clnt_task_board.js create mode 100644 app/panels/clnt_task_boardOld/filter.js create mode 100644 app/panels/clnt_task_boardOld/index.js create mode 100644 app/panels/clnt_task_boardOld/task_card.js create mode 100644 app/panels/clnt_task_boardOld/tasks_category.js create mode 100644 app/panels/clnt_task_boardOld2/clnt_task_board.js create mode 100644 app/panels/clnt_task_boardOld2/components/filter_dialog.js create mode 100644 app/panels/clnt_task_boardOld2/components/filter_input_field.js create mode 100644 app/panels/clnt_task_boardOld2/components/task_card.js create mode 100644 app/panels/clnt_task_boardOld2/components/task_card_settings.js create mode 100644 app/panels/clnt_task_boardOld2/components/task_form.js create mode 100644 app/panels/clnt_task_boardOld2/components/task_notes.js create mode 100644 app/panels/clnt_task_boardOld2/components/tasks_group.js create mode 100644 app/panels/clnt_task_boardOld2/filter.js create mode 100644 app/panels/clnt_task_boardOld2/hooks.js create mode 100644 app/panels/clnt_task_boardOld2/index.js 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 ( +
+ + Фильтр отбора + + close + + + + selectEventType(filter.type, pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + + + selectSendPerson(filter.sendPerson, pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + + + selectSendDivision(filter.sendDivision, pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + + + selectSendUsrGrp(filter.sendUsrGrp, pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + + {docs.length > 0 && initial.type === filter.type ? ( + + + + ) : null} + + + + + + + +
+ ); +}; + +//Контроль свойств компонента - Диалоговое окно фильтра отбора +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 ( + + + more_vert + + + {menuItems.map(action => { + return ( + { + //Выполняем действие + action.func(taskRn, action.needReload ? handleReload : null); + //Закрываем меню + handleMethodsMenuClose(); + }} + > + {action.icon} + {action.name} + + ); + })} + + + ); +}; + +//Контроль свойств - Действия карточки события +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 ( +
+ + Настройки + + close + + + + + Цвет + + + + + + + + + +
+ ); +}; + +//Контроль свойств компонента - Диалоговое окно настройки карточки событий +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 ( + + ); +} + +//Контроль свойств - Вкладка информации +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 ( + + + + + {onClose ? ( + + {taskRn ? ( + + ) : ( + + )} + + + ) : null} + + ); +}; + +//Контроль свойств - Диалог с формой события +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 ( + handleOrderChanged(item)}> + {caption} + {order ? order.direction === "ASC" ? arrow_upward : arrow_downward : null} + + ); +}; + +//Контроль свойств компонента - Элемент меню сортировок +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 ( +
+ + Фильтр отбора + + close + + + + selectEventType(pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + + + selectSendPerson(pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + + + selectSendDivision(pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + + + selectSendUsrGrp(pOnlineShowDictionary, callBack)} + onChange={handleFilterItemChange} + /> + + + + + + + + +
+ ); +}; + +//Контроль свойств компонента - Диалоговое окно фильтра отбора +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 ( + + + more_vert + + + {menuItems.map(el => { + return ( + { + el.func(taskRn); + handleMethodsMenuClose(); + }} + > + {el.icon} + {el.name} + + ); + })} + + + ); +}; + +//Контроль свойств - Действия карты показателя +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 ( +
+ + Настройки + + close + + + + + Цвет + + + + + + + + + +
+ ); +}; + +//Контроль свойств компонента - Диалоговое окно настройки карточки событий +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 ( + + ); +} + +//Контроль свойств - Вкладка информации +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 ( + + + + + {onClose ? ( + + {taskRn ? ( + + ) : ( + + )} + + + ) : null} + + ); +}; + +//Контроль свойств - Диалог с формой события +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;